@agent-native/core 0.114.9 → 0.114.11

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.
Files changed (150) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +19 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/engine/ai-sdk-engine.ts +31 -3
  5. package/corpus/core/src/agent/engine/anthropic-engine.ts +20 -3
  6. package/corpus/core/src/agent/engine/builder-engine.ts +56 -9
  7. package/corpus/core/src/agent/engine/first-event-timeout.ts +64 -0
  8. package/corpus/core/src/cli/pr-visual-recap-workflow.ts +2 -3
  9. package/corpus/core/src/client/AssistantChat.tsx +7 -3
  10. package/corpus/core/src/client/resources/ResourcesPanel.tsx +1 -1
  11. package/corpus/core/src/extensions/actions.ts +219 -0
  12. package/corpus/core/src/integrations/computer-supervision-store.ts +4 -4
  13. package/corpus/core/src/integrations/pending-tasks-store.ts +1 -1
  14. package/corpus/core/src/integrations/remote-commands-store.ts +5 -5
  15. package/corpus/core/src/integrations/remote-devices-store.ts +3 -3
  16. package/corpus/core/src/integrations/remote-push-store.ts +3 -3
  17. package/corpus/core/src/observability/traces.ts +38 -1
  18. package/corpus/core/src/server/action-routes.ts +23 -1
  19. package/corpus/core/src/server/http-response-telemetry.ts +6 -1
  20. package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +19 -0
  21. package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +1 -1
  22. package/corpus/core/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
  23. package/corpus/templates/analytics/actions/account-deep-dive.ts +41 -6
  24. package/corpus/templates/analytics/changelog/2026-07-20-account-deep-dives-finish-with-partial-data-when-hubspot-lo.md +6 -0
  25. package/corpus/templates/analytics/changelog/2026-07-20-dashboard-email-reports-now-include-an-image-when-a-single-p.md +6 -0
  26. package/corpus/templates/analytics/server/lib/dashboard-report.ts +11 -3
  27. package/corpus/templates/calendar/actions/create-event.ts +5 -0
  28. package/corpus/templates/calendar/app/components/ThemeToggle.tsx +4 -1
  29. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +0 -1
  30. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +309 -421
  31. package/corpus/templates/calendar/app/components/calendar/InlineEventPickers.tsx +349 -0
  32. package/corpus/templates/calendar/app/i18n-data.ts +110 -0
  33. package/corpus/templates/calendar/app/lib/event-form-utils.ts +6 -0
  34. package/corpus/templates/calendar/app/pages/CalendarView.tsx +3 -0
  35. package/corpus/templates/calendar/app/root.tsx +4 -2
  36. package/corpus/templates/calendar/changelog/2026-07-20-events-can-be-updated-inline-with-date-time-timezone-and-rep.md +6 -0
  37. package/corpus/templates/calendar/changelog/2026-07-20-public-booking-pages-no-longer-hydrate-with-a-theme-mismatch.md +6 -0
  38. package/corpus/templates/calendar/shared/api.ts +1 -0
  39. package/corpus/templates/clips/AGENTS.md +7 -0
  40. package/corpus/templates/clips/actions/create-recording.ts +51 -10
  41. package/corpus/templates/clips/actions/get-recording-player-data.ts +34 -5
  42. package/corpus/templates/clips/actions/lib/transcript-preview.ts +48 -0
  43. package/corpus/templates/clips/actions/list-recordings.ts +15 -4
  44. package/corpus/templates/clips/actions/regenerate-summary.ts +32 -0
  45. package/corpus/templates/clips/actions/search-recordings.ts +28 -6
  46. package/corpus/templates/clips/actions/view-screen.ts +54 -47
  47. package/corpus/templates/clips/app/components/library/library-grid.tsx +1 -5
  48. package/corpus/templates/clips/app/components/library/recording-card.tsx +29 -32
  49. package/corpus/templates/clips/app/components/meetings/quick-ask-sidebar.tsx +13 -0
  50. package/corpus/templates/clips/app/components/player/video-player.tsx +48 -0
  51. package/corpus/templates/clips/app/lib/fmp4.ts +26 -4
  52. package/corpus/templates/clips/app/lib/mse-video-loader.ts +13 -2
  53. package/corpus/templates/clips/changelog/2026-07-20-agent-summaries-use-bounded-transcript-context.md +6 -0
  54. package/corpus/templates/clips/changelog/2026-07-20-clip-summaries-now-finish-without-sending-a-long-transcript-.md +6 -0
  55. package/corpus/templates/clips/changelog/2026-07-20-clips-agents-only-discover-recordings-you-own-or-have-alread.md +6 -0
  56. package/corpus/templates/clips/changelog/2026-07-20-hosted-recordings-no-longer-start-with-an-impossible-buffered-upload.md +6 -0
  57. package/corpus/templates/clips/changelog/2026-07-20-meeting-recordings-now-offer-a-cleaner-transcript-view-with-.md +6 -0
  58. package/corpus/templates/clips/changelog/2026-07-20-rewind-capture-status.md +6 -0
  59. package/corpus/templates/clips/changelog/2026-07-20-shared-clips-can-be-shared-with-limited-access-and-cards-sho.md +6 -0
  60. package/corpus/templates/clips/desktop/src/app.tsx +81 -71
  61. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +12 -8
  62. package/corpus/templates/clips/desktop/src/lib/rewind-status.ts +103 -0
  63. package/corpus/templates/clips/desktop/src/overlays/pill-logo.tsx +2 -15
  64. package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +107 -340
  65. package/corpus/templates/clips/desktop/src/styles.css +37 -145
  66. package/corpus/templates/clips/desktop/src-tauri/src/recording_indicator.rs +6 -7
  67. package/corpus/templates/clips/server/lib/agent-recording-access.ts +73 -0
  68. package/corpus/templates/clips/server/plugins/agent-chat.ts +5 -2
  69. package/corpus/templates/plan/app/lib/plan-local-bridge.ts +18 -0
  70. package/corpus/templates/plan/app/pages/PlansPage.tsx +10 -11
  71. package/corpus/templates/plan/changelog/2026-07-20-bridged-local-plans-now-load-comments-from-the-local-workspace.md +6 -0
  72. package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
  73. package/dist/agent/engine/ai-sdk-engine.js +27 -4
  74. package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
  75. package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
  76. package/dist/agent/engine/anthropic-engine.js +19 -4
  77. package/dist/agent/engine/anthropic-engine.js.map +1 -1
  78. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  79. package/dist/agent/engine/builder-engine.js +40 -7
  80. package/dist/agent/engine/builder-engine.js.map +1 -1
  81. package/dist/agent/engine/first-event-timeout.d.ts +25 -0
  82. package/dist/agent/engine/first-event-timeout.d.ts.map +1 -0
  83. package/dist/agent/engine/first-event-timeout.js +49 -0
  84. package/dist/agent/engine/first-event-timeout.js.map +1 -0
  85. package/dist/cli/pr-visual-recap-workflow.d.ts +2 -2
  86. package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
  87. package/dist/cli/pr-visual-recap-workflow.js +2 -2
  88. package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
  89. package/dist/client/AssistantChat.d.ts.map +1 -1
  90. package/dist/client/AssistantChat.js +7 -3
  91. package/dist/client/AssistantChat.js.map +1 -1
  92. package/dist/client/resources/ResourcesPanel.js +1 -1
  93. package/dist/client/resources/ResourcesPanel.js.map +1 -1
  94. package/dist/collab/awareness.d.ts +2 -2
  95. package/dist/collab/awareness.d.ts.map +1 -1
  96. package/dist/collab/routes.d.ts +1 -1
  97. package/dist/collab/struct-routes.d.ts +1 -1
  98. package/dist/extensions/actions.d.ts.map +1 -1
  99. package/dist/extensions/actions.js +193 -1
  100. package/dist/extensions/actions.js.map +1 -1
  101. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  102. package/dist/integrations/computer-supervision-store.js +4 -4
  103. package/dist/integrations/computer-supervision-store.js.map +1 -1
  104. package/dist/integrations/pending-tasks-store.js +1 -1
  105. package/dist/integrations/pending-tasks-store.js.map +1 -1
  106. package/dist/integrations/remote-commands-store.js +5 -5
  107. package/dist/integrations/remote-commands-store.js.map +1 -1
  108. package/dist/integrations/remote-devices-store.js +3 -3
  109. package/dist/integrations/remote-devices-store.js.map +1 -1
  110. package/dist/integrations/remote-push-store.js +3 -3
  111. package/dist/integrations/remote-push-store.js.map +1 -1
  112. package/dist/notifications/routes.d.ts +2 -2
  113. package/dist/observability/routes.d.ts +3 -3
  114. package/dist/observability/traces.d.ts.map +1 -1
  115. package/dist/observability/traces.js +23 -1
  116. package/dist/observability/traces.js.map +1 -1
  117. package/dist/progress/routes.d.ts +1 -1
  118. package/dist/resources/handlers.d.ts +1 -1
  119. package/dist/secrets/routes.d.ts +3 -3
  120. package/dist/server/action-routes.d.ts.map +1 -1
  121. package/dist/server/action-routes.js +23 -1
  122. package/dist/server/action-routes.js.map +1 -1
  123. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  124. package/dist/server/http-response-telemetry.d.ts +2 -0
  125. package/dist/server/http-response-telemetry.d.ts.map +1 -1
  126. package/dist/server/http-response-telemetry.js +5 -1
  127. package/dist/server/http-response-telemetry.js.map +1 -1
  128. package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +19 -0
  129. package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +1 -1
  130. package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
  131. package/package.json +2 -2
  132. package/src/agent/engine/ai-sdk-engine.ts +31 -3
  133. package/src/agent/engine/anthropic-engine.ts +20 -3
  134. package/src/agent/engine/builder-engine.ts +56 -9
  135. package/src/agent/engine/first-event-timeout.ts +64 -0
  136. package/src/cli/pr-visual-recap-workflow.ts +2 -3
  137. package/src/client/AssistantChat.tsx +7 -3
  138. package/src/client/resources/ResourcesPanel.tsx +1 -1
  139. package/src/extensions/actions.ts +219 -0
  140. package/src/integrations/computer-supervision-store.ts +4 -4
  141. package/src/integrations/pending-tasks-store.ts +1 -1
  142. package/src/integrations/remote-commands-store.ts +5 -5
  143. package/src/integrations/remote-devices-store.ts +3 -3
  144. package/src/integrations/remote-push-store.ts +3 -3
  145. package/src/observability/traces.ts +38 -1
  146. package/src/server/action-routes.ts +23 -1
  147. package/src/server/http-response-telemetry.ts +6 -1
  148. package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +19 -0
  149. package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +1 -1
  150. package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
@@ -100,7 +100,7 @@ export async function decideComputerApproval(input) {
100
100
  SET status = ?, decision_result_json = ?, decided_by = ?,
101
101
  decided_at = ?, updated_at = ?
102
102
  WHERE id = ? AND owner_email = ?
103
- AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)
103
+ AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)
104
104
  AND action_hash = ? AND status = 'pending' AND expires_at > ?`,
105
105
  args: [
106
106
  input.decision,
@@ -125,7 +125,7 @@ export async function getComputerApprovalForOwner(input) {
125
125
  const { rows } = await getDbExec().execute({
126
126
  sql: `SELECT * FROM integration_computer_approvals
127
127
  WHERE id = ? AND owner_email = ?
128
- AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)
128
+ AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)
129
129
  LIMIT 1`,
130
130
  args: [
131
131
  input.id,
@@ -140,7 +140,7 @@ export async function listComputerApprovalsForOwner(input) {
140
140
  await ensureComputerApprovalStore();
141
141
  const clauses = [
142
142
  "owner_email = ?",
143
- "((org_id IS NULL AND ? IS NULL) OR org_id = ?)",
143
+ "((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)",
144
144
  ];
145
145
  const args = [
146
146
  input.ownerEmail,
@@ -192,7 +192,7 @@ export async function authorizeComputerOperation(input, client = getDbExec()) {
192
192
  const { rows } = await client.execute({
193
193
  sql: `SELECT * FROM integration_computer_approvals
194
194
  WHERE id = ? AND owner_email = ?
195
- AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)
195
+ AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)
196
196
  AND device_id = ? AND task_id = ? AND run_id = ?
