@agent-native/core 0.119.5 → 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 +6 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/durable-background.ts +1 -0
- 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/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/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/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +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 +3 -3
- 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/secrets/routes.d.ts +3 -3
- 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/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/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
|
@@ -11,12 +11,24 @@ export interface TaskQueueStats {
|
|
|
11
11
|
failed_last_hour: number;
|
|
12
12
|
oldest_pending_age_seconds: number;
|
|
13
13
|
recent_failures: RecentFailure[];
|
|
14
|
+
recent_tasks: Array<{
|
|
15
|
+
id: string;
|
|
16
|
+
platform: string;
|
|
17
|
+
status: string;
|
|
18
|
+
attempts: number;
|
|
19
|
+
dispatch_attempts: number;
|
|
20
|
+
last_dispatch_outcome: string | null;
|
|
21
|
+
age_seconds: number;
|
|
22
|
+
}>;
|
|
23
|
+
}
|
|
24
|
+
export interface TaskQueueStatsScope {
|
|
25
|
+
ownerEmail: string;
|
|
26
|
+
orgId: string | null;
|
|
14
27
|
}
|
|
15
28
|
/**
|
|
16
29
|
* Get a snapshot of the integration task queue health.
|
|
17
30
|
*
|
|
18
|
-
*
|
|
19
|
-
* pending-tasks store has initialised the schema.
|
|
31
|
+
* Safe to call before the pending-tasks store has initialized the schema.
|
|
20
32
|
*/
|
|
21
|
-
export declare function getTaskQueueStats(): Promise<TaskQueueStats>;
|
|
33
|
+
export declare function getTaskQueueStats(scope: TaskQueueStatsScope): Promise<TaskQueueStats>;
|
|
22
34
|
//# sourceMappingURL=task-queue-stats.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-queue-stats.d.ts","sourceRoot":"","sources":["../../src/integrations/task-queue-stats.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"task-queue-stats.d.ts","sourceRoot":"","sources":["../../src/integrations/task-queue-stats.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,gBAAgB,EAAE,MAAM,CAAC;IACzB,0BAA0B,EAAE,MAAM,CAAC;IACnC,eAAe,EAAE,aAAa,EAAE,CAAC;IACjC,YAAY,EAAE,KAAK,CAAC;QAClB,EAAE,EAAE,MAAM,CAAC;QACX,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;QACrC,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAmBD;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,cAAc,CAAC,CAkIzB"}
|
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Lives in its own file so it stays out of `pending-tasks-store.ts`, which is
|
|
5
5
|
* actively being edited by the agent that owns the queue itself. These
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Queue reads never expose payloads or user text. The helper first runs the
|
|
7
|
+
* additive schema guard so older deployments gain the dispatch diagnostic
|
|
8
|
+
* columns before the SELECTs execute.
|
|
9
9
|
*/
|
|
10
10
|
import { getDbExec } from "../db/client.js";
|
|
11
|
+
import { ensurePendingTasksTable } from "./pending-tasks-store.js";
|
|
11
12
|
const ZERO_STATS = {
|
|
12
13
|
pending: 0,
|
|
13
14
|
processing: 0,
|
|
@@ -15,6 +16,7 @@ const ZERO_STATS = {
|
|
|
15
16
|
failed_last_hour: 0,
|
|
16
17
|
oldest_pending_age_seconds: 0,
|
|
17
18
|
recent_failures: [],
|
|
19
|
+
recent_tasks: [],
|
|
18
20
|
};
|
|
19
21
|
function isMissingTableError(err) {
|
|
20
22
|
const msg = err instanceof Error ? err.message : String(err ?? "");
|
|
@@ -23,20 +25,24 @@ function isMissingTableError(err) {
|
|
|
23
25
|
/**
|
|
24
26
|
* Get a snapshot of the integration task queue health.
|
|
25
27
|
*
|
|
26
|
-
*
|
|
27
|
-
* pending-tasks store has initialised the schema.
|
|
28
|
+
* Safe to call before the pending-tasks store has initialized the schema.
|
|
28
29
|
*/
|
|
29
|
-
export async function getTaskQueueStats() {
|
|
30
|
+
export async function getTaskQueueStats(scope) {
|
|
31
|
+
await ensurePendingTasksTable();
|
|
30
32
|
const client = getDbExec();
|
|
31
33
|
const now = Date.now();
|
|
32
34
|
const oneHourAgo = now - 60 * 60 * 1000;
|
|
35
|
+
const scopeSql = `owner_email = ?
|
|
36
|
+
AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)`;
|
|
37
|
+
const scopeArgs = [scope.ownerEmail, scope.orgId, scope.orgId];
|
|
33
38
|
try {
|
|
34
39
|
// Status counts (pending, processing) — only need the live ones.
|
|
35
40
|
const liveCounts = await client.execute({
|
|
36
41
|
sql: `SELECT status, COUNT(*) AS c FROM integration_pending_tasks
|
|
37
|
-
WHERE
|
|
42
|
+
WHERE ${scopeSql}
|
|
43
|
+
AND status IN ('pending', 'processing')
|
|
38
44
|
GROUP BY status`,
|
|
39
|
-
args:
|
|
45
|
+
args: scopeArgs,
|
|
40
46
|
});
|
|
41
47
|
let pending = 0;
|
|
42
48
|
let processing = 0;
|
|
@@ -53,9 +59,10 @@ export async function getTaskQueueStats() {
|
|
|
53
59
|
// miss tasks queued >1h ago that just finished now.
|
|
54
60
|
const lastHourCounts = await client.execute({
|
|
55
61
|
sql: `SELECT status, COUNT(*) AS c FROM integration_pending_tasks
|
|
56
|
-
WHERE
|
|
62
|
+
WHERE ${scopeSql}
|
|
63
|
+
AND status IN ('completed', 'failed') AND updated_at >= ?
|
|
57
64
|
GROUP BY status`,
|
|
58
|
-
args: [oneHourAgo],
|
|
65
|
+
args: [...scopeArgs, oneHourAgo],
|
|
59
66
|
});
|
|
60
67
|
let completedLastHour = 0;
|
|
61
68
|
let failedLastHour = 0;
|
|
@@ -72,10 +79,11 @@ export async function getTaskQueueStats() {
|
|
|
72
79
|
if (pending > 0) {
|
|
73
80
|
const oldest = await client.execute({
|
|
74
81
|
sql: `SELECT created_at FROM integration_pending_tasks
|
|
75
|
-
WHERE
|
|
82
|
+
WHERE ${scopeSql}
|
|
83
|
+
AND status = 'pending'
|
|
76
84
|
ORDER BY created_at ASC
|
|
77
85
|
LIMIT 1`,
|
|
78
|
-
args:
|
|
86
|
+
args: scopeArgs,
|
|
79
87
|
});
|
|
80
88
|
const oldestRow = oldest.rows[0];
|
|
81
89
|
if (oldestRow) {
|
|
@@ -87,10 +95,11 @@ export async function getTaskQueueStats() {
|
|
|
87
95
|
// blowing up the response payload.
|
|
88
96
|
const failures = await client.execute({
|
|
89
97
|
sql: `SELECT id, platform, error_message, attempts FROM integration_pending_tasks
|
|
90
|
-
WHERE
|
|
98
|
+
WHERE ${scopeSql}
|
|
99
|
+
AND status = 'failed' AND updated_at >= ?
|
|
91
100
|
ORDER BY updated_at DESC
|
|
92
101
|
LIMIT 5`,
|
|
93
|
-
args: [oneHourAgo],
|
|
102
|
+
args: [...scopeArgs, oneHourAgo],
|
|
94
103
|
});
|
|
95
104
|
const recentFailures = failures.rows.map((row) => ({
|
|
96
105
|
id: String(row.id ?? ""),
|
|
@@ -98,6 +107,26 @@ export async function getTaskQueueStats() {
|
|
|
98
107
|
error: String(row.error_message ?? ""),
|
|
99
108
|
attempts: Number(row.attempts ?? 0),
|
|
100
109
|
}));
|
|
110
|
+
const recent = await client.execute({
|
|
111
|
+
sql: `SELECT id, platform, status, attempts, dispatch_attempts,
|
|
112
|
+
last_dispatch_outcome, created_at
|
|
113
|
+
FROM integration_pending_tasks
|
|
114
|
+
WHERE ${scopeSql}
|
|
115
|
+
ORDER BY created_at DESC
|
|
116
|
+
LIMIT 10`,
|
|
117
|
+
args: scopeArgs,
|
|
118
|
+
});
|
|
119
|
+
const recentTasks = recent.rows.map((row) => ({
|
|
120
|
+
id: String(row.id ?? ""),
|
|
121
|
+
platform: String(row.platform ?? ""),
|
|
122
|
+
status: String(row.status ?? ""),
|
|
123
|
+
attempts: Number(row.attempts ?? 0),
|
|
124
|
+
dispatch_attempts: Number(row.dispatch_attempts ?? 0),
|
|
125
|
+
last_dispatch_outcome: row.last_dispatch_outcome == null
|
|
126
|
+
? null
|
|
127
|
+
: String(row.last_dispatch_outcome),
|
|
128
|
+
age_seconds: Math.max(0, Math.floor((now - Number(row.created_at ?? now)) / 1000)),
|
|
129
|
+
}));
|
|
101
130
|
return {
|
|
102
131
|
pending,
|
|
103
132
|
processing,
|
|
@@ -105,11 +134,12 @@ export async function getTaskQueueStats() {
|
|
|
105
134
|
failed_last_hour: failedLastHour,
|
|
106
135
|
oldest_pending_age_seconds: oldestPendingAgeSeconds,
|
|
107
136
|
recent_failures: recentFailures,
|
|
137
|
+
recent_tasks: recentTasks,
|
|
108
138
|
};
|
|
109
139
|
}
|
|
110
140
|
catch (err) {
|
|
111
141
|
if (isMissingTableError(err)) {
|
|
112
|
-
return { ...ZERO_STATS, recent_failures: [] };
|
|
142
|
+
return { ...ZERO_STATS, recent_failures: [], recent_tasks: [] };
|
|
113
143
|
}
|
|
114
144
|
throw err;
|
|
115
145
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-queue-stats.js","sourceRoot":"","sources":["../../src/integrations/task-queue-stats.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Read-only observability helpers for the integration task queue.\n *\n * Lives in its own file so it stays out of `pending-tasks-store.ts`, which is\n * actively being edited by the agent that owns the queue itself. These\n * helpers only SELECT — they never write — and they degrade gracefully if\n * the `integration_pending_tasks` table doesn't exist yet (returning zeroed\n * stats instead of throwing).\n */\nimport { getDbExec } from \"../db/client.js\";\n\nexport interface RecentFailure {\n id: string;\n platform: string;\n error: string;\n attempts: number;\n}\n\nexport interface TaskQueueStats {\n pending: number;\n processing: number;\n completed_last_hour: number;\n failed_last_hour: number;\n oldest_pending_age_seconds: number;\n recent_failures: RecentFailure[];\n}\n\nconst ZERO_STATS: TaskQueueStats = {\n pending: 0,\n processing: 0,\n completed_last_hour: 0,\n failed_last_hour: 0,\n oldest_pending_age_seconds: 0,\n recent_failures: [],\n};\n\nfunction isMissingTableError(err: unknown): boolean {\n const msg = err instanceof Error ? err.message : String(err ?? \"\");\n return /no such table|does not exist|relation .* does not exist|undefined_table/i.test(\n msg,\n );\n}\n\n/**\n * Get a snapshot of the integration task queue health.\n *\n * Returns zeros if the table doesn't exist yet — safe to call before the\n * pending-tasks store has initialised the schema.\n */\nexport async function getTaskQueueStats(): Promise<TaskQueueStats> {\n const client = getDbExec();\n const now = Date.now();\n const oneHourAgo = now - 60 * 60 * 1000;\n\n try {\n // Status counts (pending, processing) — only need the live ones.\n const liveCounts = await client.execute({\n sql: `SELECT status, COUNT(*) AS c FROM integration_pending_tasks\n WHERE status IN ('pending', 'processing')\n GROUP BY status`,\n args: [],\n });\n\n let pending = 0;\n let processing = 0;\n for (const row of liveCounts.rows as Array<Record<string, unknown>>) {\n const status = row.status as string;\n const count = Number(row.c ?? 0);\n if (status === \"pending\") pending = count;\n else if (status === \"processing\") processing = count;\n }\n\n // Last-hour completion + failure counts. updated_at is the most reliable\n // column — completed_at can be null on failed tasks, and created_at would\n // miss tasks queued >1h ago that just finished now.\n const lastHourCounts = await client.execute({\n sql: `SELECT status, COUNT(*) AS c FROM integration_pending_tasks\n WHERE status IN ('completed', 'failed') AND updated_at >= ?\n GROUP BY status`,\n args: [oneHourAgo],\n });\n\n let completedLastHour = 0;\n let failedLastHour = 0;\n for (const row of lastHourCounts.rows as Array<Record<string, unknown>>) {\n const status = row.status as string;\n const count = Number(row.c ?? 0);\n if (status === \"completed\") completedLastHour = count;\n else if (status === \"failed\") failedLastHour = count;\n }\n\n // Oldest pending task — used to surface stuck queues.\n let oldestPendingAgeSeconds = 0;\n if (pending > 0) {\n const oldest = await client.execute({\n sql: `SELECT created_at FROM integration_pending_tasks\n WHERE status = 'pending'\n ORDER BY created_at ASC\n LIMIT 1`,\n args: [],\n });\n const oldestRow = oldest.rows[0] as Record<string, unknown> | undefined;\n if (oldestRow) {\n const createdAt = Number(oldestRow.created_at ?? now);\n oldestPendingAgeSeconds = Math.max(\n 0,\n Math.floor((now - createdAt) / 1000),\n );\n }\n }\n\n // Recent failures, capped at 5 — enough to spot patterns without\n // blowing up the response payload.\n const failures = await client.execute({\n sql: `SELECT id, platform, error_message, attempts FROM integration_pending_tasks\n WHERE status = 'failed' AND updated_at >= ?\n ORDER BY updated_at DESC\n LIMIT 5`,\n args: [oneHourAgo],\n });\n const recentFailures: RecentFailure[] = (\n failures.rows as Array<Record<string, unknown>>\n ).map((row) => ({\n id: String(row.id ?? \"\"),\n platform: String(row.platform ?? \"\"),\n error: String(row.error_message ?? \"\"),\n attempts: Number(row.attempts ?? 0),\n }));\n\n return {\n pending,\n processing,\n completed_last_hour: completedLastHour,\n failed_last_hour: failedLastHour,\n oldest_pending_age_seconds: oldestPendingAgeSeconds,\n recent_failures: recentFailures,\n };\n } catch (err) {\n if (isMissingTableError(err)) {\n return { ...ZERO_STATS, recent_failures: [] };\n }\n throw err;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"task-queue-stats.js","sourceRoot":"","sources":["../../src/integrations/task-queue-stats.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Read-only observability helpers for the integration task queue.\n *\n * Lives in its own file so it stays out of `pending-tasks-store.ts`, which is\n * actively being edited by the agent that owns the queue itself. These\n * Queue reads never expose payloads or user text. The helper first runs the\n * additive schema guard so older deployments gain the dispatch diagnostic\n * columns before the SELECTs execute.\n */\nimport { getDbExec } from \"../db/client.js\";\nimport { ensurePendingTasksTable } from \"./pending-tasks-store.js\";\n\nexport interface RecentFailure {\n id: string;\n platform: string;\n error: string;\n attempts: number;\n}\n\nexport interface TaskQueueStats {\n pending: number;\n processing: number;\n completed_last_hour: number;\n failed_last_hour: number;\n oldest_pending_age_seconds: number;\n recent_failures: RecentFailure[];\n recent_tasks: Array<{\n id: string;\n platform: string;\n status: string;\n attempts: number;\n dispatch_attempts: number;\n last_dispatch_outcome: string | null;\n age_seconds: number;\n }>;\n}\n\nexport interface TaskQueueStatsScope {\n ownerEmail: string;\n orgId: string | null;\n}\n\nconst ZERO_STATS: TaskQueueStats = {\n pending: 0,\n processing: 0,\n completed_last_hour: 0,\n failed_last_hour: 0,\n oldest_pending_age_seconds: 0,\n recent_failures: [],\n recent_tasks: [],\n};\n\nfunction isMissingTableError(err: unknown): boolean {\n const msg = err instanceof Error ? err.message : String(err ?? \"\");\n return /no such table|does not exist|relation .* does not exist|undefined_table/i.test(\n msg,\n );\n}\n\n/**\n * Get a snapshot of the integration task queue health.\n *\n * Safe to call before the pending-tasks store has initialized the schema.\n */\nexport async function getTaskQueueStats(\n scope: TaskQueueStatsScope,\n): Promise<TaskQueueStats> {\n await ensurePendingTasksTable();\n const client = getDbExec();\n const now = Date.now();\n const oneHourAgo = now - 60 * 60 * 1000;\n const scopeSql = `owner_email = ?\n AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)`;\n const scopeArgs = [scope.ownerEmail, scope.orgId, scope.orgId];\n\n try {\n // Status counts (pending, processing) — only need the live ones.\n const liveCounts = await client.execute({\n sql: `SELECT status, COUNT(*) AS c FROM integration_pending_tasks\n WHERE ${scopeSql}\n AND status IN ('pending', 'processing')\n GROUP BY status`,\n args: scopeArgs,\n });\n\n let pending = 0;\n let processing = 0;\n for (const row of liveCounts.rows as Array<Record<string, unknown>>) {\n const status = row.status as string;\n const count = Number(row.c ?? 0);\n if (status === \"pending\") pending = count;\n else if (status === \"processing\") processing = count;\n }\n\n // Last-hour completion + failure counts. updated_at is the most reliable\n // column — completed_at can be null on failed tasks, and created_at would\n // miss tasks queued >1h ago that just finished now.\n const lastHourCounts = await client.execute({\n sql: `SELECT status, COUNT(*) AS c FROM integration_pending_tasks\n WHERE ${scopeSql}\n AND status IN ('completed', 'failed') AND updated_at >= ?\n GROUP BY status`,\n args: [...scopeArgs, oneHourAgo],\n });\n\n let completedLastHour = 0;\n let failedLastHour = 0;\n for (const row of lastHourCounts.rows as Array<Record<string, unknown>>) {\n const status = row.status as string;\n const count = Number(row.c ?? 0);\n if (status === \"completed\") completedLastHour = count;\n else if (status === \"failed\") failedLastHour = count;\n }\n\n // Oldest pending task — used to surface stuck queues.\n let oldestPendingAgeSeconds = 0;\n if (pending > 0) {\n const oldest = await client.execute({\n sql: `SELECT created_at FROM integration_pending_tasks\n WHERE ${scopeSql}\n AND status = 'pending'\n ORDER BY created_at ASC\n LIMIT 1`,\n args: scopeArgs,\n });\n const oldestRow = oldest.rows[0] as Record<string, unknown> | undefined;\n if (oldestRow) {\n const createdAt = Number(oldestRow.created_at ?? now);\n oldestPendingAgeSeconds = Math.max(\n 0,\n Math.floor((now - createdAt) / 1000),\n );\n }\n }\n\n // Recent failures, capped at 5 — enough to spot patterns without\n // blowing up the response payload.\n const failures = await client.execute({\n sql: `SELECT id, platform, error_message, attempts FROM integration_pending_tasks\n WHERE ${scopeSql}\n AND status = 'failed' AND updated_at >= ?\n ORDER BY updated_at DESC\n LIMIT 5`,\n args: [...scopeArgs, oneHourAgo],\n });\n const recentFailures: RecentFailure[] = (\n failures.rows as Array<Record<string, unknown>>\n ).map((row) => ({\n id: String(row.id ?? \"\"),\n platform: String(row.platform ?? \"\"),\n error: String(row.error_message ?? \"\"),\n attempts: Number(row.attempts ?? 0),\n }));\n\n const recent = await client.execute({\n sql: `SELECT id, platform, status, attempts, dispatch_attempts,\n last_dispatch_outcome, created_at\n FROM integration_pending_tasks\n WHERE ${scopeSql}\n ORDER BY created_at DESC\n LIMIT 10`,\n args: scopeArgs,\n });\n const recentTasks = (recent.rows as Array<Record<string, unknown>>).map(\n (row) => ({\n id: String(row.id ?? \"\"),\n platform: String(row.platform ?? \"\"),\n status: String(row.status ?? \"\"),\n attempts: Number(row.attempts ?? 0),\n dispatch_attempts: Number(row.dispatch_attempts ?? 0),\n last_dispatch_outcome:\n row.last_dispatch_outcome == null\n ? null\n : String(row.last_dispatch_outcome),\n age_seconds: Math.max(\n 0,\n Math.floor((now - Number(row.created_at ?? now)) / 1000),\n ),\n }),\n );\n\n return {\n pending,\n processing,\n completed_last_hour: completedLastHour,\n failed_last_hour: failedLastHour,\n oldest_pending_age_seconds: oldestPendingAgeSeconds,\n recent_failures: recentFailures,\n recent_tasks: recentTasks,\n };\n } catch (err) {\n if (isMissingTableError(err)) {\n return { ...ZERO_STATS, recent_failures: [], recent_tasks: [] };\n }\n throw err;\n }\n}\n"]}
|
|
@@ -81,7 +81,7 @@ export declare function resolveIntegrationApiKey(engineOption: WebhookHandlerOpt
|
|
|
81
81
|
* 2. Verify webhook signature
|
|
82
82
|
* 3. Parse incoming message (null = ignored event)
|
|
83
83
|
* 4. Persist task to SQL
|
|
84
|
-
* 5.
|
|
84
|
+
* 5. Dispatch the queued task through the configured processor handoff
|
|
85
85
|
* (a fresh function execution with its own timeout budget)
|
|
86
86
|
* 6. Return HTTP 200 immediately (within Slack's 3s SLA)
|
|
87
87
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook-handler.d.ts","sourceRoot":"","sources":["../../src/integrations/webhook-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAElC,OAAO,EAIL,KAAK,mBAAmB,EAEzB,MAAM,6BAA6B,CAAC;AAcrC,OAAO,KAAK,EAAE,WAAW,EAAiB,MAAM,0BAA0B,CAAC;AAC3E,OAAO,EAOL,KAAK,WAAW,EACjB,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"webhook-handler.d.ts","sourceRoot":"","sources":["../../src/integrations/webhook-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,14 +14,13 @@ import { updateThreadData } from "../chat-threads/store.js";
|
|
|
14
14
|
import { isLocalDatabase } from "../db/client.js";
|
|
15
15
|
import { getOrgA2ASecret, resolveOrgIdForEmail } from "../org/context.js";
|
|
16
16
|
import { withConfiguredAppBasePath } from "../server/app-base-path.js";
|
|
17
|
-
import { FRAMEWORK_ROUTE_PREFIX } from "../server/core-routes-plugin.js";
|
|
18
17
|
import { canUseDeployCredentialFallbackForRequest, readDeployCredentialEnv, } from "../server/credential-provider.js";
|
|
19
18
|
import { runWithRequestContext } from "../server/request-context.js";
|
|
20
19
|
import { normalizeReasoningEffortForRequest } from "../shared/reasoning-effort.js";
|
|
21
20
|
import { A2A_CONTINUATION_QUEUED_MARKER } from "./a2a-continuation-marker.js";
|
|
22
21
|
import { clearIntegrationAwaitingInput, setIntegrationAwaitingInput, } from "./awaiting-input-store.js";
|
|
22
|
+
import { dispatchPendingIntegrationTask, integrationDispatchScopeValue, } from "./integration-durable-dispatch.js";
|
|
23
23
|
import { loadIntegrationMemoryPrompt } from "./integration-memory.js";
|
|
24
|
-
import { signInternalToken } from "./internal-token.js";
|
|
25
24
|
import { insertPendingTask, isDuplicateEventError, stageTaskDeliveryPayload, } from "./pending-tasks-store.js";
|
|
26
25
|
import { integrationScopeSubjectKey } from "./scope-store.js";
|
|
27
26
|
import { getThreadMapping, saveThreadMapping } from "./thread-mapping-store.js";
|
|
@@ -138,7 +137,7 @@ export async function resolveIntegrationApiKey(engineOption, ownerEmail, fallbac
|
|
|
138
137
|
* 2. Verify webhook signature
|
|
139
138
|
* 3. Parse incoming message (null = ignored event)
|
|
140
139
|
* 4. Persist task to SQL
|
|
141
|
-
* 5.
|
|
140
|
+
* 5. Dispatch the queued task through the configured processor handoff
|
|
142
141
|
* (a fresh function execution with its own timeout budget)
|
|
143
142
|
* 6. Return HTTP 200 immediately (within Slack's 3s SLA)
|
|
144
143
|
*
|
|
@@ -280,50 +279,27 @@ async function enqueueAndDispatch(event, incoming, options, handlerStartedAt = D
|
|
|
280
279
|
// platform produce the same key, so the unique index rejects the
|
|
281
280
|
// second insert (H3 in the webhook security audit).
|
|
282
281
|
externalEventKey: buildEventDedupKey(incoming),
|
|
282
|
+
dispatchScope: integrationDispatchScopeValue({
|
|
283
|
+
platform: incoming.platform,
|
|
284
|
+
externalThreadId: incoming.externalThreadId,
|
|
285
|
+
platformContext: incoming.platformContext,
|
|
286
|
+
}),
|
|
283
287
|
});
|
|
284
288
|
const baseUrl = resolveBaseUrl(event);
|
|
285
|
-
const processUrl = `${baseUrl}${FRAMEWORK_ROUTE_PREFIX}/integrations/process-task`;
|
|
286
|
-
// Sign the dispatch with an HMAC token so the processor endpoint can
|
|
287
|
-
// verify the request came from us and not the public internet. The
|
|
288
|
-
// processor refuses unsigned requests in production (C3 in the webhook
|
|
289
|
-
// security audit). In dev, dispatching unsigned is allowed and falls
|
|
290
|
-
// through to the SQL atomic claim for double-processing protection.
|
|
291
|
-
const headers = {
|
|
292
|
-
"Content-Type": "application/json",
|
|
293
|
-
};
|
|
294
|
-
try {
|
|
295
|
-
headers["Authorization"] = `Bearer ${signInternalToken(taskId)}`;
|
|
296
|
-
}
|
|
297
|
-
catch (err) {
|
|
298
|
-
// Distinguish "secret not configured" (the documented dev path) from
|
|
299
|
-
// a real signing failure — silently swallowing both made malformed
|
|
300
|
-
// secrets fail invisibly (L5 in the audit).
|
|
301
|
-
if (err instanceof Error && !/A2A_SECRET/i.test(err.message)) {
|
|
302
|
-
console.error(`[integrations] signInternalToken failed unexpectedly for ${taskId}:`, err);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
// Fire-and-forget: do NOT await the full response (the processor's run
|
|
306
|
-
// takes minutes — we don't want to block the caller). BUT on Netlify
|
|
307
|
-
// Lambda, when we return immediately, the runtime can freeze the function
|
|
308
|
-
// before the outbound TCP handshake even starts, which leaves the dispatch
|
|
309
|
-
// request stuck waiting for the 60s retry-sweep job. Race the fetch
|
|
310
|
-
// against a short timer so the request gets a reasonable chance to leave
|
|
311
|
-
// the box; the trade-off is at most a couple seconds of added webhook
|
|
312
|
-
// latency, still inside Slack's timeout window.
|
|
313
|
-
const dispatchPromise = fetch(processUrl, {
|
|
314
|
-
method: "POST",
|
|
315
|
-
headers,
|
|
316
|
-
body: JSON.stringify({ taskId }),
|
|
317
|
-
}).catch((err) => {
|
|
318
|
-
console.error("[integrations] Failed to dispatch processor request:", err);
|
|
319
|
-
});
|
|
320
289
|
const settleWaitMs = options.adapter.capabilities?.deferredWebhookResponse
|
|
321
290
|
? Math.min(DEFERRED_RESPONSE_DISPATCH_SETTLE_WAIT_MS, Math.max(0, DEFERRED_RESPONSE_MAX_HANDLER_MS - (Date.now() - handlerStartedAt)))
|
|
322
291
|
: PROCESSOR_DISPATCH_SETTLE_WAIT_MS;
|
|
323
|
-
await
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
292
|
+
await dispatchPendingIntegrationTask({
|
|
293
|
+
taskId,
|
|
294
|
+
task: {
|
|
295
|
+
platform: incoming.platform,
|
|
296
|
+
externalThreadId: incoming.externalThreadId,
|
|
297
|
+
platformContext: incoming.platformContext,
|
|
298
|
+
},
|
|
299
|
+
event,
|
|
300
|
+
baseUrl,
|
|
301
|
+
portableSettleMs: settleWaitMs,
|
|
302
|
+
});
|
|
327
303
|
}
|
|
328
304
|
/**
|
|
329
305
|
* Resolve the base URL we should dispatch the processor request to.
|