@vellumai/assistant 0.5.5 → 0.5.7

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 (382) hide show
  1. package/.env.example +16 -2
  2. package/ARCHITECTURE.md +6 -75
  3. package/Dockerfile +4 -5
  4. package/README.md +0 -2
  5. package/bun.lock +0 -414
  6. package/docs/architecture/keychain-broker.md +45 -240
  7. package/docs/architecture/security.md +0 -17
  8. package/docs/credential-execution-service.md +2 -2
  9. package/node_modules/@vellumai/ces-contracts/package.json +1 -0
  10. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +119 -0
  11. package/node_modules/@vellumai/credential-storage/package.json +1 -0
  12. package/node_modules/@vellumai/egress-proxy/package.json +1 -0
  13. package/package.json +2 -3
  14. package/src/__tests__/actor-token-service.test.ts +1 -2
  15. package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
  16. package/src/__tests__/browser-skill-endstate.test.ts +6 -5
  17. package/src/__tests__/btw-routes.test.ts +0 -39
  18. package/src/__tests__/call-domain.test.ts +0 -128
  19. package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -0
  20. package/src/__tests__/channel-approval-routes.test.ts +0 -5
  21. package/src/__tests__/channel-readiness-service.test.ts +1 -60
  22. package/src/__tests__/checker.test.ts +4 -2
  23. package/src/__tests__/cli-command-risk-guard.test.ts +112 -0
  24. package/src/__tests__/config-schema-cmd.test.ts +0 -1
  25. package/src/__tests__/config-schema.test.ts +3 -3
  26. package/src/__tests__/context-window-manager.test.ts +78 -0
  27. package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
  28. package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
  29. package/src/__tests__/conversation-skill-tools.test.ts +0 -54
  30. package/src/__tests__/conversation-title-service.test.ts +117 -1
  31. package/src/__tests__/credential-execution-feature-gates.test.ts +28 -14
  32. package/src/__tests__/credential-execution-managed-contract.test.ts +33 -18
  33. package/src/__tests__/credential-security-e2e.test.ts +0 -66
  34. package/src/__tests__/credential-security-invariants.test.ts +4 -45
  35. package/src/__tests__/credentials-cli.test.ts +78 -0
  36. package/src/__tests__/db-migration-rollback.test.ts +2015 -1
  37. package/src/__tests__/docker-signing-key-bootstrap.test.ts +98 -0
  38. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
  39. package/src/__tests__/guardian-routing-state.test.ts +0 -5
  40. package/src/__tests__/host-shell-tool.test.ts +6 -7
  41. package/src/__tests__/http-user-message-parity.test.ts +3 -103
  42. package/src/__tests__/inbound-invite-redemption.test.ts +0 -4
  43. package/src/__tests__/inline-skill-load-permissions.test.ts +6 -8
  44. package/src/__tests__/intent-routing.test.ts +0 -13
  45. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +178 -0
  46. package/src/__tests__/keychain-broker-client.test.ts +161 -22
  47. package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
  48. package/src/__tests__/memory-regressions.test.ts +8 -30
  49. package/src/__tests__/migration-export-http.test.ts +2 -2
  50. package/src/__tests__/migration-import-commit-http.test.ts +2 -2
  51. package/src/__tests__/migration-import-preflight-http.test.ts +2 -2
  52. package/src/__tests__/migration-validate-http.test.ts +2 -2
  53. package/src/__tests__/non-member-access-request.test.ts +0 -5
  54. package/src/__tests__/notification-decision-fallback.test.ts +4 -0
  55. package/src/__tests__/notification-decision-identity.test.ts +4 -0
  56. package/src/__tests__/permission-types.test.ts +1 -0
  57. package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
  58. package/src/__tests__/qdrant-manager.test.ts +28 -2
  59. package/src/__tests__/registry.test.ts +0 -6
  60. package/src/__tests__/require-fresh-approval.test.ts +4 -0
  61. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
  62. package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
  63. package/src/__tests__/secure-keys.test.ts +83 -263
  64. package/src/__tests__/shell-identity.test.ts +96 -6
  65. package/src/__tests__/skill-feature-flags-integration.test.ts +22 -14
  66. package/src/__tests__/skill-feature-flags.test.ts +46 -45
  67. package/src/__tests__/skill-load-feature-flag.test.ts +7 -10
  68. package/src/__tests__/skill-load-inline-command.test.ts +8 -12
  69. package/src/__tests__/skill-load-inline-includes.test.ts +6 -10
  70. package/src/__tests__/skill-load-tool.test.ts +0 -2
  71. package/src/__tests__/skill-projection-feature-flag.test.ts +33 -29
  72. package/src/__tests__/skills.test.ts +0 -2
  73. package/src/__tests__/slack-inbound-verification.test.ts +0 -4
  74. package/src/__tests__/suggestion-routes.test.ts +1 -32
  75. package/src/__tests__/system-prompt.test.ts +0 -1
  76. package/src/__tests__/tool-executor-lifecycle-events.test.ts +4 -0
  77. package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
  78. package/src/__tests__/tool-executor.test.ts +4 -0
  79. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
  80. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
  81. package/src/__tests__/update-bulletin.test.ts +0 -2
  82. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +6 -9
  83. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -6
  84. package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +252 -0
  85. package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +218 -0
  86. package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
  87. package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
  88. package/src/calls/audio-store.test.ts +97 -0
  89. package/src/calls/audio-store.ts +205 -0
  90. package/src/calls/call-controller.ts +85 -7
  91. package/src/calls/call-domain.ts +3 -0
  92. package/src/calls/call-store.ts +10 -3
  93. package/src/calls/fish-audio-client.ts +117 -0
  94. package/src/calls/relay-server.ts +27 -0
  95. package/src/calls/twilio-routes.ts +2 -1
  96. package/src/calls/types.ts +1 -0
  97. package/src/calls/voice-ingress-preflight.ts +0 -42
  98. package/src/calls/voice-quality.ts +26 -5
  99. package/src/calls/voice-session-bridge.ts +6 -12
  100. package/src/cli/commands/config.ts +1 -4
  101. package/src/cli/commands/conversations.ts +0 -18
  102. package/src/cli/commands/credentials.ts +34 -4
  103. package/src/cli/commands/oauth/index.ts +7 -0
  104. package/src/cli/commands/oauth/platform.ts +179 -0
  105. package/src/cli/commands/platform.ts +3 -3
  106. package/src/config/assistant-feature-flags.ts +186 -5
  107. package/src/config/bundled-skills/messaging/SKILL.md +5 -5
  108. package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
  109. package/src/config/bundled-skills/settings/TOOLS.json +2 -2
  110. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
  111. package/src/config/bundled-tool-registry.ts +1 -11
  112. package/src/config/env-registry.ts +1 -1
  113. package/src/config/env.ts +16 -16
  114. package/src/config/feature-flag-registry.json +48 -16
  115. package/src/config/loader.ts +98 -31
  116. package/src/config/schema.ts +4 -25
  117. package/src/config/schemas/calls.ts +13 -0
  118. package/src/config/schemas/fish-audio.ts +39 -0
  119. package/src/config/schemas/memory.ts +0 -4
  120. package/src/config/schemas/platform.ts +1 -1
  121. package/src/config/schemas/security.ts +4 -4
  122. package/src/config/types.ts +0 -1
  123. package/src/contacts/contact-store.ts +39 -0
  124. package/src/contacts/types.ts +2 -0
  125. package/src/context/window-manager.ts +53 -2
  126. package/src/credential-execution/approval-bridge.ts +1 -0
  127. package/src/credential-execution/executable-discovery.ts +28 -4
  128. package/src/credential-execution/feature-gates.ts +16 -0
  129. package/src/credential-execution/process-manager.ts +38 -0
  130. package/src/daemon/assistant-attachments.ts +9 -0
  131. package/src/daemon/config-watcher.ts +6 -4
  132. package/src/daemon/conversation-agent-loop.ts +0 -60
  133. package/src/daemon/conversation-memory.ts +0 -117
  134. package/src/daemon/conversation-runtime-assembly.ts +0 -2
  135. package/src/daemon/conversation-tool-setup.ts +0 -105
  136. package/src/daemon/conversation.ts +10 -1
  137. package/src/daemon/handlers/config-vercel.ts +92 -0
  138. package/src/daemon/handlers/conversations.ts +0 -11
  139. package/src/daemon/handlers/skills.ts +2 -15
  140. package/src/daemon/install-symlink.ts +195 -0
  141. package/src/daemon/lifecycle.ts +229 -96
  142. package/src/daemon/message-types/conversations.ts +3 -4
  143. package/src/daemon/message-types/diagnostics.ts +3 -22
  144. package/src/daemon/message-types/messages.ts +0 -2
  145. package/src/daemon/message-types/upgrades.ts +8 -0
  146. package/src/daemon/server.ts +30 -92
  147. package/src/events/domain-events.ts +2 -1
  148. package/src/followups/followup-store.ts +5 -2
  149. package/src/inbound/platform-callback-registration.ts +3 -3
  150. package/src/instrument.ts +8 -5
  151. package/src/memory/conversation-crud.ts +0 -236
  152. package/src/memory/conversation-title-service.ts +76 -11
  153. package/src/memory/db-init.ts +15 -11
  154. package/src/memory/indexer.ts +15 -106
  155. package/src/memory/items-extractor.ts +15 -1
  156. package/src/memory/job-handlers/conversation-starters.ts +4 -1
  157. package/src/memory/job-handlers/embedding.ts +0 -79
  158. package/src/memory/job-utils.ts +1 -1
  159. package/src/memory/jobs-store.ts +30 -13
  160. package/src/memory/jobs-worker.ts +31 -27
  161. package/src/memory/migrations/001-job-deferrals.ts +19 -0
  162. package/src/memory/migrations/004-entity-relation-dedup.ts +10 -0
  163. package/src/memory/migrations/005-fingerprint-scope-unique.ts +76 -0
  164. package/src/memory/migrations/006-scope-salted-fingerprints.ts +50 -0
  165. package/src/memory/migrations/007-assistant-id-to-self.ts +10 -0
  166. package/src/memory/migrations/008-remove-assistant-id-columns.ts +34 -0
  167. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +26 -0
  168. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +10 -0
  169. package/src/memory/migrations/015-drop-active-search-index.ts +17 -0
  170. package/src/memory/migrations/019-notification-tables-schema-migration.ts +12 -0
  171. package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +121 -0
  172. package/src/memory/migrations/024-embedding-vector-blob.ts +74 -0
  173. package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +82 -0
  174. package/src/memory/migrations/036-normalize-phone-identities.ts +11 -0
  175. package/src/memory/migrations/116-messages-fts.ts +106 -1
  176. package/src/memory/migrations/126-backfill-guardian-principal-id.ts +52 -0
  177. package/src/memory/migrations/127-guardian-principal-id-not-null.ts +77 -0
  178. package/src/memory/migrations/134-contacts-notes-column.ts +13 -0
  179. package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +20 -0
  180. package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -0
  181. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +13 -0
  182. package/src/memory/migrations/141-rename-verification-table.ts +54 -0
  183. package/src/memory/migrations/142-rename-verification-session-id-column.ts +25 -0
  184. package/src/memory/migrations/143-rename-guardian-verification-values.ts +35 -0
  185. package/src/memory/migrations/144-rename-voice-to-phone.ts +136 -0
  186. package/src/memory/migrations/145-drop-accounts-table.ts +32 -0
  187. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +14 -1
  188. package/src/memory/migrations/148-drop-reminders-table.ts +35 -1
  189. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +69 -1
  190. package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +290 -0
  191. package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +51 -1
  192. package/src/memory/migrations/174-rename-thread-starters-table.ts +47 -1
  193. package/src/memory/migrations/176-drop-capability-card-state.ts +13 -0
  194. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +16 -0
  195. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +28 -1
  196. package/src/memory/migrations/189-drop-simplified-memory.ts +42 -0
  197. package/src/memory/migrations/190-call-session-skip-disclosure.ts +15 -0
  198. package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +64 -0
  199. package/src/memory/migrations/192-contacts-user-file-column.ts +15 -0
  200. package/src/memory/migrations/index.ts +5 -3
  201. package/src/memory/migrations/registry.ts +90 -0
  202. package/src/memory/migrations/validate-migration-state.ts +137 -11
  203. package/src/memory/qdrant-circuit-breaker.ts +9 -0
  204. package/src/memory/qdrant-client.ts +4 -6
  205. package/src/memory/qdrant-manager.ts +64 -7
  206. package/src/memory/schema/calls.ts +1 -0
  207. package/src/memory/schema/contacts.ts +1 -0
  208. package/src/memory/schema/conversations.ts +0 -3
  209. package/src/memory/schema/index.ts +0 -2
  210. package/src/messaging/draft-store.ts +2 -2
  211. package/src/notifications/decision-engine.ts +4 -1
  212. package/src/oauth/connection-resolver.ts +6 -4
  213. package/src/permissions/checker.ts +0 -38
  214. package/src/permissions/defaults.ts +3 -3
  215. package/src/permissions/shell-identity.ts +76 -22
  216. package/src/permissions/trust-client.ts +2 -13
  217. package/src/permissions/trust-store.ts +8 -3
  218. package/src/permissions/types.ts +4 -2
  219. package/src/platform/client.ts +35 -7
  220. package/src/prompts/persona-resolver.ts +138 -0
  221. package/src/prompts/system-prompt.ts +36 -4
  222. package/src/prompts/templates/users/default.md +1 -0
  223. package/src/providers/registry.ts +27 -40
  224. package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
  225. package/src/runtime/auth/__tests__/external-assistant-id.test.ts +13 -68
  226. package/src/runtime/auth/external-assistant-id.ts +13 -59
  227. package/src/runtime/auth/route-policy.ts +29 -1
  228. package/src/runtime/auth/token-service.ts +53 -15
  229. package/src/runtime/channel-readiness-service.ts +1 -16
  230. package/src/runtime/http-server.ts +29 -2
  231. package/src/runtime/middleware/error-handler.ts +1 -9
  232. package/src/runtime/routes/audio-routes.ts +40 -0
  233. package/src/runtime/routes/btw-routes.ts +0 -17
  234. package/src/runtime/routes/conversation-management-routes.ts +0 -36
  235. package/src/runtime/routes/conversation-query-routes.ts +106 -2
  236. package/src/runtime/routes/conversation-routes.ts +4 -43
  237. package/src/runtime/routes/diagnostics-routes.ts +1 -477
  238. package/src/runtime/routes/identity-routes.ts +18 -29
  239. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -33
  240. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
  241. package/src/runtime/routes/integrations/vercel.ts +89 -0
  242. package/src/runtime/routes/log-export-routes.ts +5 -0
  243. package/src/runtime/routes/memory-item-routes.test.ts +221 -3
  244. package/src/runtime/routes/memory-item-routes.ts +144 -4
  245. package/src/runtime/routes/migration-rollback-routes.ts +209 -0
  246. package/src/runtime/routes/migration-routes.ts +17 -1
  247. package/src/runtime/routes/notification-routes.ts +58 -0
  248. package/src/runtime/routes/schedule-routes.ts +65 -0
  249. package/src/runtime/routes/settings-routes.ts +41 -1
  250. package/src/runtime/routes/tts-routes.ts +86 -0
  251. package/src/runtime/routes/upgrade-broadcast-routes.ts +175 -0
  252. package/src/runtime/routes/workspace-commit-routes.ts +62 -0
  253. package/src/runtime/routes/workspace-routes.test.ts +22 -1
  254. package/src/runtime/routes/workspace-routes.ts +1 -1
  255. package/src/runtime/routes/workspace-utils.ts +86 -2
  256. package/src/schedule/schedule-store.ts +0 -21
  257. package/src/security/ces-credential-client.ts +59 -22
  258. package/src/security/ces-rpc-credential-backend.ts +85 -0
  259. package/src/security/credential-backend.ts +12 -88
  260. package/src/security/keychain-broker-client.ts +10 -2
  261. package/src/security/secure-keys.ts +94 -113
  262. package/src/skills/catalog-install.ts +13 -7
  263. package/src/skills/inline-command-render.ts +5 -1
  264. package/src/skills/inline-command-runner.ts +30 -2
  265. package/src/telemetry/usage-telemetry-reporter.ts +4 -2
  266. package/src/tools/calls/call-start.ts +1 -0
  267. package/src/tools/executor.ts +0 -4
  268. package/src/tools/memory/handlers.ts +1 -129
  269. package/src/tools/network/script-proxy/session-manager.ts +19 -4
  270. package/src/tools/network/web-fetch.ts +3 -1
  271. package/src/tools/permission-checker.ts +18 -0
  272. package/src/tools/skills/execute.ts +1 -1
  273. package/src/tools/skills/load.ts +9 -2
  274. package/src/tools/types.ts +0 -8
  275. package/src/util/errors.ts +0 -12
  276. package/src/util/platform.ts +8 -55
  277. package/src/util/xml.ts +8 -0
  278. package/src/workspace/git-service.ts +5 -2
  279. package/src/workspace/heartbeat-service.ts +5 -24
  280. package/src/workspace/migrations/001-avatar-rename.ts +15 -0
  281. package/src/workspace/migrations/003-seed-device-id.ts +17 -1
  282. package/src/workspace/migrations/004-extract-collect-usage-data.ts +33 -0
  283. package/src/workspace/migrations/005-add-send-diagnostics.ts +3 -0
  284. package/src/workspace/migrations/006-services-config.ts +49 -0
  285. package/src/workspace/migrations/007-web-search-provider-rename.ts +27 -0
  286. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +3 -0
  287. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +4 -0
  288. package/src/workspace/migrations/010-app-dir-rename.ts +78 -0
  289. package/src/workspace/migrations/011-backfill-installation-id.ts +11 -0
  290. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +44 -0
  291. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +5 -0
  292. package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +153 -0
  293. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +156 -0
  294. package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +150 -0
  295. package/src/workspace/migrations/017-seed-persona-dirs.ts +95 -0
  296. package/src/workspace/migrations/migrate-to-workspace-volume.ts +23 -1
  297. package/src/workspace/migrations/registry.ts +8 -0
  298. package/src/workspace/migrations/runner.ts +106 -2
  299. package/src/workspace/migrations/types.ts +4 -0
  300. package/src/__tests__/archive-recall.test.ts +0 -560
  301. package/src/__tests__/claude-code-skill-regression.test.ts +0 -206
  302. package/src/__tests__/claude-code-tool-profiles.test.ts +0 -99
  303. package/src/__tests__/conversation-memory-dirty-tail.test.ts +0 -150
  304. package/src/__tests__/conversation-switch-memory-reduction.test.ts +0 -474
  305. package/src/__tests__/db-memory-archive-migration.test.ts +0 -372
  306. package/src/__tests__/db-memory-brief-state-migration.test.ts +0 -213
  307. package/src/__tests__/db-memory-reducer-checkpoints.test.ts +0 -273
  308. package/src/__tests__/diagnostics-export.test.ts +0 -288
  309. package/src/__tests__/local-gateway-health.test.ts +0 -209
  310. package/src/__tests__/memory-brief-open-loops.test.ts +0 -530
  311. package/src/__tests__/memory-brief-time.test.ts +0 -285
  312. package/src/__tests__/memory-brief-wrapper.test.ts +0 -311
  313. package/src/__tests__/memory-chunk-archive.test.ts +0 -400
  314. package/src/__tests__/memory-chunk-dual-write.test.ts +0 -453
  315. package/src/__tests__/memory-episode-archive.test.ts +0 -370
  316. package/src/__tests__/memory-episode-dual-write.test.ts +0 -626
  317. package/src/__tests__/memory-observation-archive.test.ts +0 -375
  318. package/src/__tests__/memory-observation-dual-write.test.ts +0 -318
  319. package/src/__tests__/memory-reducer-job.test.ts +0 -538
  320. package/src/__tests__/memory-reducer-scheduling.test.ts +0 -473
  321. package/src/__tests__/memory-reducer-store.test.ts +0 -728
  322. package/src/__tests__/memory-reducer-types.test.ts +0 -707
  323. package/src/__tests__/memory-reducer.test.ts +0 -704
  324. package/src/__tests__/memory-simplified-config.test.ts +0 -281
  325. package/src/__tests__/secret-ingress-handler.test.ts +0 -120
  326. package/src/__tests__/simplified-memory-e2e.test.ts +0 -666
  327. package/src/__tests__/simplified-memory-runtime.test.ts +0 -616
  328. package/src/__tests__/swarm-conversation-integration.test.ts +0 -358
  329. package/src/__tests__/swarm-dag-pathological.test.ts +0 -547
  330. package/src/__tests__/swarm-orchestrator.test.ts +0 -463
  331. package/src/__tests__/swarm-plan-validator.test.ts +0 -384
  332. package/src/__tests__/swarm-recursion.test.ts +0 -197
  333. package/src/__tests__/swarm-router-planner.test.ts +0 -234
  334. package/src/__tests__/swarm-tool.test.ts +0 -185
  335. package/src/__tests__/swarm-worker-backend.test.ts +0 -144
  336. package/src/__tests__/swarm-worker-runner.test.ts +0 -288
  337. package/src/commands/__tests__/cc-command-registry.test.ts +0 -396
  338. package/src/commands/cc-command-registry.ts +0 -248
  339. package/src/config/bundled-skills/claude-code/SKILL.md +0 -53
  340. package/src/config/bundled-skills/claude-code/TOOLS.json +0 -47
  341. package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -12
  342. package/src/config/bundled-skills/orchestration/SKILL.md +0 -33
  343. package/src/config/bundled-skills/orchestration/TOOLS.json +0 -35
  344. package/src/config/bundled-skills/orchestration/tools/swarm-delegate.ts +0 -12
  345. package/src/config/schemas/memory-simplified.ts +0 -101
  346. package/src/config/schemas/swarm.ts +0 -82
  347. package/src/logfire.ts +0 -135
  348. package/src/memory/archive-recall.ts +0 -516
  349. package/src/memory/archive-store.ts +0 -400
  350. package/src/memory/brief-formatting.ts +0 -33
  351. package/src/memory/brief-open-loops.ts +0 -266
  352. package/src/memory/brief-time.ts +0 -162
  353. package/src/memory/brief.ts +0 -75
  354. package/src/memory/job-handlers/backfill-simplified-memory.ts +0 -462
  355. package/src/memory/job-handlers/reduce-conversation-memory.ts +0 -229
  356. package/src/memory/migrations/185-memory-brief-state.ts +0 -52
  357. package/src/memory/migrations/186-memory-archive.ts +0 -109
  358. package/src/memory/migrations/187-memory-reducer-checkpoints.ts +0 -19
  359. package/src/memory/reducer-scheduler.ts +0 -242
  360. package/src/memory/reducer-store.ts +0 -271
  361. package/src/memory/reducer-types.ts +0 -106
  362. package/src/memory/reducer.ts +0 -467
  363. package/src/memory/schema/memory-archive.ts +0 -121
  364. package/src/memory/schema/memory-brief.ts +0 -55
  365. package/src/runtime/local-gateway-health.ts +0 -275
  366. package/src/security/secret-ingress.ts +0 -68
  367. package/src/swarm/backend-claude-code.ts +0 -225
  368. package/src/swarm/checkpoint.ts +0 -137
  369. package/src/swarm/graph-utils.ts +0 -53
  370. package/src/swarm/index.ts +0 -55
  371. package/src/swarm/limits.ts +0 -66
  372. package/src/swarm/orchestrator.ts +0 -424
  373. package/src/swarm/plan-validator.ts +0 -117
  374. package/src/swarm/router-planner.ts +0 -162
  375. package/src/swarm/router-prompts.ts +0 -39
  376. package/src/swarm/synthesizer.ts +0 -81
  377. package/src/swarm/types.ts +0 -72
  378. package/src/swarm/worker-backend.ts +0 -131
  379. package/src/swarm/worker-prompts.ts +0 -80
  380. package/src/swarm/worker-runner.ts +0 -170
  381. package/src/tools/claude-code/claude-code.ts +0 -610
  382. package/src/tools/swarm/delegate.ts +0 -205
