Skip to content

feat: sync private provider updates (batch 1) - #222

Closed
hyrious wants to merge 10 commits into
mainfrom
provider-sync-20260729-batch-1
Closed

feat: sync private provider updates (batch 1)#222
hyrious wants to merge 10 commits into
mainfrom
provider-sync-20260729-batch-1

Conversation

@hyrious

@hyrious hyrious commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

概要

同步第一批 10 个 provider 边界变更:

  • 新增 Heartbeat、Pod AI、TriggerCMD、Salesloft、Postalytics、TemplateFox、Cartes.io
  • 同步 Shopify REST Admin 的文章标签与作者 action
  • 同步 DataForSEO Amazon 7 个异步任务 action
  • Qdrant 回归覆盖已迁入私有仓库,移除 OSS provider-local runtime test

边界

  • 每个 provider 保持 OSS 的 credential、proxy、SSRF 与 runtime 结构
  • 未迁移私有 tests、docs、coverage notes 或商业存储字段
  • 未修改共享 helper

验证

  • npm run generate:catalog
  • npm run fix-check
  • npm run build
  • git diff --check origin/main...HEAD

未运行全量测试;纯 provider 移植按仓库规则使用 catalog、typecheck/build 与逐 provider probes 验证。

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Summary by CodeRabbit

  • New Features
    • Added integrations for Cartes.io, Heartbeat, Pod AI, Postalytics, Salesloft, TemplateFox, and TRIGGERcmd.
    • Added map, communication, outbound calling, campaign, sales, document, automation, and template actions.
    • Added DataForSEO Amazon Products, ASINs, and Sellers task workflows with asynchronous status tracking.
    • Added Shopify actions for blog article tags and article authors.
  • Updates
    • Refined Shopify page-count filtering and renamed the REST provider as legacy.

Walkthrough

Adds Cartes.io, Heartbeat, Pod AI, Postalytics, Salesloft, TemplateFox, and TRIGGERcmd provider integrations with action schemas, API runtimes, credential validation, executor wiring, and proxy configuration. Extends DataForSEO with asynchronous Amazon task actions and normalization. Expands Shopify with blog article tag and author actions while narrowing page-count filters.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title follows the required type: subject format and accurately describes the provider sync changes.
Description check ✅ Passed The description is clearly related to the provider sync, Shopify, DataForSEO, and Qdrant changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch provider-sync-20260729-batch-1

Comment @coderabbitai help to get the list of available commands.

@hyrious

hyrious commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator Author

按维护者安排,改为本地合并整理,关闭此拆分 PR。

@hyrious hyrious closed this Jul 29, 2026
@hyrious
hyrious deleted the provider-sync-20260729-batch-1 branch July 29, 2026 09:25

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (7)
src/providers/cartes/runtime.ts (1)

420-439: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Drop the unused is_spam from markerBody.

create_marker's input schema (actions.ts lines 142-161) has no isSpam property, so this is always undefined and stripped by compactObject. markerUpdateBody already owns the spam flag.

♻️ Proposed refactor
     roll: input.roll,
     speed: input.speed,
-    is_spam: input.isSpam,
   };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/providers/cartes/runtime.ts` around lines 420 - 439, Remove the is_spam
property from the object returned by markerBody, since create_marker does not
provide isSpam and markerUpdateBody owns the spam flag. Leave the remaining
marker fields unchanged.
src/providers/cartes/executors.ts (1)

16-19: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

customizeRequest clobbers caller-supplied content-type.

defineProviderProxy runs customizeRequest after normalizing caller headers (provider-runtime.ts lines 388-393), so a proxy caller sending text/csv or multipart/form-data gets overwritten with application/json. Prefer setting it only when absent.

♻️ Proposed refactor
   customizeRequest({ headers }) {
     headers.set("accept", "application/json");
-    headers.set("content-type", "application/json");
+    if (!headers.has("content-type")) {
+      headers.set("content-type", "application/json");
+    }
   },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/providers/cartes/executors.ts` around lines 16 - 19, Update
