@desplega.ai/agent-swarm 1.131.0 → 1.133.1

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 (265) hide show
  1. package/README.md +2 -2
  2. package/dist/{actions-w53mbns9.js → actions-cskpayd7.js} +11 -9
  3. package/dist/{anthropic-messages-rjmd2e7n.js → anthropic-messages-tpxtbx7b.js} +28 -12
  4. package/dist/{app-m9gwqsqv.js → app-ang77nbb.js} +8 -6
  5. package/dist/{assistant-bp65m6f3.js → assistant-b4exebdw.js} +16 -13
  6. package/dist/{azure-openai-responses-c0yjmbfd.js → azure-openai-responses-m3921t4y.js} +8 -8
  7. package/dist/{boot-reembed-62p0at2k.js → boot-reembed-ps2aftpb.js} +9 -7
  8. package/dist/{boot-reembed-xmfp6wdh.js → boot-reembed-znyk3nxb.js} +10 -8
  9. package/dist/{boot-scrub-logs-rr8q2dms.js → boot-scrub-logs-2ax3xgz1.js} +7 -5
  10. package/dist/{claude-adapter-2y47mrkd.js → claude-adapter-3ct8kgxw.js} +13 -9
  11. package/dist/{claude-managed-adapter-gv19tdew.js → claude-managed-adapter-akx4ntf2.js} +2 -2
  12. package/dist/{cli-t1xze5qh.js → cli-13w2hwkh.js} +1 -1
  13. package/dist/{cli-efxfnq6q.js → cli-1532szb1.js} +8 -6
  14. package/dist/{cli-d2tfkcga.js → cli-23d3rseb.js} +33 -17
  15. package/dist/{cli-nm5gtp1x.js → cli-2a75z9c5.js} +196 -7
  16. package/dist/{cli-fywms69x.js → cli-2t30rj0d.js} +1 -1
  17. package/dist/{cli-cvgjwnh1.js → cli-3c3tdgab.js} +10 -10
  18. package/dist/{cli-pm6xbh84.js → cli-3zzxa9jz.js} +46 -16
  19. package/dist/{cli-cdeg08wx.js → cli-4k6233gc.js} +1 -1
  20. package/dist/{cli-t55yw3a1.js → cli-5p2tz05r.js} +2 -2
  21. package/dist/{cli-0rcyq6d9.js → cli-5z2yhzvd.js} +125 -47
  22. package/dist/{cli-3kf51j15.js → cli-6zfq31nj.js} +9 -3
  23. package/dist/{cli-er3a4bb0.js → cli-8m9de10a.js} +6 -1
  24. package/dist/{cli-gm9cbhbq.js → cli-8t14vg9w.js} +27 -8
  25. package/dist/{cli-72q8m8gk.js → cli-9nfc2es8.js} +6 -6
  26. package/dist/{cli-5ze9rv97.js → cli-9ta3pwms.js} +11 -4
  27. package/dist/{cli-78xwvkhj.js → cli-avr0ns7z.js} +2 -2
  28. package/dist/{cli-n6h8qt8n.js → cli-c897wyhw.js} +12 -7
  29. package/dist/{cli-2q72qz2s.js → cli-dgk20ft7.js} +15 -6
  30. package/dist/cli-et1bbwqf.js +13 -0
  31. package/dist/{cli-bqw0mt1n.js → cli-f5w27pj3.js} +3 -3
  32. package/dist/{cli-2wadx7p2.js → cli-f8dre4bz.js} +2 -2
  33. package/dist/{cli-e7ckcg4v.js → cli-ftwpv451.js} +170 -387
  34. package/dist/{cli-g6b8x1h8.js → cli-g854s2dw.js} +1 -1
  35. package/dist/{cli-5kx36evn.js → cli-ggmdxeqa.js} +13 -8
  36. package/dist/cli-hkwfe3qj.js +530 -0
  37. package/dist/{cli-5dsbcjjw.js → cli-jqwnqx9m.js} +1 -1
  38. package/dist/{cli-9dr5qzc0.js → cli-ny122vh4.js} +21 -10
  39. package/dist/{cli-5v4rw6yd.js → cli-pvzqhbs9.js} +2 -2
  40. package/dist/cli-pzq5rjym.js +15 -0
  41. package/dist/{cli-xpxde7rm.js → cli-qes8ef67.js} +48 -30
  42. package/dist/{cli-sajgpkqg.js → cli-qs9hhsvb.js} +1723 -325
  43. package/dist/cli-qys3rhhz.js +27 -0
  44. package/dist/{cli-rzbpmxdr.js → cli-r6tpm37h.js} +2 -2
  45. package/dist/{cli-qv61hp37.js → cli-rch80c76.js} +2 -2
  46. package/dist/{cli-0258203w.js → cli-rpp0x4zy.js} +137 -17
  47. package/dist/{cli-5n6e5yfn.js → cli-s1k0wxhe.js} +12 -7
  48. package/dist/{cli-psh3c4va.js → cli-sfd9v7sj.js} +1 -1
  49. package/dist/{cli-70xqj0z3.js → cli-sh3ytsyj.js} +49 -39
  50. package/dist/{cli-zsspvvm2.js → cli-w1mp9jy7.js} +1 -1
  51. package/dist/{cli-d3x3x1c9.js → cli-xdb4mpnp.js} +2 -2
  52. package/dist/{cli-57khth04.js → cli-xmyqv00z.js} +120 -4
  53. package/dist/{cli-yw8vghx2.js → cli-xv8t8p3c.js} +65 -32
  54. package/dist/{cli-f1am9szv.js → cli-xxv5pn4f.js} +1 -1
  55. package/dist/{cli-t76vw5db.js → cli-y08jvxzx.js} +601 -140
  56. package/dist/{cli-a4jzdpa3.js → cli-zfbf3srj.js} +650 -245
  57. package/dist/{cli-a40rnf71.js → cli-zmq89k34.js} +6 -6
  58. package/dist/{cli-qmdnhpej.js → cli-zr9q011v.js} +23 -13
  59. package/dist/cli.js +22 -22
  60. package/dist/{codex-adapter-d24q75wk.js → codex-adapter-bef93pq4.js} +9 -7
  61. package/dist/{codex-hook-hq54t9ft.js → codex-hook-e8dba65p.js} +3 -2
  62. package/dist/{codex-session-runner-g50vcsex.js → codex-session-runner-5pkxys7x.js} +9 -7
  63. package/dist/{commands-c3nrc5hg.js → commands-e23s1bff.js} +9 -7
  64. package/dist/{db-nt7fg3tw.js → db-m3nabatn.js} +17 -5
  65. package/dist/{google-generative-ai-hysrd8qx.js → google-generative-ai-8maw55xh.js} +11 -8
  66. package/dist/{google-vertex-p5arrga0.js → google-vertex-8vjqmx8g.js} +11 -8
  67. package/dist/{handlers-fkr6swat.js → handlers-akazedvr.js} +16 -13
  68. package/dist/{hook-77wpvnjc.js → hook-ze4cpf0v.js} +55 -41
  69. package/dist/{hook-g019yehy.js → hook-zfqyyenw.js} +10 -7
  70. package/dist/{http-jewj0syk.js → http-sja0n6kw.js} +730 -150
  71. package/dist/index-0gr8vd6k.js +39 -0
  72. package/dist/{index-7n2xpbd4.js → index-354vkhdv.js} +154 -74
  73. package/dist/{index-070v4eyb.js → index-5qthek5t.js} +2 -3
  74. package/dist/{index-hfvggd0z.js → index-95t3cqq5.js} +16 -14
  75. package/dist/{index-9znhar3x.js → index-sea66619.js} +3 -4
  76. package/dist/{index-6gm5he8h.js → index-vpn36mma.js} +15 -13
  77. package/dist/{keepalive-j0fjvqfd.js → keepalive-3sp3meqj.js} +16 -10
  78. package/dist/{lead-1n8x30xe.js → lead-mefeewkf.js} +39 -36
  79. package/dist/{maintenance-gvev11sa.js → maintenance-8tbrbjfg.js} +10 -8
  80. package/dist/mistral-conversations-00zrp7g4.js +717 -0
  81. package/dist/{oauth-refresh-sweep-s9ecsvh0.js → oauth-refresh-sweep-srjwjz6m.js} +8 -6
  82. package/dist/{onboard-pvm5b38w.js → onboard-mt2tgyjg.js} +7 -7
  83. package/dist/{openai-codex-responses-5g27pd6f.js → openai-codex-responses-map32h2s.js} +18 -18
  84. package/dist/{openai-completions-3vkmfz2a.js → openai-completions-w871b1cq.js} +12 -11
  85. package/dist/{openai-responses-dg4t7j7a.js → openai-responses-b0j7xnw8.js} +15 -12
  86. package/dist/{opencode-adapter-1gm38aba.js → opencode-adapter-b39jefcj.js} +5 -3
  87. package/dist/{openrouter-images-g3gm8931.js → openrouter-images-srfkywy0.js} +4 -4
  88. package/dist/{otel-impl-p6jvgd9c.js → otel-impl-z6pxt3j9.js} +224 -9
  89. package/dist/{pi-mono-adapter-t091s27g.js → pi-mono-adapter-btvqmj58.js} +1138 -408
  90. package/dist/{pricing-refresh-k49rcyed.js → pricing-refresh-kyq8cezh.js} +9 -7
  91. package/dist/{rbac-roles-cr80g96t.js → rbac-roles-3ryfrfna.js} +8 -6
  92. package/dist/{rbac-roles-30pspvy7.js → rbac-roles-6qfvbee1.js} +9 -7
  93. package/dist/{render-v2-xm457ng1.js → render-v2-p61ppjey.js} +8 -6
  94. package/dist/{seed-pricing-yhpv35fr.js → seed-pricing-j56bbj4y.js} +8 -6
  95. package/dist/{setup-nytnmwwr.js → setup-tpr6c1kn.js} +2 -2
  96. package/dist/{worker-msytr2cw.js → worker-9w1v0mbb.js} +39 -36
  97. package/openapi.json +542 -16
  98. package/package.json +6 -6
  99. package/src/be/budget-refusal-notify.ts +14 -7
  100. package/src/be/db-queries/task-artifacts.ts +182 -0
  101. package/src/be/db.ts +783 -169
  102. package/src/be/events.ts +5 -0
  103. package/src/be/migrations/130_scratch_script_retention_grace.sql +6 -0
  104. package/src/be/migrations/131_session_logs_created_at_index.sql +2 -0
  105. package/src/be/migrations/132_multi_runtime_instances.sql +44 -0
  106. package/src/be/migrations/133_workflow_run_trigger_type.sql +12 -0
  107. package/src/be/migrations/135_backfill_task_pull_request_attachments.sql +228 -0
  108. package/src/be/migrations/136_task_requester_provenance.sql +37 -0
  109. package/src/be/migrations/137_memory_retrieval_composite_index.sql +1 -0
  110. package/src/be/multi-runtime.ts +430 -0
  111. package/src/be/scripts/db.ts +43 -1
  112. package/src/be/scripts/extract-schema.ts +3 -1
  113. package/src/be/scripts/retention.ts +164 -0
  114. package/src/be/scripts/run-saved.ts +16 -1
  115. package/src/be/scripts/typecheck.ts +3 -2
  116. package/src/be/seed-skills/index.ts +7 -0
  117. package/src/be/steering.ts +1 -1
  118. package/src/be/swarm-config-guard.ts +18 -0
  119. package/src/commands/credential-wait.ts +54 -0
  120. package/src/commands/onboard/steps/core-credentials.tsx +1 -1
  121. package/src/commands/onboard/steps/custom-templates.tsx +1 -1
  122. package/src/commands/onboard/steps/harness-credentials.tsx +1 -1
  123. package/src/commands/onboard/steps/health-check.tsx +1 -1
  124. package/src/commands/onboard/steps/start.tsx +1 -1
  125. package/src/commands/profile-sync-audit.ts +56 -0
  126. package/src/commands/profile-sync.ts +287 -15
  127. package/src/commands/provider-credentials.ts +46 -19
  128. package/src/commands/runner.ts +196 -33
  129. package/src/commands/templates.ts +22 -0
  130. package/src/github/handlers.ts +55 -9
  131. package/src/heartbeat/heartbeat.ts +71 -12
  132. package/src/hooks/hook.ts +49 -33
  133. package/src/http/active-sessions.ts +11 -1
  134. package/src/http/agents.ts +261 -30
  135. package/src/http/all-routes.ts +1 -0
  136. package/src/http/config.ts +13 -2
  137. package/src/http/core.ts +102 -4
  138. package/src/http/db-query-bounded.ts +313 -0
  139. package/src/http/db-query-shared.ts +249 -0
  140. package/src/http/db-query.ts +54 -42
  141. package/src/http/events.ts +2 -0
  142. package/src/http/index.ts +27 -2
  143. package/src/http/mcp-bridge.ts +37 -3
  144. package/src/http/mcp.ts +12 -0
  145. package/src/http/memory.ts +13 -2
  146. package/src/http/openapi.ts +1 -0
  147. package/src/http/poll.ts +70 -27
  148. package/src/http/route-def.ts +43 -9
  149. package/src/http/schedules.ts +2 -2
  150. package/src/http/scripts.ts +31 -4
  151. package/src/http/session-data.ts +44 -0
  152. package/src/http/tasks.ts +2 -2
  153. package/src/http/users.ts +74 -52
  154. package/src/http/webhooks.ts +5 -2
  155. package/src/http/workflows.ts +1 -4
  156. package/src/http/x.ts +14 -0
  157. package/src/integrations/kapso/inbound.ts +1 -1
  158. package/src/jira/outbound.ts +39 -8
  159. package/src/jira/webhook-lifecycle.ts +13 -2
  160. package/src/linear/outbound.ts +43 -10
  161. package/src/oauth/keepalive.ts +13 -2
  162. package/src/prompts/base-prompt.ts +1 -1
  163. package/src/prompts/session-templates.ts +45 -7
  164. package/src/providers/claude-adapter.ts +6 -0
  165. package/src/providers/claude-managed-models.ts +34 -10
  166. package/src/providers/codex-adapter.ts +6 -0
  167. package/src/providers/opencode-adapter.ts +6 -0
  168. package/src/providers/pi-mono-adapter.ts +8 -0
  169. package/src/providers/pricing-sources.md +24 -0
  170. package/src/queue-stall-alarm.ts +216 -0
  171. package/src/scheduler/scheduler.ts +7 -0
  172. package/src/script-workflows/executor.ts +2 -1
  173. package/src/scripts-runtime/executors/native.ts +2 -1
  174. package/src/scripts-runtime/executors/types.ts +5 -0
  175. package/src/scripts-runtime/loader.ts +5 -0
  176. package/src/scripts-runtime/swarm-config.ts +8 -0
  177. package/src/scripts-runtime/swarm-sdk.ts +7 -0
  178. package/src/scripts-runtime/types/stdlib.d.ts +1 -0
  179. package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -0
  180. package/src/tests/budget-claim-gate.test.ts +20 -0
  181. package/src/tests/claude-adapter.test.ts +32 -0
  182. package/src/tests/claude-managed-adapter.test.ts +38 -7
  183. package/src/tests/codex-adapter.test.ts +30 -0
  184. package/src/tests/create-task-validation.test.ts +128 -0
  185. package/src/tests/credential-wait.test.ts +67 -1
  186. package/src/tests/db-query.test.ts +711 -2
  187. package/src/tests/http-api-integration.test.ts +81 -0
  188. package/src/tests/http-users.test.ts +166 -1
  189. package/src/tests/identity-field-budget.test.ts +176 -0
  190. package/src/tests/mcp-tools-user.test.ts +34 -0
  191. package/src/tests/mcp-user-route.test.ts +17 -0
  192. package/src/tests/metrics-http.test.ts +44 -0
  193. package/src/tests/multi-runtime-registration.test.ts +2703 -0
  194. package/src/tests/openapi-response-contract.test.ts +24 -0
  195. package/src/tests/openapi-route-headers.test.ts +83 -0
  196. package/src/tests/opencode-adapter.test.ts +40 -0
  197. package/src/tests/outbound-listener-rejection.test.ts +138 -0
  198. package/src/tests/pi-mono-adapter.test.ts +98 -0
  199. package/src/tests/profile-sync-warning.test.ts +277 -0
  200. package/src/tests/profile-sync.test.ts +232 -6
  201. package/src/tests/prompt-template-session.test.ts +32 -3
  202. package/src/tests/queue-stall-alarm.test.ts +226 -0
  203. package/src/tests/requester-comms.test.ts +32 -0
  204. package/src/tests/runner-requester-profile.test.ts +38 -0
  205. package/src/tests/sandboxed-process.test.ts +45 -0
  206. package/src/tests/schedule-target-type.test.ts +38 -0
  207. package/src/tests/script-connections.test.ts +16 -3
  208. package/src/tests/scripts-external-api.test.ts +8 -3
  209. package/src/tests/scripts-http.test.ts +12 -0
  210. package/src/tests/scripts-retention.test.ts +384 -0
  211. package/src/tests/scripts-runtime-identity.test.ts +375 -0
  212. package/src/tests/scripts-runtime.test.ts +4 -3
  213. package/src/tests/sdk-allowlist.test.ts +47 -0
  214. package/src/tests/send-task-requested-by.test.ts +9 -0
  215. package/src/tests/session-costs.test.ts +506 -1
  216. package/src/tests/shutdown-error-scrubbing.test.ts +92 -0
  217. package/src/tests/steering-core.test.ts +28 -0
  218. package/src/tests/task-artifacts.test.ts +386 -0
  219. package/src/tests/task-attachments-schema.test.ts +17 -1
  220. package/src/tests/task-completion-idempotency.test.ts +57 -0
  221. package/src/tests/task-pull-request-backfill-migration.test.ts +321 -0
  222. package/src/tests/update-profile-api.test.ts +67 -39
  223. package/src/tests/update-profile-auth.test.ts +48 -4
  224. package/src/tests/user-attribution-migration.test.ts +54 -0
  225. package/src/tests/user-identity.test.ts +72 -0
  226. package/src/tests/workflow-async-v2.test.ts +39 -1
  227. package/src/tests/workflow-http-v2.test.ts +3 -2
  228. package/src/tests/workflow-mcp-trigger-schema.test.ts +52 -3
  229. package/src/tests/workflow-retry-v2.test.ts +45 -1
  230. package/src/tests/workflow-swarm-script.test.ts +14 -5
  231. package/src/tools/db-query.ts +18 -8
  232. package/src/tools/get-task-details.ts +24 -3
  233. package/src/tools/manage-user.ts +36 -3
  234. package/src/tools/poll-task.ts +93 -14
  235. package/src/tools/script-common.ts +5 -0
  236. package/src/tools/send-task.ts +1 -4
  237. package/src/tools/store-progress.ts +13 -2
  238. package/src/tools/swarm-config/delete-config.ts +9 -0
  239. package/src/tools/swarm-config/set-config.ts +3 -2
  240. package/src/tools/task-action.ts +30 -0
  241. package/src/tools/task-tool-ctx.ts +8 -1
  242. package/src/tools/update-profile.ts +16 -21
  243. package/src/tools/utils.ts +5 -0
  244. package/src/tools/workflows/trigger-workflow.ts +7 -1
  245. package/src/types.ts +165 -1
  246. package/src/utils/github-pull-request.ts +76 -0
  247. package/src/utils/identity-field-budget.ts +73 -0
  248. package/src/utils/multi-runtime.ts +20 -0
  249. package/src/utils/requester-comms.ts +24 -0
  250. package/src/utils/sandboxed-process.ts +40 -14
  251. package/src/workflows/engine.ts +22 -2
  252. package/src/workflows/executors/swarm-script.ts +24 -1
  253. package/src/workflows/resume.ts +19 -11
  254. package/src/workflows/retry-poller.ts +1 -0
  255. package/src/workflows/triggers.ts +4 -0
  256. package/templates/schedules/daily-compounding-reflection/content.md +12 -3
  257. package/templates/skills/db-query-guidance/config.json +14 -0
  258. package/templates/skills/db-query-guidance/content.md +57 -0
  259. package/dist/cli-3te4jdkm.js +0 -225
  260. package/dist/cli-bmze986d.js +0 -7
  261. package/dist/cli-kh3ba2zf.js +0 -607
  262. package/dist/index-hp1kdbjj.js +0 -36
  263. package/dist/mistral-conversations-81fwav0j.js +0 -30578
  264. package/dist/otel-nqa12wf3.js +0 -3149
  265. package/dist/{pi-messages-tqj8q6b9.js → pi-messages-sssy32x4.js} +3 -3