@@ -1,275 +0,0 @@
1
- import { getGatewayInternalBaseUrl } from "../config/env.js";
2
- import { getBaseDataDir, getIsContainerized } from "../config/env-registry.js";
3
- import { readLockfile } from "../util/platform.js";
4
- import { sleep } from "../util/retry.js";
5
-
6
- const DEFAULT_PROBE_TIMEOUT_MS = 2_000;
7
- const DEFAULT_RECOVERY_POLL_TIMEOUT_MS = 30_000;
8
- const DEFAULT_RECOVERY_POLL_INTERVAL_MS = 250;
9
- const DEFAULT_WAKE_TIMEOUT_MS = 90_000;
10
-
11
- interface LockfileAssistantEntry {
12
- assistantId?: string;
13
- cloud?: string;
14
- hatchedAt?: string | number | Date;
15
- }
16
-
17
- export interface WakeCommandResult {
18
- exitCode: number;
19
- stdout: string;
20
- stderr: string;
21
- }
22
-
23
- export interface LocalGatewayHealthResult {
24
- target: string;
25
- healthy: boolean;
26
- localDeployment: boolean;
27
- error?: string;
28
- }
29
-
30
- export interface EnsureLocalGatewayReadyResult extends LocalGatewayHealthResult {
31
- recovered: boolean;
32
- recoveryAttempted: boolean;
33
- recoverySkipped: boolean;
34
- }
35
-
36
- export interface ProbeLocalGatewayHealthOptions {
37
- timeoutMs?: number;
38
- fetchImpl?: typeof fetch;
39
- }
40
-
41
- export interface EnsureLocalGatewayReadyOptions extends ProbeLocalGatewayHealthOptions {
42
- pollTimeoutMs?: number;
43
- pollIntervalMs?: number;
44
- wakeTimeoutMs?: number;
45
- runWakeCommand?: () => Promise<WakeCommandResult>;
46
- sleepImpl?: (ms: number) => Promise<void>;
47
- }
48
-
49
- function getLatestAssistantEntry(): LockfileAssistantEntry | null {
50
- try {
51
- const lockData = readLockfile();
52
- const assistants = lockData?.assistants;
53
- if (!Array.isArray(assistants) || assistants.length === 0) {
54
- return null;
55
- }
56
-
57
- const sorted = [...assistants].sort((a, b) => {
58
- const dateA = new Date(
59
- (a as LockfileAssistantEntry).hatchedAt || 0,
60
- ).getTime();
61
- const dateB = new Date(
62
- (b as LockfileAssistantEntry).hatchedAt || 0,
63
- ).getTime();
64
- return dateB - dateA;
65
- });
66
-
67
- return (sorted[0] as LockfileAssistantEntry) ?? null;
68
- } catch {
69
- return null;
70
- }
71
- }
72
-
73
- function resolveLocalDeployment(): boolean {
74
- if (getIsContainerized()) {
75
- return false;
76
- }
77
-
78
- const latestAssistant = getLatestAssistantEntry();
79
- if (typeof latestAssistant?.cloud === "string") {
80
- return latestAssistant.cloud === "local";
81
- }
82
-
83
- return true;
84
- }
85
-
86
- /**
87
- * Derive instance name from BASE_DATA_DIR which follows the multi-instance
88
- * path pattern (~/.local/share/vellum/assistants/<name>/).
89
- */
90
- function resolveInstanceNameFromBaseDataDir(): string | undefined {
91
- const base = getBaseDataDir();
92
- if (!base || typeof base !== "string") return undefined;
93
-
94
- const normalized = base.replace(/\\/g, "/").replace(/\/+$/, "");
95
- const match = normalized.match(/\/assistants\/([^/]+)$/);
96
- if (match) return match[1];
97
- return undefined;
98
- }
99
-
100
- function resolveLocalAssistantName(): string | undefined {
101
- const fromPath = resolveInstanceNameFromBaseDataDir();
102
- if (fromPath) return fromPath;
103
-
104
- const latestAssistant = getLatestAssistantEntry();
105
- if (
106
- latestAssistant &&
107
- typeof latestAssistant.assistantId === "string" &&
108
- latestAssistant.assistantId.trim().length > 0
109
- ) {
110
- return latestAssistant.assistantId.trim();
111
- }
112
-
113
- return undefined;
114
- }
115
-
116
- function formatError(err: unknown): string {
117
- return err instanceof Error ? err.message : String(err);
118
- }
119
-
120
- async function runDefaultWakeCommand(
121
- timeoutMs: number,
122
- ): Promise<WakeCommandResult> {
123
- const assistantName = resolveLocalAssistantName();
124
- const command = assistantName
125
- ? ["vellum", "wake", assistantName]
126
- : ["vellum", "wake"];
127
-
128
- // Only when the assistant name came from the instance path (e.g.
129
- // ~/.local/share/vellum/assistants/<name>/), unset BASE_DATA_DIR so the
130
- // spawned CLI reads the global lockfile. When the name came from the
131
- // lockfile, keep BASE_DATA_DIR — vellum wake resolves names through the
132
- // lockfile rooted at BASE_DATA_DIR, so clearing it would read the wrong
133
- // lockfile (e.g. $HOME) and fail or wake the wrong assistant.
134
- const fromInstancePath = resolveInstanceNameFromBaseDataDir();
135
- const env =
136
- fromInstancePath && getBaseDataDir()
137
- ? { ...process.env, BASE_DATA_DIR: undefined }
138
- : process.env;
139
-
140
- return new Promise((resolve, reject) => {
141
- const proc = Bun.spawn(command, {
142
- stdout: "pipe",
143
- stderr: "pipe",
144
- env: {
145
- ...env,
146
- PATH: [env.PATH, "/opt/homebrew/bin", "/usr/local/bin"]
147
- .filter(Boolean)
148
- .join(":"),
149
- },
150
- });
151
- const timer = setTimeout(() => {
152
- proc.kill();
153
- reject(
154
- new Error(`Process timed out after ${timeoutMs}ms: ${command[0]}`),
155
- );
156
- }, timeoutMs);
157
- proc.exited.then(async (exitCode) => {
158
- clearTimeout(timer);
159
- const stdout = await new Response(proc.stdout).text();
160
- const stderr = await new Response(proc.stderr).text();
161
- resolve({ exitCode, stdout, stderr });
162
- });
163
- });
164
- }
165
-
166
- export async function probeLocalGatewayHealth(
167
- options: ProbeLocalGatewayHealthOptions = {},
168
- ): Promise<LocalGatewayHealthResult> {
169
- const target = getGatewayInternalBaseUrl();
170
- const localDeployment = resolveLocalDeployment();
171
- const fetchImpl = options.fetchImpl ?? fetch;
172
- const timeoutMs = options.timeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS;
173
-
174
- try {
175
- const response = await fetchImpl(`${target}/healthz`, {
176
- method: "GET",
177
- signal: AbortSignal.timeout(timeoutMs),
178
- });
179
- if (!response.ok) {
180
- return {
181
- target,
182
- healthy: false,
183
- localDeployment,
184
- error: `Gateway health check returned HTTP ${response.status}`,
185
- };
186
- }
187
-
188
- return {
189
- target,
190
- healthy: true,
191
- localDeployment,
192
- };
193
- } catch (err) {
194
- return {
195
- target,
196
- healthy: false,
197
- localDeployment,
198
- error: formatError(err),
199
- };
200
- }
201
- }
202
-
203
- export async function ensureLocalGatewayReady(
204
- options: EnsureLocalGatewayReadyOptions = {},
205
- ): Promise<EnsureLocalGatewayReadyResult> {
206
- const initialProbe = await probeLocalGatewayHealth(options);
207
- if (initialProbe.healthy) {
208
- return {
209
- ...initialProbe,
210
- recovered: false,
211
- recoveryAttempted: false,
212
- recoverySkipped: false,
213
- };
214
- }
215
-
216
- if (!initialProbe.localDeployment) {
217
- return {
218
- ...initialProbe,
219
- recovered: false,
220
- recoveryAttempted: false,
221
- recoverySkipped: true,
222
- error:
223
- initialProbe.error ??
224
- "Skipped gateway recovery because this assistant is not locally managed",
225
- };
226
- }
227
-
228
- const runWakeCommand =
229
- options.runWakeCommand ??
230
- (() =>
231
- runDefaultWakeCommand(options.wakeTimeoutMs ?? DEFAULT_WAKE_TIMEOUT_MS));
232
- const sleepImpl = options.sleepImpl ?? sleep;
233
- const pollTimeoutMs =
234
- options.pollTimeoutMs ?? DEFAULT_RECOVERY_POLL_TIMEOUT_MS;
235
- const pollIntervalMs =
236
- options.pollIntervalMs ?? DEFAULT_RECOVERY_POLL_INTERVAL_MS;
237
-
238
- let wakeError: string | undefined;
239
- try {
240
- const wakeResult = await runWakeCommand();
241
- if (wakeResult.exitCode !== 0) {
242
- const detail = wakeResult.stderr.trim() || wakeResult.stdout.trim();
243
- wakeError = detail
244
- ? `vellum wake exited with code ${wakeResult.exitCode}: ${detail}`
245
- : `vellum wake exited with code ${wakeResult.exitCode}`;
246
- }
247
- } catch (err) {
248
- wakeError = `Failed to run vellum wake: ${formatError(err)}`;
249
- }
250
-
251
- const deadline = Date.now() + pollTimeoutMs;
252
- let probe = await probeLocalGatewayHealth(options);
253
- while (!probe.healthy && Date.now() < deadline) {
254
- await sleepImpl(pollIntervalMs);
255
- probe = await probeLocalGatewayHealth(options);
256
- }
257
-
258
- if (probe.healthy) {
259
- return {
260
- ...probe,
261
- recovered: true,
262
- recoveryAttempted: true,
263
- recoverySkipped: false,
264
- };
265
- }
266
-
267
- const combinedError = [wakeError, probe.error].filter(Boolean).join("; ");
268
- return {
269
- ...probe,
270
- recovered: false,
271
- recoveryAttempted: true,
272
- recoverySkipped: false,
273
- error: combinedError || undefined,
274
- };
275
- }
@@ -1,68 +0,0 @@
1
- import { getConfig } from "../config/loader.js";
2
- import { getLogger } from "../util/logger.js";
3
- import { compileCustomPatterns, scanText } from "./secret-scanner.js";
4
-
5
- const log = getLogger("secret-ingress");
6
-
7
- export interface IngressCheckResult {
8
- /** Whether the message should be blocked from entering the model context. */
9
- blocked: boolean;
10
- /** Secret types detected (empty if none). */
11
- detectedTypes: string[];
12
- /**
13
- * User-facing notice explaining why the message was blocked.
14
- * Does NOT echo the secret value — only describes what was found.
15
- */
16
- userNotice?: string;
17
- }
18
-
19
- /**
20
- * Scan inbound user text for secrets before it enters model context.
21
- *
22
- * When `secretDetection.blockIngress` is `true` (default), any message
23
- * containing a detected secret is rejected with a safe notice. This is
24
- * independent of `secretDetection.action`, which only controls how
25
- * secrets in tool *output* are handled.
26
- *
27
- * SECURITY: This function intentionally never logs the message content.
28
- */
29
- export function checkIngressForSecrets(content: string): IngressCheckResult {
30
- const config = getConfig();
31
- if (!config.secretDetection.enabled) {
32
- return { blocked: false, detectedTypes: [] };
33
- }
34
-
35
- if (!config.secretDetection.blockIngress) {
36
- return { blocked: false, detectedTypes: [] };
37
- }
38
-
39
- const entropyConfig = {
40
- enabled: true,
41
- base64Threshold: config.secretDetection.entropyThreshold,
42
- };
43
- const compiledCustom = config.secretDetection.customPatterns?.length
44
- ? compileCustomPatterns(config.secretDetection.customPatterns)
45
- : undefined;
46
- const matches = scanText(content, entropyConfig, compiledCustom);
47
-
48
- if (matches.length === 0) {
49
- return { blocked: false, detectedTypes: [] };
50
- }
51
-
52
- const detectedTypes = [...new Set(matches.map((m) => m.type))];
53
- log.warn(
54
- { detectedTypes, matchCount: matches.length },
55
- "Blocked inbound message containing secrets",
56
- );
57
-
58
- return {
59
- blocked: true,
60
- detectedTypes,
61
- userNotice:
62
- `Your message appears to contain sensitive information (${detectedTypes.join(
63
- ", ",
64
- )}). ` +
65
- `For security, it was not sent to the AI. ` +
66
- `Please use the secure credential prompt instead — the assistant will ask for secrets when it needs them.`,
67
- };
68
- }
@@ -1,225 +0,0 @@
1
- /**
2
- * Claude Code worker backend for swarm execution.
3
- *
4
- * Extracted from the swarm delegate tool so backend construction
5
- * is testable and swappable independently of the tool adapter.
6
- */
7
-
8
- import { resolveModelIntent } from "../providers/model-intents.js";
9
- import type { ModelIntent } from "../providers/types.js";
10
- import { getProviderKeyAsync } from "../security/secure-keys.js";
11
- import { getLogger } from "../util/logger.js";
12
- import type {
13
- SwarmWorkerBackend,
14
- SwarmWorkerBackendInput,
15
- } from "./worker-backend.js";
16
- import { getProfilePolicy } from "./worker-backend.js";
17
-
18
- const log = getLogger("swarm-backend-claude-code");
19
-
20
- const MAX_CLAUDE_CODE_DEPTH = 1;
21
- const DEPTH_ENV_VAR = "VELLUM_CLAUDE_CODE_DEPTH";
22
-
23
- /**
24
- * Create a Claude Code worker backend that enforces profile-based tool policies.
25
- * Uses the Claude Agent SDK to run autonomous worker tasks.
26
- */
27
- export function createClaudeCodeBackend(): SwarmWorkerBackend {
28
- return {
29
- name: "claude_code",
30
-
31
- async isAvailable(): Promise<boolean> {
32
- const apiKey = await getProviderKeyAsync("anthropic");
33
- return !!apiKey;
34
- },
35
-
36
- async runTask(input: SwarmWorkerBackendInput) {
37
- const start = Date.now();
38
- const stderrLines: string[] = [];
39
- try {
40
- const { query } = await import("@anthropic-ai/claude-agent-sdk");
41
- const apiKey = await getProviderKeyAsync("anthropic");
42
- if (!apiKey) {
43
- return {
44
- success: false,
45
- output: "No API key",
46
- failureReason: "backend_unavailable" as const,
47
- durationMs: 0,
48
- };
49
- }
50
-
51
- const profilePolicy = getProfilePolicy(input.profile);
52
-
53
- // Enforce profile restrictions — swarm workers run autonomously so
54
- // there is no user to prompt; denied tools are blocked, everything
55
- // else is allowed.
56
- const canUseTool: import("@anthropic-ai/claude-agent-sdk").CanUseTool =
57
- async (toolName) => {
58
- if (profilePolicy.deny.has(toolName)) {
59
- log.debug(
60
- { toolName, profile: input.profile },
61
- "Swarm worker tool denied by profile",
62
- );
63
- return {
64
- behavior: "deny" as const,
65
- message: `Tool "${toolName}" is denied by profile "${input.profile}"`,
66
- };
67
- }
68
- return { behavior: "allow" as const };
69
- };
70
-
71
- // Enforce nesting depth limit
72
- const currentDepth = parseInt(process.env[DEPTH_ENV_VAR] ?? "0", 10);
73
- if (currentDepth >= MAX_CLAUDE_CODE_DEPTH) {
74
- log.warn(
75
- { currentDepth, max: MAX_CLAUDE_CODE_DEPTH },
76
- "Swarm worker nesting depth exceeded",
77
- );
78
- return {
79
- success: false,
80
- output: `Nesting depth exceeded (depth ${currentDepth}, max ${MAX_CLAUDE_CODE_DEPTH})`,
81
- failureReason: "backend_unavailable" as const,
82
- durationMs: Date.now() - start,
83
- };
84
- }
85
-
86
- // Strip the SDK's nesting guard but set our own depth counter.
87
- const subprocessEnv: Record<string, string | undefined> = {
88
- ...process.env,
89
- ANTHROPIC_API_KEY: apiKey,
90
- [DEPTH_ENV_VAR]: String(currentDepth + 1),
91
- };
92
- delete subprocessEnv.CLAUDECODE;
93
- delete subprocessEnv.CLAUDE_CODE_ENTRYPOINT;
94
-
95
- const conversation = query({
96
- prompt: input.prompt,
97
- options: {
98
- cwd: input.workingDir,
99
- model: input.modelIntent
100
- ? resolveModelIntent(
101
- "anthropic",
102
- input.modelIntent as ModelIntent,
103
- )
104
- : "claude-sonnet-4-6",
105
- canUseTool,
106
- permissionMode: "default",
107
- maxTurns: 30,
108
- env: subprocessEnv,
109
- stderr: (data: string) => {
110
- const trimmed = data.trimEnd();
111
- if (trimmed) {
112
- stderrLines.push(trimmed);
113
- log.debug(
114
- { stderr: trimmed },
115
- "Swarm worker subprocess stderr",
116
- );
117
- }
118
- },
119
- },
120
- });
121
-
122
- let resultText = "";
123
- let hasError = false;
124
- for await (const message of conversation) {
125
- if (input.signal?.aborted) break;
126
- if (message.type === "assistant") {
127
- if (message.error) {
128
- log.error(
129
- { error: message.error, conversationId: message.session_id },
130
- "Swarm worker assistant message error",
131
- );
132
- hasError = true;
133
- resultText += `\n[Claude Code error: ${message.error}]`;
134
- }
135
- if (message.message?.content) {
136
- for (const block of message.message.content) {
137
- if (block.type === "text") resultText += block.text;
138
- }
139
- }
140
- } else if (message.type === "result") {
141
- if (message.subtype === "success") {
142
- log.info(
143
- {
144
- numTurns: message.num_turns,
145
- durationMs: message.duration_ms,
146
- costUsd: message.total_cost_usd,
147
- },
148
- "Swarm worker completed",
149
- );
150
- if (message.result && !resultText) {
151
- resultText = message.result;
152
- }
153
- } else {
154
- hasError = true;
155
- const errors = message.errors ?? [];
156
- const denials = message.permission_denials ?? [];
157
- log.error(
158
- {
159
- subtype: message.subtype,
160
- errors,
161
- permissionDenials: denials.length,
162
- numTurns: message.num_turns,
163
- durationMs: message.duration_ms,
164
- },
165
- "Swarm worker session failed",
166
- );
167
-
168
- const parts: string[] = [
169
- `[${message.subtype}] (${message.num_turns} turns, ${(
170
- message.duration_ms / 1000
171
- ).toFixed(1)}s)`,
172
- ];
173
- if (errors.length > 0) parts.push(`Errors: ${errors.join("; ")}`);
174
- if (denials.length > 0)
175
- parts.push(
176
- `Permission denied: ${denials
177
- .map((d: { tool_name: string }) => d.tool_name)
178
- .join(", ")}`,
179
- );
180
- resultText += `\n${parts.join("\n")}`;
181
- }
182
- }
183
- }
184
-
185
- // Treat abort as non-retryable cancellation, not a retryable timeout
186
- if (input.signal?.aborted) {
187
- return {
188
- success: false,
189
- output: "Cancelled (aborted)",
190
- failureReason: "cancelled" as const,
191
- durationMs: Date.now() - start,
192
- };
193
- }
194
-
195
- return {
196
- success: !hasError,
197
- output: resultText || "Completed",
198
- durationMs: Date.now() - start,
199
- };
200
- } catch (err) {
201
- const errMessage = err instanceof Error ? err.message : String(err);
202
- const recentStderr = stderrLines.slice(-20);
203
- log.error(
204
- { err, stderrTail: recentStderr },
205
- "Swarm worker execution failed",
206
- );
207
-
208
- const parts = [errMessage];
209
- if (recentStderr.length > 0) {
210
- parts.push(
211
- `\nSubprocess stderr (last ${
212
- recentStderr.length
213
- } lines):\n${recentStderr.join("\n")}`,
214
- );
215
- }
216
- return {
217
- success: false,
218
- output: parts.join(""),
219
- failureReason: "backend_unavailable" as const,
220
- durationMs: Date.now() - start,
221
- };
222
- }
223
- },
224
- };
225
- }
@@ -1,137 +0,0 @@
1
- import {
2
- existsSync,
3
- mkdirSync,
4
- readFileSync,
5
- renameSync,
6
- unlinkSync,
7
- writeFileSync,
8
- } from "node:fs";
9
- import { dirname, join } from "node:path";
10
-
11
- import { getLogger } from "../util/logger.js";
12
- import { getRootDir } from "../util/platform.js";
13
- import type { SwarmPlan, SwarmTaskResult } from "./types.js";
14
-
15
- const log = getLogger("swarm-checkpoint");
16
-
17
- /** Only allow safe token characters in runId (alphanumeric, hyphens, underscores, dots). */
18
- const SAFE_RUN_ID = /^[a-zA-Z0-9._-]+$/;
19
-
20
- function assertSafeRunId(runId: string): void {
21
- if (!SAFE_RUN_ID.test(runId)) {
22
- throw new Error(
23
- `Invalid runId: must match ${SAFE_RUN_ID} (got "${runId}")`,
24
- );
25
- }
26
- }
27
-
28
- export interface SwarmCheckpoint {
29
- runId: string;
30
- objective: string;
31
- /** Serialized plan for integrity verification on resume. */
32
- planTaskIds: string[];
33
- /** Stringified task dependency map for structural integrity on resume. */
34
- planHash: string;
35
- results: SwarmTaskResult[];
36
- /** Set of task IDs whose dependents were blocked due to failure. */
37
- blockedTaskIds: string[];
38
- updatedAt: string;
39
- }
40
-
41
- function getCheckpointDir(): string {
42
- return join(getRootDir(), "swarm-checkpoints");
43
- }
44
-
45
- function getCheckpointPath(runId: string): string {
46
- assertSafeRunId(runId);
47
- return join(getCheckpointDir(), `${runId}.json`);
48
- }
49
-
50
- /**
51
- * Deterministic fingerprint of a plan's structure: objective, task IDs,
52
- * roles, and dependency edges. Two plans with the same hash are structurally
53
- * identical and safe to resume from.
54
- */
55
- function computePlanHash(plan: SwarmPlan): string {
56
- const parts = plan.tasks.map(
57
- (t) => `${t.id}:${t.role}:${[...t.dependencies].sort().join(",")}`,
58
- );
59
- return `${plan.objective}|${parts.sort().join("|")}`;
60
- }
61
-
62
- /** Persist the current swarm progress to disk. */
63
- export function writeCheckpoint(
64
- runId: string,
65
- plan: SwarmPlan,
66
- results: Map<string, SwarmTaskResult>,
67
- blockedTaskIds: Set<string>,
68
- ): void {
69
- try {
70
- const path = getCheckpointPath(runId);
71
- const checkpoint: SwarmCheckpoint = {
72
- runId,
73
- objective: plan.objective,
74
- planTaskIds: plan.tasks.map((t) => t.id),
75
- planHash: computePlanHash(plan),
76
- results: Array.from(results.values()),
77
- blockedTaskIds: Array.from(blockedTaskIds),
78
- updatedAt: new Date().toISOString(),
79
- };
80
-
81
- mkdirSync(dirname(path), { recursive: true });
82
- // Atomic-ish write: write to temp then rename to avoid partial reads
83
- const tmpPath = path + ".tmp";
84
- writeFileSync(tmpPath, JSON.stringify(checkpoint, null, 2) + "\n");
85
- renameSync(tmpPath, path);
86
- } catch (err) {
87
- // Checkpoint failures should not crash the orchestrator
88
- log.warn(
89
- { runId, error: (err as Error).message },
90
- "Failed to write checkpoint",
91
- );
92
- }
93
- }
94
-
95
- /** Load a checkpoint from disk, or null if none exists. */
96
- export function loadCheckpoint(runId: string): SwarmCheckpoint | null {
97
- const path = getCheckpointPath(runId);
98
- if (!existsSync(path)) return null;
99
-
100
- try {
101
- const data = readFileSync(path, "utf-8");
102
- return JSON.parse(data) as SwarmCheckpoint;
103
- } catch (err) {
104
- log.warn(
105
- { runId, error: (err as Error).message },
106
- "Failed to read checkpoint, starting fresh",
107
- );
108
- return null;
109
- }
110
- }
111
-
112
- /** Remove a checkpoint file after successful completion. */
113
- export function removeCheckpoint(runId: string): void {
114
- const path = getCheckpointPath(runId);
115
- try {
116
- if (existsSync(path)) unlinkSync(path);
117
- } catch {
118
- // Best-effort cleanup
119
- }
120
- }
121
-
122
- /**
123
- * Validate that a checkpoint matches the current plan.
124
- * Compares objective, task IDs, roles, and dependency structure via planHash.
125
- * Falls back to subset check for checkpoints written before planHash existed.
126
- */
127
- export function isCheckpointCompatible(
128
- checkpoint: SwarmCheckpoint,
129
- plan: SwarmPlan,
130
- ): boolean {
131
- if (checkpoint.planHash) {
132
- return checkpoint.planHash === computePlanHash(plan);
133
- }
134
- // Legacy checkpoint without planHash — fall back to subset check
135
- const planTaskIds = new Set(plan.tasks.map((t) => t.id));
136
- return checkpoint.planTaskIds.every((id) => planTaskIds.has(id));
137
- }