customizeRequest in defineProviderProxy so the default content-type is set only
when the caller has not supplied one, preserving caller-provided values such as
text/csv or multipart/form-data. Keep the existing application/json default and
accept-header behavior unchanged.
src/providers/cartes/actions.ts (1)

194-196: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Hoist heading/pitch/roll schemas like the neighbouring coordinate constants.

These three are byte-identical to lines 136-138; the file already hoists latitudeSchema, zoomSchema, speedSchema, etc., so keeping these inline is inconsistent and invites drift.

♻️ Proposed refactor
+const headingSchema = s.number("Heading in degrees.", { minimum: 0, maximum: 360 });
+const pitchSchema = s.number("Pitch in degrees.", { minimum: -90, maximum: 90 });
+const rollSchema = s.number("Roll in degrees.", { minimum: -180, maximum: 180 });
-    heading: s.number("Heading in degrees.", { minimum: 0, maximum: 360 }),
-    pitch: s.number("Pitch in degrees.", { minimum: -90, maximum: 90 }),
-    roll: s.number("Roll in degrees.", { minimum: -180, maximum: 180 }),
+    heading: headingSchema,
+    pitch: pitchSchema,
+    roll: rollSchema,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/providers/cartes/actions.ts` around lines 194 - 196, Hoist the heading,
pitch, and roll schemas into reusable constants alongside the existing
coordinate schema constants, then replace the inline definitions in the affected
action schema with those constants. Reuse the existing schema descriptions and
bounds exactly, and avoid changing validation behavior.
src/providers/templatefox/runtime.ts (2)

225-235: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract the request options into a named interface.

The inline object type spans ten lines and is reused by every handler; a TemplatefoxRequestOptions interface reads better and documents the contract.

As per coding guidelines, "Prefer named options/input interfaces over inline object types when a function signature spans multiple lines or crosses module boundaries."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/providers/templatefox/runtime.ts` around lines 225 - 235, Define a named
TemplatefoxRequestOptions interface containing the fields currently declared in
the inline input object for requestTemplatefoxJson, then update that function
signature to accept the interface while preserving all field types and
optionality.

Source: Coding guidelines


238-251: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Abort/cancellation is reclassified as an upstream 502.

When context.signal fires, the AbortError is caught and reported as templatefox request failed, hiding client cancellation and timeouts behind a provider-failure status.