@@ -444,35 +444,62 @@ export async function buildCredStatusReport(
444
444
  }
445
445
 
446
446
  /**
447
- * Fire-and-forget POST of a `cred_status` snapshot to the API. Used by the
448
- * worker boot path (`runner.ts` after `awaitCredentials`) and the post-task
449
- * cache-miss path (also in `runner.ts`). Failures are logged, not thrown —
450
- * a stale dashboard is acceptable; blocking the worker is not.
447
+ * Strict PUT of credential readiness to the API: propagates network errors
448
+ * and non-2xx responses. The post-credential-wait boot path uses this — after
449
+ * a stale-runtime revival this write is the only transition out of
450
+ * `waiting_for_credentials`, so the caller must be able to observe (and
451
+ * retry) a failure instead of proceeding as though it landed.
451
452
  *
452
453
  * Posts to the existing `PUT /api/agents/:id/credential-status` endpoint
453
- * which (per migration 055) now accepts an optional `cred_status` field.
454
- * `ready` and `missing` are also included so legacy `agents.credentialMissing`
455
- * and `agents.status='waiting_for_credentials'` keep tracking.
454
+ * which (per migration 055) accepts an optional `cred_status` field. `ready`
455
+ * and `missing` are always included so legacy `agents.credentialMissing` and
456
+ * `agents.status='waiting_for_credentials'` keep tracking; the snapshot is
457
+ * optional so a hiccup while building it cannot lose the transition.
458
+ */
459
+ export async function sendCredStatusReport(
460
+ apiUrl: string,
461
+ apiKey: string,
462
+ agentId: string,
463
+ runtimeInstanceId: string | undefined,
464
+ report: { ready: boolean; missing: string[]; credStatus?: AgentCredStatus },
465
+ ): Promise<void> {
466
+ const res = await fetch(`${apiUrl}/api/agents/${encodeURIComponent(agentId)}/credential-status`, {
467
+ method: "PUT",
468
+ headers: {
469
+ Authorization: `Bearer ${apiKey}`,
470
+ "X-Agent-ID": agentId,
471
+ ...(runtimeInstanceId ? { "X-Runtime-Instance-ID": runtimeInstanceId } : {}),
472
+ "Content-Type": "application/json",
473
+ },
474
+ body: JSON.stringify({
475
+ ready: report.ready,
476
+ missing: report.missing,
477
+ ...(report.credStatus ? { cred_status: report.credStatus } : {}),
478
+ }),
479
+ });
480
+ if (!res.ok) {
481
+ const body = await res.text().catch(() => "");
482
+ throw new Error(`credential-status report failed: ${res.status} ${body}`.trim());
483
+ }
484
+ }
485
+
486
+ /**
487
+ * Fire-and-forget wrapper around {@link sendCredStatusReport}. Used by the
488
+ * post-task cache-miss path (`runner.ts`), where a stale dashboard is
489
+ * acceptable and blocking the worker is not.
456
490
  */
