@agent-native/core 0.119.4 → 0.119.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +14 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/durable-background.ts +1 -0
- package/corpus/core/src/agent/engine/index.ts +2 -0
- package/corpus/core/src/agent/engine/registry.ts +95 -2
- package/corpus/core/src/deploy/build.ts +144 -2
- package/corpus/core/src/deploy/workspace-deploy.ts +123 -1
- package/corpus/core/src/integrations/integration-durable-dispatch-config.ts +22 -0
- package/corpus/core/src/integrations/integration-durable-dispatch.ts +190 -0
- package/corpus/core/src/integrations/pending-tasks-retry-job.ts +175 -87
- package/corpus/core/src/integrations/pending-tasks-store.ts +114 -11
- package/corpus/core/src/integrations/plugin.ts +113 -60
- package/corpus/core/src/integrations/task-queue-stats.ts +68 -15
- package/corpus/core/src/integrations/webhook-handler.ts +21 -46
- package/corpus/core/src/scripts/agent-engines/list-agent-engines.ts +8 -0
- package/corpus/core/src/scripts/agent-engines/manage-agent-engine.ts +5 -1
- package/corpus/core/src/scripts/agent-engines/set-agent-engine.ts +9 -1
- package/corpus/core/src/server/auth.ts +6 -0
- package/corpus/core/src/templates/default/.agents/skills/integration-webhooks/SKILL.md +63 -22
- package/corpus/core/src/templates/headless/.agents/skills/integration-webhooks/SKILL.md +63 -22
- package/corpus/core/src/templates/workspace-core/.agents/skills/integration-webhooks/SKILL.md +63 -22
- package/corpus/templates/dispatch/changelog/2026-07-21-recover-stalled-slack-agent-work.md +6 -0
- package/corpus/templates/slides/actions/patch-deck.ts +2 -1
- package/corpus/templates/slides/changelog/2026-07-23-opening-or-refining-a-deck-no-longer-crashes-with-a-blank-er.md +6 -0
- package/corpus/templates/slides/shared/aspect-ratios.ts +4 -1
- package/corpus/templates/tasks/.oxfmtrc.json +8 -0
- package/corpus/templates/tasks/AGENTS.md +34 -41
- package/corpus/templates/tasks/README.md +13 -6
- package/corpus/templates/tasks/actions/bulk-delete-inbox-items.ts +10 -8
- package/corpus/templates/tasks/actions/bulk-delete-tasks.ts +10 -8
- package/corpus/templates/tasks/actions/bulk-mark-inbox-items-ready.ts +1 -1
- package/corpus/templates/tasks/actions/bulk-update-tasks.ts +12 -10
- package/corpus/templates/tasks/actions/create-inbox-item.ts +7 -4
- package/corpus/templates/tasks/actions/create-task.ts +2 -1
- package/corpus/templates/tasks/actions/delete-inbox-item.ts +6 -4
- package/corpus/templates/tasks/actions/delete-task.ts +1 -1
- package/corpus/templates/tasks/actions/list-inbox-items.ts +4 -2
- package/corpus/templates/tasks/actions/list-visible-task-fields.ts +3 -1
- package/corpus/templates/tasks/actions/mark-inbox-item-ready.ts +6 -4
- package/corpus/templates/tasks/actions/navigate.ts +16 -14
- package/corpus/templates/tasks/actions/reorder-inbox-items.ts +9 -7
- package/corpus/templates/tasks/actions/update-inbox-item.ts +6 -4
- package/corpus/templates/tasks/actions/update-task.ts +12 -9
- package/corpus/templates/tasks/actions/update-visible-task-fields.ts +10 -8
- package/corpus/templates/tasks/actions/view-screen.ts +1 -1
- package/corpus/templates/tasks/app/components/tasks/fields/TaskFieldsSidebar.tsx +15 -0
- package/corpus/templates/tasks/changelog/2026-07-23-task-detail-extensions.md +6 -0
- package/corpus/templates/tasks/drizzle.config.ts +3 -0
- package/corpus/templates/tasks/package.json +4 -1
- package/corpus/templates/tasks/server/db/schema.ts +0 -10
- package/corpus/templates/tasks/server/db/test-tasks-table.ts +0 -5
- package/corpus/templates/tasks/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/tasks/server/plugins/db.ts +43 -10
- package/corpus/templates/tasks/server/user-config/store.ts +42 -81
- package/corpus/templates/tasks/tsconfig.json +1 -2
- package/corpus/templates/tasks/vitest.config.ts +10 -1
- package/dist/agent/durable-background.d.ts +1 -0
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +1 -0
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/agent/engine/index.d.ts +1 -1
- package/dist/agent/engine/index.d.ts.map +1 -1
- package/dist/agent/engine/index.js +1 -1
- package/dist/agent/engine/index.js.map +1 -1
- package/dist/agent/engine/registry.d.ts +32 -1
- package/dist/agent/engine/registry.d.ts.map +1 -1
- package/dist/agent/engine/registry.js +75 -3
- package/dist/agent/engine/registry.js.map +1 -1
- package/dist/deploy/build.d.ts +3 -0
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +106 -3
- package/dist/deploy/build.js.map +1 -1
- package/dist/deploy/workspace-deploy.d.ts.map +1 -1
- package/dist/deploy/workspace-deploy.js +103 -2
- package/dist/deploy/workspace-deploy.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/integrations/integration-durable-dispatch-config.d.ts +7 -0
- package/dist/integrations/integration-durable-dispatch-config.d.ts.map +1 -0
- package/dist/integrations/integration-durable-dispatch-config.js +16 -0
- package/dist/integrations/integration-durable-dispatch-config.js.map +1 -0
- package/dist/integrations/integration-durable-dispatch.d.ts +23 -0
- package/dist/integrations/integration-durable-dispatch.d.ts.map +1 -0
- package/dist/integrations/integration-durable-dispatch.js +115 -0
- package/dist/integrations/integration-durable-dispatch.js.map +1 -0
- package/dist/integrations/pending-tasks-retry-job.d.ts +13 -1
- package/dist/integrations/pending-tasks-retry-job.d.ts.map +1 -1
- package/dist/integrations/pending-tasks-retry-job.js +133 -74
- package/dist/integrations/pending-tasks-retry-job.js.map +1 -1
- package/dist/integrations/pending-tasks-store.d.ts +14 -2
- package/dist/integrations/pending-tasks-store.d.ts.map +1 -1
- package/dist/integrations/pending-tasks-store.js +75 -11
- package/dist/integrations/pending-tasks-store.js.map +1 -1
- package/dist/integrations/plugin.d.ts.map +1 -1
- package/dist/integrations/plugin.js +88 -55
- package/dist/integrations/plugin.js.map +1 -1
- package/dist/integrations/task-queue-stats.d.ts +15 -3
- package/dist/integrations/task-queue-stats.d.ts.map +1 -1
- package/dist/integrations/task-queue-stats.js +45 -15
- package/dist/integrations/task-queue-stats.js.map +1 -1
- package/dist/integrations/webhook-handler.d.ts +1 -1
- package/dist/integrations/webhook-handler.d.ts.map +1 -1
- package/dist/integrations/webhook-handler.js +18 -42
- package/dist/integrations/webhook-handler.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +5 -5
- package/dist/provider-api/actions/provider-api.d.ts +4 -4
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/scripts/agent-engines/list-agent-engines.d.ts.map +1 -1
- package/dist/scripts/agent-engines/list-agent-engines.js +8 -2
- package/dist/scripts/agent-engines/list-agent-engines.js.map +1 -1
- package/dist/scripts/agent-engines/manage-agent-engine.d.ts.map +1 -1
- package/dist/scripts/agent-engines/manage-agent-engine.js +5 -2
- package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
- package/dist/scripts/agent-engines/set-agent-engine.d.ts.map +1 -1
- package/dist/scripts/agent-engines/set-agent-engine.js +9 -2
- package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +5 -0
- package/dist/server/auth.js.map +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/default/.agents/skills/integration-webhooks/SKILL.md +63 -22
- package/dist/templates/headless/.agents/skills/integration-webhooks/SKILL.md +63 -22
- package/dist/templates/workspace-core/.agents/skills/integration-webhooks/SKILL.md +63 -22
- package/package.json +1 -1
- package/src/agent/durable-background.ts +1 -0
- package/src/agent/engine/index.ts +2 -0
- package/src/agent/engine/registry.ts +95 -2
- package/src/deploy/build.ts +144 -2
- package/src/deploy/workspace-deploy.ts +123 -1
- package/src/integrations/integration-durable-dispatch-config.ts +22 -0
- package/src/integrations/integration-durable-dispatch.ts +190 -0
- package/src/integrations/pending-tasks-retry-job.ts +175 -87
- package/src/integrations/pending-tasks-store.ts +114 -11
- package/src/integrations/plugin.ts +113 -60
- package/src/integrations/task-queue-stats.ts +68 -15
- package/src/integrations/webhook-handler.ts +21 -46
- package/src/scripts/agent-engines/list-agent-engines.ts +8 -0
- package/src/scripts/agent-engines/manage-agent-engine.ts +5 -1
- package/src/scripts/agent-engines/set-agent-engine.ts +9 -1
- package/src/server/auth.ts +6 -0
- package/src/templates/default/.agents/skills/integration-webhooks/SKILL.md +63 -22
- package/src/templates/headless/.agents/skills/integration-webhooks/SKILL.md +63 -22
- package/src/templates/workspace-core/.agents/skills/integration-webhooks/SKILL.md +63 -22
- package/corpus/templates/tasks/.prettierrc +0 -5
|
@@ -17,12 +17,12 @@ declare const _default: import("../../action.js").ActionDefinition<{
|
|
|
17
17
|
id?: undefined;
|
|
18
18
|
provider?: undefined;
|
|
19
19
|
} | {
|
|
20
|
-
error?: undefined;
|
|
21
20
|
configured?: undefined;
|
|
22
21
|
connectPath?: undefined;
|
|
23
22
|
url: string;
|
|
24
23
|
id: string;
|
|
25
24
|
provider: string;
|
|
25
|
+
error?: undefined;
|
|
26
26
|
}>;
|
|
27
27
|
export default _default;
|
|
28
28
|
//# sourceMappingURL=upload-image.d.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const INTEGRATION_DURABLE_DISPATCH_ENV = "AGENT_INTEGRATION_DURABLE_DISPATCH";
|
|
2
|
+
export declare const INTEGRATION_DURABLE_DISPATCH_SCOPES_ENV = "AGENT_INTEGRATION_DURABLE_DISPATCH_SCOPES";
|
|
3
|
+
export declare const INTEGRATION_PROCESS_TASK_PATH = "/_agent-native/integrations/process-task";
|
|
4
|
+
export declare const INTEGRATION_RETRY_SWEEP_PATH = "/_agent-native/integrations/retry-stuck-tasks";
|
|
5
|
+
export declare const INTEGRATION_RETRY_SWEEP_TOKEN_SUBJECT = "integration-pending-tasks-sweep";
|
|
6
|
+
export declare function isIntegrationDurableDispatchConfigured(): boolean;
|
|
7
|
+
//# sourceMappingURL=integration-durable-dispatch-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integration-durable-dispatch-config.d.ts","sourceRoot":"","sources":["../../src/integrations/integration-durable-dispatch-config.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gCAAgC,uCACP,CAAC;AACvC,eAAO,MAAM,uCAAuC,8CACP,CAAC;AAC9C,eAAO,MAAM,6BAA6B,6CACE,CAAC;AAC7C,eAAO,MAAM,4BAA4B,kDACQ,CAAC;AAClD,eAAO,MAAM,qCAAqC,oCACf,CAAC;AAEpC,wBAAgB,sCAAsC,IAAI,OAAO,CAUhE"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const INTEGRATION_DURABLE_DISPATCH_ENV = "AGENT_INTEGRATION_DURABLE_DISPATCH";
|
|
2
|
+
export const INTEGRATION_DURABLE_DISPATCH_SCOPES_ENV = "AGENT_INTEGRATION_DURABLE_DISPATCH_SCOPES";
|
|
3
|
+
export const INTEGRATION_PROCESS_TASK_PATH = "/_agent-native/integrations/process-task";
|
|
4
|
+
export const INTEGRATION_RETRY_SWEEP_PATH = "/_agent-native/integrations/retry-stuck-tasks";
|
|
5
|
+
export const INTEGRATION_RETRY_SWEEP_TOKEN_SUBJECT = "integration-pending-tasks-sweep";
|
|
6
|
+
export function isIntegrationDurableDispatchConfigured() {
|
|
7
|
+
const value = process.env.AGENT_INTEGRATION_DURABLE_DISPATCH;
|
|
8
|
+
if (!value)
|
|
9
|
+
return false;
|
|
10
|
+
const normalized = value.trim().toLowerCase();
|
|
11
|
+
return (normalized === "1" ||
|
|
12
|
+
normalized === "true" ||
|
|
13
|
+
normalized === "yes" ||
|
|
14
|
+
normalized === "on");
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=integration-durable-dispatch-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integration-durable-dispatch-config.js","sourceRoot":"","sources":["../../src/integrations/integration-durable-dispatch-config.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,gCAAgC,GAC3C,oCAAoC,CAAC;AACvC,MAAM,CAAC,MAAM,uCAAuC,GAClD,2CAA2C,CAAC;AAC9C,MAAM,CAAC,MAAM,6BAA6B,GACxC,0CAA0C,CAAC;AAC7C,MAAM,CAAC,MAAM,4BAA4B,GACvC,+CAA+C,CAAC;AAClD,MAAM,CAAC,MAAM,qCAAqC,GAChD,iCAAiC,CAAC;AAEpC,MAAM,UAAU,sCAAsC;IACpD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,kCAAkC,CAAC;IAC7D,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACzB,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC9C,OAAO,CACL,UAAU,KAAK,GAAG;QAClB,UAAU,KAAK,MAAM;QACrB,UAAU,KAAK,KAAK;QACpB,UAAU,KAAK,IAAI,CACpB,CAAC;AACJ,CAAC","sourcesContent":["export const INTEGRATION_DURABLE_DISPATCH_ENV =\n \"AGENT_INTEGRATION_DURABLE_DISPATCH\";\nexport const INTEGRATION_DURABLE_DISPATCH_SCOPES_ENV =\n \"AGENT_INTEGRATION_DURABLE_DISPATCH_SCOPES\";\nexport const INTEGRATION_PROCESS_TASK_PATH =\n \"/_agent-native/integrations/process-task\";\nexport const INTEGRATION_RETRY_SWEEP_PATH =\n \"/_agent-native/integrations/retry-stuck-tasks\";\nexport const INTEGRATION_RETRY_SWEEP_TOKEN_SUBJECT =\n \"integration-pending-tasks-sweep\";\n\nexport function isIntegrationDurableDispatchConfigured(): boolean {\n const value = process.env.AGENT_INTEGRATION_DURABLE_DISPATCH;\n if (!value) return false;\n const normalized = value.trim().toLowerCase();\n return (\n normalized === \"1\" ||\n normalized === \"true\" ||\n normalized === \"yes\" ||\n normalized === \"on\"\n );\n}\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { INTEGRATION_DURABLE_DISPATCH_ENV, INTEGRATION_DURABLE_DISPATCH_SCOPES_ENV, INTEGRATION_PROCESS_TASK_PATH, INTEGRATION_RETRY_SWEEP_PATH, INTEGRATION_RETRY_SWEEP_TOKEN_SUBJECT, isIntegrationDurableDispatchConfigured } from "./integration-durable-dispatch-config.js";
|
|
2
|
+
export { INTEGRATION_DURABLE_DISPATCH_ENV, INTEGRATION_DURABLE_DISPATCH_SCOPES_ENV, INTEGRATION_PROCESS_TASK_PATH, INTEGRATION_RETRY_SWEEP_PATH, INTEGRATION_RETRY_SWEEP_TOKEN_SUBJECT, isIntegrationDurableDispatchConfigured, };
|
|
3
|
+
export type IntegrationDispatchOutcome = "background-acknowledged" | "portable-unconfirmed" | "failed";
|
|
4
|
+
export interface IntegrationDispatchTaskScope {
|
|
5
|
+
platform: string;
|
|
6
|
+
externalThreadId: string;
|
|
7
|
+
platformContext?: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
export interface IntegrationDurableDispatchScope {
|
|
10
|
+
platform: string;
|
|
11
|
+
value: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function integrationDispatchScopeValue(task: IntegrationDispatchTaskScope): string | null;
|
|
14
|
+
export declare function configuredIntegrationDurableDispatchScopes(): IntegrationDurableDispatchScope[] | null;
|
|
15
|
+
export declare function isIntegrationDurableDispatchEnabledForTask(task: IntegrationDispatchTaskScope): boolean;
|
|
16
|
+
export declare function dispatchPendingIntegrationTask(input: {
|
|
17
|
+
taskId: string;
|
|
18
|
+
task: IntegrationDispatchTaskScope;
|
|
19
|
+
event?: unknown;
|
|
20
|
+
baseUrl?: string;
|
|
21
|
+
portableSettleMs?: number;
|
|
22
|
+
}): Promise<IntegrationDispatchOutcome>;
|
|
23
|
+
//# sourceMappingURL=integration-durable-dispatch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integration-durable-dispatch.d.ts","sourceRoot":"","sources":["../../src/integrations/integration-durable-dispatch.ts"],"names":[],"mappings":"AAQA,OAAO,EACL,gCAAgC,EAChC,uCAAuC,EACvC,6BAA6B,EAC7B,4BAA4B,EAC5B,qCAAqC,EACrC,sCAAsC,EACvC,MAAM,0CAA0C,CAAC;AAGlD,OAAO,EACL,gCAAgC,EAChC,uCAAuC,EACvC,6BAA6B,EAC7B,4BAA4B,EAC5B,qCAAqC,EACrC,sCAAsC,GACvC,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAClC,yBAAyB,GACzB,sBAAsB,GACtB,QAAQ,CAAC;AAEb,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC3C;AAED,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,4BAA4B,GACjC,MAAM,GAAG,IAAI,CAGf;AAED,wBAAgB,0CAA0C,IACtD,+BAA+B,EAAE,GACjC,IAAI,CAeP;AAiBD,wBAAgB,0CAA0C,CACxD,IAAI,EAAE,4BAA4B,GACjC,OAAO,CAkBT;AA4BD,wBAAsB,8BAA8B,CAAC,KAAK,EAAE;IAC1D,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,4BAA4B,CAAC;IACnC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAmDtC"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { hasConfiguredA2ASecret } from "../a2a/auth-policy.js";
|
|
2
|
+
import { AGENT_BACKGROUND_PROCESSOR_FIELD, AGENT_BACKGROUND_PROCESSOR_INTEGRATION, dispatchPathTargetsNetlifyBackgroundFunction, resolveDurableBackgroundDispatchPath, } from "../agent/durable-background.js";
|
|
3
|
+
import { fireInternalDispatch } from "../server/self-dispatch.js";
|
|
4
|
+
import { INTEGRATION_DURABLE_DISPATCH_ENV, INTEGRATION_DURABLE_DISPATCH_SCOPES_ENV, INTEGRATION_PROCESS_TASK_PATH, INTEGRATION_RETRY_SWEEP_PATH, INTEGRATION_RETRY_SWEEP_TOKEN_SUBJECT, isIntegrationDurableDispatchConfigured, } from "./integration-durable-dispatch-config.js";
|
|
5
|
+
import { recordPendingTaskDispatchAttempt } from "./pending-tasks-store.js";
|
|
6
|
+
export { INTEGRATION_DURABLE_DISPATCH_ENV, INTEGRATION_DURABLE_DISPATCH_SCOPES_ENV, INTEGRATION_PROCESS_TASK_PATH, INTEGRATION_RETRY_SWEEP_PATH, INTEGRATION_RETRY_SWEEP_TOKEN_SUBJECT, isIntegrationDurableDispatchConfigured, };
|
|
7
|
+
export function integrationDispatchScopeValue(task) {
|
|
8
|
+
const value = task.platformContext?.channelId;
|
|
9
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
10
|
+
}
|
|
11
|
+
export function configuredIntegrationDurableDispatchScopes() {
|
|
12
|
+
const raw = process.env.AGENT_INTEGRATION_DURABLE_DISPATCH_SCOPES;
|
|
13
|
+
if (!raw?.trim())
|
|
14
|
+
return null;
|
|
15
|
+
return raw
|
|
16
|
+
.split(",")
|
|
17
|
+
.map((value) => value.trim())
|
|
18
|
+
.filter(Boolean)
|
|
19
|
+
.map((scope) => {
|
|
20
|
+
const separator = scope.indexOf(":");
|
|
21
|
+
return {
|
|
22
|
+
platform: separator > 0 ? scope.slice(0, separator) : "",
|
|
23
|
+
value: separator > 0 ? scope.slice(separator + 1) : "",
|
|
24
|
+
};
|
|
25
|
+
})
|
|
26
|
+
.filter((scope) => scope.platform && scope.value);
|
|
27
|
+
}
|
|
28
|
+
function taskScopeCandidates(task) {
|
|
29
|
+
const candidates = [
|
|
30
|
+
`${task.platform}:*`,
|
|
31
|
+
`${task.platform}:${task.externalThreadId}`,
|
|
32
|
+
];
|
|
33
|
+
const explicitScope = integrationDispatchScopeValue(task);
|
|
34
|
+
if (explicitScope) {
|
|
35
|
+
candidates.push(`${task.platform}:${explicitScope}`);
|
|
36
|
+
}
|
|
37
|
+
else if (task.platform === "slack") {
|
|
38
|
+
const channelId = task.externalThreadId.split(":")[2];
|
|
39
|
+
if (channelId)
|
|
40
|
+
candidates.push(`slack:${channelId}`);
|
|
41
|
+
}
|
|
42
|
+
return candidates;
|
|
43
|
+
}
|
|
44
|
+
export function isIntegrationDurableDispatchEnabledForTask(task) {
|
|
45
|
+
if (!isIntegrationDurableDispatchConfigured())
|
|
46
|
+
return false;
|
|
47
|
+
const scopes = configuredIntegrationDurableDispatchScopes();
|
|
48
|
+
if (scopes &&
|
|
49
|
+
!taskScopeCandidates(task).some((candidate) => scopes.some((scope) => `${scope.platform}:${scope.value}` === candidate))) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
const path = resolveDurableBackgroundDispatchPath(INTEGRATION_PROCESS_TASK_PATH);
|
|
53
|
+
return (dispatchPathTargetsNetlifyBackgroundFunction(path) &&
|
|
54
|
+
hasConfiguredA2ASecret());
|
|
55
|
+
}
|
|
56
|
+
async function recordDispatch(taskId, outcome) {
|
|
57
|
+
try {
|
|
58
|
+
await recordPendingTaskDispatchAttempt(taskId, outcome);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
console.error(`[integrations] Failed to record dispatch outcome for ${taskId}:`, error);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function logDispatch(taskId, outcome, startedAt) {
|
|
65
|
+
console.info("[integrations] pending task dispatch", {
|
|
66
|
+
taskId,
|
|
67
|
+
outcome,
|
|
68
|
+
durationMs: Date.now() - startedAt,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
export async function dispatchPendingIntegrationTask(input) {
|
|
72
|
+
const startedAt = Date.now();
|
|
73
|
+
const durable = isIntegrationDurableDispatchEnabledForTask(input.task);
|
|
74
|
+
if (durable) {
|
|
75
|
+
const backgroundPath = resolveDurableBackgroundDispatchPath(INTEGRATION_PROCESS_TASK_PATH);
|
|
76
|
+
try {
|
|
77
|
+
await fireInternalDispatch({
|
|
78
|
+
event: input.event,
|
|
79
|
+
baseUrl: input.baseUrl,
|
|
80
|
+
path: backgroundPath,
|
|
81
|
+
taskId: input.taskId,
|
|
82
|
+
body: {
|
|
83
|
+
[AGENT_BACKGROUND_PROCESSOR_FIELD]: AGENT_BACKGROUND_PROCESSOR_INTEGRATION,
|
|
84
|
+
},
|
|
85
|
+
awaitResponse: true,
|
|
86
|
+
responseTimeoutMs: 2_000,
|
|
87
|
+
});
|
|
88
|
+
await recordDispatch(input.taskId, "background-acknowledged");
|
|
89
|
+
logDispatch(input.taskId, "background-acknowledged", startedAt);
|
|
90
|
+
return "background-acknowledged";
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
console.error(`[integrations] Background dispatch failed for ${input.taskId}; trying the portable processor:`, error);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
await fireInternalDispatch({
|
|
98
|
+
event: input.event,
|
|
99
|
+
baseUrl: input.baseUrl,
|
|
100
|
+
path: INTEGRATION_PROCESS_TASK_PATH,
|
|
101
|
+
taskId: input.taskId,
|
|
102
|
+
settleMs: input.portableSettleMs,
|
|
103
|
+
});
|
|
104
|
+
await recordDispatch(input.taskId, "portable-unconfirmed");
|
|
105
|
+
logDispatch(input.taskId, "portable-unconfirmed", startedAt);
|
|
106
|
+
return "portable-unconfirmed";
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
console.error(`[integrations] Portable dispatch failed for ${input.taskId}:`, error);
|
|
110
|
+
await recordDispatch(input.taskId, "failed");
|
|
111
|
+
logDispatch(input.taskId, "failed", startedAt);
|
|
112
|
+
return "failed";
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=integration-durable-dispatch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integration-durable-dispatch.js","sourceRoot":"","sources":["../../src/integrations/integration-durable-dispatch.ts"],"names":[],"mappings":"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","sourcesContent":["import { hasConfiguredA2ASecret } from \"../a2a/auth-policy.js\";\nimport {\n AGENT_BACKGROUND_PROCESSOR_FIELD,\n AGENT_BACKGROUND_PROCESSOR_INTEGRATION,\n dispatchPathTargetsNetlifyBackgroundFunction,\n resolveDurableBackgroundDispatchPath,\n} from \"../agent/durable-background.js\";\nimport { fireInternalDispatch } from \"../server/self-dispatch.js\";\nimport {\n INTEGRATION_DURABLE_DISPATCH_ENV,\n INTEGRATION_DURABLE_DISPATCH_SCOPES_ENV,\n INTEGRATION_PROCESS_TASK_PATH,\n INTEGRATION_RETRY_SWEEP_PATH,\n INTEGRATION_RETRY_SWEEP_TOKEN_SUBJECT,\n isIntegrationDurableDispatchConfigured,\n} from \"./integration-durable-dispatch-config.js\";\nimport { recordPendingTaskDispatchAttempt } from \"./pending-tasks-store.js\";\n\nexport {\n INTEGRATION_DURABLE_DISPATCH_ENV,\n INTEGRATION_DURABLE_DISPATCH_SCOPES_ENV,\n INTEGRATION_PROCESS_TASK_PATH,\n INTEGRATION_RETRY_SWEEP_PATH,\n INTEGRATION_RETRY_SWEEP_TOKEN_SUBJECT,\n isIntegrationDurableDispatchConfigured,\n};\n\nexport type IntegrationDispatchOutcome =\n | \"background-acknowledged\"\n | \"portable-unconfirmed\"\n | \"failed\";\n\nexport interface IntegrationDispatchTaskScope {\n platform: string;\n externalThreadId: string;\n platformContext?: Record<string, unknown>;\n}\n\nexport interface IntegrationDurableDispatchScope {\n platform: string;\n value: string;\n}\n\nexport function integrationDispatchScopeValue(\n task: IntegrationDispatchTaskScope,\n): string | null {\n const value = task.platformContext?.channelId;\n return typeof value === \"string\" && value.trim() ? value.trim() : null;\n}\n\nexport function configuredIntegrationDurableDispatchScopes():\n | IntegrationDurableDispatchScope[]\n | null {\n const raw = process.env.AGENT_INTEGRATION_DURABLE_DISPATCH_SCOPES;\n if (!raw?.trim()) return null;\n return raw\n .split(\",\")\n .map((value) => value.trim())\n .filter(Boolean)\n .map((scope) => {\n const separator = scope.indexOf(\":\");\n return {\n platform: separator > 0 ? scope.slice(0, separator) : \"\",\n value: separator > 0 ? scope.slice(separator + 1) : \"\",\n };\n })\n .filter((scope) => scope.platform && scope.value);\n}\n\nfunction taskScopeCandidates(task: IntegrationDispatchTaskScope): string[] {\n const candidates = [\n `${task.platform}:*`,\n `${task.platform}:${task.externalThreadId}`,\n ];\n const explicitScope = integrationDispatchScopeValue(task);\n if (explicitScope) {\n candidates.push(`${task.platform}:${explicitScope}`);\n } else if (task.platform === \"slack\") {\n const channelId = task.externalThreadId.split(\":\")[2];\n if (channelId) candidates.push(`slack:${channelId}`);\n }\n return candidates;\n}\n\nexport function isIntegrationDurableDispatchEnabledForTask(\n task: IntegrationDispatchTaskScope,\n): boolean {\n if (!isIntegrationDurableDispatchConfigured()) return false;\n const scopes = configuredIntegrationDurableDispatchScopes();\n if (\n scopes &&\n !taskScopeCandidates(task).some((candidate) =>\n scopes.some((scope) => `${scope.platform}:${scope.value}` === candidate),\n )\n ) {\n return false;\n }\n const path = resolveDurableBackgroundDispatchPath(\n INTEGRATION_PROCESS_TASK_PATH,\n );\n return (\n dispatchPathTargetsNetlifyBackgroundFunction(path) &&\n hasConfiguredA2ASecret()\n );\n}\n\nasync function recordDispatch(\n taskId: string,\n outcome: IntegrationDispatchOutcome,\n): Promise<void> {\n try {\n await recordPendingTaskDispatchAttempt(taskId, outcome);\n } catch (error) {\n console.error(\n `[integrations] Failed to record dispatch outcome for ${taskId}:`,\n error,\n );\n }\n}\n\nfunction logDispatch(\n taskId: string,\n outcome: IntegrationDispatchOutcome,\n startedAt: number,\n): void {\n console.info(\"[integrations] pending task dispatch\", {\n taskId,\n outcome,\n durationMs: Date.now() - startedAt,\n });\n}\n\nexport async function dispatchPendingIntegrationTask(input: {\n taskId: string;\n task: IntegrationDispatchTaskScope;\n event?: unknown;\n baseUrl?: string;\n portableSettleMs?: number;\n}): Promise<IntegrationDispatchOutcome> {\n const startedAt = Date.now();\n const durable = isIntegrationDurableDispatchEnabledForTask(input.task);\n if (durable) {\n const backgroundPath = resolveDurableBackgroundDispatchPath(\n INTEGRATION_PROCESS_TASK_PATH,\n );\n try {\n await fireInternalDispatch({\n event: input.event,\n baseUrl: input.baseUrl,\n path: backgroundPath,\n taskId: input.taskId,\n body: {\n [AGENT_BACKGROUND_PROCESSOR_FIELD]:\n AGENT_BACKGROUND_PROCESSOR_INTEGRATION,\n },\n awaitResponse: true,\n responseTimeoutMs: 2_000,\n });\n await recordDispatch(input.taskId, \"background-acknowledged\");\n logDispatch(input.taskId, \"background-acknowledged\", startedAt);\n return \"background-acknowledged\";\n } catch (error) {\n console.error(\n `[integrations] Background dispatch failed for ${input.taskId}; trying the portable processor:`,\n error,\n );\n }\n }\n\n try {\n await fireInternalDispatch({\n event: input.event,\n baseUrl: input.baseUrl,\n path: INTEGRATION_PROCESS_TASK_PATH,\n taskId: input.taskId,\n settleMs: input.portableSettleMs,\n });\n await recordDispatch(input.taskId, \"portable-unconfirmed\");\n logDispatch(input.taskId, \"portable-unconfirmed\", startedAt);\n return \"portable-unconfirmed\";\n } catch (error) {\n console.error(\n `[integrations] Portable dispatch failed for ${input.taskId}:`,\n error,\n );\n await recordDispatch(input.taskId, \"failed\");\n logDispatch(input.taskId, \"failed\", startedAt);\n return \"failed\";\n }\n}\n"]}
|
|
@@ -1,8 +1,20 @@
|
|
|
1
|
+
export interface PendingTasksSweepResult {
|
|
2
|
+
selected: number;
|
|
3
|
+
dispatched: number;
|
|
4
|
+
markedFailed: number;
|
|
5
|
+
skipped: number;
|
|
6
|
+
dispatchFailed: number;
|
|
7
|
+
}
|
|
8
|
+
export interface PendingTasksSweepOptions {
|
|
9
|
+
webhookBaseUrl?: string;
|
|
10
|
+
limit?: number;
|
|
11
|
+
durableOnly?: boolean;
|
|
12
|
+
}
|
|
1
13
|
/**
|
|
2
14
|
* One pass: find stuck tasks and re-fire the processor for each.
|
|
3
15
|
* Exported for tests and for manual triggers.
|
|
4
16
|
*/
|
|
5
|
-
export declare function retryStuckPendingTasks(
|
|
17
|
+
export declare function retryStuckPendingTasks(input?: string | PendingTasksSweepOptions): Promise<PendingTasksSweepResult>;
|
|
6
18
|
/**
|
|
7
19
|
* Start the periodic retry loop. Safe to call multiple times — second call
|
|
8
20
|
* is a no-op.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pending-tasks-retry-job.d.ts","sourceRoot":"","sources":["../../src/integrations/pending-tasks-retry-job.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pending-tasks-retry-job.d.ts","sourceRoot":"","sources":["../../src/integrations/pending-tasks-retry-job.ts"],"names":[],"mappings":"AAqEA,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,wBAAwB;IACvC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAkDD;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,CAAC,EAAE,MAAM,GAAG,wBAAwB,GACxC,OAAO,CAAC,uBAAuB,CAAC,CA6KlC;AAcD;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,CAAC,EAAE;IAClD,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,GAAG,IAAI,CA0BP;AAED,2BAA2B;AAC3B,wBAAgB,wBAAwB,IAAI,IAAI,CAU/C"}
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
import { getDbExec } from "../db/client.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { signInternalToken } from "./internal-token.js";
|
|
5
|
-
import { MAX_PENDING_TASK_ATTEMPTS } from "./pending-tasks-store.js";
|
|
2
|
+
import { configuredIntegrationDurableDispatchScopes, dispatchPendingIntegrationTask, isIntegrationDurableDispatchEnabledForTask, } from "./integration-durable-dispatch.js";
|
|
3
|
+
import { ensurePendingTasksTable, MAX_PENDING_TASK_ATTEMPTS, } from "./pending-tasks-store.js";
|
|
6
4
|
/**
|
|
7
5
|
* Retries stuck integration webhook tasks.
|
|
8
6
|
*
|
|
9
7
|
* The integration webhook flow enqueues work into `integration_pending_tasks`
|
|
10
8
|
* (see `pending-tasks-store.ts`) and then fires a self-webhook to the
|
|
11
9
|
* `/_agent-native/integrations/process-task` endpoint to drain the queue.
|
|
12
|
-
* If that
|
|
10
|
+
* If that initial dispatch is lost (e.g. transient network blip), the
|
|
13
11
|
* row stays in `pending` forever. Likewise, if the processor is killed mid-
|
|
14
12
|
* processing (function timeout, container shutdown), a row can remain in
|
|
15
13
|
* `processing` forever.
|
|
16
14
|
*
|
|
17
|
-
*
|
|
15
|
+
* The in-process fallback runs every 60s. Durable deployments also invoke the
|
|
16
|
+
* same bounded sweep from an external scheduler so recovery does not depend on
|
|
17
|
+
* a serverless process remaining alive.
|
|
18
|
+
*
|
|
19
|
+
* Each sweep re-fires the processor endpoint for tasks that
|
|
18
20
|
* look stuck:
|
|
19
21
|
* - status='pending' AND created_at older than 90s (initial dispatch lost)
|
|
20
22
|
* - status='processing' AND updated_at older than the host-specific
|
|
@@ -33,8 +35,9 @@ const PENDING_STUCK_AFTER_MS = 90_000;
|
|
|
33
35
|
/** Tasks "processing" longer than this are considered killed mid-flight. */
|
|
34
36
|
const DEFAULT_PROCESSING_STUCK_AFTER_MS = 5 * 60 * 1000;
|
|
35
37
|
const SERVERLESS_PROCESSING_STUCK_AFTER_MS = 75_000;
|
|
38
|
+
const DURABLE_BACKGROUND_PROCESSING_STUCK_AFTER_MS = 16 * 60 * 1000;
|
|
36
39
|
/** After this many attempts we give up and mark the task failed */
|
|
37
|
-
const
|
|
40
|
+
const DEFAULT_SWEEP_LIMIT = 100;
|
|
38
41
|
let retryInterval = null;
|
|
39
42
|
let initialTimer = null;
|
|
40
43
|
let activeWebhookBaseUrl;
|
|
@@ -43,58 +46,137 @@ let activeWebhookBaseUrl;
|
|
|
43
46
|
* minute when the queue isn't in use yet on a given deployment.
|
|
44
47
|
*/
|
|
45
48
|
let tableExists = null;
|
|
49
|
+
function affectedRows(result) {
|
|
50
|
+
return Number(result.rowsAffected ??
|
|
51
|
+
result.rowCount ??
|
|
52
|
+
0);
|
|
53
|
+
}
|
|
54
|
+
function isMissingPendingTasksTableError(error) {
|
|
55
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
56
|
+
return /no such table|relation .* does not exist|undefined_table/i.test(message);
|
|
57
|
+
}
|
|
58
|
+
function durableScopeSql() {
|
|
59
|
+
const scopes = configuredIntegrationDurableDispatchScopes();
|
|
60
|
+
if (scopes === null)
|
|
61
|
+
return { clause: "", args: [] };
|
|
62
|
+
if (scopes.length === 0)
|
|
63
|
+
return { clause: " AND 1 = 0", args: [] };
|
|
64
|
+
const clauses = [];
|
|
65
|
+
const args = [];
|
|
66
|
+
for (const scope of scopes) {
|
|
67
|
+
if (scope.value === "*") {
|
|
68
|
+
clauses.push("platform = ?");
|
|
69
|
+
args.push(scope.platform);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (scope.platform === "slack" && /^[A-Za-z0-9]+$/.test(scope.value)) {
|
|
73
|
+
clauses.push("(platform = ? AND (external_thread_id = ? OR dispatch_scope = ? OR external_thread_id LIKE ?))");
|
|
74
|
+
args.push(scope.platform, scope.value, scope.value, `%:%:${scope.value}:%`);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
clauses.push("(platform = ? AND (external_thread_id = ? OR dispatch_scope = ?))");
|
|
78
|
+
args.push(scope.platform, scope.value, scope.value);
|
|
79
|
+
}
|
|
80
|
+
return { clause: ` AND (${clauses.join(" OR ")})`, args };
|
|
81
|
+
}
|
|
46
82
|
/**
|
|
47
83
|
* One pass: find stuck tasks and re-fire the processor for each.
|
|
48
84
|
* Exported for tests and for manual triggers.
|
|
49
85
|
*/
|
|
50
|
-
export async function retryStuckPendingTasks(
|
|
51
|
-
const
|
|
86
|
+
export async function retryStuckPendingTasks(input) {
|
|
87
|
+
const options = typeof input === "string" ? { webhookBaseUrl: input } : (input ?? {});
|
|
88
|
+
const baseUrl = options.webhookBaseUrl ?? activeWebhookBaseUrl;
|
|
89
|
+
const limit = Math.max(1, Math.min(500, Math.floor(options.limit ?? DEFAULT_SWEEP_LIMIT)));
|
|
90
|
+
const scopeSql = options.durableOnly
|
|
91
|
+
? durableScopeSql()
|
|
92
|
+
: { clause: "", args: [] };
|
|
93
|
+
const result = {
|
|
94
|
+
selected: 0,
|
|
95
|
+
dispatched: 0,
|
|
96
|
+
markedFailed: 0,
|
|
97
|
+
skipped: 0,
|
|
98
|
+
dispatchFailed: 0,
|
|
99
|
+
};
|
|
100
|
+
await ensurePendingTasksTable();
|
|
52
101
|
const client = getDbExec();
|
|
53
102
|
const now = Date.now();
|
|
54
103
|
const pendingCutoff = now - PENDING_STUCK_AFTER_MS;
|
|
55
104
|
const processingCutoff = now - getProcessingStuckAfterMs();
|
|
105
|
+
const durableProcessingCutoff = now - DURABLE_BACKGROUND_PROCESSING_STUCK_AFTER_MS;
|
|
56
106
|
let stuckRows;
|
|
57
107
|
try {
|
|
58
108
|
const { rows } = await client.execute({
|
|
59
109
|
sql: `
|
|
60
|
-
SELECT id, status, attempts
|
|
110
|
+
SELECT id, platform, external_thread_id, dispatch_scope, status, attempts, updated_at
|
|
61
111
|
FROM integration_pending_tasks
|
|
62
|
-
WHERE (status = 'pending' AND created_at <= ? AND updated_at <= ?)
|
|
63
|
-
OR (status = 'processing' AND
|
|
112
|
+
WHERE ((status = 'pending' AND created_at <= ? AND updated_at <= ?)
|
|
113
|
+
OR (status = 'processing' AND (
|
|
114
|
+
(last_dispatch_outcome = 'background-acknowledged'
|
|
115
|
+
AND updated_at <= ?)
|
|
116
|
+
OR ((last_dispatch_outcome IS NULL
|
|
117
|
+
OR last_dispatch_outcome <> 'background-acknowledged')
|
|
118
|
+
AND updated_at <= ?)
|
|
119
|
+
)))
|
|
120
|
+
${scopeSql.clause}
|
|
121
|
+
ORDER BY updated_at ASC
|
|
122
|
+
LIMIT ?
|
|
64
123
|
`,
|
|
65
124
|
// `updated_at` is initialized to `created_at` on insert, so a genuinely
|
|
66
125
|
// stuck pending row still matches on the first sweep. The retry path
|
|
67
126
|
// below touches `updated_at`, which (with this predicate) keeps the row
|
|
68
127
|
// from being re-selected — and re-firing the processor — on every tick.
|
|
69
|
-
args: [
|
|
128
|
+
args: [
|
|
129
|
+
pendingCutoff,
|
|
130
|
+
pendingCutoff,
|
|
131
|
+
durableProcessingCutoff,
|
|
132
|
+
processingCutoff,
|
|
133
|
+
...scopeSql.args,
|
|
134
|
+
limit,
|
|
135
|
+
],
|
|
70
136
|
});
|
|
71
137
|
stuckRows = rows.map((r) => ({
|
|
72
138
|
id: r.id,
|
|
139
|
+
platform: r.platform,
|
|
140
|
+
externalThreadId: r.external_thread_id,
|
|
73
141
|
status: r.status,
|
|
74
142
|
attempts: Number(r.attempts ?? 0),
|
|
143
|
+
updatedAt: Number(r.updated_at ?? 0),
|
|
144
|
+
dispatchScope: r.dispatch_scope ?? null,
|
|
75
145
|
}));
|
|
76
146
|
tableExists = true;
|
|
77
147
|
}
|
|
78
148
|
catch (err) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
// no-op until the table appears.
|
|
149
|
+
if (!isMissingPendingTasksTableError(err))
|
|
150
|
+
throw err;
|
|
82
151
|
if (tableExists !== false) {
|
|
83
152
|
tableExists = false;
|
|
84
153
|
if (process.env.DEBUG) {
|
|
85
154
|
console.log("[integrations] pending-tasks retry job: table not present yet, skipping");
|
|
86
155
|
}
|
|
87
156
|
}
|
|
88
|
-
return;
|
|
157
|
+
return result;
|
|
89
158
|
}
|
|
159
|
+
stuckRows = stuckRows.filter((row) => {
|
|
160
|
+
const durable = isIntegrationDurableDispatchEnabledForTask({
|
|
161
|
+
platform: row.platform,
|
|
162
|
+
externalThreadId: row.externalThreadId,
|
|
163
|
+
platformContext: row.dispatchScope
|
|
164
|
+
? { channelId: row.dispatchScope }
|
|
165
|
+
: undefined,
|
|
166
|
+
});
|
|
167
|
+
if (options.durableOnly && !durable)
|
|
168
|
+
return false;
|
|
169
|
+
return true;
|
|
170
|
+
});
|
|
171
|
+
result.selected = stuckRows.length;
|
|
90
172
|
if (stuckRows.length === 0)
|
|
91
|
-
return;
|
|
173
|
+
return result;
|
|
92
174
|
for (const row of stuckRows) {
|
|
93
175
|
try {
|
|
94
176
|
// Cap retries — mark failed and move on so the row stops bouncing
|
|
95
177
|
// between pending and processing forever.
|
|
96
178
|
if (row.attempts >= MAX_PENDING_TASK_ATTEMPTS) {
|
|
97
|
-
await client.execute({
|
|
179
|
+
const update = await client.execute({
|
|
98
180
|
sql: `
|
|
99
181
|
UPDATE integration_pending_tasks
|
|
100
182
|
SET status = 'failed',
|
|
@@ -104,14 +186,21 @@ export async function retryStuckPendingTasks(webhookBaseUrl) {
|
|
|
104
186
|
external_event_key = NULL
|
|
105
187
|
WHERE id = ?
|
|
106
188
|
AND status = ?
|
|
189
|
+
AND updated_at = ?
|
|
107
190
|
`,
|
|
108
191
|
args: [
|
|
109
192
|
Date.now(),
|
|
110
193
|
`Retry job: exceeded ${MAX_PENDING_TASK_ATTEMPTS} attempts`,
|
|
111
194
|
row.id,
|
|
112
195
|
row.status,
|
|
196
|
+
row.updatedAt,
|
|
113
197
|
],
|
|
114
198
|
});
|
|
199
|
+
if (affectedRows(update) === 0) {
|
|
200
|
+
result.skipped += 1;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
result.markedFailed += 1;
|
|
115
204
|
console.warn(`[integrations] Pending task ${row.id} exceeded ${MAX_PENDING_TASK_ATTEMPTS} attempts — marking failed`);
|
|
116
205
|
continue;
|
|
117
206
|
}
|
|
@@ -120,21 +209,43 @@ export async function retryStuckPendingTasks(webhookBaseUrl) {
|
|
|
120
209
|
// Without this, processing rows stay stuck forever.
|
|
121
210
|
// For pending rows, just touch updated_at to avoid re-firing every tick.
|
|
122
211
|
const newStatus = row.status === "processing" ? "pending" : row.status;
|
|
123
|
-
await client.execute({
|
|
212
|
+
const update = await client.execute({
|
|
124
213
|
sql: `
|
|
125
214
|
UPDATE integration_pending_tasks
|
|
126
215
|
SET status = ?, updated_at = ?
|
|
127
216
|
WHERE id = ?
|
|
128
217
|
AND status = ?
|
|
218
|
+
AND updated_at = ?
|
|
129
219
|
`,
|
|
130
|
-
args: [newStatus, Date.now(), row.id, row.status],
|
|
220
|
+
args: [newStatus, Date.now(), row.id, row.status, row.updatedAt],
|
|
221
|
+
});
|
|
222
|
+
if (affectedRows(update) === 0) {
|
|
223
|
+
result.skipped += 1;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const outcome = await dispatchPendingIntegrationTask({
|
|
227
|
+
taskId: row.id,
|
|
228
|
+
task: {
|
|
229
|
+
platform: row.platform,
|
|
230
|
+
externalThreadId: row.externalThreadId,
|
|
231
|
+
platformContext: row.dispatchScope
|
|
232
|
+
? { channelId: row.dispatchScope }
|
|
233
|
+
: undefined,
|
|
234
|
+
},
|
|
235
|
+
baseUrl,
|
|
236
|
+
portableSettleMs: 1_000,
|
|
131
237
|
});
|
|
132
|
-
|
|
238
|
+
if (outcome === "failed")
|
|
239
|
+
result.dispatchFailed += 1;
|
|
240
|
+
else
|
|
241
|
+
result.dispatched += 1;
|
|
133
242
|
}
|
|
134
243
|
catch (err) {
|
|
244
|
+
result.dispatchFailed += 1;
|
|
135
245
|
console.error(`[integrations] Failed to retry pending task ${row.id}:`, err);
|
|
136
246
|
}
|
|
137
247
|
}
|
|
248
|
+
return result;
|
|
138
249
|
}
|
|
139
250
|
function getProcessingStuckAfterMs() {
|
|
140
251
|
if (process.env.NETLIFY ||
|
|
@@ -145,58 +256,6 @@ function getProcessingStuckAfterMs() {
|
|
|
145
256
|
}
|
|
146
257
|
return DEFAULT_PROCESSING_STUCK_AFTER_MS;
|
|
147
258
|
}
|
|
148
|
-
/**
|
|
149
|
-
* Fire-and-forget POST to the processor endpoint for a single task id.
|
|
150
|
-
* Mirrors the original dispatch from the webhook handler, including the
|
|
151
|
-
* short-lived HMAC bearer token bound to this taskId.
|
|
152
|
-
*/
|
|
153
|
-
async function refireProcessor(taskId, webhookBaseUrl) {
|
|
154
|
-
const baseUrl = webhookBaseUrl ||
|
|
155
|
-
process.env.WEBHOOK_BASE_URL ||
|
|
156
|
-
process.env.APP_URL ||
|
|
157
|
-
process.env.URL ||
|
|
158
|
-
process.env.DEPLOY_URL ||
|
|
159
|
-
`http://localhost:${process.env.PORT || 3000}`;
|
|
160
|
-
const url = `${withConfiguredAppBasePath(baseUrl)}${PROCESSOR_PATH}`;
|
|
161
|
-
// Sign with HMAC if A2A_SECRET is configured. In production we MUST sign —
|
|
162
|
-
// an unsigned dispatch in production lets attackers re-trigger any queued
|
|
163
|
-
// task with a guessable id (C3 in the webhook security audit). In dev we
|
|
164
|
-
// fall back to unsigned so contributors can iterate without configuring
|
|
165
|
-
// A2A_SECRET locally.
|
|
166
|
-
const headers = {
|
|
167
|
-
"Content-Type": "application/json",
|
|
168
|
-
};
|
|
169
|
-
try {
|
|
170
|
-
headers["Authorization"] = `Bearer ${signInternalToken(taskId)}`;
|
|
171
|
-
}
|
|
172
|
-
catch (err) {
|
|
173
|
-
if (process.env.NODE_ENV === "production") {
|
|
174
|
-
console.error(`[integrations] Refusing to dispatch task ${taskId} — A2A_SECRET not configured. ` +
|
|
175
|
-
"Set A2A_SECRET to enable signed retry dispatches.");
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
// Dev: proceed unsigned. Log the underlying error path so a malformed
|
|
179
|
-
// secret (different from "not set") doesn't fail silently (L5 in the audit).
|
|
180
|
-
if (err instanceof Error && !/A2A_SECRET/i.test(err.message)) {
|
|
181
|
-
console.error(`[integrations] signInternalToken failed unexpectedly for ${taskId}:`, err);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
// Don't await the body — we just want the request to leave the box.
|
|
185
|
-
// A short timeout avoids tying up the retry loop on a hung processor.
|
|
186
|
-
const controller = new AbortController();
|
|
187
|
-
const timer = setTimeout(() => controller.abort(), 5_000);
|
|
188
|
-
try {
|
|
189
|
-
await fetch(url, {
|
|
190
|
-
method: "POST",
|
|
191
|
-
headers,
|
|
192
|
-
body: JSON.stringify({ taskId }),
|
|
193
|
-
signal: controller.signal,
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
finally {
|
|
197
|
-
clearTimeout(timer);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
259
|
/**
|
|
201
260
|
* Start the periodic retry loop. Safe to call multiple times — second call
|
|
202
261
|
* is a no-op.
|