197
197
  LIMIT 1`,
198
198
  args: [
@@ -1 +1 @@
1
- {"version":3,"file":"computer-supervision-store.js","sourceRoot":"","sources":["../../src/integrations/computer-supervision-store.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DbExec } from \"../db/client.js\";\nimport {\n getDbExec,\n intType,\n isPostgres,\n retryOnDdlRace,\n} from \"../db/client.js\";\nimport { ensureIndexExists, ensureTableExists } from \"../db/ddl-guard.js\";\nimport {\n assertValidComputerCommandEnvelope,\n ComputerSupervisionError,\n computerOperationRequiresApproval,\n} from \"./computer-supervision.js\";\nimport { getRemoteDeviceForOwner } from \"./remote-devices-store.js\";\nimport { serializeBoundedRemoteJson } from \"./remote-json-safety.js\";\nimport type {\n ComputerApprovalScope,\n ComputerCommandEnvelope,\n ComputerOperationClass,\n} from \"./remote-types.js\";\n\nexport type ComputerApprovalStatus =\n | \"pending\"\n | \"approved\"\n | \"denied\"\n | \"consumed\"\n | \"expired\";\n\nexport interface ComputerApprovalRecord {\n id: string;\n ownerEmail: string;\n orgId: string | null;\n deviceId: string;\n taskId: string;\n runId: string;\n operationClass: ComputerOperationClass;\n scope: ComputerApprovalScope;\n actionHash: string;\n status: ComputerApprovalStatus;\n decisionResult: Record<string, unknown> | null;\n decidedBy: string | null;\n decidedAt: number | null;\n expiresAt: number;\n consumedAt: number | null;\n createdAt: number;\n updatedAt: number;\n}\n\nlet _initPromise: Promise<void> | undefined;\n\nexport async function ensureComputerApprovalStore(): Promise<void> {\n if (!_initPromise) {\n _initPromise = (async () => {\n const createSql = `CREATE TABLE IF NOT EXISTS integration_computer_approvals (\n id TEXT PRIMARY KEY,\n owner_email TEXT NOT NULL,\n org_id TEXT,\n device_id TEXT NOT NULL,\n task_id TEXT NOT NULL,\n run_id TEXT NOT NULL,\n operation_class TEXT NOT NULL,\n approval_scope TEXT NOT NULL,\n action_hash TEXT NOT NULL,\n status TEXT NOT NULL,\n decision_result_json TEXT,\n decided_by TEXT,\n decided_at ${intType()},\n expires_at ${intType()} NOT NULL,\n consumed_at ${intType()},\n created_at ${intType()} NOT NULL,\n updated_at ${intType()} NOT NULL\n)`;\n if (isPostgres()) {\n await ensureTableExists(\"integration_computer_approvals\", createSql);\n await ensureIndexExists(\n \"idx_computer_approvals_owner\",\n `CREATE INDEX IF NOT EXISTS idx_computer_approvals_owner ON integration_computer_approvals(owner_email, org_id, updated_at)`,\n );\n await ensureIndexExists(\n \"idx_computer_approvals_binding\",\n `CREATE INDEX IF NOT EXISTS idx_computer_approvals_binding ON integration_computer_approvals(device_id, task_id, run_id, action_hash)`,\n );\n return;\n }\n const client = getDbExec();\n await retryOnDdlRace(() => client.execute(createSql));\n await retryOnDdlRace(() =>\n client.execute(\n `CREATE INDEX IF NOT EXISTS idx_computer_approvals_owner ON integration_computer_approvals(owner_email, org_id, updated_at)`,\n ),\n );\n await retryOnDdlRace(() =>\n client.execute(\n `CREATE INDEX IF NOT EXISTS idx_computer_approvals_binding ON integration_computer_approvals(device_id, task_id, run_id, action_hash)`,\n ),\n );\n })().catch((error) => {\n _initPromise = undefined;\n throw error;\n });\n }\n return _initPromise;\n}\n\nexport async function createComputerApprovalRequest(input: {\n ownerEmail: string;\n orgId?: string | null;\n deviceId: string;\n envelope: ComputerCommandEnvelope;\n}): Promise<ComputerApprovalRecord> {\n const device = await getRemoteDeviceForOwner({\n id: input.deviceId,\n ownerEmail: input.ownerEmail,\n orgId: input.orgId,\n });\n if (!device || device.status !== \"active\") {\n throw new ComputerSupervisionError(\n \"approval-mismatch\",\n \"Computer device does not belong to this owner and organization\",\n );\n }\n await ensureComputerApprovalStore();\n const envelope = await assertValidComputerCommandEnvelope(input.envelope);\n const now = Date.now();\n const id = `computer-approval-${now}-${randomHex(8)}`;\n await getDbExec().execute({\n sql: `INSERT INTO integration_computer_approvals\n (id, owner_email, org_id, device_id, task_id, run_id, operation_class,\n approval_scope, action_hash, status, decision_result_json, decided_by,\n decided_at, expires_at, consumed_at, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n args: [\n id,\n input.ownerEmail,\n input.orgId ?? null,\n input.deviceId,\n envelope.taskId,\n envelope.runId,\n envelope.operationClass,\n envelope.approval.scope,\n envelope.approval.actionHash,\n \"pending\",\n null,\n null,\n null,\n envelope.leaseExpiresAt,\n null,\n now,\n now,\n ],\n });\n const approval = await getComputerApprovalForOwner({\n id,\n ownerEmail: input.ownerEmail,\n orgId: input.orgId,\n });\n if (!approval) throw new Error(\"computer approval insert failed\");\n return approval;\n}\n\nexport async function decideComputerApproval(input: {\n id: string;\n ownerEmail: string;\n orgId?: string | null;\n actionHash: string;\n decision: \"approved\" | \"denied\";\n decidedBy: string;\n result?: Record<string, unknown> | null;\n}): Promise<ComputerApprovalRecord | null> {\n await ensureComputerApprovalStore();\n const now = Date.now();\n const resultJson = serializeDecisionResult(input.result);\n const result = await getDbExec().execute({\n sql: `UPDATE integration_computer_approvals\n SET status = ?, decision_result_json = ?, decided_by = ?,\n decided_at = ?, updated_at = ?\n WHERE id = ? AND owner_email = ?\n AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)\n AND action_hash = ? AND status = 'pending' AND expires_at > ?`,\n args: [\n input.decision,\n resultJson,\n input.decidedBy.slice(0, 240),\n now,\n now,\n input.id,\n input.ownerEmail,\n input.orgId ?? null,\n input.orgId ?? null,\n input.actionHash,\n now,\n ],\n });\n if (affectedRows(result) === 0) return null;\n return getComputerApprovalForOwner(input);\n}\n\nexport async function getComputerApprovalForOwner(input: {\n id: string;\n ownerEmail: string;\n orgId?: string | null;\n}): Promise<ComputerApprovalRecord | null> {\n await ensureComputerApprovalStore();\n const { rows } = await getDbExec().execute({\n sql: `SELECT * FROM integration_computer_approvals\n WHERE id = ? AND owner_email = ?\n AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)\n LIMIT 1`,\n args: [\n input.id,\n input.ownerEmail,\n input.orgId ?? null,\n input.orgId ?? null,\n ],\n });\n return rows[0] ? rowToApproval(rows[0] as Record<string, unknown>) : null;\n}\n\nexport async function listComputerApprovalsForOwner(input: {\n ownerEmail: string;\n orgId?: string | null;\n deviceId?: string;\n taskId?: string;\n runId?: string;\n status?: ComputerApprovalStatus;\n limit?: number;\n}): Promise<ComputerApprovalRecord[]> {\n await ensureComputerApprovalStore();\n const clauses = [\n \"owner_email = ?\",\n \"((org_id IS NULL AND ? IS NULL) OR org_id = ?)\",\n ];\n const args: Array<string | number | null> = [\n input.ownerEmail,\n input.orgId ?? null,\n input.orgId ?? null,\n ];\n if (input.deviceId) {\n clauses.push(\"device_id = ?\");\n args.push(input.deviceId);\n }\n if (input.taskId) {\n clauses.push(\"task_id = ?\");\n args.push(input.taskId);\n }\n if (input.runId) {\n clauses.push(\"run_id = ?\");\n args.push(input.runId);\n }\n if (input.status) {\n clauses.push(\"status = ?\");\n args.push(input.status);\n }\n args.push(Math.max(1, Math.min(input.limit ?? 100, 250)));\n const { rows } = await getDbExec().execute({\n sql: `SELECT * FROM integration_computer_approvals\n WHERE ${clauses.join(\" AND \")}\n ORDER BY updated_at DESC\n LIMIT ?`,\n args,\n });\n return rows.map((row) => rowToApproval(row as Record<string, unknown>));\n}\n\n/**\n * Authorizes an operation immediately before it is enqueued. Callers should\n * pass their transaction handle so one-shot approval consumption and command\n * insertion commit atomically.\n */\nexport async function authorizeComputerOperation(\n input: {\n ownerEmail: string;\n orgId?: string | null;\n deviceId: string;\n envelope: ComputerCommandEnvelope;\n now?: number;\n },\n client: DbExec = getDbExec(),\n): Promise<void> {\n const now = input.now ?? Date.now();\n if (input.envelope.leaseExpiresAt <= now) {\n throw new ComputerSupervisionError(\n \"expired-lease\",\n \"Computer operation lease has expired\",\n );\n }\n if (!computerOperationRequiresApproval(input.envelope.operationClass)) return;\n const approvalId = input.envelope.approval.id;\n if (!approvalId) {\n throw new ComputerSupervisionError(\n \"approval-required\",\n \"Computer control requires an approved action binding\",\n );\n }\n const { rows } = await client.execute({\n sql: `SELECT * FROM integration_computer_approvals\n WHERE id = ? AND owner_email = ?\n AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)\n AND device_id = ? AND task_id = ? AND run_id = ?\n LIMIT 1`,\n args: [\n approvalId,\n input.ownerEmail,\n input.orgId ?? null,\n input.orgId ?? null,\n input.deviceId,\n input.envelope.taskId,\n input.envelope.runId,\n ],\n });\n if (!rows[0]) {\n throw new ComputerSupervisionError(\n \"approval-mismatch\",\n \"Computer approval does not belong to this owner, device, task, and run\",\n );\n }\n const approval = rowToApproval(rows[0] as Record<string, unknown>);\n if (\n approval.actionHash !== input.envelope.approval.actionHash ||\n approval.operationClass !== input.envelope.operationClass ||\n approval.scope !== input.envelope.approval.scope\n ) {\n throw new ComputerSupervisionError(\n \"approval-mismatch\",\n \"Computer approval does not match the requested action\",\n );\n }\n if (approval.expiresAt <= now) {\n await client.execute({\n sql: `UPDATE integration_computer_approvals\n SET status = 'expired', updated_at = ?\n WHERE id = ? AND status IN ('pending', 'approved')`,\n args: [now, approval.id],\n });\n throw new ComputerSupervisionError(\n \"approval-denied\",\n \"Computer approval has expired\",\n );\n }\n if (approval.status !== \"approved\") {\n throw new ComputerSupervisionError(\n approval.status === \"consumed\" ? \"replay\" : \"approval-denied\",\n approval.status === \"consumed\"\n ? \"One-shot computer approval has already been consumed\"\n : \"Computer action is not approved\",\n );\n }\n if (approval.scope !== \"once\") return;\n const result = await client.execute({\n sql: `UPDATE integration_computer_approvals\n SET status = 'consumed', consumed_at = ?, updated_at = ?\n WHERE id = ? AND status = 'approved'`,\n args: [now, now, approval.id],\n });\n if (affectedRows(result) === 0) {\n throw new ComputerSupervisionError(\n \"replay\",\n \"One-shot computer approval has already been consumed\",\n );\n }\n}\n\nfunction rowToApproval(row: Record<string, unknown>): ComputerApprovalRecord {\n return {\n id: String(row.id),\n ownerEmail: String(row.owner_email),\n orgId: row.org_id == null ? null : String(row.org_id),\n deviceId: String(row.device_id),\n taskId: String(row.task_id),\n runId: String(row.run_id),\n operationClass: row.operation_class as ComputerOperationClass,\n scope: row.approval_scope as ComputerApprovalScope,\n actionHash: String(row.action_hash),\n status: row.status as ComputerApprovalStatus,\n decisionResult: parseRecord(row.decision_result_json),\n decidedBy: row.decided_by == null ? null : String(row.decided_by),\n decidedAt: row.decided_at == null ? null : Number(row.decided_at),\n expiresAt: Number(row.expires_at),\n consumedAt: row.consumed_at == null ? null : Number(row.consumed_at),\n createdAt: Number(row.created_at),\n updatedAt: Number(row.updated_at),\n };\n}\n\nfunction serializeDecisionResult(\n result: Record<string, unknown> | null | undefined,\n): string | null {\n if (!result) return null;\n return serializeBoundedRemoteJson(result, {\n label: \"Computer approval result\",\n maxBytes: 8_192,\n });\n}\n\nfunction parseRecord(value: unknown): Record<string, unknown> | null {\n if (value == null) return null;\n try {\n const parsed = JSON.parse(String(value));\n return parsed && typeof parsed === \"object\" && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : null;\n } catch {\n return null;\n }\n}\n\nfunction affectedRows(result: {\n rowsAffected?: number;\n rows?: unknown[];\n}): number {\n return (\n result.rowsAffected ??\n Number((result as { rowCount?: unknown }).rowCount ?? 0)\n );\n}\n\nfunction randomHex(byteLength: number): string {\n const bytes = new Uint8Array(byteLength);\n globalThis.crypto.getRandomValues(bytes);\n return Array.from(bytes)\n .map((byte) => byte.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n"]}
1
+ {"version":3,"file":"computer-supervision-store.js","sourceRoot":"","sources":["../../src/integrations/computer-supervision-store.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DbExec } from \"../db/client.js\";\nimport {\n getDbExec,\n intType,\n isPostgres,\n retryOnDdlRace,\n} from \"../db/client.js\";\nimport { ensureIndexExists, ensureTableExists } from \"../db/ddl-guard.js\";\nimport {\n assertValidComputerCommandEnvelope,\n ComputerSupervisionError,\n computerOperationRequiresApproval,\n} from \"./computer-supervision.js\";\nimport { getRemoteDeviceForOwner } from \"./remote-devices-store.js\";\nimport { serializeBoundedRemoteJson } from \"./remote-json-safety.js\";\nimport type {\n ComputerApprovalScope,\n ComputerCommandEnvelope,\n ComputerOperationClass,\n} from \"./remote-types.js\";\n\nexport type ComputerApprovalStatus =\n | \"pending\"\n | \"approved\"\n | \"denied\"\n | \"consumed\"\n | \"expired\";\n\nexport interface ComputerApprovalRecord {\n id: string;\n ownerEmail: string;\n orgId: string | null;\n deviceId: string;\n taskId: string;\n runId: string;\n operationClass: ComputerOperationClass;\n scope: ComputerApprovalScope;\n actionHash: string;\n status: ComputerApprovalStatus;\n decisionResult: Record<string, unknown> | null;\n decidedBy: string | null;\n decidedAt: number | null;\n expiresAt: number;\n consumedAt: number | null;\n createdAt: number;\n updatedAt: number;\n}\n\nlet _initPromise: Promise<void> | undefined;\n\nexport async function ensureComputerApprovalStore(): Promise<void> {\n if (!_initPromise) {\n _initPromise = (async () => {\n const createSql = `CREATE TABLE IF NOT EXISTS integration_computer_approvals (\n id TEXT PRIMARY KEY,\n owner_email TEXT NOT NULL,\n org_id TEXT,\n device_id TEXT NOT NULL,\n task_id TEXT NOT NULL,\n run_id TEXT NOT NULL,\n operation_class TEXT NOT NULL,\n approval_scope TEXT NOT NULL,\n action_hash TEXT NOT NULL,\n status TEXT NOT NULL,\n decision_result_json TEXT,\n decided_by TEXT,\n decided_at ${intType()},\n expires_at ${intType()} NOT NULL,\n consumed_at ${intType()},\n created_at ${intType()} NOT NULL,\n updated_at ${intType()} NOT NULL\n)`;\n if (isPostgres()) {\n await ensureTableExists(\"integration_computer_approvals\", createSql);\n await ensureIndexExists(\n \"idx_computer_approvals_owner\",\n `CREATE INDEX IF NOT EXISTS idx_computer_approvals_owner ON integration_computer_approvals(owner_email, org_id, updated_at)`,\n );\n await ensureIndexExists(\n \"idx_computer_approvals_binding\",\n `CREATE INDEX IF NOT EXISTS idx_computer_approvals_binding ON integration_computer_approvals(device_id, task_id, run_id, action_hash)`,\n );\n return;\n }\n const client = getDbExec();\n await retryOnDdlRace(() => client.execute(createSql));\n await retryOnDdlRace(() =>\n client.execute(\n `CREATE INDEX IF NOT EXISTS idx_computer_approvals_owner ON integration_computer_approvals(owner_email, org_id, updated_at)`,\n ),\n );\n await retryOnDdlRace(() =>\n client.execute(\n `CREATE INDEX IF NOT EXISTS idx_computer_approvals_binding ON integration_computer_approvals(device_id, task_id, run_id, action_hash)`,\n ),\n );\n })().catch((error) => {\n _initPromise = undefined;\n throw error;\n });\n }\n return _initPromise;\n}\n\nexport async function createComputerApprovalRequest(input: {\n ownerEmail: string;\n orgId?: string | null;\n deviceId: string;\n envelope: ComputerCommandEnvelope;\n}): Promise<ComputerApprovalRecord> {\n const device = await getRemoteDeviceForOwner({\n id: input.deviceId,\n ownerEmail: input.ownerEmail,\n orgId: input.orgId,\n });\n if (!device || device.status !== \"active\") {\n throw new ComputerSupervisionError(\n \"approval-mismatch\",\n \"Computer device does not belong to this owner and organization\",\n );\n }\n await ensureComputerApprovalStore();\n const envelope = await assertValidComputerCommandEnvelope(input.envelope);\n const now = Date.now();\n const id = `computer-approval-${now}-${randomHex(8)}`;\n await getDbExec().execute({\n sql: `INSERT INTO integration_computer_approvals\n (id, owner_email, org_id, device_id, task_id, run_id, operation_class,\n approval_scope, action_hash, status, decision_result_json, decided_by,\n decided_at, expires_at, consumed_at, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n args: [\n id,\n input.ownerEmail,\n input.orgId ?? null,\n input.deviceId,\n envelope.taskId,\n envelope.runId,\n envelope.operationClass,\n envelope.approval.scope,\n envelope.approval.actionHash,\n \"pending\",\n null,\n null,\n null,\n envelope.leaseExpiresAt,\n null,\n now,\n now,\n ],\n });\n const approval = await getComputerApprovalForOwner({\n id,\n ownerEmail: input.ownerEmail,\n orgId: input.orgId,\n });\n if (!approval) throw new Error(\"computer approval insert failed\");\n return approval;\n}\n\nexport async function decideComputerApproval(input: {\n id: string;\n ownerEmail: string;\n orgId?: string | null;\n actionHash: string;\n decision: \"approved\" | \"denied\";\n decidedBy: string;\n result?: Record<string, unknown> | null;\n}): Promise<ComputerApprovalRecord | null> {\n await ensureComputerApprovalStore();\n const now = Date.now();\n const resultJson = serializeDecisionResult(input.result);\n const result = await getDbExec().execute({\n sql: `UPDATE integration_computer_approvals\n SET status = ?, decision_result_json = ?, decided_by = ?,\n decided_at = ?, updated_at = ?\n WHERE id = ? AND owner_email = ?\n AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)\n AND action_hash = ? AND status = 'pending' AND expires_at > ?`,\n args: [\n input.decision,\n resultJson,\n input.decidedBy.slice(0, 240),\n now,\n now,\n input.id,\n input.ownerEmail,\n input.orgId ?? null,\n input.orgId ?? null,\n input.actionHash,\n now,\n ],\n });\n if (affectedRows(result) === 0) return null;\n return getComputerApprovalForOwner(input);\n}\n\nexport async function getComputerApprovalForOwner(input: {\n id: string;\n ownerEmail: string;\n orgId?: string | null;\n}): Promise<ComputerApprovalRecord | null> {\n await ensureComputerApprovalStore();\n const { rows } = await getDbExec().execute({\n sql: `SELECT * FROM integration_computer_approvals\n WHERE id = ? AND owner_email = ?\n AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)\n LIMIT 1`,\n args: [\n input.id,\n input.ownerEmail,\n input.orgId ?? null,\n input.orgId ?? null,\n ],\n });\n return rows[0] ? rowToApproval(rows[0] as Record<string, unknown>) : null;\n}\n\nexport async function listComputerApprovalsForOwner(input: {\n ownerEmail: string;\n orgId?: string | null;\n deviceId?: string;\n taskId?: string;\n runId?: string;\n status?: ComputerApprovalStatus;\n limit?: number;\n}): Promise<ComputerApprovalRecord[]> {\n await ensureComputerApprovalStore();\n const clauses = [\n \"owner_email = ?\",\n \"((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)\",\n ];\n const args: Array<string | number | null> = [\n input.ownerEmail,\n input.orgId ?? null,\n input.orgId ?? null,\n ];\n if (input.deviceId) {\n clauses.push(\"device_id = ?\");\n args.push(input.deviceId);\n }\n if (input.taskId) {\n clauses.push(\"task_id = ?\");\n args.push(input.taskId);\n }\n if (input.runId) {\n clauses.push(\"run_id = ?\");\n args.push(input.runId);\n }\n if (input.status) {\n clauses.push(\"status = ?\");\n args.push(input.status);\n }\n args.push(Math.max(1, Math.min(input.limit ?? 100, 250)));\n const { rows } = await getDbExec().execute({\n sql: `SELECT * FROM integration_computer_approvals\n WHERE ${clauses.join(\" AND \")}\n ORDER BY updated_at DESC\n LIMIT ?`,\n args,\n });\n return rows.map((row) => rowToApproval(row as Record<string, unknown>));\n}\n\n/**\n * Authorizes an operation immediately before it is enqueued. Callers should\n * pass their transaction handle so one-shot approval consumption and command\n * insertion commit atomically.\n */\nexport async function authorizeComputerOperation(\n input: {\n ownerEmail: string;\n orgId?: string | null;\n deviceId: string;\n envelope: ComputerCommandEnvelope;\n now?: number;\n },\n client: DbExec = getDbExec(),\n): Promise<void> {\n const now = input.now ?? Date.now();\n if (input.envelope.leaseExpiresAt <= now) {\n throw new ComputerSupervisionError(\n \"expired-lease\",\n \"Computer operation lease has expired\",\n );\n }\n if (!computerOperationRequiresApproval(input.envelope.operationClass)) return;\n const approvalId = input.envelope.approval.id;\n if (!approvalId) {\n throw new ComputerSupervisionError(\n \"approval-required\",\n \"Computer control requires an approved action binding\",\n );\n }\n const { rows } = await client.execute({\n sql: `SELECT * FROM integration_computer_approvals\n WHERE id = ? AND owner_email = ?\n AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)\n AND device_id = ? AND task_id = ? AND run_id = ?\n LIMIT 1`,\n args: [\n approvalId,\n input.ownerEmail,\n input.orgId ?? null,\n input.orgId ?? null,\n input.deviceId,\n input.envelope.taskId,\n input.envelope.runId,\n ],\n });\n if (!rows[0]) {\n throw new ComputerSupervisionError(\n \"approval-mismatch\",\n \"Computer approval does not belong to this owner, device, task, and run\",\n );\n }\n const approval = rowToApproval(rows[0] as Record<string, unknown>);\n if (\n approval.actionHash !== input.envelope.approval.actionHash ||\n approval.operationClass !== input.envelope.operationClass ||\n approval.scope !== input.envelope.approval.scope\n ) {\n throw new ComputerSupervisionError(\n \"approval-mismatch\",\n \"Computer approval does not match the requested action\",\n );\n }\n if (approval.expiresAt <= now) {\n await client.execute({\n sql: `UPDATE integration_computer_approvals\n SET status = 'expired', updated_at = ?\n WHERE id = ? AND status IN ('pending', 'approved')`,\n args: [now, approval.id],\n });\n throw new ComputerSupervisionError(\n \"approval-denied\",\n \"Computer approval has expired\",\n );\n }\n if (approval.status !== \"approved\") {\n throw new ComputerSupervisionError(\n approval.status === \"consumed\" ? \"replay\" : \"approval-denied\",\n approval.status === \"consumed\"\n ? \"One-shot computer approval has already been consumed\"\n : \"Computer action is not approved\",\n );\n }\n if (approval.scope !== \"once\") return;\n const result = await client.execute({\n sql: `UPDATE integration_computer_approvals\n SET status = 'consumed', consumed_at = ?, updated_at = ?\n WHERE id = ? AND status = 'approved'`,\n args: [now, now, approval.id],\n });\n if (affectedRows(result) === 0) {\n throw new ComputerSupervisionError(\n \"replay\",\n \"One-shot computer approval has already been consumed\",\n );\n }\n}\n\nfunction rowToApproval(row: Record<string, unknown>): ComputerApprovalRecord {\n return {\n id: String(row.id),\n ownerEmail: String(row.owner_email),\n orgId: row.org_id == null ? null : String(row.org_id),\n deviceId: String(row.device_id),\n taskId: String(row.task_id),\n runId: String(row.run_id),\n operationClass: row.operation_class as ComputerOperationClass,\n scope: row.approval_scope as ComputerApprovalScope,\n actionHash: String(row.action_hash),\n status: row.status as ComputerApprovalStatus,\n decisionResult: parseRecord(row.decision_result_json),\n decidedBy: row.decided_by == null ? null : String(row.decided_by),\n decidedAt: row.decided_at == null ? null : Number(row.decided_at),\n expiresAt: Number(row.expires_at),\n consumedAt: row.consumed_at == null ? null : Number(row.consumed_at),\n createdAt: Number(row.created_at),\n updatedAt: Number(row.updated_at),\n };\n}\n\nfunction serializeDecisionResult(\n result: Record<string, unknown> | null | undefined,\n): string | null {\n if (!result) return null;\n return serializeBoundedRemoteJson(result, {\n label: \"Computer approval result\",\n maxBytes: 8_192,\n });\n}\n\nfunction parseRecord(value: unknown): Record<string, unknown> | null {\n if (value == null) return null;\n try {\n const parsed = JSON.parse(String(value));\n return parsed && typeof parsed === \"object\" && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : null;\n } catch {\n return null;\n }\n}\n\nfunction affectedRows(result: {\n rowsAffected?: number;\n rows?: unknown[];\n}): number {\n return (\n result.rowsAffected ??\n Number((result as { rowCount?: unknown }).rowCount ?? 0)\n );\n}\n\nfunction randomHex(byteLength: number): string {\n const bytes = new Uint8Array(byteLength);\n globalThis.crypto.getRandomValues(bytes);\n return Array.from(bytes)\n .map((byte) => byte.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n"]}
@@ -222,7 +222,7 @@ export async function resolveIntegrationSourceContext(id, ownerEmail, orgId) {
222
222
  FROM integration_pending_tasks
223
223
  WHERE id = ?
224
224
  AND owner_email = ?
225
- AND (org_id = ? OR (org_id IS NULL AND ? IS NULL))
225
+ AND (org_id = ? OR (org_id IS NULL AND CAST(? AS TEXT) IS NULL))
226
226
  AND platform = 'slack'
227
227
  LIMIT 1`,
228
228
  args: [id, ownerEmail, orgId, orgId],
@@ -1 +1 @@
1
- {"version":3,"file":"pending-tasks-store.js","sourceRoot":"","sources":["../../src/integrations/pending-tasks-store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * SQL-backed pending task queue for integration webhooks.\n *\n * Why this exists: serverless platforms (Netlify Lambda, Vercel, Cloudflare\n * Workers) freeze the function execution as soon as the HTTP response is\n * returned. Fire-and-forget background `Promise`s get killed mid-flight,\n * meaning agent loops triggered from a Slack/Telegram webhook never finish.\n *\n * Solution: persist the inbound message to SQL inside the webhook handler,\n * then dispatch a fresh HTTP POST to a separate processor endpoint. Each\n * invocation gets its own fresh function timeout budget.\n */\nimport { getDbExec, isPostgres, intType } from \"../db/client.js\";\nimport {\n ensureTableExists,\n ensureColumnExists,\n ensureIndexExists,\n} from \"../db/ddl-guard.js\";\n\nlet _initPromise: Promise<void> | undefined;\nexport const MAX_PENDING_TASK_ATTEMPTS = 3;\n\nasync function ensureTable(): Promise<void> {\n if (!_initPromise) {\n _initPromise = (async () => {\n const client = getDbExec();\n const createSql = `CREATE TABLE IF NOT EXISTS integration_pending_tasks (\n id TEXT PRIMARY KEY,\n platform TEXT NOT NULL,\n external_thread_id TEXT NOT NULL,\n payload TEXT NOT NULL,\n owner_email TEXT NOT NULL,\n org_id TEXT,\n status TEXT NOT NULL,\n attempts ${intType()} NOT NULL DEFAULT 0,\n error_message TEXT,\n created_at ${intType()} NOT NULL,\n updated_at ${intType()} NOT NULL,\n completed_at ${intType()}\n)`;\n\n if (isPostgres()) {\n // PG guard: probe via information_schema, only issue DDL if missing, bounded lock_timeout\n await ensureTableExists(\"integration_pending_tasks\", createSql);\n await ensureColumnExists(\n \"integration_pending_tasks\",\n \"external_event_key\",\n `ALTER TABLE integration_pending_tasks ADD COLUMN IF NOT EXISTS external_event_key TEXT`,\n );\n await ensureIndexExists(\n \"idx_pending_tasks_status_created\",\n `CREATE INDEX IF NOT EXISTS idx_pending_tasks_status_created ON integration_pending_tasks(status, created_at)`,\n );\n await ensureIndexExists(\n \"idx_pending_tasks_event_key\",\n `CREATE UNIQUE INDEX IF NOT EXISTS idx_pending_tasks_event_key ON integration_pending_tasks(platform, external_event_key)`,\n );\n return;\n }\n\n await client.execute(createSql);\n await client.execute(\n `CREATE INDEX IF NOT EXISTS idx_pending_tasks_status_created ON integration_pending_tasks(status, created_at)`,\n );\n // Additive migration: add a stable per-event dedup key so duplicate\n // webhook deliveries from the same platform get rejected at the SQL\n // layer instead of via an in-memory Map (which doesn't survive\n // serverless cold starts — H3 in the webhook security audit). The\n // unique index ensures a duplicate INSERT raises an error we can\n // catch as \"already-enqueued\".\n await ensureExternalEventKey(client);\n await client.execute(\n `CREATE UNIQUE INDEX IF NOT EXISTS idx_pending_tasks_event_key ON integration_pending_tasks(platform, external_event_key)`,\n );\n })().catch((err) => {\n // Retry init on the next call after a failed startup.\n _initPromise = undefined;\n throw err;\n });\n }\n return _initPromise;\n}\n\nasync function ensureExternalEventKey(\n client: ReturnType<typeof getDbExec>,\n): Promise<void> {\n if (isPostgres()) {\n await client.execute(\n `ALTER TABLE integration_pending_tasks ADD COLUMN IF NOT EXISTS external_event_key TEXT`,\n );\n return;\n }\n // SQLite doesn't support `ADD COLUMN IF NOT EXISTS` until 3.35; swallow\n // the duplicate-column error so reruns are idempotent.\n try {\n await client.execute(\n `ALTER TABLE integration_pending_tasks ADD COLUMN external_event_key TEXT`,\n );\n } catch (err: any) {\n if (\n !String(err?.message ?? err)\n .toLowerCase()\n .includes(\"duplicate\")\n ) {\n throw err;\n }\n }\n}\n\n/** Status values for an integration pending task. */\nexport type PendingTaskStatus =\n | \"pending\"\n | \"processing\"\n | \"completed\"\n | \"failed\";\n\nexport interface PendingTask {\n id: string;\n platform: string;\n externalThreadId: string;\n payload: string;\n ownerEmail: string;\n orgId: string | null;\n status: PendingTaskStatus;\n attempts: number;\n errorMessage: string | null;\n createdAt: number;\n updatedAt: number;\n completedAt: number | null;\n}\n\n/**\n * Whether a provider thread currently has queued or executing work.\n *\n * Messaging adapters use this to accept unmentioned replies only while an\n * agent task is active. This prevents broad message subscriptions from turning\n * every channel message into an agent invocation.\n */\nexport async function hasActivePendingTask(\n platform: string,\n externalThreadId: string,\n): Promise<boolean> {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT 1 AS active\n FROM integration_pending_tasks\n WHERE platform = ?\n AND external_thread_id = ?\n AND status IN ('pending', 'processing')\n LIMIT 1`,\n args: [platform, externalThreadId],\n });\n return rows.length > 0;\n}\n\nfunction rowToTask(row: Record<string, unknown>): PendingTask {\n return {\n id: row.id as string,\n platform: row.platform as string,\n externalThreadId: row.external_thread_id as string,\n payload: row.payload as string,\n ownerEmail: row.owner_email as string,\n orgId: (row.org_id as string | null) ?? null,\n status: row.status as PendingTaskStatus,\n attempts: Number(row.attempts ?? 0),\n errorMessage: (row.error_message as string | null) ?? null,\n createdAt: Number(row.created_at ?? 0),\n updatedAt: Number(row.updated_at ?? 0),\n completedAt:\n row.completed_at == null ? null : Number(row.completed_at as number),\n };\n}\n\n/**\n * Insert a new pending task. Returns the generated task id.\n *\n * If `externalEventKey` is supplied, the unique index on\n * `(platform, external_event_key)` will reject duplicates — callers should\n * catch the resulting constraint-violation error and treat it as\n * \"already enqueued\" instead of a hard failure (H3 in the webhook security\n * audit). This is the SQL-backed replacement for the in-memory dedup map.\n */\nexport async function insertPendingTask(input: {\n id: string;\n platform: string;\n externalThreadId: string;\n payload: string;\n ownerEmail: string;\n orgId?: string | null;\n externalEventKey?: string | null;\n}): Promise<void> {\n await ensureTable();\n const client = getDbExec();\n const now = Date.now();\n await client.execute({\n sql: `INSERT INTO integration_pending_tasks\n (id, platform, external_thread_id, payload, owner_email, org_id, status, attempts, created_at, updated_at, external_event_key)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n args: [\n input.id,\n input.platform,\n input.externalThreadId,\n input.payload,\n input.ownerEmail,\n input.orgId ?? null,\n \"pending\",\n 0,\n now,\n now,\n input.externalEventKey ?? null,\n ],\n });\n}\n\n/**\n * Returns whether a duplicate-event error from `insertPendingTask` looks\n * like a unique-constraint violation on `(platform, external_event_key)`.\n *\n * Postgres surfaces these as `error.code === \"23505\"`, while SQLite uses\n * a substring match on the error text. Used by the webhook handler to\n * distinguish \"already enqueued\" (silently OK) from genuine insert failures.\n */\nexport function isDuplicateEventError(err: unknown): boolean {\n const e = err as { code?: string; message?: string } | null;\n if (!e) return false;\n if (e.code === \"23505\") return true; // Postgres unique-violation\n const msg = String(e.message ?? \"\").toLowerCase();\n return (\n msg.includes(\"unique\") ||\n msg.includes(\"duplicate entry\") ||\n msg.includes(\"duplicate key\")\n );\n}\n\n/** Fetch a pending task by id. */\nexport async function getPendingTask(id: string): Promise<PendingTask | null> {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT id, platform, external_thread_id, payload, owner_email, org_id, status, attempts, error_message, created_at, updated_at, completed_at\n FROM integration_pending_tasks WHERE id = ? LIMIT 1`,\n args: [id],\n });\n if (rows.length === 0) return null;\n return rowToTask(rows[0] as Record<string, unknown>);\n}\n\nexport interface ResolvedIntegrationSourceContext {\n platform: \"slack\";\n sourceUrl: string;\n}\n\ntype PendingTaskSourceRow = Pick<\n PendingTask,\n \"platform\" | \"payload\" | \"ownerEmail\" | \"orgId\"\n>;\n\nexport function sourceContextFromPendingTask(\n task: PendingTaskSourceRow | null,\n ownerEmail: string,\n orgId: string | null,\n): ResolvedIntegrationSourceContext | null {\n if (\n !task ||\n task.ownerEmail !== ownerEmail ||\n task.orgId !== orgId ||\n task.platform !== \"slack\"\n ) {\n return null;\n }\n\n try {\n const payload = JSON.parse(task.payload) as Record<string, unknown>;\n const incoming = payload.incoming;\n if (!incoming || typeof incoming !== \"object\") return null;\n const incomingRecord = incoming as Record<string, unknown>;\n if (incomingRecord.platform !== \"slack\") return null;\n const sourceUrl = incomingRecord.sourceUrl;\n if (\n typeof sourceUrl !== \"string\" ||\n !sourceUrl ||\n sourceUrl !== sourceUrl.trim()\n ) {\n return null;\n }\n\n const parsed = new URL(sourceUrl);\n const isSlackHost =\n parsed.hostname === \"slack.com\" || parsed.hostname.endsWith(\".slack.com\");\n if (\n parsed.protocol !== \"https:\" ||\n !isSlackHost ||\n parsed.username ||\n parsed.password ||\n parsed.port\n ) {\n return null;\n }\n return { platform: \"slack\", sourceUrl };\n } catch {\n return null;\n }\n}\n\n/** Resolve trusted Slack provenance without exposing the stored task payload. */\nexport async function resolveIntegrationSourceContext(\n id: string,\n ownerEmail: string,\n orgId: string | null,\n): Promise<ResolvedIntegrationSourceContext | null> {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT platform, payload, owner_email, org_id\n FROM integration_pending_tasks\n WHERE id = ?\n AND owner_email = ?\n AND (org_id = ? OR (org_id IS NULL AND ? IS NULL))\n AND platform = 'slack'\n LIMIT 1`,\n args: [id, ownerEmail, orgId, orgId],\n });\n if (rows.length === 0) return null;\n const row = rows[0] as Record<string, unknown>;\n return sourceContextFromPendingTask(\n {\n platform: row.platform as string,\n payload: row.payload as string,\n ownerEmail: row.owner_email as string,\n orgId: (row.org_id as string | null) ?? null,\n },\n ownerEmail,\n orgId,\n );\n}\n\n/**\n * Atomically claim a task: transition pending → processing and increment\n * attempts. Returns the updated task if the transition succeeded, otherwise\n * null (e.g. the task was already claimed by a concurrent worker).\n */\nexport async function claimPendingTask(\n id: string,\n): Promise<PendingTask | null> {\n await ensureTable();\n const client = getDbExec();\n const now = Date.now();\n\n // Conditional update: only flip if currently pending. Failed tasks are\n // terminal unless an explicit retry path resets them to pending first.\n const result = await client.execute({\n sql: isPostgres()\n ? `UPDATE integration_pending_tasks\n SET status = ?, attempts = attempts + 1, updated_at = ?\n WHERE id = ? AND status = 'pending'\n AND NOT EXISTS (\n SELECT 1 FROM integration_pending_tasks active\n WHERE active.platform = integration_pending_tasks.platform\n AND active.external_thread_id = integration_pending_tasks.external_thread_id\n AND active.status = 'processing'\n AND active.id <> integration_pending_tasks.id\n )\n AND NOT EXISTS (\n SELECT 1 FROM integration_pending_tasks earlier\n WHERE earlier.platform = integration_pending_tasks.platform\n AND earlier.external_thread_id = integration_pending_tasks.external_thread_id\n AND earlier.status = 'pending'\n AND (\n earlier.created_at < integration_pending_tasks.created_at\n OR (\n earlier.created_at = integration_pending_tasks.created_at\n AND earlier.id < integration_pending_tasks.id\n )\n )\n )\n RETURNING id, platform, external_thread_id, payload, owner_email, org_id, status, attempts, error_message, created_at, updated_at, completed_at`\n : `UPDATE integration_pending_tasks\n SET status = ?, attempts = attempts + 1, updated_at = ?\n WHERE id = ? AND status = 'pending'\n AND NOT EXISTS (\n SELECT 1 FROM integration_pending_tasks active\n WHERE active.platform = integration_pending_tasks.platform\n AND active.external_thread_id = integration_pending_tasks.external_thread_id\n AND active.status = 'processing'\n AND active.id <> integration_pending_tasks.id\n )\n AND NOT EXISTS (\n SELECT 1 FROM integration_pending_tasks earlier\n WHERE earlier.platform = integration_pending_tasks.platform\n AND earlier.external_thread_id = integration_pending_tasks.external_thread_id\n AND earlier.status = 'pending'\n AND (\n earlier.created_at < integration_pending_tasks.created_at\n OR (\n earlier.created_at = integration_pending_tasks.created_at\n AND earlier.id < integration_pending_tasks.id\n )\n )\n )`,\n args: [\"processing\", now, id],\n });\n const rows = result.rows ?? [];\n\n if (isPostgres()) {\n if (rows.length === 0) return null;\n return rowToTask(rows[0] as Record<string, unknown>);\n }\n\n // SQLite: no RETURNING, so re-read after the update. Confirm we actually\n // moved it into 'processing' (vs. lost the race).\n const affected =\n (result as { rowsAffected?: number; rowCount?: number }).rowsAffected ??\n (result as { rowsAffected?: number; rowCount?: number }).rowCount;\n if (affected === 0) return null;\n const fetched = await getPendingTask(id);\n if (!fetched || fetched.status !== \"processing\") return null;\n return fetched;\n}\n\n/** Next queued turn for a provider thread after its current task completes. */\nexport async function getNextPendingTaskIdForThread(\n platform: string,\n externalThreadId: string,\n): Promise<string | null> {\n await ensureTable();\n const { rows } = await getDbExec().execute({\n sql: `SELECT id FROM integration_pending_tasks\n WHERE platform = ? AND external_thread_id = ? AND status = 'pending'\n ORDER BY created_at ASC, id ASC LIMIT 1`,\n args: [platform, externalThreadId],\n });\n return rows[0]?.id ? String(rows[0].id) : null;\n}\n\n/** Mark a task as completed. */\nexport async function markTaskCompleted(id: string): Promise<void> {\n await ensureTable();\n const client = getDbExec();\n const now = Date.now();\n await client.execute({\n sql: `UPDATE integration_pending_tasks\n SET status = ?, updated_at = ?, completed_at = ?, payload = ?\n WHERE id = ?`,\n // The payload can contain short-lived provider credentials such as a\n // Discord interaction token. Once terminal, no retry needs the inbound\n // body, so erase it instead of retaining secrets or user text indefinitely.\n args: [\"completed\", now, now, \"{}\", id],\n });\n}\n\n/**\n * Return a transiently failed task to the retryable queue without erasing its\n * payload. The payload may contain the only copy of the inbound message and is\n * scrubbed only when the task reaches a permanent terminal state.\n */\nexport async function markTaskRetryable(\n id: string,\n errorMessage: string,\n): Promise<void> {\n await ensureTable();\n const client = getDbExec();\n const now = Date.now();\n await client.execute({\n sql: `UPDATE integration_pending_tasks\n SET status = ?, updated_at = ?, error_message = ?\n WHERE id = ? AND status = 'processing'`,\n args: [\"pending\", now, errorMessage.slice(0, 2000), id],\n });\n}\n\nexport async function stageTaskDeliveryPayload(\n id: string,\n payload: string,\n): Promise<void> {\n await ensureTable();\n const client = getDbExec();\n const now = Date.now();\n const result = await client.execute({\n sql: `UPDATE integration_pending_tasks\n SET payload = ?, updated_at = ?\n WHERE id = ? AND status = 'processing'`,\n args: [payload, now, id],\n });\n const affected = Number(\n (result as { rowsAffected?: number }).rowsAffected ??\n (result as { rowCount?: number }).rowCount ??\n 0,\n );\n if (affected === 0) {\n throw new Error(\"Integration task is no longer claimable for delivery\");\n }\n}\n\nexport async function markTaskDeliveryRetryable(\n id: string,\n payload: string,\n errorMessage: string,\n): Promise<void> {\n await ensureTable();\n const client = getDbExec();\n const result = await client.execute({\n sql: `UPDATE integration_pending_tasks\n SET status = ?, payload = ?, updated_at = ?, error_message = ?\n WHERE id = ? AND status = 'processing'`,\n args: [\"pending\", payload, Date.now(), errorMessage.slice(0, 2000), id],\n });\n const affected = Number(\n (result as { rowsAffected?: number }).rowsAffected ??\n (result as { rowCount?: number }).rowCount ??\n 0,\n );\n if (affected === 0) {\n throw new Error(\n \"Integration task is no longer claimable for delivery retry\",\n );\n }\n}\n\nexport async function failTaskDeliveryTransition(\n id: string,\n errorMessage: string,\n): Promise<void> {\n await ensureTable();\n const result = await getDbExec().execute({\n sql: `UPDATE integration_pending_tasks\n SET status = ?, updated_at = ?, error_message = ?, payload = ?\n WHERE id = ? AND status = 'processing'`,\n args: [\"failed\", Date.now(), errorMessage.slice(0, 2000), \"{}\", id],\n });\n const affected = Number(\n (result as { rowsAffected?: number }).rowsAffected ??\n (result as { rowCount?: number }).rowCount ??\n 0,\n );\n if (affected === 0) {\n throw new Error(\n \"Integration task delivery failure transition lost its race\",\n );\n }\n}\n\n/** Mark a task as failed and stash an error message. */\nexport async function markTaskFailed(\n id: string,\n errorMessage: string,\n): Promise<void> {\n await ensureTable();\n const client = getDbExec();\n const now = Date.now();\n await client.execute({\n sql: `UPDATE integration_pending_tasks\n SET status = ?, updated_at = ?, error_message = ?, payload = ?, external_event_key = NULL\n WHERE id = ?`,\n args: [\"failed\", now, errorMessage.slice(0, 2000), \"{}\", id],\n });\n}\n"]}
1
+ {"version":3,"file":"pending-tasks-store.js","sourceRoot":"","sources":["../../src/integrations/pending-tasks-store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * SQL-backed pending task queue for integration webhooks.\n *\n * Why this exists: serverless platforms (Netlify Lambda, Vercel, Cloudflare\n * Workers) freeze the function execution as soon as the HTTP response is\n * returned. Fire-and-forget background `Promise`s get killed mid-flight,\n * meaning agent loops triggered from a Slack/Telegram webhook never finish.\n *\n * Solution: persist the inbound message to SQL inside the webhook handler,\n * then dispatch a fresh HTTP POST to a separate processor endpoint. Each\n * invocation gets its own fresh function timeout budget.\n */\nimport { getDbExec, isPostgres, intType } from \"../db/client.js\";\nimport {\n ensureTableExists,\n ensureColumnExists,\n ensureIndexExists,\n} from \"../db/ddl-guard.js\";\n\nlet _initPromise: Promise<void> | undefined;\nexport const MAX_PENDING_TASK_ATTEMPTS = 3;\n\nasync function ensureTable(): Promise<void> {\n if (!_initPromise) {\n _initPromise = (async () => {\n const client = getDbExec();\n const createSql = `CREATE TABLE IF NOT EXISTS integration_pending_tasks (\n id TEXT PRIMARY KEY,\n platform TEXT NOT NULL,\n external_thread_id TEXT NOT NULL,\n payload TEXT NOT NULL,\n owner_email TEXT NOT NULL,\n org_id TEXT,\n status TEXT NOT NULL,\n attempts ${intType()} NOT NULL DEFAULT 0,\n error_message TEXT,\n created_at ${intType()} NOT NULL,\n updated_at ${intType()} NOT NULL,\n completed_at ${intType()}\n)`;\n\n if (isPostgres()) {\n // PG guard: probe via information_schema, only issue DDL if missing, bounded lock_timeout\n await ensureTableExists(\"integration_pending_tasks\", createSql);\n await ensureColumnExists(\n \"integration_pending_tasks\",\n \"external_event_key\",\n `ALTER TABLE integration_pending_tasks ADD COLUMN IF NOT EXISTS external_event_key TEXT`,\n );\n await ensureIndexExists(\n \"idx_pending_tasks_status_created\",\n `CREATE INDEX IF NOT EXISTS idx_pending_tasks_status_created ON integration_pending_tasks(status, created_at)`,\n );\n await ensureIndexExists(\n \"idx_pending_tasks_event_key\",\n `CREATE UNIQUE INDEX IF NOT EXISTS idx_pending_tasks_event_key ON integration_pending_tasks(platform, external_event_key)`,\n );\n return;\n }\n\n await client.execute(createSql);\n await client.execute(\n `CREATE INDEX IF NOT EXISTS idx_pending_tasks_status_created ON integration_pending_tasks(status, created_at)`,\n );\n // Additive migration: add a stable per-event dedup key so duplicate\n // webhook deliveries from the same platform get rejected at the SQL\n // layer instead of via an in-memory Map (which doesn't survive\n // serverless cold starts — H3 in the webhook security audit). The\n // unique index ensures a duplicate INSERT raises an error we can\n // catch as \"already-enqueued\".\n await ensureExternalEventKey(client);\n await client.execute(\n `CREATE UNIQUE INDEX IF NOT EXISTS idx_pending_tasks_event_key ON integration_pending_tasks(platform, external_event_key)`,\n );\n })().catch((err) => {\n // Retry init on the next call after a failed startup.\n _initPromise = undefined;\n throw err;\n });\n }\n return _initPromise;\n}\n\nasync function ensureExternalEventKey(\n client: ReturnType<typeof getDbExec>,\n): Promise<void> {\n if (isPostgres()) {\n await client.execute(\n `ALTER TABLE integration_pending_tasks ADD COLUMN IF NOT EXISTS external_event_key TEXT`,\n );\n return;\n }\n // SQLite doesn't support `ADD COLUMN IF NOT EXISTS` until 3.35; swallow\n // the duplicate-column error so reruns are idempotent.\n try {\n await client.execute(\n `ALTER TABLE integration_pending_tasks ADD COLUMN external_event_key TEXT`,\n );\n } catch (err: any) {\n if (\n !String(err?.message ?? err)\n .toLowerCase()\n .includes(\"duplicate\")\n ) {\n throw err;\n }\n }\n}\n\n/** Status values for an integration pending task. */\nexport type PendingTaskStatus =\n | \"pending\"\n | \"processing\"\n | \"completed\"\n | \"failed\";\n\nexport interface PendingTask {\n id: string;\n platform: string;\n externalThreadId: string;\n payload: string;\n ownerEmail: string;\n orgId: string | null;\n status: PendingTaskStatus;\n attempts: number;\n errorMessage: string | null;\n createdAt: number;\n updatedAt: number;\n completedAt: number | null;\n}\n\n/**\n * Whether a provider thread currently has queued or executing work.\n *\n * Messaging adapters use this to accept unmentioned replies only while an\n * agent task is active. This prevents broad message subscriptions from turning\n * every channel message into an agent invocation.\n */\nexport async function hasActivePendingTask(\n platform: string,\n externalThreadId: string,\n): Promise<boolean> {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT 1 AS active\n FROM integration_pending_tasks\n WHERE platform = ?\n AND external_thread_id = ?\n AND status IN ('pending', 'processing')\n LIMIT 1`,\n args: [platform, externalThreadId],\n });\n return rows.length > 0;\n}\n\nfunction rowToTask(row: Record<string, unknown>): PendingTask {\n return {\n id: row.id as string,\n platform: row.platform as string,\n externalThreadId: row.external_thread_id as string,\n payload: row.payload as string,\n ownerEmail: row.owner_email as string,\n orgId: (row.org_id as string | null) ?? null,\n status: row.status as PendingTaskStatus,\n attempts: Number(row.attempts ?? 0),\n errorMessage: (row.error_message as string | null) ?? null,\n createdAt: Number(row.created_at ?? 0),\n updatedAt: Number(row.updated_at ?? 0),\n completedAt:\n row.completed_at == null ? null : Number(row.completed_at as number),\n };\n}\n\n/**\n * Insert a new pending task. Returns the generated task id.\n *\n * If `externalEventKey` is supplied, the unique index on\n * `(platform, external_event_key)` will reject duplicates — callers should\n * catch the resulting constraint-violation error and treat it as\n * \"already enqueued\" instead of a hard failure (H3 in the webhook security\n * audit). This is the SQL-backed replacement for the in-memory dedup map.\n */\nexport async function insertPendingTask(input: {\n id: string;\n platform: string;\n externalThreadId: string;\n payload: string;\n ownerEmail: string;\n orgId?: string | null;\n externalEventKey?: string | null;\n}): Promise<void> {\n await ensureTable();\n const client = getDbExec();\n const now = Date.now();\n await client.execute({\n sql: `INSERT INTO integration_pending_tasks\n (id, platform, external_thread_id, payload, owner_email, org_id, status, attempts, created_at, updated_at, external_event_key)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n args: [\n input.id,\n input.platform,\n input.externalThreadId,\n input.payload,\n input.ownerEmail,\n input.orgId ?? null,\n \"pending\",\n 0,\n now,\n now,\n input.externalEventKey ?? null,\n ],\n });\n}\n\n/**\n * Returns whether a duplicate-event error from `insertPendingTask` looks\n * like a unique-constraint violation on `(platform, external_event_key)`.\n *\n * Postgres surfaces these as `error.code === \"23505\"`, while SQLite uses\n * a substring match on the error text. Used by the webhook handler to\n * distinguish \"already enqueued\" (silently OK) from genuine insert failures.\n */\nexport function isDuplicateEventError(err: unknown): boolean {\n const e = err as { code?: string; message?: string } | null;\n if (!e) return false;\n if (e.code === \"23505\") return true; // Postgres unique-violation\n const msg = String(e.message ?? \"\").toLowerCase();\n return (\n msg.includes(\"unique\") ||\n msg.includes(\"duplicate entry\") ||\n msg.includes(\"duplicate key\")\n );\n}\n\n/** Fetch a pending task by id. */\nexport async function getPendingTask(id: string): Promise<PendingTask | null> {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT id, platform, external_thread_id, payload, owner_email, org_id, status, attempts, error_message, created_at, updated_at, completed_at\n FROM integration_pending_tasks WHERE id = ? LIMIT 1`,\n args: [id],\n });\n if (rows.length === 0) return null;\n return rowToTask(rows[0] as Record<string, unknown>);\n}\n\nexport interface ResolvedIntegrationSourceContext {\n platform: \"slack\";\n sourceUrl: string;\n}\n\ntype PendingTaskSourceRow = Pick<\n PendingTask,\n \"platform\" | \"payload\" | \"ownerEmail\" | \"orgId\"\n>;\n\nexport function sourceContextFromPendingTask(\n task: PendingTaskSourceRow | null,\n ownerEmail: string,\n orgId: string | null,\n): ResolvedIntegrationSourceContext | null {\n if (\n !task ||\n task.ownerEmail !== ownerEmail ||\n task.orgId !== orgId ||\n task.platform !== \"slack\"\n ) {\n return null;\n }\n\n try {\n const payload = JSON.parse(task.payload) as Record<string, unknown>;\n const incoming = payload.incoming;\n if (!incoming || typeof incoming !== \"object\") return null;\n const incomingRecord = incoming as Record<string, unknown>;\n if (incomingRecord.platform !== \"slack\") return null;\n const sourceUrl = incomingRecord.sourceUrl;\n if (\n typeof sourceUrl !== \"string\" ||\n !sourceUrl ||\n sourceUrl !== sourceUrl.trim()\n ) {\n return null;\n }\n\n const parsed = new URL(sourceUrl);\n const isSlackHost =\n parsed.hostname === \"slack.com\" || parsed.hostname.endsWith(\".slack.com\");\n if (\n parsed.protocol !== \"https:\" ||\n !isSlackHost ||\n parsed.username ||\n parsed.password ||\n parsed.port\n ) {\n return null;\n }\n return { platform: \"slack\", sourceUrl };\n } catch {\n return null;\n }\n}\n\n/** Resolve trusted Slack provenance without exposing the stored task payload. */\nexport async function resolveIntegrationSourceContext(\n id: string,\n ownerEmail: string,\n orgId: string | null,\n): Promise<ResolvedIntegrationSourceContext | null> {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT platform, payload, owner_email, org_id\n FROM integration_pending_tasks\n WHERE id = ?\n AND owner_email = ?\n AND (org_id = ? OR (org_id IS NULL AND CAST(? AS TEXT) IS NULL))\n AND platform = 'slack'\n LIMIT 1`,\n args: [id, ownerEmail, orgId, orgId],\n });\n if (rows.length === 0) return null;\n const row = rows[0] as Record<string, unknown>;\n return sourceContextFromPendingTask(\n {\n platform: row.platform as string,\n payload: row.payload as string,\n ownerEmail: row.owner_email as string,\n orgId: (row.org_id as string | null) ?? null,\n },\n ownerEmail,\n orgId,\n );\n}\n\n/**\n * Atomically claim a task: transition pending → processing and increment\n * attempts. Returns the updated task if the transition succeeded, otherwise\n * null (e.g. the task was already claimed by a concurrent worker).\n */\nexport async function claimPendingTask(\n id: string,\n): Promise<PendingTask | null> {\n await ensureTable();\n const client = getDbExec();\n const now = Date.now();\n\n // Conditional update: only flip if currently pending. Failed tasks are\n // terminal unless an explicit retry path resets them to pending first.\n const result = await client.execute({\n sql: isPostgres()\n ? `UPDATE integration_pending_tasks\n SET status = ?, attempts = attempts + 1, updated_at = ?\n WHERE id = ? AND status = 'pending'\n AND NOT EXISTS (\n SELECT 1 FROM integration_pending_tasks active\n WHERE active.platform = integration_pending_tasks.platform\n AND active.external_thread_id = integration_pending_tasks.external_thread_id\n AND active.status = 'processing'\n AND active.id <> integration_pending_tasks.id\n )\n AND NOT EXISTS (\n SELECT 1 FROM integration_pending_tasks earlier\n WHERE earlier.platform = integration_pending_tasks.platform\n AND earlier.external_thread_id = integration_pending_tasks.external_thread_id\n AND earlier.status = 'pending'\n AND (\n earlier.created_at < integration_pending_tasks.created_at\n OR (\n earlier.created_at = integration_pending_tasks.created_at\n AND earlier.id < integration_pending_tasks.id\n )\n )\n )\n RETURNING id, platform, external_thread_id, payload, owner_email, org_id, status, attempts, error_message, created_at, updated_at, completed_at`\n : `UPDATE integration_pending_tasks\n SET status = ?, attempts = attempts + 1, updated_at = ?\n WHERE id = ? AND status = 'pending'\n AND NOT EXISTS (\n SELECT 1 FROM integration_pending_tasks active\n WHERE active.platform = integration_pending_tasks.platform\n AND active.external_thread_id = integration_pending_tasks.external_thread_id\n AND active.status = 'processing'\n AND active.id <> integration_pending_tasks.id\n )\n AND NOT EXISTS (\n SELECT 1 FROM integration_pending_tasks earlier\n WHERE earlier.platform = integration_pending_tasks.platform\n AND earlier.external_thread_id = integration_pending_tasks.external_thread_id\n AND earlier.status = 'pending'\n AND (\n earlier.created_at < integration_pending_tasks.created_at\n OR (\n earlier.created_at = integration_pending_tasks.created_at\n AND earlier.id < integration_pending_tasks.id\n )\n )\n )`,\n args: [\"processing\", now, id],\n });\n const rows = result.rows ?? [];\n\n if (isPostgres()) {\n if (rows.length === 0) return null;\n return rowToTask(rows[0] as Record<string, unknown>);\n }\n\n // SQLite: no RETURNING, so re-read after the update. Confirm we actually\n // moved it into 'processing' (vs. lost the race).\n const affected =\n (result as { rowsAffected?: number; rowCount?: number }).rowsAffected ??\n (result as { rowsAffected?: number; rowCount?: number }).rowCount;\n if (affected === 0) return null;\n const fetched = await getPendingTask(id);\n if (!fetched || fetched.status !== \"processing\") return null;\n return fetched;\n}\n\n/** Next queued turn for a provider thread after its current task completes. */\nexport async function getNextPendingTaskIdForThread(\n platform: string,\n externalThreadId: string,\n): Promise<string | null> {\n await ensureTable();\n const { rows } = await getDbExec().execute({\n sql: `SELECT id FROM integration_pending_tasks\n WHERE platform = ? AND external_thread_id = ? AND status = 'pending'\n ORDER BY created_at ASC, id ASC LIMIT 1`,\n args: [platform, externalThreadId],\n });\n return rows[0]?.id ? String(rows[0].id) : null;\n}\n\n/** Mark a task as completed. */\nexport async function markTaskCompleted(id: string): Promise<void> {\n await ensureTable();\n const client = getDbExec();\n const now = Date.now();\n await client.execute({\n sql: `UPDATE integration_pending_tasks\n SET status = ?, updated_at = ?, completed_at = ?, payload = ?\n WHERE id = ?`,\n // The payload can contain short-lived provider credentials such as a\n // Discord interaction token. Once terminal, no retry needs the inbound\n // body, so erase it instead of retaining secrets or user text indefinitely.\n args: [\"completed\", now, now, \"{}\", id],\n });\n}\n\n/**\n * Return a transiently failed task to the retryable queue without erasing its\n * payload. The payload may contain the only copy of the inbound message and is\n * scrubbed only when the task reaches a permanent terminal state.\n */\nexport async function markTaskRetryable(\n id: string,\n errorMessage: string,\n): Promise<void> {\n await ensureTable();\n const client = getDbExec();\n const now = Date.now();\n await client.execute({\n sql: `UPDATE integration_pending_tasks\n SET status = ?, updated_at = ?, error_message = ?\n WHERE id = ? AND status = 'processing'`,\n args: [\"pending\", now, errorMessage.slice(0, 2000), id],\n });\n}\n\nexport async function stageTaskDeliveryPayload(\n id: string,\n payload: string,\n): Promise<void> {\n await ensureTable();\n const client = getDbExec();\n const now = Date.now();\n const result = await client.execute({\n sql: `UPDATE integration_pending_tasks\n SET payload = ?, updated_at = ?\n WHERE id = ? AND status = 'processing'`,\n args: [payload, now, id],\n });\n const affected = Number(\n (result as { rowsAffected?: number }).rowsAffected ??\n (result as { rowCount?: number }).rowCount ??\n 0,\n );\n if (affected === 0) {\n throw new Error(\"Integration task is no longer claimable for delivery\");\n }\n}\n\nexport async function markTaskDeliveryRetryable(\n id: string,\n payload: string,\n errorMessage: string,\n): Promise<void> {\n await ensureTable();\n const client = getDbExec();\n const result = await client.execute({\n sql: `UPDATE integration_pending_tasks\n SET status = ?, payload = ?, updated_at = ?, error_message = ?\n WHERE id = ? AND status = 'processing'`,\n args: [\"pending\", payload, Date.now(), errorMessage.slice(0, 2000), id],\n });\n const affected = Number(\n (result as { rowsAffected?: number }).rowsAffected ??\n (result as { rowCount?: number }).rowCount ??\n 0,\n );\n if (affected === 0) {\n throw new Error(\n \"Integration task is no longer claimable for delivery retry\",\n );\n }\n}\n\nexport async function failTaskDeliveryTransition(\n id: string,\n errorMessage: string,\n): Promise<void> {\n await ensureTable();\n const result = await getDbExec().execute({\n sql: `UPDATE integration_pending_tasks\n SET status = ?, updated_at = ?, error_message = ?, payload = ?\n WHERE id = ? AND status = 'processing'`,\n args: [\"failed\", Date.now(), errorMessage.slice(0, 2000), \"{}\", id],\n });\n const affected = Number(\n (result as { rowsAffected?: number }).rowsAffected ??\n (result as { rowCount?: number }).rowCount ??\n 0,\n );\n if (affected === 0) {\n throw new Error(\n \"Integration task delivery failure transition lost its race\",\n );\n }\n}\n\n/** Mark a task as failed and stash an error message. */\nexport async function markTaskFailed(\n id: string,\n errorMessage: string,\n): Promise<void> {\n await ensureTable();\n const client = getDbExec();\n const now = Date.now();\n await client.execute({\n sql: `UPDATE integration_pending_tasks\n SET status = ?, updated_at = ?, error_message = ?, payload = ?, external_event_key = NULL\n WHERE id = ?`,\n args: [\"failed\", now, errorMessage.slice(0, 2000), \"{}\", id],\n });\n}\n"]}
@@ -275,7 +275,7 @@ export async function listRemoteCommandsForOwner(input) {
275
275
  const { rows } = await getDbExec().execute({
276
276
  sql: `SELECT * FROM integration_remote_commands
277
277
  WHERE owner_email = ?
278
- AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)
278
+ AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)
279
279
  ORDER BY updated_at DESC
280
280
  LIMIT ?`,
281
281
  args: [input.ownerEmail, input.orgId ?? null, input.orgId ?? null, limit],
@@ -334,7 +334,7 @@ export async function claimNextComputerCommand(input) {
334
334
  const { rows } = await client.execute({
335
335
  sql: `SELECT * FROM integration_remote_commands
336
336
  WHERE device_id = ? AND owner_email = ?
337
- AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)
337
+ AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)
338
338
  AND kind = 'computer-operation' AND status = 'pending'
339
339
  AND next_check_at <= ?${operationClassClause}
340
340
  ORDER BY computer_sequence ASC, created_at ASC
@@ -370,7 +370,7 @@ export async function claimNextComputerCommand(input) {
370
370
  sql: `UPDATE integration_remote_commands
371
371
  SET status = 'failed', error_message = ?, completed_at = ?, updated_at = ?
372
372
  WHERE id = ? AND device_id = ? AND owner_email = ?
373
- AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)
373
+ AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)
374
374
  AND status = 'pending'`,
375
375
  args: [
376
376
  error instanceof Error
@@ -392,13 +392,13 @@ export async function claimNextComputerCommand(input) {
392
392
  ? `UPDATE integration_remote_commands
393
393
  SET status = 'claimed', attempts = attempts + 1, claimed_at = ?, updated_at = ?
394
394
  WHERE id = ? AND device_id = ? AND owner_email = ?
395
- AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)
395
+ AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)
396
396
  AND status = 'pending' AND lease_expires_at > ?
397
397
  RETURNING *`
398
398
  : `UPDATE integration_remote_commands
399
399
  SET status = 'claimed', attempts = attempts + 1, claimed_at = ?, updated_at = ?
400
400
  WHERE id = ? AND device_id = ? AND owner_email = ?
401
- AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)
401
+ AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)
402
402
  AND status = 'pending' AND lease_expires_at > ?`,
403
403
  args: [
404
404
  now,
@@ -1 +1 @@
1
- {"version":3,"file":"remote-commands-store.js","sourceRoot":"","sources":["../../src/integrations/remote-commands-store.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n getDbExec,\n intType,\n isPostgres,\n retryOnDdlRace,\n} from \"../db/client.js\";\nimport {\n ensureColumnExists,\n ensureTableExists,\n ensureIndexExists,\n} from \"../db/ddl-guard.js\";\nimport {\n authorizeComputerOperation,\n ensureComputerApprovalStore,\n} from \"./computer-supervision-store.js\";\nimport {\n assertValidComputerCommandEnvelope,\n ComputerSupervisionError,\n} from \"./computer-supervision.js\";\nimport {\n getRemoteComputerCapabilities,\n getRemoteDeviceForOwner,\n} from \"./remote-devices-store.js\";\nimport {\n assertNoBinaryPayload,\n serializeBoundedRemoteJson,\n} from \"./remote-json-safety.js\";\nimport type {\n ComputerCommandEnvelope,\n ComputerOperationClass,\n RemoteCommand,\n RemoteCommandKind,\n RemoteCommandStatus,\n} from \"./remote-types.js\";\n\nlet _initPromise: Promise<void> | undefined;\n\nconst REMOTE_COMMAND_KINDS: RemoteCommandKind[] = [\n \"create-run\",\n \"list-runs\",\n \"get-run\",\n \"append-followup\",\n \"approve\",\n \"deny\",\n \"stop\",\n \"status\",\n];\n\nconst TERMINAL_STATUSES = new Set<RemoteCommandStatus>([\"completed\", \"failed\"]);\n\nasync function ensureTable(): Promise<void> {\n if (!_initPromise) {\n _initPromise = (async () => {\n const client = getDbExec();\n const createSql = `CREATE TABLE IF NOT EXISTS integration_remote_commands (\n id TEXT PRIMARY KEY,\n device_id TEXT NOT NULL,\n owner_email TEXT NOT NULL,\n org_id TEXT,\n kind TEXT NOT NULL,\n params_json TEXT NOT NULL,\n status TEXT NOT NULL,\n result_json TEXT,\n platform TEXT,\n external_thread_id TEXT,\n computer_task_id TEXT,\n computer_run_id TEXT,\n computer_sequence ${intType()},\n idempotency_key TEXT,\n operation_class TEXT,\n approval_scope TEXT,\n action_hash TEXT,\n lease_expires_at ${intType()},\n attempts ${intType()} NOT NULL DEFAULT 0,\n next_check_at ${intType()} NOT NULL,\n claimed_at ${intType()},\n completed_at ${intType()},\n error_message TEXT,\n created_at ${intType()} NOT NULL,\n updated_at ${intType()} NOT NULL\n)`;\n\n if (isPostgres()) {\n // PG guard: probe via information_schema, only issue DDL if missing, bounded lock_timeout\n await ensureTableExists(\"integration_remote_commands\", createSql);\n await ensureComputerCommandColumns();\n await ensureIndexExists(\n \"idx_remote_commands_device_status_next\",\n `CREATE INDEX IF NOT EXISTS idx_remote_commands_device_status_next ON integration_remote_commands(device_id, status, next_check_at)`,\n );\n await ensureIndexExists(\n \"idx_remote_commands_owner\",\n `CREATE INDEX IF NOT EXISTS idx_remote_commands_owner ON integration_remote_commands(owner_email, org_id)`,\n );\n await ensureComputerCommandIndexes();\n return;\n }\n\n // SQLite: keep existing behavior\n await retryOnDdlRace(() => client.execute(createSql));\n await ensureComputerCommandColumns();\n await retryOnDdlRace(() =>\n client.execute(\n `CREATE INDEX IF NOT EXISTS idx_remote_commands_device_status_next ON integration_remote_commands(device_id, status, next_check_at)`,\n ),\n );\n await ensureComputerCommandIndexes();\n await retryOnDdlRace(() =>\n client.execute(\n `CREATE INDEX IF NOT EXISTS idx_remote_commands_owner ON integration_remote_commands(owner_email, org_id)`,\n ),\n );\n })().catch((err) => {\n // Retry init on the next call after a failed startup.\n _initPromise = undefined;\n throw err;\n });\n }\n return _initPromise;\n}\n\nfunction rowToCommand(row: Record<string, unknown>): RemoteCommand {\n const params = parseJson(row.params_json, {});\n return {\n id: row.id as string,\n deviceId: row.device_id as string,\n ownerEmail: row.owner_email as string,\n orgId: (row.org_id as string | null) ?? null,\n kind: row.kind as RemoteCommandKind,\n params,\n status: row.status as RemoteCommandStatus,\n result: parseJson(row.result_json, null),\n platform: (row.platform as string | null) ?? null,\n externalThreadId: (row.external_thread_id as string | null) ?? null,\n computerOperation:\n row.kind === \"computer-operation\"\n ? ((params as { envelope?: ComputerCommandEnvelope })?.envelope ?? null)\n : null,\n attempts: Number(row.attempts ?? 0),\n nextCheckAt: Number(row.next_check_at ?? 0),\n claimedAt: row.claimed_at == null ? null : Number(row.claimed_at as number),\n completedAt:\n row.completed_at == null ? null : Number(row.completed_at as number),\n errorMessage: (row.error_message as string | null) ?? null,\n createdAt: Number(row.created_at ?? 0),\n updatedAt: Number(row.updated_at ?? 0),\n };\n}\n\nasync function ensureComputerCommandColumns(): Promise<void> {\n const columns: Array<[string, string]> = [\n [\"computer_task_id\", \"TEXT\"],\n [\"computer_run_id\", \"TEXT\"],\n [\"computer_sequence\", intType()],\n [\"idempotency_key\", \"TEXT\"],\n [\"operation_class\", \"TEXT\"],\n [\"approval_scope\", \"TEXT\"],\n [\"action_hash\", \"TEXT\"],\n [\"lease_expires_at\", intType()],\n ];\n for (const [name, definition] of columns) {\n const sql = `ALTER TABLE integration_remote_commands ADD COLUMN${isPostgres() ? \" IF NOT EXISTS\" : \"\"} ${name} ${definition}`;\n if (isPostgres()) {\n await ensureColumnExists(\"integration_remote_commands\", name, sql);\n continue;\n }\n try {\n await retryOnDdlRace(() => getDbExec().execute(sql));\n } catch (error) {\n if (!isDuplicateColumnError(error)) throw error;\n }\n }\n}\n\nasync function ensureComputerCommandIndexes(): Promise<void> {\n const indexes = [\n [\n \"idx_remote_commands_computer_sequence\",\n `CREATE UNIQUE INDEX IF NOT EXISTS idx_remote_commands_computer_sequence ON integration_remote_commands(device_id, computer_task_id, computer_run_id, computer_sequence)`,\n ],\n [\n \"idx_remote_commands_idempotency\",\n `CREATE UNIQUE INDEX IF NOT EXISTS idx_remote_commands_idempotency ON integration_remote_commands(device_id, idempotency_key)`,\n ],\n ] as const;\n for (const [name, sql] of indexes) {\n if (isPostgres()) await ensureIndexExists(name, sql);\n else await retryOnDdlRace(() => getDbExec().execute(sql));\n }\n}\n\nexport function isRemoteCommandKind(\n value: unknown,\n): value is RemoteCommandKind {\n return (\n typeof value === \"string\" &&\n REMOTE_COMMAND_KINDS.includes(value as RemoteCommandKind)\n );\n}\n\nexport async function enqueueRemoteCommand(input: {\n deviceId: string;\n ownerEmail: string;\n orgId?: string | null;\n kind: RemoteCommandKind;\n params?: unknown;\n platform?: string | null;\n externalThreadId?: string | null;\n nextCheckAt?: number;\n}): Promise<RemoteCommand> {\n if (input.kind === \"computer-operation\") {\n throw new ComputerSupervisionError(\n \"invalid-envelope\",\n \"Computer operations must use enqueueComputerCommand\",\n );\n }\n await ensureTable();\n const client = getDbExec();\n const now = Date.now();\n const id = `remote-command-${now}-${randomHex(8)}`;\n\n await client.execute({\n sql: `INSERT INTO integration_remote_commands\n (id, device_id, owner_email, org_id, kind, params_json, status, result_json,\n platform, external_thread_id, attempts, next_check_at, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n args: [\n id,\n input.deviceId,\n input.ownerEmail,\n input.orgId ?? null,\n input.kind,\n JSON.stringify(input.params ?? {}),\n \"pending\",\n null,\n input.platform ?? null,\n input.externalThreadId ?? null,\n 0,\n input.nextCheckAt ?? now,\n now,\n now,\n ],\n });\n\n const command = await getRemoteCommand(id);\n if (!command) throw new Error(\"remote command insert failed\");\n return command;\n}\n\nexport async function enqueueComputerCommand(input: {\n deviceId: string;\n ownerEmail: string;\n orgId?: string | null;\n envelope: ComputerCommandEnvelope;\n platform?: string | null;\n}): Promise<RemoteCommand> {\n const envelope = await assertValidComputerCommandEnvelope(input.envelope);\n const device = await getRemoteDeviceForOwner({\n id: input.deviceId,\n ownerEmail: input.ownerEmail,\n orgId: input.orgId,\n });\n if (!device || device.status !== \"active\") {\n throw new ComputerSupervisionError(\n \"approval-mismatch\",\n \"Computer device does not belong to this owner and organization\",\n );\n }\n const capabilities = getRemoteComputerCapabilities(device);\n const [surface, mode] = envelope.operationClass.split(\".\") as [\n \"browser\" | \"desktop\",\n \"observe\" | \"control\",\n ];\n if (capabilities?.[surface]?.[mode] !== true) {\n throw new ComputerSupervisionError(\n \"approval-denied\",\n `Remote device did not advertise ${envelope.operationClass}`,\n );\n }\n await ensureTable();\n await ensureComputerApprovalStore();\n const client = getDbExec();\n const insert = async (tx: typeof client): Promise<string> => {\n await authorizeComputerOperation({ ...input, envelope }, tx);\n const now = Date.now();\n const id = `remote-command-${now}-${randomHex(8)}`;\n try {\n await tx.execute({\n sql: `INSERT INTO integration_remote_commands\n (id, device_id, owner_email, org_id, kind, params_json, status,\n result_json, platform, external_thread_id, computer_task_id,\n computer_run_id, computer_sequence, idempotency_key, operation_class,\n approval_scope, action_hash, lease_expires_at, attempts, next_check_at,\n created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n args: [\n id,\n input.deviceId,\n input.ownerEmail,\n input.orgId ?? null,\n \"computer-operation\",\n JSON.stringify({ envelope }),\n \"pending\",\n null,\n input.platform ?? null,\n null,\n envelope.taskId,\n envelope.runId,\n envelope.sequence,\n envelope.idempotencyKey,\n envelope.operationClass,\n envelope.approval.scope,\n envelope.approval.actionHash,\n envelope.leaseExpiresAt,\n 0,\n now,\n now,\n now,\n ],\n });\n } catch (error) {\n if (isUniqueConstraintError(error)) {\n throw new ComputerSupervisionError(\n \"replay\",\n \"Computer operation sequence or idempotency key was already used\",\n );\n }\n throw error;\n }\n return id;\n };\n const id = client.transaction\n ? await client.transaction((tx) => insert(tx))\n : await insert(client);\n const command = await getRemoteCommand(id);\n if (!command) throw new Error(\"computer command insert failed\");\n return command;\n}\n\nexport async function getRemoteCommand(\n id: string,\n): Promise<RemoteCommand | null> {\n await ensureTable();\n const { rows } = await getDbExec().execute({\n sql: `SELECT * FROM integration_remote_commands WHERE id = ? LIMIT 1`,\n args: [id],\n });\n return rows[0] ? rowToCommand(rows[0] as Record<string, unknown>) : null;\n}\n\nexport async function listRemoteCommandsForOwner(input: {\n ownerEmail: string;\n orgId?: string | null;\n limit?: number;\n}): Promise<RemoteCommand[]> {\n await ensureTable();\n const limit = Math.max(1, Math.min(input.limit ?? 100, 250));\n if (!Object.prototype.hasOwnProperty.call(input, \"orgId\")) {\n const { rows } = await getDbExec().execute({\n sql: `SELECT * FROM integration_remote_commands\n WHERE owner_email = ?\n ORDER BY updated_at DESC\n LIMIT ?`,\n args: [input.ownerEmail, limit],\n });\n return rows.map((row) => rowToCommand(row as Record<string, unknown>));\n }\n const { rows } = await getDbExec().execute({\n sql: `SELECT * FROM integration_remote_commands\n WHERE owner_email = ?\n AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)\n ORDER BY updated_at DESC\n LIMIT ?`,\n args: [input.ownerEmail, input.orgId ?? null, input.orgId ?? null, limit],\n });\n return rows.map((row) => rowToCommand(row as Record<string, unknown>));\n}\n\nexport async function claimNextRemoteCommand(\n deviceId: string,\n): Promise<RemoteCommand | null> {\n await ensureTable();\n const client = getDbExec();\n const now = Date.now();\n const { rows } = await client.execute({\n sql: `SELECT id FROM integration_remote_commands\n WHERE device_id = ?\n AND status = 'pending'\n AND kind <> 'computer-operation'\n AND next_check_at <= ?\n ORDER BY created_at ASC\n LIMIT 1`,\n args: [deviceId, now],\n });\n const id = rows[0]?.id as string | undefined;\n if (!id) return null;\n\n const result = await client.execute({\n sql: isPostgres()\n ? `UPDATE integration_remote_commands\n SET status = ?, attempts = attempts + 1, claimed_at = ?, updated_at = ?\n WHERE id = ? AND device_id = ? AND status = 'pending'\n RETURNING *`\n : `UPDATE integration_remote_commands\n SET status = ?, attempts = attempts + 1, claimed_at = ?, updated_at = ?\n WHERE id = ? AND device_id = ? AND status = 'pending'`,\n args: [\"claimed\", now, now, id, deviceId],\n });\n if (isPostgres()) {\n const row = result.rows?.[0];\n return row ? rowToCommand(row as Record<string, unknown>) : null;\n }\n const affected = result.rowsAffected ?? (result as any).rowCount;\n if (affected === 0) return null;\n\n const command = await getRemoteCommand(id);\n if (!command || command.status !== \"claimed\") return null;\n return command;\n}\n\nexport async function claimNextComputerCommand(input: {\n deviceId: string;\n ownerEmail: string;\n orgId?: string | null;\n operationClasses?: ComputerOperationClass[];\n now?: number;\n}): Promise<RemoteCommand | null> {\n await ensureTable();\n if (input.operationClasses?.length === 0) return null;\n const client = getDbExec();\n const now = input.now ?? Date.now();\n const operationClassClause = input.operationClasses?.length\n ? ` AND operation_class IN (${input.operationClasses.map(() => \"?\").join(\", \")})`\n : \"\";\n const { rows } = await client.execute({\n sql: `SELECT * FROM integration_remote_commands\n WHERE device_id = ? AND owner_email = ?\n AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)\n AND kind = 'computer-operation' AND status = 'pending'\n AND next_check_at <= ?${operationClassClause}\n ORDER BY computer_sequence ASC, created_at ASC\n LIMIT 1`,\n args: [\n input.deviceId,\n input.ownerEmail,\n input.orgId ?? null,\n input.orgId ?? null,\n now,\n ...(input.operationClasses ?? []),\n ],\n });\n if (!rows[0]) return null;\n const candidate = rowToCommand(rows[0] as Record<string, unknown>);\n try {\n const envelope = await assertValidComputerCommandEnvelope(\n candidate.computerOperation,\n { now },\n );\n const row = rows[0] as Record<string, unknown>;\n if (\n envelope.taskId !== row.computer_task_id ||\n envelope.runId !== row.computer_run_id ||\n envelope.sequence !== Number(row.computer_sequence) ||\n envelope.idempotencyKey !== row.idempotency_key ||\n envelope.operationClass !== row.operation_class ||\n envelope.approval.scope !== row.approval_scope ||\n envelope.approval.actionHash !== row.action_hash ||\n envelope.leaseExpiresAt !== Number(row.lease_expires_at)\n ) {\n throw new ComputerSupervisionError(\n \"action-hash-mismatch\",\n \"Stored computer command binding does not match its envelope\",\n );\n }\n } catch (error) {\n await client.execute({\n sql: `UPDATE integration_remote_commands\n SET status = 'failed', error_message = ?, completed_at = ?, updated_at = ?\n WHERE id = ? AND device_id = ? AND owner_email = ?\n AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)\n AND status = 'pending'`,\n args: [\n error instanceof Error\n ? error.message.slice(0, 2000)\n : \"Invalid computer operation\",\n now,\n now,\n candidate.id,\n input.deviceId,\n input.ownerEmail,\n input.orgId ?? null,\n input.orgId ?? null,\n ],\n });\n return null;\n }\n\n const result = await client.execute({\n sql: isPostgres()\n ? `UPDATE integration_remote_commands\n SET status = 'claimed', attempts = attempts + 1, claimed_at = ?, updated_at = ?\n WHERE id = ? AND device_id = ? AND owner_email = ?\n AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)\n AND status = 'pending' AND lease_expires_at > ?\n RETURNING *`\n : `UPDATE integration_remote_commands\n SET status = 'claimed', attempts = attempts + 1, claimed_at = ?, updated_at = ?\n WHERE id = ? AND device_id = ? AND owner_email = ?\n AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)\n AND status = 'pending' AND lease_expires_at > ?`,\n args: [\n now,\n now,\n candidate.id,\n input.deviceId,\n input.ownerEmail,\n input.orgId ?? null,\n input.orgId ?? null,\n now,\n ],\n });\n if (isPostgres()) {\n const row = result.rows?.[0];\n return row ? rowToCommand(row as Record<string, unknown>) : null;\n }\n if (affectedRows(result) === 0) return null;\n const command = await getRemoteCommand(candidate.id);\n return command?.status === \"claimed\" ? command : null;\n}\n\nexport async function updateRemoteCommandResult(input: {\n deviceId: string;\n commandId: string;\n status: \"running\" | \"completed\" | \"failed\";\n result?: unknown;\n errorMessage?: string | null;\n}): Promise<RemoteCommand | null> {\n await ensureTable();\n const client = getDbExec();\n const now = Date.now();\n const completedAt = TERMINAL_STATUSES.has(input.status) ? now : null;\n const resultJson =\n input.result === undefined\n ? undefined\n : serializeBoundedRemoteJson(input.result, {\n label: \"Remote command result\",\n maxBytes: 256_000,\n });\n if (input.errorMessage) {\n assertNoBinaryPayload(input.errorMessage, \"Remote command error\");\n }\n\n await client.execute({\n sql: `UPDATE integration_remote_commands\n SET status = ?,\n result_json = COALESCE(?, result_json),\n error_message = ?,\n completed_at = COALESCE(?, completed_at),\n updated_at = ?\n WHERE id = ? AND device_id = ?`,\n args: [\n input.status,\n resultJson ?? null,\n input.errorMessage ? input.errorMessage.slice(0, 2000) : null,\n completedAt,\n now,\n input.commandId,\n input.deviceId,\n ],\n });\n\n const command = await getRemoteCommand(input.commandId);\n if (!command || command.deviceId !== input.deviceId) return null;\n return command;\n}\n\nexport async function retryStaleRemoteCommands(options?: {\n claimedStaleAfterMs?: number;\n runningStaleAfterMs?: number;\n maxAttempts?: number;\n limit?: number;\n}): Promise<{ retried: number; failed: number }> {\n await ensureTable();\n const client = getDbExec();\n const now = Date.now();\n const claimedCutoff = now - (options?.claimedStaleAfterMs ?? 75_000);\n const runningCutoff = now - (options?.runningStaleAfterMs ?? 5 * 60_000);\n const maxAttempts = options?.maxAttempts ?? 3;\n const limit = options?.limit ?? 50;\n\n const { rows } = await client.execute({\n sql: `SELECT id, status, attempts FROM integration_remote_commands\n WHERE (status = 'claimed' AND updated_at <= ?)\n OR (status = 'running' AND updated_at <= ?)\n ORDER BY updated_at ASC\n LIMIT ?`,\n args: [claimedCutoff, runningCutoff, limit],\n });\n\n let retried = 0;\n let failed = 0;\n for (const row of rows) {\n const id = row.id as string;\n const status = row.status as RemoteCommandStatus;\n const attempts = Number(row.attempts ?? 0);\n if (attempts >= maxAttempts) {\n const result = await client.execute({\n sql: `UPDATE integration_remote_commands\n SET status = 'failed',\n error_message = COALESCE(error_message, ?),\n completed_at = ?,\n updated_at = ?\n WHERE id = ? AND status = ?`,\n args: [\n `Retry job: exceeded ${maxAttempts} attempts`,\n now,\n now,\n id,\n status,\n ],\n });\n if ((result.rowsAffected ?? (result as any).rowCount) > 0) failed++;\n continue;\n }\n\n const result = await client.execute({\n sql: `UPDATE integration_remote_commands\n SET status = 'pending',\n next_check_at = ?,\n updated_at = ?\n WHERE id = ? AND status = ?`,\n args: [now, now, id, status],\n });\n if ((result.rowsAffected ?? (result as any).rowCount) > 0) retried++;\n }\n\n return { retried, failed };\n}\n\nfunction parseJson(value: unknown, fallback: unknown): unknown {\n if (value == null) return fallback;\n try {\n return JSON.parse(String(value));\n } catch {\n return fallback;\n }\n}\n\nfunction randomHex(byteLength: number): string {\n const bytes = new Uint8Array(byteLength);\n globalThis.crypto.getRandomValues(bytes);\n return Array.from(bytes)\n .map((byte) => byte.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n\nfunction affectedRows(result: {\n rowsAffected?: number;\n rows?: unknown[];\n}): number {\n return (\n result.rowsAffected ??\n Number((result as { rowCount?: unknown }).rowCount ?? 0)\n );\n}\n\nfunction isDuplicateColumnError(error: unknown): boolean {\n const code = String((error as { code?: unknown })?.code ?? \"\");\n const message = String((error as { message?: unknown })?.message ?? error)\n .toLowerCase()\n .trim();\n return (\n code === \"42701\" ||\n message.includes(\"duplicate column\") ||\n message.includes(\"already exists\")\n );\n}\n\nfunction isUniqueConstraintError(error: unknown): boolean {\n const code = String((error as { code?: unknown })?.code ?? \"\");\n const message = String((error as { message?: unknown })?.message ?? error)\n .toLowerCase()\n .trim();\n return (\n code === \"23505\" ||\n code === \"2067\" ||\n message.includes(\"unique constraint\") ||\n message.includes(\"duplicate key\")\n );\n}\n"]}
1
+ {"version":3,"file":"remote-commands-store.js","sourceRoot":"","sources":["../../src/integrations/remote-commands-store.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n getDbExec,\n intType,\n isPostgres,\n retryOnDdlRace,\n} from \"../db/client.js\";\nimport {\n ensureColumnExists,\n ensureTableExists,\n ensureIndexExists,\n} from \"../db/ddl-guard.js\";\nimport {\n authorizeComputerOperation,\n ensureComputerApprovalStore,\n} from \"./computer-supervision-store.js\";\nimport {\n assertValidComputerCommandEnvelope,\n ComputerSupervisionError,\n} from \"./computer-supervision.js\";\nimport {\n getRemoteComputerCapabilities,\n getRemoteDeviceForOwner,\n} from \"./remote-devices-store.js\";\nimport {\n assertNoBinaryPayload,\n serializeBoundedRemoteJson,\n} from \"./remote-json-safety.js\";\nimport type {\n ComputerCommandEnvelope,\n ComputerOperationClass,\n RemoteCommand,\n RemoteCommandKind,\n RemoteCommandStatus,\n} from \"./remote-types.js\";\n\nlet _initPromise: Promise<void> | undefined;\n\nconst REMOTE_COMMAND_KINDS: RemoteCommandKind[] = [\n \"create-run\",\n \"list-runs\",\n \"get-run\",\n \"append-followup\",\n \"approve\",\n \"deny\",\n \"stop\",\n \"status\",\n];\n\nconst TERMINAL_STATUSES = new Set<RemoteCommandStatus>([\"completed\", \"failed\"]);\n\nasync function ensureTable(): Promise<void> {\n if (!_initPromise) {\n _initPromise = (async () => {\n const client = getDbExec();\n const createSql = `CREATE TABLE IF NOT EXISTS integration_remote_commands (\n id TEXT PRIMARY KEY,\n device_id TEXT NOT NULL,\n owner_email TEXT NOT NULL,\n org_id TEXT,\n kind TEXT NOT NULL,\n params_json TEXT NOT NULL,\n status TEXT NOT NULL,\n result_json TEXT,\n platform TEXT,\n external_thread_id TEXT,\n computer_task_id TEXT,\n computer_run_id TEXT,\n computer_sequence ${intType()},\n idempotency_key TEXT,\n operation_class TEXT,\n approval_scope TEXT,\n action_hash TEXT,\n lease_expires_at ${intType()},\n attempts ${intType()} NOT NULL DEFAULT 0,\n next_check_at ${intType()} NOT NULL,\n claimed_at ${intType()},\n completed_at ${intType()},\n error_message TEXT,\n created_at ${intType()} NOT NULL,\n updated_at ${intType()} NOT NULL\n)`;\n\n if (isPostgres()) {\n // PG guard: probe via information_schema, only issue DDL if missing, bounded lock_timeout\n await ensureTableExists(\"integration_remote_commands\", createSql);\n await ensureComputerCommandColumns();\n await ensureIndexExists(\n \"idx_remote_commands_device_status_next\",\n `CREATE INDEX IF NOT EXISTS idx_remote_commands_device_status_next ON integration_remote_commands(device_id, status, next_check_at)`,\n );\n await ensureIndexExists(\n \"idx_remote_commands_owner\",\n `CREATE INDEX IF NOT EXISTS idx_remote_commands_owner ON integration_remote_commands(owner_email, org_id)`,\n );\n await ensureComputerCommandIndexes();\n return;\n }\n\n // SQLite: keep existing behavior\n await retryOnDdlRace(() => client.execute(createSql));\n await ensureComputerCommandColumns();\n await retryOnDdlRace(() =>\n client.execute(\n `CREATE INDEX IF NOT EXISTS idx_remote_commands_device_status_next ON integration_remote_commands(device_id, status, next_check_at)`,\n ),\n );\n await ensureComputerCommandIndexes();\n await retryOnDdlRace(() =>\n client.execute(\n `CREATE INDEX IF NOT EXISTS idx_remote_commands_owner ON integration_remote_commands(owner_email, org_id)`,\n ),\n );\n })().catch((err) => {\n // Retry init on the next call after a failed startup.\n _initPromise = undefined;\n throw err;\n });\n }\n return _initPromise;\n}\n\nfunction rowToCommand(row: Record<string, unknown>): RemoteCommand {\n const params = parseJson(row.params_json, {});\n return {\n id: row.id as string,\n deviceId: row.device_id as string,\n ownerEmail: row.owner_email as string,\n orgId: (row.org_id as string | null) ?? null,\n kind: row.kind as RemoteCommandKind,\n params,\n status: row.status as RemoteCommandStatus,\n result: parseJson(row.result_json, null),\n platform: (row.platform as string | null) ?? null,\n externalThreadId: (row.external_thread_id as string | null) ?? null,\n computerOperation:\n row.kind === \"computer-operation\"\n ? ((params as { envelope?: ComputerCommandEnvelope })?.envelope ?? null)\n : null,\n attempts: Number(row.attempts ?? 0),\n nextCheckAt: Number(row.next_check_at ?? 0),\n claimedAt: row.claimed_at == null ? null : Number(row.claimed_at as number),\n completedAt:\n row.completed_at == null ? null : Number(row.completed_at as number),\n errorMessage: (row.error_message as string | null) ?? null,\n createdAt: Number(row.created_at ?? 0),\n updatedAt: Number(row.updated_at ?? 0),\n };\n}\n\nasync function ensureComputerCommandColumns(): Promise<void> {\n const columns: Array<[string, string]> = [\n [\"computer_task_id\", \"TEXT\"],\n [\"computer_run_id\", \"TEXT\"],\n [\"computer_sequence\", intType()],\n [\"idempotency_key\", \"TEXT\"],\n [\"operation_class\", \"TEXT\"],\n [\"approval_scope\", \"TEXT\"],\n [\"action_hash\", \"TEXT\"],\n [\"lease_expires_at\", intType()],\n ];\n for (const [name, definition] of columns) {\n const sql = `ALTER TABLE integration_remote_commands ADD COLUMN${isPostgres() ? \" IF NOT EXISTS\" : \"\"} ${name} ${definition}`;\n if (isPostgres()) {\n await ensureColumnExists(\"integration_remote_commands\", name, sql);\n continue;\n }\n try {\n await retryOnDdlRace(() => getDbExec().execute(sql));\n } catch (error) {\n if (!isDuplicateColumnError(error)) throw error;\n }\n }\n}\n\nasync function ensureComputerCommandIndexes(): Promise<void> {\n const indexes = [\n [\n \"idx_remote_commands_computer_sequence\",\n `CREATE UNIQUE INDEX IF NOT EXISTS idx_remote_commands_computer_sequence ON integration_remote_commands(device_id, computer_task_id, computer_run_id, computer_sequence)`,\n ],\n [\n \"idx_remote_commands_idempotency\",\n `CREATE UNIQUE INDEX IF NOT EXISTS idx_remote_commands_idempotency ON integration_remote_commands(device_id, idempotency_key)`,\n ],\n ] as const;\n for (const [name, sql] of indexes) {\n if (isPostgres()) await ensureIndexExists(name, sql);\n else await retryOnDdlRace(() => getDbExec().execute(sql));\n }\n}\n\nexport function isRemoteCommandKind(\n value: unknown,\n): value is RemoteCommandKind {\n return (\n typeof value === \"string\" &&\n REMOTE_COMMAND_KINDS.includes(value as RemoteCommandKind)\n );\n}\n\nexport async function enqueueRemoteCommand(input: {\n deviceId: string;\n ownerEmail: string;\n orgId?: string | null;\n kind: RemoteCommandKind;\n params?: unknown;\n platform?: string | null;\n externalThreadId?: string | null;\n nextCheckAt?: number;\n}): Promise<RemoteCommand> {\n if (input.kind === \"computer-operation\") {\n throw new ComputerSupervisionError(\n \"invalid-envelope\",\n \"Computer operations must use enqueueComputerCommand\",\n );\n }\n await ensureTable();\n const client = getDbExec();\n const now = Date.now();\n const id = `remote-command-${now}-${randomHex(8)}`;\n\n await client.execute({\n sql: `INSERT INTO integration_remote_commands\n (id, device_id, owner_email, org_id, kind, params_json, status, result_json,\n platform, external_thread_id, attempts, next_check_at, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n args: [\n id,\n input.deviceId,\n input.ownerEmail,\n input.orgId ?? null,\n input.kind,\n JSON.stringify(input.params ?? {}),\n \"pending\",\n null,\n input.platform ?? null,\n input.externalThreadId ?? null,\n 0,\n input.nextCheckAt ?? now,\n now,\n now,\n ],\n });\n\n const command = await getRemoteCommand(id);\n if (!command) throw new Error(\"remote command insert failed\");\n return command;\n}\n\nexport async function enqueueComputerCommand(input: {\n deviceId: string;\n ownerEmail: string;\n orgId?: string | null;\n envelope: ComputerCommandEnvelope;\n platform?: string | null;\n}): Promise<RemoteCommand> {\n const envelope = await assertValidComputerCommandEnvelope(input.envelope);\n const device = await getRemoteDeviceForOwner({\n id: input.deviceId,\n ownerEmail: input.ownerEmail,\n orgId: input.orgId,\n });\n if (!device || device.status !== \"active\") {\n throw new ComputerSupervisionError(\n \"approval-mismatch\",\n \"Computer device does not belong to this owner and organization\",\n );\n }\n const capabilities = getRemoteComputerCapabilities(device);\n const [surface, mode] = envelope.operationClass.split(\".\") as [\n \"browser\" | \"desktop\",\n \"observe\" | \"control\",\n ];\n if (capabilities?.[surface]?.[mode] !== true) {\n throw new ComputerSupervisionError(\n \"approval-denied\",\n `Remote device did not advertise ${envelope.operationClass}`,\n );\n }\n await ensureTable();\n await ensureComputerApprovalStore();\n const client = getDbExec();\n const insert = async (tx: typeof client): Promise<string> => {\n await authorizeComputerOperation({ ...input, envelope }, tx);\n const now = Date.now();\n const id = `remote-command-${now}-${randomHex(8)}`;\n try {\n await tx.execute({\n sql: `INSERT INTO integration_remote_commands\n (id, device_id, owner_email, org_id, kind, params_json, status,\n result_json, platform, external_thread_id, computer_task_id,\n computer_run_id, computer_sequence, idempotency_key, operation_class,\n approval_scope, action_hash, lease_expires_at, attempts, next_check_at,\n created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n args: [\n id,\n input.deviceId,\n input.ownerEmail,\n input.orgId ?? null,\n \"computer-operation\",\n JSON.stringify({ envelope }),\n \"pending\",\n null,\n input.platform ?? null,\n null,\n envelope.taskId,\n envelope.runId,\n envelope.sequence,\n envelope.idempotencyKey,\n envelope.operationClass,\n envelope.approval.scope,\n envelope.approval.actionHash,\n envelope.leaseExpiresAt,\n 0,\n now,\n now,\n now,\n ],\n });\n } catch (error) {\n if (isUniqueConstraintError(error)) {\n throw new ComputerSupervisionError(\n \"replay\",\n \"Computer operation sequence or idempotency key was already used\",\n );\n }\n throw error;\n }\n return id;\n };\n const id = client.transaction\n ? await client.transaction((tx) => insert(tx))\n : await insert(client);\n const command = await getRemoteCommand(id);\n if (!command) throw new Error(\"computer command insert failed\");\n return command;\n}\n\nexport async function getRemoteCommand(\n id: string,\n): Promise<RemoteCommand | null> {\n await ensureTable();\n const { rows } = await getDbExec().execute({\n sql: `SELECT * FROM integration_remote_commands WHERE id = ? LIMIT 1`,\n args: [id],\n });\n return rows[0] ? rowToCommand(rows[0] as Record<string, unknown>) : null;\n}\n\nexport async function listRemoteCommandsForOwner(input: {\n ownerEmail: string;\n orgId?: string | null;\n limit?: number;\n}): Promise<RemoteCommand[]> {\n await ensureTable();\n const limit = Math.max(1, Math.min(input.limit ?? 100, 250));\n if (!Object.prototype.hasOwnProperty.call(input, \"orgId\")) {\n const { rows } = await getDbExec().execute({\n sql: `SELECT * FROM integration_remote_commands\n WHERE owner_email = ?\n ORDER BY updated_at DESC\n LIMIT ?`,\n args: [input.ownerEmail, limit],\n });\n return rows.map((row) => rowToCommand(row as Record<string, unknown>));\n }\n const { rows } = await getDbExec().execute({\n sql: `SELECT * FROM integration_remote_commands\n WHERE owner_email = ?\n AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)\n ORDER BY updated_at DESC\n LIMIT ?`,\n args: [input.ownerEmail, input.orgId ?? null, input.orgId ?? null, limit],\n });\n return rows.map((row) => rowToCommand(row as Record<string, unknown>));\n}\n\nexport async function claimNextRemoteCommand(\n deviceId: string,\n): Promise<RemoteCommand | null> {\n await ensureTable();\n const client = getDbExec();\n const now = Date.now();\n const { rows } = await client.execute({\n sql: `SELECT id FROM integration_remote_commands\n WHERE device_id = ?\n AND status = 'pending'\n AND kind <> 'computer-operation'\n AND next_check_at <= ?\n ORDER BY created_at ASC\n LIMIT 1`,\n args: [deviceId, now],\n });\n const id = rows[0]?.id as string | undefined;\n if (!id) return null;\n\n const result = await client.execute({\n sql: isPostgres()\n ? `UPDATE integration_remote_commands\n SET status = ?, attempts = attempts + 1, claimed_at = ?, updated_at = ?\n WHERE id = ? AND device_id = ? AND status = 'pending'\n RETURNING *`\n : `UPDATE integration_remote_commands\n SET status = ?, attempts = attempts + 1, claimed_at = ?, updated_at = ?\n WHERE id = ? AND device_id = ? AND status = 'pending'`,\n args: [\"claimed\", now, now, id, deviceId],\n });\n if (isPostgres()) {\n const row = result.rows?.[0];\n return row ? rowToCommand(row as Record<string, unknown>) : null;\n }\n const affected = result.rowsAffected ?? (result as any).rowCount;\n if (affected === 0) return null;\n\n const command = await getRemoteCommand(id);\n if (!command || command.status !== \"claimed\") return null;\n return command;\n}\n\nexport async function claimNextComputerCommand(input: {\n deviceId: string;\n ownerEmail: string;\n orgId?: string | null;\n operationClasses?: ComputerOperationClass[];\n now?: number;\n}): Promise<RemoteCommand | null> {\n await ensureTable();\n if (input.operationClasses?.length === 0) return null;\n const client = getDbExec();\n const now = input.now ?? Date.now();\n const operationClassClause = input.operationClasses?.length\n ? ` AND operation_class IN (${input.operationClasses.map(() => \"?\").join(\", \")})`\n : \"\";\n const { rows } = await client.execute({\n sql: `SELECT * FROM integration_remote_commands\n WHERE device_id = ? AND owner_email = ?\n AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)\n AND kind = 'computer-operation' AND status = 'pending'\n AND next_check_at <= ?${operationClassClause}\n ORDER BY computer_sequence ASC, created_at ASC\n LIMIT 1`,\n args: [\n input.deviceId,\n input.ownerEmail,\n input.orgId ?? null,\n input.orgId ?? null,\n now,\n ...(input.operationClasses ?? []),\n ],\n });\n if (!rows[0]) return null;\n const candidate = rowToCommand(rows[0] as Record<string, unknown>);\n try {\n const envelope = await assertValidComputerCommandEnvelope(\n candidate.computerOperation,\n { now },\n );\n const row = rows[0] as Record<string, unknown>;\n if (\n envelope.taskId !== row.computer_task_id ||\n envelope.runId !== row.computer_run_id ||\n envelope.sequence !== Number(row.computer_sequence) ||\n envelope.idempotencyKey !== row.idempotency_key ||\n envelope.operationClass !== row.operation_class ||\n envelope.approval.scope !== row.approval_scope ||\n envelope.approval.actionHash !== row.action_hash ||\n envelope.leaseExpiresAt !== Number(row.lease_expires_at)\n ) {\n throw new ComputerSupervisionError(\n \"action-hash-mismatch\",\n \"Stored computer command binding does not match its envelope\",\n );\n }\n } catch (error) {\n await client.execute({\n sql: `UPDATE integration_remote_commands\n SET status = 'failed', error_message = ?, completed_at = ?, updated_at = ?\n WHERE id = ? AND device_id = ? AND owner_email = ?\n AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)\n AND status = 'pending'`,\n args: [\n error instanceof Error\n ? error.message.slice(0, 2000)\n : \"Invalid computer operation\",\n now,\n now,\n candidate.id,\n input.deviceId,\n input.ownerEmail,\n input.orgId ?? null,\n input.orgId ?? null,\n ],\n });\n return null;\n }\n\n const result = await client.execute({\n sql: isPostgres()\n ? `UPDATE integration_remote_commands\n SET status = 'claimed', attempts = attempts + 1, claimed_at = ?, updated_at = ?\n WHERE id = ? AND device_id = ? AND owner_email = ?\n AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)\n AND status = 'pending' AND lease_expires_at > ?\n RETURNING *`\n : `UPDATE integration_remote_commands\n SET status = 'claimed', attempts = attempts + 1, claimed_at = ?, updated_at = ?\n WHERE id = ? AND device_id = ? AND owner_email = ?\n AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)\n AND status = 'pending' AND lease_expires_at > ?`,\n args: [\n now,\n now,\n candidate.id,\n input.deviceId,\n input.ownerEmail,\n input.orgId ?? null,\n input.orgId ?? null,\n now,\n ],\n });\n if (isPostgres()) {\n const row = result.rows?.[0];\n return row ? rowToCommand(row as Record<string, unknown>) : null;\n }\n if (affectedRows(result) === 0) return null;\n const command = await getRemoteCommand(candidate.id);\n return command?.status === \"claimed\" ? command : null;\n}\n\nexport async function updateRemoteCommandResult(input: {\n deviceId: string;\n commandId: string;\n status: \"running\" | \"completed\" | \"failed\";\n result?: unknown;\n errorMessage?: string | null;\n}): Promise<RemoteCommand | null> {\n await ensureTable();\n const client = getDbExec();\n const now = Date.now();\n const completedAt = TERMINAL_STATUSES.has(input.status) ? now : null;\n const resultJson =\n input.result === undefined\n ? undefined\n : serializeBoundedRemoteJson(input.result, {\n label: \"Remote command result\",\n maxBytes: 256_000,\n });\n if (input.errorMessage) {\n assertNoBinaryPayload(input.errorMessage, \"Remote command error\");\n }\n\n await client.execute({\n sql: `UPDATE integration_remote_commands\n SET status = ?,\n result_json = COALESCE(?, result_json),\n error_message = ?,\n completed_at = COALESCE(?, completed_at),\n updated_at = ?\n WHERE id = ? AND device_id = ?`,\n args: [\n input.status,\n resultJson ?? null,\n input.errorMessage ? input.errorMessage.slice(0, 2000) : null,\n completedAt,\n now,\n input.commandId,\n input.deviceId,\n ],\n });\n\n const command = await getRemoteCommand(input.commandId);\n if (!command || command.deviceId !== input.deviceId) return null;\n return command;\n}\n\nexport async function retryStaleRemoteCommands(options?: {\n claimedStaleAfterMs?: number;\n runningStaleAfterMs?: number;\n maxAttempts?: number;\n limit?: number;\n}): Promise<{ retried: number; failed: number }> {\n await ensureTable();\n const client = getDbExec();\n const now = Date.now();\n const claimedCutoff = now - (options?.claimedStaleAfterMs ?? 75_000);\n const runningCutoff = now - (options?.runningStaleAfterMs ?? 5 * 60_000);\n const maxAttempts = options?.maxAttempts ?? 3;\n const limit = options?.limit ?? 50;\n\n const { rows } = await client.execute({\n sql: `SELECT id, status, attempts FROM integration_remote_commands\n WHERE (status = 'claimed' AND updated_at <= ?)\n OR (status = 'running' AND updated_at <= ?)\n ORDER BY updated_at ASC\n LIMIT ?`,\n args: [claimedCutoff, runningCutoff, limit],\n });\n\n let retried = 0;\n let failed = 0;\n for (const row of rows) {\n const id = row.id as string;\n const status = row.status as RemoteCommandStatus;\n const attempts = Number(row.attempts ?? 0);\n if (attempts >= maxAttempts) {\n const result = await client.execute({\n sql: `UPDATE integration_remote_commands\n SET status = 'failed',\n error_message = COALESCE(error_message, ?),\n completed_at = ?,\n updated_at = ?\n WHERE id = ? AND status = ?`,\n args: [\n `Retry job: exceeded ${maxAttempts} attempts`,\n now,\n now,\n id,\n status,\n ],\n });\n if ((result.rowsAffected ?? (result as any).rowCount) > 0) failed++;\n continue;\n }\n\n const result = await client.execute({\n sql: `UPDATE integration_remote_commands\n SET status = 'pending',\n next_check_at = ?,\n updated_at = ?\n WHERE id = ? AND status = ?`,\n args: [now, now, id, status],\n });\n if ((result.rowsAffected ?? (result as any).rowCount) > 0) retried++;\n }\n\n return { retried, failed };\n}\n\nfunction parseJson(value: unknown, fallback: unknown): unknown {\n if (value == null) return fallback;\n try {\n return JSON.parse(String(value));\n } catch {\n return fallback;\n }\n}\n\nfunction randomHex(byteLength: number): string {\n const bytes = new Uint8Array(byteLength);\n globalThis.crypto.getRandomValues(bytes);\n return Array.from(bytes)\n .map((byte) => byte.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n\nfunction affectedRows(result: {\n rowsAffected?: number;\n rows?: unknown[];\n}): number {\n return (\n result.rowsAffected ??\n Number((result as { rowCount?: unknown }).rowCount ?? 0)\n );\n}\n\nfunction isDuplicateColumnError(error: unknown): boolean {\n const code = String((error as { code?: unknown })?.code ?? \"\");\n const message = String((error as { message?: unknown })?.message ?? error)\n .toLowerCase()\n .trim();\n return (\n code === \"42701\" ||\n message.includes(\"duplicate column\") ||\n message.includes(\"already exists\")\n );\n}\n\nfunction isUniqueConstraintError(error: unknown): boolean {\n const code = String((error as { code?: unknown })?.code ?? \"\");\n const message = String((error as { message?: unknown })?.message ?? error)\n .toLowerCase()\n .trim();\n return (\n code === \"23505\" ||\n code === \"2067\" ||\n message.includes(\"unique constraint\") ||\n message.includes(\"duplicate key\")\n );\n}\n"]}
@@ -163,7 +163,7 @@ export async function getRemoteDeviceForOwner(input) {
163
163
  sql: `SELECT * FROM integration_remote_devices
164
164
  WHERE id = ?
165
165
  AND owner_email = ?
166
- AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)
166
+ AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)
167
167
  LIMIT 1`,
168
168
  args: [
169
169
  input.id,
@@ -203,7 +203,7 @@ export async function listRemoteDevicesForOwner(input) {
203
203
  const { rows } = await getDbExec().execute({
204
204
  sql: `SELECT * FROM integration_remote_devices
205
205
  WHERE owner_email = ?
206
- AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)${statusClause}
206
+ AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)${statusClause}
207
207
  ORDER BY COALESCE(last_seen_at, updated_at) DESC
208
208
  LIMIT ?`,
209
209
  args,
@@ -281,7 +281,7 @@ export async function revokeRemoteDeviceForOwner(input) {
281
281
  updated_at = ?
282
282
  WHERE id = ?
283
283
  AND owner_email = ?
284
- AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)`,
284
+ AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)`,
285
285
  args: [
286
286
  now,
287
287
  now,