457
491
  export async function reportCredStatus(
458
492
  apiUrl: string,
459
493
  apiKey: string,
460
494
  agentId: string,
495
+ runtimeInstanceId: string | undefined,
461
496
  credStatus: AgentCredStatus,
462
497
  ): Promise<void> {
463
498
  try {
464
- await fetch(`${apiUrl}/api/agents/${encodeURIComponent(agentId)}/credential-status`, {
465
- method: "PUT",
466
- headers: {
467
- Authorization: `Bearer ${apiKey}`,
468
- "X-Agent-ID": agentId,
469
- "Content-Type": "application/json",
470
- },
471
- body: JSON.stringify({
472
- ready: credStatus.ready,
473
- missing: credStatus.missing,
474
- cred_status: credStatus,
475
- }),
499
+ await sendCredStatusReport(apiUrl, apiKey, agentId, runtimeInstanceId, {
500
+ ready: credStatus.ready,
501
+ missing: credStatus.missing,
502
+ credStatus,
476
503
  });
477
504
  } catch (err) {
478
505
  console.warn(`[cred-status] POST failed (non-fatal): ${err}`);
@@ -65,9 +65,15 @@ import { interpolate } from "../utils/template.ts";
65
65
  import { detectVcsProvider } from "../vcs/index.ts";
66
66
  import { validateJsonSchema } from "../workflows/json-schema-validator.ts";
67
67
  import { buildContextPreamble, buildResumeContextPreamble } from "./context-preamble.ts";
68
- import { awaitCredentials, BootMaxWaitExceededError, EX_CONFIG } from "./credential-wait.ts";
68
+ import {
69
+ awaitCredentials,
70
+ BootMaxWaitExceededError,
71
+ EX_CONFIG,
72
+ retryBootStep,
73
+ } from "./credential-wait.ts";
69
74
  import {
70
75
  contentSha256,
76
+ prependProfileSyncRejectionBanner,
71
77
  resolveClaudeMdPath,
72
78
  syncProfileFilesToServer,
73
79
  writeIdentityBaselines,
@@ -80,6 +86,7 @@ import {
80
86
  isCredCheckDisabled,
81
87
  reportCredStatus,
82
88
  reportLatestModel,
89
+ sendCredStatusReport,
83
90
  } from "./provider-credentials.ts";
84
91
  import {
85
92
  type ResumeSessionCandidate,
@@ -481,6 +488,13 @@ export interface ApiConfig {
481
488
  apiUrl: string;
482
489
  apiKey: string;
483
490
  agentId: string;
491
+ /**
492
+ * Per-boot identity for this worker PROCESS, as opposed to `agentId` (the
493
+ * logical agent). Sent as the X-Runtime-Instance-ID header on ping/close so
494
+ * a server can track this process's liveness and retire only this runtime.
495
+ * Servers ignore it unless MULTI_RUNTIME_ENABLED.
496
+ */
497
+ runtimeInstanceId?: string;
484
498
  }
485
499
 
486
500
  export interface SteeringDispatchState {
@@ -589,6 +603,9 @@ async function pingServer(config: ApiConfig, _role: string): Promise<void> {
589
603
  if (config.apiKey) {
590
604
  headers.Authorization = `Bearer ${config.apiKey}`;
591
605
  }
606
+ if (config.runtimeInstanceId) {
607
+ headers["X-Runtime-Instance-ID"] = config.runtimeInstanceId;
608
+ }
592
609
 
593
610
  try {
594
611
  await fetch(`${config.apiUrl}/ping`, {
@@ -608,6 +625,9 @@ async function closeAgent(config: ApiConfig, role: string): Promise<void> {
608
625
  if (config.apiKey) {
609
626
  headers.Authorization = `Bearer ${config.apiKey}`;
610
627
  }
628
+ if (config.runtimeInstanceId) {
629
+ headers["X-Runtime-Instance-ID"] = config.runtimeInstanceId;
630
+ }
611
631
 
612
632
  try {
613
633
  console.log(`[${role}] Marking agent as offline...`);
@@ -2448,6 +2468,7 @@ async function registerActiveSession(
2448
2468
  triggerType: session.triggerType,
2449
2469
  taskDescription: session.taskDescription,
2450
2470
  runnerSessionId: session.runnerSessionId,
2471
+ runtimeInstanceId: config.runtimeInstanceId,
2451
2472
  }),
2452
2473
  });
2453
2474
  } catch {
@@ -2563,7 +2584,14 @@ interface Trigger {
2563
2584
  text?: string;
2564
2585
  }>;
2565
2586
  cursorUpdates?: Array<{ channelId: string; ts: string }>; // Deferred cursor commits for channel_activity
2566
- requestedBy?: { name: string; email?: string; role?: string; notes?: string };
2587
+ requestedBy?: {
2588
+ name: string;
2589
+ email?: string;
2590
+ role?: string;
2591
+ notes?: string;
2592
+ /** Structured communication preferences from `users.metadata.comms`. */
2593
+ comms?: { tone?: string; language?: string; verbosity?: string };
2594
+ };
2567
2595
  // Phase 4 — budget_refused fields. The server emits this envelope from
2568
2596
  // /api/poll and MCP task-action accept when an admission gate refuses to
2569
2597
  // let the agent claim a task. Worker reads cause + reset/spend/budget for
@@ -2581,6 +2609,8 @@ interface PollOptions {
2581
2609
  apiUrl: string;
2582
2610
  apiKey: string;
2583
2611
  agentId: string;
2612
+ /** Identifies the polling process so a retired runtime is not given work. */
2613
+ runtimeInstanceId?: string;
2584
2614
  pollInterval: number;
2585
2615
  pollTimeout: number;
2586
2616
  since?: string; // Optional: for filtering finished tasks
@@ -2591,15 +2621,24 @@ type RequesterProfile = NonNullable<Trigger["requestedBy"]>;
2591
2621
  export async function buildRequesterProfilePrompt(
2592
2622
  requestedBy: RequesterProfile | undefined,
2593
2623
  ): Promise<string> {
2594
- if (!requestedBy?.role && !requestedBy?.notes) return "";
2595
-
2596
- const notes = requestedBy.notes?.trim();
2624
+ const comms = requestedBy?.comms;
2625
+ const commsParts = [
2626
+ comms?.tone ? `tone: ${comms.tone}` : undefined,
2627
+ comms?.language ? `language: ${comms.language}` : undefined,
2628
+ comms?.verbosity ? `verbosity: ${comms.verbosity}` : undefined,
2629
+ ].filter((part): part is string => Boolean(part));
2630
+ if (!requestedBy?.role && !requestedBy?.notes && commsParts.length === 0) return "";
2631
+
2632
+ const commsSection =
2633
+ commsParts.length > 0 ? `\nTheir communication preferences: ${commsParts.join(", ")}.` : "";
2634
+ const notes = requestedBy?.notes?.trim();
2597
2635
  const notesSection = notes
2598
2636
  ? `\nTheir stated notes for how you should respond and act:\n${notes}`
2599
2637
  : "";
2600
2638
  const result = await resolveTemplateAsync("task.requester.profile", {
2601
- requester_name: requestedBy.name,
2602
- requester_role_suffix: requestedBy.role ? ` (${requestedBy.role})` : "",
2639
+ requester_name: requestedBy?.name ?? "",
2640
+ requester_role_suffix: requestedBy?.role ? ` (${requestedBy.role})` : "",
2641
+ requester_comms_section: commsSection,
2603
2642
  requester_notes_section: notesSection,
2604
2643
  });
2605
2644
 
@@ -2623,6 +2662,7 @@ async function registerAgent(opts: {
2623
2662
  * haven't migrated to passing it explicitly.
2624
2663
  */
2625
2664
  harnessProvider?: ProviderName;
2665
+ runtimeInstanceId?: string;
2626
2666
  }): Promise<{ serverCapabilities?: string[] }> {
2627
2667
  const headers: Record<string, string> = {
2628
2668
  "Content-Type": "application/json",
@@ -2654,6 +2694,7 @@ async function registerAgent(opts: {
2654
2694
  maxTasks: opts.maxTasks,
2655
2695
  provider,
2656
2696
  harness_provider: harnessProvider,
2697
+ runtimeInstanceId: opts.runtimeInstanceId,
2657
2698
  }),
2658
2699
  });
2659
2700
 
@@ -2702,6 +2743,9 @@ async function pollForTriggerOnce(opts: PollOptions): Promise<Trigger | null> {
2702
2743
  const headers: Record<string, string> = {
2703
2744
  "X-Agent-ID": opts.agentId,
2704
2745
  };
2746
+ if (opts.runtimeInstanceId) {
2747
+ headers["X-Runtime-Instance-ID"] = opts.runtimeInstanceId;
2748
+ }
2705
2749
  if (opts.apiKey) {
2706
2750
  headers.Authorization = `Bearer ${opts.apiKey}`;
2707
2751
  }
@@ -3395,7 +3439,12 @@ async function spawnProviderProcess(
3395
3439
  function bufferEvent(evt: BufferedEvent) {
3396
3440
  eventBuffer.push(evt);
3397
3441
  if (eventBuffer.length >= EVENT_BUFFER_MAX) {
3398
- flushEvents();
3442
+ flushEvents().catch((err) =>
3443
+ console.error(
3444
+ "[runner] event flush failed:",
3445
+ scrubSecrets(err instanceof Error ? err.message : String(err)),
3446
+ ),
3447
+ );
3399
3448
  }
3400
3449
  }
3401
3450
 
@@ -4086,7 +4135,12 @@ async function checkCompletedProcesses(
4086
4135
  cancelledSignaled?.delete(taskId);
4087
4136
 
4088
4137
  if (apiConfig) {
4089
- removeActiveSession(apiConfig, taskId);
4138
+ removeActiveSession(apiConfig, taskId).catch((err) =>
4139
+ console.error(
4140
+ "[runner] active-session removal failed:",
4141
+ scrubSecrets(err instanceof Error ? err.message : String(err)),
4142
+ ),
4143
+ );
4090
4144
  }
4091
4145
 
4092
4146
  // Detect VCS before finishing — last chance to link a PR
@@ -4699,8 +4753,16 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
4699
4753
  let lastBedrockRefreshAt = 0;
4700
4754
  const BEDROCK_REFRESH_INTERVAL_MS = 5 * 60 * 1000;
4701
4755
 
4756
+ // Fresh per boot and shared by registration, ping, and close: a restarted
4757
+ // process is a new runtime, but one process presents one identity.
4758
+ const runtimeInstanceId = crypto.randomUUID();
4759
+
4760
+ // Session MCP configs are built in provider adapters, which read this to
4761
+ // stamp the runtime identity onto worker tool calls.
4762
+ process.env.SWARM_RUNTIME_INSTANCE_ID = runtimeInstanceId;
4763
+
4702
4764
  // Create API config for ping/close
4703
- const apiConfig: ApiConfig = { apiUrl, apiKey, agentId };
4765
+ const apiConfig: ApiConfig = { apiUrl, apiKey, agentId, runtimeInstanceId };
4704
4766
 
4705
4767
  // Setup graceful shutdown handlers with API config and runner state access
4706
4768
  setupShutdownHandlers(role, apiConfig, () => state);
@@ -4821,22 +4883,32 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
4821
4883
  }
4822
4884
  };
4823
4885
 
4824
- /** Push the current live state back to the API so the dashboard reflects it. */
4886
+ /** Register this boot's runtime with the current live state; propagates failure. */
4887
+ const registerCurrentRuntime = async () => {
4888
+ const reg = await registerAgent({
4889
+ apiUrl,
4890
+ apiKey,
4891
+ agentId,
4892
+ name: agentName,
4893
+ role,
4894
+ isLead,
4895
+ capabilities,
4896
+ maxTasks: state.maxConcurrent,
4897
+ harnessProvider: state.harnessProvider,
4898
+ runtimeInstanceId,
4899
+ });
4900
+ lastServerCapsRefreshAt = Date.now();
4901
+ await applyServerCapabilities(reg.serverCapabilities);
4902
+ };
4903
+
4904
+ /**
4905
+ * Best-effort wrapper for the periodic/fire-and-forget callers: the
4906
+ * dashboard tolerates a missed refresh. Recovery paths that must observe
4907
+ * failure use registerCurrentRuntime directly.
4908
+ */
4825
4909
  const reregisterAgent = async () => {
4826
4910
  try {
4827
- const reg = await registerAgent({
4828
- apiUrl,
4829
- apiKey,
4830
- agentId,
4831
- name: agentName,
4832
- role,
4833
- isLead,
4834
- capabilities,
4835
- maxTasks: state.maxConcurrent,
4836
- harnessProvider: state.harnessProvider,
4837
- });
4838
- lastServerCapsRefreshAt = Date.now();
4839
- await applyServerCapabilities(reg.serverCapabilities);
4911
+ await registerCurrentRuntime();
4840
4912
  } catch (err) {
4841
4913
  console.warn(`[${role}] [config] Re-register failed (non-fatal): ${err}`);
4842
4914
  }
@@ -4852,6 +4924,7 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
4852
4924
  capabilities,
4853
4925
  maxTasks: maxConcurrent,
4854
4926
  harnessProvider: bootProvider,
4927
+ runtimeInstanceId,
4855
4928
  });
4856
4929
  lastServerCapsRefreshAt = Date.now();
4857
4930
  // Rebuilds the prompt immediately: the initial build above ran before
@@ -4918,6 +4991,7 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
4918
4991
  headers: {
4919
4992
  Authorization: `Bearer ${apiKey}`,
4920
4993
  "X-Agent-ID": agentId,
4994
+ "X-Runtime-Instance-ID": runtimeInstanceId,
4921
4995
  "Content-Type": "application/json",
4922
4996
  },
4923
4997
  body: JSON.stringify({ ready: status.ready, missing: status.missing }),
@@ -4934,16 +5008,71 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
4934
5008
  throw err;
4935
5009
  }
4936
5010
 
5011
+ // Re-register before reporting readiness or doing any work. A worker
5012
+ // blocked on credentials is not usable execution capacity, so its runtime
5013
+ // is allowed to expire during the wait rather than being kept alive
5014
+ // artificially — but once it can work again the server has to know, or
5015
+ // its pings and polls would reference a runtime that no longer exists
5016
+ // until the periodic refresh (up to 5 min later) happened to fix it.
5017
+ // Same per-boot runtime id, so this refreshes the original row instead of
5018
+ // creating a second one. Ordering matters: a readiness report sent while
5019
+ // the row is still stale is dropped (only a live runtime may report), and
5020
+ // the revival preserves credential_ready — reporting first would leave
5021
+ // the agent parked on waiting_for_credentials forever. Best-effort is not
5022
+ // enough here for the same reason: a swallowed transient failure would
5023
+ // still let the report target the stale row, so recovery retries and
5024
+ // treats exhaustion like boot-registration failure.
5025
+ try {
5026
+ await retryBootStep(registerCurrentRuntime, {
5027
+ label: "runtime re-registration",
5028
+ log: (line) => console.warn(`[${role}] ${line}`),
5029
+ });
5030
+ } catch (error) {
5031
+ console.error(`[${role}] Failed to re-register after credential wait: ${error}`);
5032
+ process.exit(1);
5033
+ }
5034
+
4937
5035
  // Migration 055: build the full snapshot (presence + live test) once
4938
5036
  // creds are ready and POST it to the agent row. Status endpoint reads
4939
5037
  // this instead of running predicates server-side. Always uses the
4940
5038
  // *current* state.harnessProvider in case it flipped during the wait.
5039
+ // The snapshot build stays best-effort — its live test can hiccup, and
5040
+ // that must not lose the readiness transition below.
5041
+ let bootCredSnapshot: Awaited<ReturnType<typeof buildCredStatusReport>> | undefined;
4941
5042
  try {
4942
- const snapshot = await buildCredStatusReport(state.harnessProvider, process.env, {}, "boot");
4943
- await reportCredStatus(apiUrl, apiKey, agentId, snapshot);
5043
+ bootCredSnapshot = await buildCredStatusReport(
5044
+ state.harnessProvider,
5045
+ process.env,
5046
+ {},
5047
+ "boot",
5048
+ );
4944
5049
  } catch (err) {
4945
- // Non-fatal — worker proceeds even if reporting fails.
4946
- console.warn(`[${role}] cred_status boot report failed (non-fatal): ${err}`);
5050
+ console.warn(`[${role}] cred_status boot snapshot failed (non-fatal): ${err}`);
5051
+ }
5052
+
5053
+ // The readiness write is the only transition out of
5054
+ // waiting_for_credentials after a stale-runtime revival (the wait's final
5055
+ // tick targeted the pre-registration row, and revival preserves the old
5056
+ // credential_ready) — so like the registration above, it retries and
5057
+ // treats exhaustion as a failed boot rather than proceeding parked.
5058
+ try {
5059
+ await retryBootStep(
5060
+ () =>
5061
+ sendCredStatusReport(apiUrl, apiKey, agentId, runtimeInstanceId, {
5062
+ // The snapshot's verdict when it built; otherwise the wait's own
5063
+ // conclusion (it only returns once credentials are ready).
5064
+ ready: bootCredSnapshot?.ready ?? true,
5065
+ missing: bootCredSnapshot?.missing ?? [],
5066
+ credStatus: bootCredSnapshot,
5067
+ }),
5068
+ {
5069
+ label: "credential readiness report",
5070
+ log: (line) => console.warn(`[${role}] ${line}`),
5071
+ },
5072
+ );
5073
+ } catch (error) {
5074
+ console.error(`[${role}] Failed to report credential readiness after recovery: ${error}`);
5075
+ process.exit(1);
4947
5076
  }
4948
5077
  }
4949
5078
 
@@ -5291,6 +5420,15 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
5291
5420
  }
5292
5421
  }
5293
5422
 
5423
+ const resumeWithSyncWarning = await prependProfileSyncRejectionBanner(resumePrompt, {
5424
+ ...apiConfig,
5425
+ claudeMdPath: resolveClaudeMdPath([state.harnessProvider]),
5426
+ });
5427
+ resumePrompt = resumeWithSyncWarning.prompt;
5428
+ if (resumeWithSyncWarning.injected) {
5429
+ console.warn(`[${role}] Injected persisted profile sync rejection warning`);
5430
+ }
5431
+
5294
5432
  // Resolve provider-aware resume: prefer own session, then parent.
5295
5433
  const resumeCandidates: ResumeSessionCandidate[] = [
5296
5434
  {
@@ -5428,7 +5566,12 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
5428
5566
  triggerType: "task_resumed",
5429
5567
  taskDescription: task.task?.slice(0, 200),
5430
5568
  runnerSessionId: resumeRunnerSessionId,
5431
- });
5569
+ }).catch((err) =>
5570
+ console.error(
5571
+ "[runner] active-session registration failed:",
5572
+ scrubSecrets(err instanceof Error ? err.message : String(err)),
5573
+ ),
5574
+ );
5432
5575
  console.log(
5433
5576
  `[${role}] Resumed task ${task.id.slice(0, 8)} (${state.activeTasks.size}/${state.maxConcurrent} active)`,
5434
5577
  );
@@ -5522,7 +5665,7 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
5522
5665
  if (currentHarness !== cachedCredHarnessProvider) {
5523
5666
  cachedCredHarnessProvider = currentHarness;
5524
5667
  buildCredStatusReport(currentHarness, process.env, {}, "post_task")
5525
- .then((snap) => reportCredStatus(apiUrl, apiKey, agentId, snap))
5668
+ .then((snap) => reportCredStatus(apiUrl, apiKey, agentId, runtimeInstanceId, snap))
5526
5669
  .catch((err) =>
5527
5670
  console.warn(`[${role}] cred_status post_task report failed (non-fatal): ${err}`),
5528
5671
  );
@@ -5538,7 +5681,7 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
5538
5681
  // account state. One bounded AWS round-trip per tick.
5539
5682
  lastBedrockRefreshAt = Date.now();
5540
5683
  buildCredStatusReport(currentHarness, process.env, {}, "post_task")
5541
- .then((snap) => reportCredStatus(apiUrl, apiKey, agentId, snap))
5684
+ .then((snap) => reportCredStatus(apiUrl, apiKey, agentId, runtimeInstanceId, snap))
5542
5685
  .catch((err) =>
5543
5686
  console.warn(`[${role}] bedrock enumeration refresh failed (non-fatal): ${err}`),
5544
5687
  );
@@ -5554,7 +5697,12 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
5554
5697
  if (!task.workingDir) continue;
5555
5698
 
5556
5699
  vcsCheckTimestamps.set(taskId, now);
5557
- detectVcsForTask(apiUrl, apiKey, taskId, task.workingDir);
5700
+ detectVcsForTask(apiUrl, apiKey, taskId, task.workingDir).catch((err) =>
5701
+ console.error(
5702
+ "[runner] VCS detection failed:",
5703
+ scrubSecrets(err instanceof Error ? err.message : String(err)),
5704
+ ),
5705
+ );
5558
5706
  }
5559
5707
 
5560
5708
  // Check for cancelled tasks and signal their subprocesses. Deliberately
@@ -5631,6 +5779,7 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
5631
5779
  apiKey,
5632
5780
  agentId,
5633
5781
  pollInterval: PollIntervalMs,
5782
+ runtimeInstanceId,
5634
5783
  pollTimeout: effectiveTimeout,
5635
5784
  });
5636
5785
 
@@ -5739,6 +5888,15 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
5739
5888
  }
5740
5889
  }
5741
5890
 
5891
+ const triggerWithSyncWarning = await prependProfileSyncRejectionBanner(triggerPrompt, {
5892
+ ...apiConfig,
5893
+ claudeMdPath: resolveClaudeMdPath([state.harnessProvider]),
5894
+ });
5895
+ triggerPrompt = triggerWithSyncWarning.prompt;
5896
+ if (triggerWithSyncWarning.injected) {
5897
+ console.warn(`[${role}] Injected persisted profile sync rejection warning`);
5898
+ }
5899
+
5742
5900
  // Resolve provider-aware resume for child tasks with parentTaskId.
5743
5901
  let resumeSessionId: string | undefined;
5744
5902
  if (taskObj?.parentTaskId) {
@@ -5990,7 +6148,12 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
5990
6148
  triggerType: trigger.type,
5991
6149
  taskDescription: taskDesc,
5992
6150
  runnerSessionId: taskRunnerSessionId,
5993
- });
6151
+ }).catch((err) =>
6152
+ console.error(
6153
+ "[runner] active-session registration failed:",
6154
+ scrubSecrets(err instanceof Error ? err.message : String(err)),
6155
+ ),
6156
+ );
5994
6157
 
5995
6158
  console.log(
5996
6159
  `[${role}] Started task ${runningTask.taskId.slice(0, 8)} (${state.activeTasks.size}/${state.maxConcurrent} active, trigger: ${trigger.type})`,
@@ -50,6 +50,28 @@ Accept if you have capacity and skills. Reject with a reason if you cannot handl
50
50
  category: "task_lifecycle",
51
51
  });
52
52
 
53
+ registerTemplate({
54
+ eventType: "task.profile_sync_rejection",
55
+ header: "",
56
+ defaultBody: `=== PERSISTED PROFILE SYNC REJECTION ===
57
+ A {{timestamp}} sync of {{field_label}} did not reach the database. Disk size: {{disk_size}}; DB size: {{db_size}}; budget: {{budget}}; delta: {{delta}}.
58
+ Audit event: {{event_id}}. {{recovery_guidance}}
59
+ === END PROFILE SYNC REJECTION ===
60
+
61
+ `,
62
+ variables: [
63
+ { name: "timestamp", description: "UTC timestamp of the rejected sync" },
64
+ { name: "field_label", description: "Profile field and its harness-specific file path" },
65
+ { name: "disk_size", description: "Rejected local file size" },
66
+ { name: "db_size", description: "Stored database field size" },
67
+ { name: "budget", description: "Configured field budget" },
68
+ { name: "delta", description: "Signed size delta" },
69
+ { name: "event_id", description: "Persisted audit event ID" },
70
+ { name: "recovery_guidance", description: "Accurate field-specific recovery instructions" },
71
+ ],
72
+ category: "task_lifecycle",
73
+ });
74
+
53
75
  registerTemplate({
54
76
  eventType: "task.trigger.unread_mentions",
55
77
  header: "",
@@ -4,6 +4,7 @@ import { findUserByExternalId } from "../be/users";
4
4
  import { resolveTemplate } from "../prompts/resolver";
5
5
  import { githubContextKey } from "../tasks/context-key";
6
6
  import { createTaskWithSiblingAwareness } from "../tasks/sibling-awareness";
7
+ import { scrubSecrets } from "../utils/secret-scrubber";
7
8
  import { getInstallationToken } from "./app";
8
9
  import {
9
10
  detectMention,
@@ -280,7 +281,12 @@ export async function handlePullRequest(
280
281
  }
281
282
 
282
283
  if (installation?.id) {
283
- addIssueReaction(repository.full_name, pr.number, "eyes", installation.id);
284
+ addIssueReaction(repository.full_name, pr.number, "eyes", installation.id).catch((err) =>
285
+ console.error(
286
+ "[GitHub] failed to add issue reaction:",
287
+ scrubSecrets(err instanceof Error ? err.message : String(err)),
288
+ ),
289
+ );
284
290
  }
285
291
 
286
292
  return { created: true, taskId: task.id };
@@ -388,7 +394,12 @@ export async function handlePullRequest(
388
394
  }
389
395
 
390
396
  if (installation?.id) {
391
- addIssueReaction(repository.full_name, pr.number, "eyes", installation.id);
397
+ addIssueReaction(repository.full_name, pr.number, "eyes", installation.id).catch((err) =>
398
+ console.error(
399
+ "[GitHub] failed to add issue reaction:",
400
+ scrubSecrets(err instanceof Error ? err.message : String(err)),
401
+ ),
402
+ );
392
403
  }
393
404
 
394
405
  return { created: true, taskId: task.id };
@@ -488,7 +499,12 @@ export async function handlePullRequest(
488
499
  }
489
500
 
490
501
  if (installation?.id) {
491
- addIssueReaction(repository.full_name, pr.number, "eyes", installation.id);
502
+ addIssueReaction(repository.full_name, pr.number, "eyes", installation.id).catch((err) =>
503
+ console.error(
504
+ "[GitHub] failed to add issue reaction:",
505
+ scrubSecrets(err instanceof Error ? err.message : String(err)),
506
+ ),
507
+ );
492
508
  }
493
509
 
494
510
  return { created: true, taskId: task.id };
@@ -577,7 +593,12 @@ export async function handlePullRequest(
577
593
 
578
594
  // Add 👀 reaction to acknowledge the mention
579
595
  if (installation?.id) {
580
- addIssueReaction(repository.full_name, pr.number, "eyes", installation.id);
596
+ addIssueReaction(repository.full_name, pr.number, "eyes", installation.id).catch((err) =>
597
+ console.error(
598
+ "[GitHub] failed to add issue reaction:",
599
+ scrubSecrets(err instanceof Error ? err.message : String(err)),
600
+ ),
601
+ );
581
602
  }
582
603
 
583
604
  return { created: true, taskId: task.id };
@@ -657,7 +678,12 @@ export async function handleIssue(
657
678
  }
658
679
 
659
680
  if (installation?.id) {
660
- addIssueReaction(repository.full_name, issue.number, "eyes", installation.id);
681
+ addIssueReaction(repository.full_name, issue.number, "eyes", installation.id).catch((err) =>
682
+ console.error(
683
+ "[GitHub] failed to add issue reaction:",
684
+ scrubSecrets(err instanceof Error ? err.message : String(err)),
685
+ ),
686
+ );
661
687
  }
662
688
 
663
689
  return { created: true, taskId: task.id };
@@ -753,7 +779,12 @@ export async function handleIssue(
753
779
  }
754
780
 
755
781
  if (installation?.id) {
756
- addIssueReaction(repository.full_name, issue.number, "eyes", installation.id);
782
+ addIssueReaction(repository.full_name, issue.number, "eyes", installation.id).catch((err) =>
783
+ console.error(
784
+ "[GitHub] failed to add issue reaction:",
785
+ scrubSecrets(err instanceof Error ? err.message : String(err)),
786
+ ),
787
+ );
757
788
  }
758
789
 
759
790
  return { created: true, taskId: task.id };
@@ -824,7 +855,12 @@ export async function handleIssue(
824
855
 
825
856
  // Add 👀 reaction to acknowledge the mention
826
857
  if (installation?.id) {
827
- addIssueReaction(repository.full_name, issue.number, "eyes", installation.id);
858
+ addIssueReaction(repository.full_name, issue.number, "eyes", installation.id).catch((err) =>
859
+ console.error(
860
+ "[GitHub] failed to add issue reaction:",
861
+ scrubSecrets(err instanceof Error ? err.message : String(err)),
862
+ ),
863
+ );
828
864
  }
829
865
 
830
866
  return { created: true, taskId: task.id };
@@ -932,7 +968,12 @@ export async function handleComment(
932
968
 
933
969
  // Add 👀 reaction to the comment to acknowledge the mention
934
970
  if (installation?.id) {
935
- addReaction(repository.full_name, comment.id, "eyes", installation.id);
971
+ addReaction(repository.full_name, comment.id, "eyes", installation.id).catch((err) =>
972
+ console.error(
973
+ "[GitHub] failed to add comment reaction:",
974
+ scrubSecrets(err instanceof Error ? err.message : String(err)),
975
+ ),
976
+ );
936
977
  }
937
978
 
938
979
  return { created: true, taskId: task.id };
@@ -1240,7 +1281,12 @@ export async function handlePullRequestReview(
1240
1281
 
1241
1282
  // Add reaction to acknowledge the review
1242
1283
  if (installation?.id) {
1243
- addIssueReaction(repository.full_name, pr.number, "eyes", installation.id);
1284
+ addIssueReaction(repository.full_name, pr.number, "eyes", installation.id).catch((err) =>
1285
+ console.error(
1286
+ "[GitHub] failed to add issue reaction:",
1287
+ scrubSecrets(err instanceof Error ? err.message : String(err)),
1288
+ ),
1289
+ );
1244
1290
  }
1245
1291
 
1246
1292
  return { created: true, taskId: task.id };