♻️ Rethrow aborts
   } catch (error) {
+    if (error instanceof Error && error.name === "AbortError") {
+      throw error;
+    }
     throw new ProviderRequestError(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/providers/templatefox/runtime.ts` around lines 238 - 251, Update the
catch block around the fetch and readTemplatefoxPayload calls to detect
abort/cancellation errors associated with input.signal and rethrow them
unchanged. Only wrap non-abort failures in ProviderRequestError with the
existing 502 behavior and message.
src/providers/templatefox/actions.ts (1)

83-83: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

pdfUrl / pdfUrls are user-supplied fetch targets with no public-URL enforcement.

The description promises a public HTTPS URL, but s.url alone won't reject http://, loopback, or metadata hosts. Validate these in the runtime handlers with the shared assertPublicHttpUrl before building the request body.

As per coding guidelines, "User-supplied content or download URLs must always use public-only assertPublicHttpUrl and public-only providerFetch" and "Prefer shared assertPublicHttpUrl and isBlockedIpAddress over bespoke provider hostname guards".

Also applies to: 180-180, 193-193

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/providers/templatefox/actions.ts` at line 83, Update the runtime handlers
for the pdfUrl/pdfUrls inputs to call the shared assertPublicHttpUrl validation
before constructing each TemplateFox request body, covering the paths around the
relevant handlers near lines 83, 180, and 193. Ensure all resulting outbound
requests use the public-only providerFetch, and do not add bespoke hostname or
IP checks.

Source: Coding guidelines

src/providers/heartbeat/actions.ts (1)

148-154: 📐 Maintainability & Code Quality | 🔵 Trivial

Inconsistent schema builder for list_events input.

Every other action in this file builds its input with s.actionInput({...}, [requiredKeys], description), but list_events uses s.object(...) with an { optional: [...] } config instead. Confirm this still satisfies the ActionDefinition.inputSchema contract expected by defineProviderAction (e.g. any action-input-specific metadata s.actionInput adds that s.object might not). If equivalent, consider switching to s.actionInput({ groupId: s.uuid(...) }, [], "...") for consistency with the rest of the file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/providers/heartbeat/actions.ts` around lines 148 - 154, Update the
list_events inputSchema to use s.actionInput with groupId as the schema field,
no required keys, and the existing description. Preserve groupId as optional and
ensure the result remains compatible with the ActionDefinition.inputSchema
contract used by defineProviderAction.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/providers/cartes/runtime.ts`:
- Around line 392-402: Update normalizeSuccess to treat a valid response body
without a literal success field as successful, preserving an explicit success:
false value when the API provides it. Keep the existing fallback for non-record
payloads and continue returning the raw record.

In `@src/providers/heartbeat/executors.ts`:
- Around line 22-102: Update the Heartbeat handlers in the executor object to
unwrap each requestHeartbeatJson response before passing it to
requireResourceArray or requireResourceObject, using the appropriate payload
property for users, groups, channels, events, and user/group lookups. Apply the
same extraction in the /channels credential-check path so wrapped valid
responses are validated correctly.

In `@src/providers/salesloft/runtime.ts`:
- Around line 156-179: Update requestSalesloft to create and apply a
provider-level timeout signal in addition to input.signal, matching the timeout
pattern used by sibling providers. Ensure the timeout resource is cleaned up
after the fetch completes while preserving the existing request, response
parsing, and error mapping behavior.

In `@src/providers/templatefox/runtime.ts`:
- Around line 393-403: Update normalizeTemplateField to read help text from the
provider’s snake_case help_text key while preserving support for the existing
camelCase helpText key, using the established nullable string parsing behavior.
- Around line 300-301: Update the error-message fallback in the runtime request
handling to treat an empty response.statusText as missing, allowing the
descriptive status-based template message to be used. Preserve
readTemplatefoxErrorMessage(payload) as the highest-priority message and keep
the existing statusText behavior for non-empty values.
- Around line 420-439: Update readOptionalModifications and readPdfUrls to use
an input-side array reader that raises the caller-facing 400 validation error
for non-array values, rather than readArray’s 502 response-error handling.
Preserve the existing undefined handling for optional modifications and retain
the current item-level validation and mapping behavior.

In `@src/providers/triggercmd/runtime.ts`:
- Line 11: Update the listCommandsPath constant used by list_commands and
credential validation to the account-wide /api/command/commandlist endpoint
instead of the per-computer /api/command/list path, preserving both callers’
existing behavior.

---

Nitpick comments:
In `@src/providers/cartes/actions.ts`:
- Around line 194-196: Hoist the heading, pitch, and roll schemas into reusable
constants alongside the existing coordinate schema constants, then replace the
inline definitions in the affected action schema with those constants. Reuse the
existing schema descriptions and bounds exactly, and avoid changing validation
behavior.

In `@src/providers/cartes/executors.ts`:
- Around line 16-19: Update customizeRequest in defineProviderProxy so the
default content-type is set only when the caller has not supplied one,
preserving caller-provided values such as text/csv or multipart/form-data. Keep
the existing application/json default and accept-header behavior unchanged.

In `@src/providers/cartes/runtime.ts`:
- Around line 420-439: Remove the is_spam property from the object returned by
markerBody, since create_marker does not provide isSpam and markerUpdateBody
owns the spam flag. Leave the remaining marker fields unchanged.

In `@src/providers/heartbeat/actions.ts`:
- Around line 148-154: Update the list_events inputSchema to use s.actionInput
with groupId as the schema field, no required keys, and the existing
description. Preserve groupId as optional and ensure the result remains
compatible with the ActionDefinition.inputSchema contract used by
defineProviderAction.

In `@src/providers/templatefox/actions.ts`:
- Line 83: Update the runtime handlers for the pdfUrl/pdfUrls inputs to call the
shared assertPublicHttpUrl validation before constructing each TemplateFox
request body, covering the paths around the relevant handlers near lines 83,
180, and 193. Ensure all resulting outbound requests use the public-only
providerFetch, and do not add bespoke hostname or IP checks.

In `@src/providers/templatefox/runtime.ts`:
- Around line 225-235: Define a named TemplatefoxRequestOptions interface
containing the fields currently declared in the inline input object for
requestTemplatefoxJson, then update that function signature to accept the
interface while preserving all field types and optionality.
- Around line 238-251: Update the catch block around the fetch and
readTemplatefoxPayload calls to detect abort/cancellation errors associated with
input.signal and rethrow them unchanged. Only wrap non-abort failures in
ProviderRequestError with the existing 502 behavior and message.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1c9d9138-ad86-4b9e-94b4-ace42286d58b

📥 Commits

Reviewing files that changed from the base of the PR and between 962d735 and b588402.

📒 Files selected for processing (33)
  • src/providers/cartes/actions.ts
  • src/providers/cartes/definition.ts
  • src/providers/cartes/executors.ts
  • src/providers/cartes/runtime.ts
  • src/providers/dataforseo/actions.ts
  • src/providers/dataforseo/runtime.ts
  • src/providers/heartbeat/actions.ts
  • src/providers/heartbeat/definition.ts
  • src/providers/heartbeat/executors.ts
  • src/providers/pod_ai/actions.ts
  • src/providers/pod_ai/definition.ts
  • src/providers/pod_ai/executors.ts
  • src/providers/pod_ai/runtime.ts
  • src/providers/postalytics/actions.ts
  • src/providers/postalytics/definition.ts
  • src/providers/postalytics/executors.ts
  • src/providers/postalytics/runtime.ts
  • src/providers/qdrant/runtime.test.ts
  • src/providers/salesloft/actions.ts
  • src/providers/salesloft/definition.ts
  • src/providers/salesloft/executors.ts
  • src/providers/salesloft/runtime.ts
  • src/providers/shopify/actions.ts
  • src/providers/shopify/definition.ts
  • src/providers/shopify/runtime.ts
  • src/providers/templatefox/actions.ts
  • src/providers/templatefox/definition.ts
  • src/providers/templatefox/executors.ts
  • src/providers/templatefox/runtime.ts
  • src/providers/triggercmd/actions.ts
  • src/providers/triggercmd/definition.ts
  • src/providers/triggercmd/executors.ts
  • src/providers/triggercmd/runtime.ts
💤 Files with no reviewable changes (1)
  • src/providers/qdrant/runtime.test.ts

Comment on lines +392 to +402
function normalizeSuccess(payload: unknown): Record<string, unknown> {
const record = optionalRecord(payload);
if (!record) {
return { success: true };
}

return {
success: record.success === true,
raw: record,
};
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

normalizeSuccess can report success: false for a successful delete.

This runs only after response.ok passed (line 309). If Cartes.io returns a 2xx body without a literal success: true field (e.g. {"message":"Deleted"}), the action reports success: false while the resource was actually deleted — callers may retry a destructive op.

🐛 Proposed fix
 function normalizeSuccess(payload: unknown): Record<string, unknown> {
   const record = optionalRecord(payload);
   if (!record) {
     return { success: true };
   }
 
   return {
-    success: record.success === true,
+    success: record.success !== false,
     raw: record,
   };
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function normalizeSuccess(payload: unknown): Record<string, unknown> {
const record = optionalRecord(payload);
if (!record) {
return { success: true };
}
return {
success: record.success === true,
raw: record,
};
}
function normalizeSuccess(payload: unknown): Record<string, unknown> {
const record = optionalRecord(payload);
if (!record) {
return { success: true };
}
return {
success: record.success !== false,
raw: record,
};
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/providers/cartes/runtime.ts` around lines 392 - 402, Update
normalizeSuccess to treat a valid response body without a literal success field
as successful, preserving an explicit success: false value when the API provides
it. Keep the existing fallback for non-record payloads and continue returning
the raw record.

Comment on lines +22 to +102
async list_users(_input, context) {
return {
users: requireResourceArray(
await requestHeartbeatJson({ context, path: "/users", phase: "execute" }),
"Heartbeat users response",
),
};
},
async get_user(input, context) {
const userId = requiredString(input.userId, "userId", providerInputError);
return {
user: requireResourceObject(
await requestHeartbeatJson({
context,
path: `/users/${encodeURIComponent(userId)}`,
phase: "execute",
}),
"Heartbeat user response",
),
};
},
async find_users_by_email(input, context) {
return {
users: requireResourceArray(
await requestHeartbeatJson({
context,
path: "/find/users",
phase: "execute",
query: {
email: requiredString(input.email, "email", providerInputError),
},
}),
"Heartbeat user search response",
),
};
},
async list_groups(_input, context) {
return {
groups: requireResourceArray(
await requestHeartbeatJson({ context, path: "/groups", phase: "execute" }),
"Heartbeat groups response",
),
};
},
async get_group(input, context) {
const groupId = requiredString(input.groupId, "groupId", providerInputError);
return {
group: requireResourceObject(
await requestHeartbeatJson({
context,
path: `/groups/${encodeURIComponent(groupId)}`,
phase: "execute",
}),
"Heartbeat group response",
),
};
},
async list_channels(_input, context) {
return {
channels: requireResourceArray(
await requestHeartbeatJson({ context, path: "/channels", phase: "execute" }),
"Heartbeat channels response",
),
};
},
async list_events(input, context) {
return {
events: requireResourceArray(
await requestHeartbeatJson({
context,
path: "/events",
phase: "execute",
query: {
groupID: optionalString(input.groupId),
},
}),
"Heartbeat events response",
),
};
},
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🌐 Web query:

What does the Heartbeat chat API (api.heartbeat.chat/v0) return for GET /users, /groups, /channels — a raw array or a wrapped object?

💡 Result:

The Heartbeat chat API (api.heartbeat.chat/v0) endpoints for /users, /groups, and /channels return a wrapped object rather than a raw array. While the official API documentation reference pages for these endpoints [1][2][3] currently display loading states for their example responses, common patterns in the Heartbeat API implementation—such as those observed in third-party integrations [4] and the use of cursor-based pagination for other collection endpoints [5]—indicate that these responses are wrapped objects. These objects typically contain a data field (or similar) holding the requested list, often accompanied by metadata required for pagination, such as cursors [4][5].

Citations:


Unwrap Heartbeat responses before validating them
requestHeartbeatJson is passed straight into requireResourceArray/requireResourceObject across these handlers, but Heartbeat collection endpoints return wrapped payloads. These calls will reject valid responses unless the list/object is extracted first. This also affects the /channels credential check.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/providers/heartbeat/executors.ts` around lines 22 - 102, Update the
Heartbeat handlers in the executor object to unwrap each requestHeartbeatJson
response before passing it to requireResourceArray or requireResourceObject,
using the appropriate payload property for users, groups, channels, events, and
user/group lookups. Apply the same extraction in the /channels credential-check
path so wrapped valid responses are validated correctly.

Comment on lines +156 to +179
async function requestSalesloft(input: SalesloftRequestInput): Promise<SalesloftPayload> {
const url = new URL(`${salesloftApiBaseUrl}${input.path}`);
if (input.query) {
appendQueryObject(url, input.query);
}

const response = await input.fetcher(url, {
method: "GET",
headers: {
accept: "application/json",
authorization: `Bearer ${input.apiKey}`,
"user-agent": providerUserAgent,
},
signal: input.signal,
});

const rawBody = await response.text();
const payload = parseSalesloftJson(rawBody, response.status);
if (!response.ok) {
throw mapSalesloftError(response.status, payload, rawBody);
}

return payload;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether context.signal already carries an enforced timeout centrally,
# and whether other providers implement it locally at the transport layer.
rg -n -A5 -B5 'AbortController|setTimeout|signal:' src/providers/provider-runtime.ts
rg -n -A5 -B5 'AbortController|setTimeout' src/providers/heartbeat/executors.ts src/providers/postalytics/runtime.ts src/providers/triggercmd/runtime.ts

Repository: oomol-lab/open-connector

Length of output: 3850


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== provider-runtime timeout plumbing ==\n'
rg -n -A8 -B8 'createProviderTimeout|signal:' src/providers/provider-runtime.ts

printf '\n== defineApiKeyProviderExecutors / context wiring ==\n'
rg -n -A10 -B10 'defineApiKeyProviderExecutors|createContext\(' src/providers/provider-runtime.ts src/providers/salesloft/runtime.ts

printf '\n== Salesloft transport ==\n'
sed -n '1,260p' src/providers/salesloft/runtime.ts

Repository: oomol-lab/open-connector

Length of output: 20305


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== executor creation path ==\n'
rg -n -A20 -B20 'createProviderTimeout\(|ProviderTimeout|executionContext\.signal|signal: context\.signal|signal: executionContext\.signal' src/providers/provider-runtime.ts

printf '\n== executor entrypoint around defineProviderExecutors ==\n'
sed -n '720,820p' src/providers/provider-runtime.ts

printf '\n== salesloft runtime tail for any timeout handling ==\n'
sed -n '260,420p' src/providers/salesloft/runtime.ts

Repository: oomol-lab/open-connector

Length of output: 14206


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== action execution context definitions ==\n'
rg -n -A6 -B6 'interface .*ExecutionContext|type .*ExecutionContext|signal: AbortSignal|AbortSignal' src | sed -n '1,240p'

printf '\n== executor runtime signal creation ==\n'
rg -n -A10 -B10 'new AbortController|AbortController\(|timeout|signal:' src | sed -n '1,260p'

Repository: oomol-lab/open-connector

Length of output: 34128


Add a request timeout to Salesloft transport
requestSalesloft only forwards context.signal; unlike the sibling providers in this stack, it doesn’t add its own timeout, so a slow Salesloft call can stall until the parent signal aborts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/providers/salesloft/runtime.ts` around lines 156 - 179, Update
requestSalesloft to create and apply a provider-level timeout signal in addition
to input.signal, matching the timeout pattern used by sibling providers. Ensure
the timeout resource is cleaned up after the fetch completes while preserving
the existing request, response parsing, and error mapping behavior.

Comment on lines +300 to +301
const message =
readTemplatefoxErrorMessage(payload) ?? response.statusText ?? `templatefox request failed with ${response.status}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Empty statusText defeats the fallback message.

response.statusText is always a string (commonly "" over HTTP/2), so ?? never reaches the final template and callers can receive a blank error message.

🐛 Proposed fix
-  const message =
-    readTemplatefoxErrorMessage(payload) ?? response.statusText ?? `templatefox request failed with ${response.status}`;
+  const message =
+    readTemplatefoxErrorMessage(payload) ||
+    response.statusText ||
+    `templatefox request failed with ${response.status}`;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const message =
readTemplatefoxErrorMessage(payload) ?? response.statusText ?? `templatefox request failed with ${response.status}`;
const message =
readTemplatefoxErrorMessage(payload) ||
response.statusText ||
`templatefox request failed with ${response.status}`;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/providers/templatefox/runtime.ts` around lines 300 - 301, Update the
error-message fallback in the runtime request handling to treat an empty
response.statusText as missing, allowing the descriptive status-based template
message to be used. Preserve readTemplatefoxErrorMessage(payload) as the
highest-priority message and keep the existing statusText behavior for non-empty
values.

Comment on lines +393 to +403
function normalizeTemplateField(record: Record<string, unknown>): Record<string, unknown> {
return {
...record,
key: readRequiredResponseString(record.key, "field.key"),
label: readRequiredResponseString(record.label, "field.label"),
type: optionalRawString(record.type) ?? "string",
required: optionalBoolean(record.required) ?? false,
helpText: readNullableRawString(record.helpText),
spec: readNullableFieldSpec(record.spec),
};
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

helpText likely never populates.

The rest of this file reads snake_case provider keys (credits_remaining, created_at, expires_in), and normalizeTemplate accepts both casings, but here only record.helpText is read — so provider help_text is dropped and the field is always null.

🐛 Proposed fix
-    helpText: readNullableRawString(record.helpText),
+    helpText: readNullableRawString(record.help_text ?? record.helpText),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function normalizeTemplateField(record: Record<string, unknown>): Record<string, unknown> {
return {
...record,
key: readRequiredResponseString(record.key, "field.key"),
label: readRequiredResponseString(record.label, "field.label"),
type: optionalRawString(record.type) ?? "string",
required: optionalBoolean(record.required) ?? false,
helpText: readNullableRawString(record.helpText),
spec: readNullableFieldSpec(record.spec),
};
}
function normalizeTemplateField(record: Record<string, unknown>): Record<string, unknown> {
return {
...record,
key: readRequiredResponseString(record.key, "field.key"),
label: readRequiredResponseString(record.label, "field.label"),
type: optionalRawString(record.type) ?? "string",
required: optionalBoolean(record.required) ?? false,
helpText: readNullableRawString(record.help_text ?? record.helpText),
spec: readNullableFieldSpec(record.spec),
};
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/providers/templatefox/runtime.ts` around lines 393 - 403, Update
normalizeTemplateField to read help text from the provider’s snake_case
help_text key while preserving support for the existing camelCase helpText key,
using the established nullable string parsing behavior.

Comment on lines +420 to +439
function readOptionalModifications(value: unknown): Array<Record<string, unknown>> | undefined {
if (value === undefined) {
return undefined;
}
return readArray(value, "modifications").map((item) => {
const record = readInputObject(item, "modification");
return compactObject({
name: readRequiredInputString(record.name, "modification.name"),
text: optionalRawString(record.text),
image_url: optionalRawString(record.imageUrl),
color: optionalRawString(record.color),
background: optionalRawString(record.background),
hidden: optionalBoolean(record.hidden),
});
});
}

function readPdfUrls(value: unknown): string[] {
return readArray(value, "pdfUrls").map((item, index) => readRequiredInputString(item, `pdfUrls[${index}]`));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Client input validated with the response-error helper.

readArray throws ProviderRequestError(502, "malformed templatefox response: ..."). Non-array modifications or pdfUrls is a caller error, so this reports a bogus upstream failure with a misleading message instead of a 400.

🐛 Add an input-side array reader
+function readInputArray(value: unknown, fieldName: string): unknown[] {
+  if (!Array.isArray(value)) {
+    throw new ProviderRequestError(400, `${fieldName} must be an array`);
+  }
+  return value;
+}
+
 function readOptionalModifications(value: unknown): Array<Record<string, unknown>> | undefined {
   if (value === undefined) {
     return undefined;
   }
-  return readArray(value, "modifications").map((item) => {
+  return readInputArray(value, "modifications").map((item) => {
 function readPdfUrls(value: unknown): string[] {
-  return readArray(value, "pdfUrls").map((item, index) => readRequiredInputString(item, `pdfUrls[${index}]`));
+  return readInputArray(value, "pdfUrls").map((item, index) => readRequiredInputString(item, `pdfUrls[${index}]`));
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function readOptionalModifications(value: unknown): Array<Record<string, unknown>> | undefined {
if (value === undefined) {
return undefined;
}
return readArray(value, "modifications").map((item) => {
const record = readInputObject(item, "modification");
return compactObject({
name: readRequiredInputString(record.name, "modification.name"),
text: optionalRawString(record.text),
image_url: optionalRawString(record.imageUrl),
color: optionalRawString(record.color),
background: optionalRawString(record.background),
hidden: optionalBoolean(record.hidden),
});
});
}
function readPdfUrls(value: unknown): string[] {
return readArray(value, "pdfUrls").map((item, index) => readRequiredInputString(item, `pdfUrls[${index}]`));
}
function readInputArray(value: unknown, fieldName: string): unknown[] {
if (!Array.isArray(value)) {
throw new ProviderRequestError(400, `${fieldName} must be an array`);
}
return value;
}
function readOptionalModifications(value: unknown): Array<Record<string, unknown>> | undefined {
if (value === undefined) {
return undefined;
}
return readInputArray(value, "modifications").map((item) => {
const record = readInputObject(item, "modification");
return compactObject({
name: readRequiredInputString(record.name, "modification.name"),
text: optionalRawString(record.text),
image_url: optionalRawString(record.imageUrl),
color: optionalRawString(record.color),
background: optionalRawString(record.background),
hidden: optionalBoolean(record.hidden),
});
});
}
function readPdfUrls(value: unknown): string[] {
return readInputArray(value, "pdfUrls").map((item, index) => readRequiredInputString(item, `pdfUrls[${index}]`));
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/providers/templatefox/runtime.ts` around lines 420 - 439, Update
readOptionalModifications and readPdfUrls to use an input-side array reader that
raises the caller-facing 400 validation error for non-array values, rather than
readArray’s 502 response-error handling. Preserve the existing undefined
handling for optional modifications and retain the current item-level validation
and mapping behavior.

export const triggercmdApiBaseUrl = "https://www.triggercmd.com";

const triggercmdRequestTimeoutMs = 30_000;
const listCommandsPath = "/api/command/list";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the referenced file around the constant and its callers.
sed -n '1,220p' src/providers/triggercmd/runtime.ts

# Search for the endpoint constant and any related path usage.
rg -n 'listCommandsPath|command/list|commandlist|computer_id' src/providers/triggercmd -S

Repository: oomol-lab/open-connector

Length of output: 6080


🌐 Web query:

TRIGGERcmd API list commands endpoint commandlist computer_id documentation

💡 Result:

To list commands via the TRIGGERcmd API, you can use two primary endpoints depending on whether you want to list all commands or commands specific to a single computer [1][2]. For all commands across all computers: Endpoint: GET https://www.triggercmd.com/api/command/commandlist Header: Authorization: Bearer (your token) Content-Type: application/json This endpoint returns a list of all commands for your account. Each command typically includes a did field, which combines the computer_id and command_id [1][2]. For commands on a specific computer: Endpoint: POST https://www.triggercmd.com/api/command/list Header: Authorization: Bearer (your token) Header: Content-Type: application/json Body: {"computer_id": "YOUR_COMPUTER_ID"} This endpoint allows you to retrieve only the commands associated with the specified computer_id [1][2]. To obtain your authorization token, check the Instructions page within your TRIGGERcmd account portal [3]. You can also retrieve a list of your computer IDs using the endpoint GET https://www.triggercmd.com/api/computer/list [1][2].

Citations:


Use the account-wide command list endpoint.

/api/command/list is the per-computer endpoint and requires computer_id; both callers omit it. Switch listCommandsPath to /api/command/commandlist so list_commands and credential validation can return the full account command list.

Proposed fix
-const listCommandsPath = "/api/command/list";
+const listCommandsPath = "/api/command/commandlist";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const listCommandsPath = "/api/command/list";
const listCommandsPath = "/api/command/commandlist";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/providers/triggercmd/runtime.ts` at line 11, Update the listCommandsPath
constant used by list_commands and credential validation to the account-wide
/api/command/commandlist endpoint instead of the per-computer /api/command/list
path, preserving both callers’ existing behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant