@vellumai/assistant 0.5.6 → 0.5.8

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 (442) hide show
  1. package/.env.example +16 -2
  2. package/ARCHITECTURE.md +6 -75
  3. package/Dockerfile +3 -2
  4. package/README.md +0 -2
  5. package/bun.lock +0 -414
  6. package/docker-entrypoint.sh +9 -0
  7. package/docs/architecture/keychain-broker.md +45 -240
  8. package/docs/architecture/memory.md +13 -11
  9. package/docs/architecture/security.md +0 -17
  10. package/docs/credential-execution-service.md +2 -2
  11. package/node_modules/@vellumai/ces-contracts/package.json +1 -0
  12. package/node_modules/@vellumai/ces-contracts/src/error.ts +1 -1
  13. package/node_modules/@vellumai/ces-contracts/src/grants.ts +1 -1
  14. package/node_modules/@vellumai/ces-contracts/src/handles.ts +1 -1
  15. package/node_modules/@vellumai/ces-contracts/src/index.ts +1 -1
  16. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +120 -1
  17. package/node_modules/@vellumai/credential-storage/package.json +1 -0
  18. package/node_modules/@vellumai/egress-proxy/package.json +1 -0
  19. package/package.json +2 -3
  20. package/src/__tests__/actor-token-service.test.ts +0 -114
  21. package/src/__tests__/approval-cascade.test.ts +0 -1
  22. package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
  23. package/src/__tests__/browser-fill-credential.test.ts +1 -1
  24. package/src/__tests__/browser-skill-endstate.test.ts +6 -5
  25. package/src/__tests__/btw-routes.test.ts +0 -39
  26. package/src/__tests__/call-controller.test.ts +0 -1
  27. package/src/__tests__/call-domain.test.ts +0 -128
  28. package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -0
  29. package/src/__tests__/ces-startup-timeout.test.ts +40 -0
  30. package/src/__tests__/channel-approval-routes.test.ts +0 -5
  31. package/src/__tests__/channel-readiness-service.test.ts +1 -60
  32. package/src/__tests__/checker.test.ts +4 -2
  33. package/src/__tests__/cli-command-risk-guard.test.ts +112 -0
  34. package/src/__tests__/config-schema-cmd.test.ts +0 -2
  35. package/src/__tests__/config-schema.test.ts +3 -1
  36. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -1
  37. package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -2
  38. package/src/__tests__/conversation-agent-loop.test.ts +2 -4
  39. package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
  40. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -1
  41. package/src/__tests__/conversation-error.test.ts +15 -1
  42. package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
  43. package/src/__tests__/conversation-messaging-secret-redirect.test.ts +1 -1
  44. package/src/__tests__/conversation-pre-run-repair.test.ts +0 -1
  45. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -1
  46. package/src/__tests__/conversation-queue.test.ts +0 -1
  47. package/src/__tests__/conversation-skill-tools.test.ts +0 -54
  48. package/src/__tests__/conversation-slash-queue.test.ts +0 -1
  49. package/src/__tests__/conversation-slash-unknown.test.ts +0 -1
  50. package/src/__tests__/conversation-title-service.test.ts +87 -0
  51. package/src/__tests__/conversation-workspace-injection.test.ts +0 -1
  52. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -1
  53. package/src/__tests__/credential-execution-client.test.ts +5 -2
  54. package/src/__tests__/credential-execution-feature-gates.test.ts +59 -30
  55. package/src/__tests__/credential-execution-managed-contract.test.ts +35 -20
  56. package/src/__tests__/credential-security-e2e.test.ts +1 -67
  57. package/src/__tests__/credential-security-invariants.test.ts +6 -50
  58. package/src/__tests__/credentials-cli.test.ts +82 -3
  59. package/src/__tests__/daemon-credential-client.test.ts +123 -0
  60. package/src/__tests__/db-migration-rollback.test.ts +2015 -1
  61. package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -0
  62. package/src/__tests__/docker-signing-key-bootstrap.test.ts +34 -143
  63. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
  64. package/src/__tests__/gateway-client-managed-outbound.test.ts +79 -1
  65. package/src/__tests__/guardian-routing-state.test.ts +0 -5
  66. package/src/__tests__/host-shell-tool.test.ts +6 -7
  67. package/src/__tests__/http-user-message-parity.test.ts +3 -103
  68. package/src/__tests__/inbound-invite-redemption.test.ts +0 -4
  69. package/src/__tests__/inline-skill-load-permissions.test.ts +6 -8
  70. package/src/__tests__/intent-routing.test.ts +0 -13
  71. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +178 -0
  72. package/src/__tests__/journal-context.test.ts +335 -0
  73. package/src/__tests__/keychain-broker-client.test.ts +161 -22
  74. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -3
  75. package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
  76. package/src/__tests__/memory-lifecycle-e2e.test.ts +70 -25
  77. package/src/__tests__/memory-recall-quality.test.ts +48 -17
  78. package/src/__tests__/memory-regressions.test.ts +408 -363
  79. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -3
  80. package/src/__tests__/migration-export-http.test.ts +2 -2
  81. package/src/__tests__/migration-import-commit-http.test.ts +2 -2
  82. package/src/__tests__/migration-import-preflight-http.test.ts +2 -2
  83. package/src/__tests__/migration-validate-http.test.ts +2 -2
  84. package/src/__tests__/non-member-access-request.test.ts +2 -7
  85. package/src/__tests__/notification-decision-fallback.test.ts +4 -0
  86. package/src/__tests__/notification-decision-identity.test.ts +4 -0
  87. package/src/__tests__/notification-decision-strategy.test.ts +71 -0
  88. package/src/__tests__/oauth-cli.test.ts +5 -1
  89. package/src/__tests__/permission-types.test.ts +1 -0
  90. package/src/__tests__/provider-commit-message-generator.test.ts +0 -37
  91. package/src/__tests__/provider-error-scenarios.test.ts +0 -267
  92. package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
  93. package/src/__tests__/provider-streaming.benchmark.test.ts +2 -81
  94. package/src/__tests__/qdrant-manager.test.ts +28 -2
  95. package/src/__tests__/registry.test.ts +0 -6
  96. package/src/__tests__/relay-server.test.ts +1 -2
  97. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
  98. package/src/__tests__/script-proxy-injection-runtime.test.ts +1 -1
  99. package/src/__tests__/secret-onetime-send.test.ts +1 -1
  100. package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
  101. package/src/__tests__/secure-keys.test.ts +95 -272
  102. package/src/__tests__/shell-identity.test.ts +96 -6
  103. package/src/__tests__/skill-feature-flags-integration.test.ts +22 -14
  104. package/src/__tests__/skill-feature-flags.test.ts +46 -45
  105. package/src/__tests__/skill-load-feature-flag.test.ts +7 -10
  106. package/src/__tests__/skill-load-inline-command.test.ts +8 -12
  107. package/src/__tests__/skill-load-inline-includes.test.ts +6 -10
  108. package/src/__tests__/skill-load-tool.test.ts +0 -2
  109. package/src/__tests__/skill-memory.test.ts +17 -3
  110. package/src/__tests__/skill-projection-feature-flag.test.ts +33 -29
  111. package/src/__tests__/skills.test.ts +0 -2
  112. package/src/__tests__/slack-inbound-verification.test.ts +0 -4
  113. package/src/__tests__/stale-approval-dedup.test.ts +171 -0
  114. package/src/__tests__/stt-hints.test.ts +437 -0
  115. package/src/__tests__/suggestion-routes.test.ts +1 -32
  116. package/src/__tests__/system-prompt.test.ts +0 -1
  117. package/src/__tests__/task-memory-cleanup.test.ts +14 -0
  118. package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
  119. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
  120. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
  121. package/src/__tests__/twilio-routes-twiml.test.ts +139 -1
  122. package/src/__tests__/update-bulletin.test.ts +0 -2
  123. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +6 -9
  124. package/src/__tests__/voice-quality.test.ts +58 -0
  125. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -7
  126. package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +252 -0
  127. package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +220 -0
  128. package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
  129. package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
  130. package/src/acp/agent-process.ts +9 -1
  131. package/src/agent/loop.ts +1 -1
  132. package/src/approvals/guardian-request-resolvers.ts +164 -38
  133. package/src/calls/__tests__/tts-text-sanitizer.test.ts +254 -0
  134. package/src/calls/audio-store.test.ts +97 -0
  135. package/src/calls/audio-store.ts +205 -0
  136. package/src/calls/call-controller.ts +90 -8
  137. package/src/calls/call-domain.ts +3 -0
  138. package/src/calls/call-store.ts +10 -3
  139. package/src/calls/fish-audio-client.ts +129 -0
  140. package/src/calls/relay-server.ts +27 -0
  141. package/src/calls/stt-hints.ts +189 -0
  142. package/src/calls/tts-text-sanitizer.ts +61 -0
  143. package/src/calls/twilio-routes.ts +34 -5
  144. package/src/calls/types.ts +1 -0
  145. package/src/calls/voice-ingress-preflight.ts +0 -42
  146. package/src/calls/voice-quality.ts +38 -5
  147. package/src/calls/voice-session-bridge.ts +7 -12
  148. package/src/cli/commands/avatar.ts +2 -2
  149. package/src/cli/commands/config.ts +1 -4
  150. package/src/cli/commands/credentials.ts +128 -82
  151. package/src/cli/commands/doctor.ts +2 -2
  152. package/src/cli/commands/keys.ts +7 -7
  153. package/src/cli/commands/memory.ts +1 -1
  154. package/src/cli/commands/oauth/connections.ts +11 -29
  155. package/src/cli/commands/oauth/index.ts +7 -0
  156. package/src/cli/commands/oauth/platform.ts +525 -0
  157. package/src/cli/commands/platform.ts +3 -3
  158. package/src/cli/lib/daemon-credential-client.ts +284 -0
  159. package/src/cli.ts +1 -1
  160. package/src/config/assistant-feature-flags.ts +186 -5
  161. package/src/config/bundled-skills/AGENTS.md +34 -0
  162. package/src/config/bundled-skills/acp/SKILL.md +10 -0
  163. package/src/config/bundled-skills/app-builder/SKILL.md +0 -4
  164. package/src/config/bundled-skills/messaging/SKILL.md +5 -5
  165. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
  166. package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
  167. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +1 -0
  168. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +1 -0
  169. package/src/config/bundled-skills/settings/SKILL.md +15 -2
  170. package/src/config/bundled-skills/settings/TOOLS.json +47 -2
  171. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +59 -0
  172. package/src/config/bundled-skills/settings/tools/avatar-update.ts +80 -0
  173. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
  174. package/src/config/bundled-skills/slack/SKILL.md +1 -1
  175. package/src/config/bundled-tool-registry.ts +5 -11
  176. package/src/config/defaults.ts +0 -2
  177. package/src/config/env-registry.ts +5 -5
  178. package/src/config/env.ts +21 -14
  179. package/src/config/feature-flag-registry.json +49 -9
  180. package/src/config/loader.ts +106 -42
  181. package/src/config/schema.ts +9 -29
  182. package/src/config/schemas/calls.ts +30 -0
  183. package/src/config/schemas/fish-audio.ts +39 -0
  184. package/src/config/schemas/inference.ts +2 -2
  185. package/src/config/schemas/journal.ts +16 -0
  186. package/src/config/schemas/memory-processing.ts +2 -2
  187. package/src/config/schemas/security.ts +0 -4
  188. package/src/config/types.ts +1 -1
  189. package/src/contacts/contact-store.ts +39 -0
  190. package/src/contacts/types.ts +2 -0
  191. package/src/credential-execution/approval-bridge.ts +1 -0
  192. package/src/credential-execution/executable-discovery.ts +28 -4
  193. package/src/credential-execution/feature-gates.ts +16 -0
  194. package/src/credential-execution/process-manager.ts +38 -0
  195. package/src/credential-execution/startup-timeout.ts +36 -0
  196. package/src/daemon/approval-generators.ts +3 -9
  197. package/src/daemon/assistant-attachments.ts +9 -0
  198. package/src/daemon/config-watcher.ts +5 -0
  199. package/src/daemon/conversation-error.ts +13 -1
  200. package/src/daemon/conversation-memory.ts +1 -2
  201. package/src/daemon/conversation-process.ts +18 -1
  202. package/src/daemon/conversation-surfaces.ts +30 -1
  203. package/src/daemon/conversation-tool-setup.ts +0 -105
  204. package/src/daemon/conversation.ts +21 -1
  205. package/src/daemon/guardian-action-generators.ts +3 -9
  206. package/src/daemon/handlers/config-vercel.ts +92 -0
  207. package/src/daemon/handlers/skills.ts +2 -15
  208. package/src/daemon/install-symlink.ts +195 -0
  209. package/src/daemon/lifecycle.ts +234 -51
  210. package/src/daemon/message-types/conversations.ts +4 -4
  211. package/src/daemon/message-types/diagnostics.ts +3 -22
  212. package/src/daemon/message-types/messages.ts +0 -2
  213. package/src/daemon/message-types/upgrades.ts +8 -0
  214. package/src/daemon/server.ts +32 -95
  215. package/src/events/domain-events.ts +2 -1
  216. package/src/inbound/platform-callback-registration.ts +3 -3
  217. package/src/instrument.ts +8 -5
  218. package/src/memory/app-store.ts +31 -0
  219. package/src/memory/conversation-title-service.ts +50 -1
  220. package/src/memory/db-init.ts +16 -0
  221. package/src/memory/indexer.ts +19 -10
  222. package/src/memory/items-extractor.ts +328 -321
  223. package/src/memory/job-handlers/conversation-starters.ts +4 -1
  224. package/src/memory/job-handlers/summarization.ts +26 -16
  225. package/src/memory/jobs-store.ts +63 -6
  226. package/src/memory/jobs-worker.ts +31 -7
  227. package/src/memory/journal-memory.ts +214 -0
  228. package/src/memory/migrations/001-job-deferrals.ts +19 -0
  229. package/src/memory/migrations/004-entity-relation-dedup.ts +10 -0
  230. package/src/memory/migrations/005-fingerprint-scope-unique.ts +76 -0
  231. package/src/memory/migrations/006-scope-salted-fingerprints.ts +50 -0
  232. package/src/memory/migrations/007-assistant-id-to-self.ts +10 -0
  233. package/src/memory/migrations/008-remove-assistant-id-columns.ts +34 -0
  234. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +26 -0
  235. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +10 -0
  236. package/src/memory/migrations/015-drop-active-search-index.ts +17 -0
  237. package/src/memory/migrations/019-notification-tables-schema-migration.ts +12 -0
  238. package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +121 -0
  239. package/src/memory/migrations/024-embedding-vector-blob.ts +74 -0
  240. package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +82 -0
  241. package/src/memory/migrations/036-normalize-phone-identities.ts +11 -0
  242. package/src/memory/migrations/116-messages-fts.ts +106 -1
  243. package/src/memory/migrations/126-backfill-guardian-principal-id.ts +52 -0
  244. package/src/memory/migrations/127-guardian-principal-id-not-null.ts +77 -0
  245. package/src/memory/migrations/134-contacts-notes-column.ts +13 -0
  246. package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +20 -0
  247. package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -0
  248. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +13 -0
  249. package/src/memory/migrations/141-rename-verification-table.ts +54 -0
  250. package/src/memory/migrations/142-rename-verification-session-id-column.ts +25 -0
  251. package/src/memory/migrations/143-rename-guardian-verification-values.ts +35 -0
  252. package/src/memory/migrations/144-rename-voice-to-phone.ts +136 -0
  253. package/src/memory/migrations/145-drop-accounts-table.ts +32 -0
  254. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +14 -1
  255. package/src/memory/migrations/148-drop-reminders-table.ts +35 -1
  256. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +69 -1
  257. package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +290 -0
  258. package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +51 -1
  259. package/src/memory/migrations/174-rename-thread-starters-table.ts +47 -1
  260. package/src/memory/migrations/176-drop-capability-card-state.ts +13 -0
  261. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +16 -0
  262. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +28 -1
  263. package/src/memory/migrations/190-call-session-skip-disclosure.ts +15 -0
  264. package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +64 -0
  265. package/src/memory/migrations/192-contacts-user-file-column.ts +15 -0
  266. package/src/memory/migrations/193-add-source-type-columns.ts +81 -0
  267. package/src/memory/migrations/index.ts +5 -0
  268. package/src/memory/migrations/registry.ts +98 -0
  269. package/src/memory/migrations/validate-migration-state.ts +137 -11
  270. package/src/memory/qdrant-circuit-breaker.ts +9 -0
  271. package/src/memory/qdrant-manager.ts +64 -7
  272. package/src/memory/retriever.test.ts +37 -25
  273. package/src/memory/retriever.ts +24 -49
  274. package/src/memory/schema/calls.ts +1 -0
  275. package/src/memory/schema/contacts.ts +1 -0
  276. package/src/memory/schema/memory-core.ts +2 -0
  277. package/src/memory/search/formatting.ts +7 -44
  278. package/src/memory/search/staleness.ts +4 -0
  279. package/src/memory/search/tier-classifier.ts +10 -2
  280. package/src/memory/search/types.ts +2 -5
  281. package/src/memory/task-memory-cleanup.ts +4 -3
  282. package/src/notifications/adapters/slack.ts +168 -6
  283. package/src/notifications/broadcaster.ts +1 -0
  284. package/src/notifications/copy-composer.ts +59 -2
  285. package/src/notifications/decision-engine.ts +4 -1
  286. package/src/notifications/signal.ts +2 -0
  287. package/src/notifications/types.ts +2 -0
  288. package/src/oauth/connection-resolver.ts +6 -4
  289. package/src/permissions/checker.ts +0 -38
  290. package/src/permissions/shell-identity.ts +76 -22
  291. package/src/permissions/types.ts +4 -2
  292. package/src/platform/client.ts +35 -7
  293. package/src/prompts/journal-context.ts +133 -0
  294. package/src/prompts/persona-resolver.ts +194 -0
  295. package/src/prompts/system-prompt.ts +44 -4
  296. package/src/prompts/templates/SOUL.md +10 -0
  297. package/src/prompts/templates/users/default.md +1 -0
  298. package/src/providers/provider-send-message.ts +3 -32
  299. package/src/providers/registry.ts +29 -179
  300. package/src/providers/types.ts +1 -1
  301. package/src/runtime/access-request-helper.ts +4 -0
  302. package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
  303. package/src/runtime/auth/__tests__/external-assistant-id.test.ts +13 -68
  304. package/src/runtime/auth/__tests__/guard-tests.test.ts +9 -50
  305. package/src/runtime/auth/external-assistant-id.ts +13 -59
  306. package/src/runtime/auth/route-policy.ts +17 -1
  307. package/src/runtime/auth/token-service.ts +43 -138
  308. package/src/runtime/channel-readiness-service.ts +1 -16
  309. package/src/runtime/gateway-client.ts +47 -4
  310. package/src/runtime/guardian-decision-types.ts +45 -4
  311. package/src/runtime/http-server.ts +31 -3
  312. package/src/runtime/middleware/error-handler.ts +1 -9
  313. package/src/runtime/routes/access-request-decision.ts +2 -2
  314. package/src/runtime/routes/app-management-routes.ts +2 -1
  315. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +219 -30
  316. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +37 -14
  317. package/src/runtime/routes/audio-routes.ts +40 -0
  318. package/src/runtime/routes/btw-routes.ts +0 -17
  319. package/src/runtime/routes/channel-readiness-routes.ts +9 -4
  320. package/src/runtime/routes/conversation-query-routes.ts +63 -1
  321. package/src/runtime/routes/conversation-routes.ts +4 -44
  322. package/src/runtime/routes/debug-routes.ts +12 -9
  323. package/src/runtime/routes/diagnostics-routes.ts +1 -477
  324. package/src/runtime/routes/guardian-approval-interception.ts +168 -11
  325. package/src/runtime/routes/guardian-approval-prompt.ts +6 -1
  326. package/src/runtime/routes/guardian-approval-reply-helpers.ts +103 -21
  327. package/src/runtime/routes/identity-routes.ts +19 -30
  328. package/src/runtime/routes/inbound-message-handler.ts +31 -1
  329. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +64 -5
  330. package/src/runtime/routes/inbound-stages/background-dispatch.ts +52 -40
  331. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -33
  332. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
  333. package/src/runtime/routes/integrations/twilio.ts +52 -10
  334. package/src/runtime/routes/integrations/vercel.ts +89 -0
  335. package/src/runtime/routes/log-export-routes.ts +5 -0
  336. package/src/runtime/routes/memory-item-routes.test.ts +3 -3
  337. package/src/runtime/routes/memory-item-routes.ts +46 -14
  338. package/src/runtime/routes/migration-rollback-routes.ts +209 -0
  339. package/src/runtime/routes/migration-routes.ts +17 -1
  340. package/src/runtime/routes/notification-routes.ts +58 -0
  341. package/src/runtime/routes/schedule-routes.ts +65 -0
  342. package/src/runtime/routes/secret-routes.ts +141 -10
  343. package/src/runtime/routes/settings-routes.ts +41 -1
  344. package/src/runtime/routes/tts-routes.ts +96 -0
  345. package/src/runtime/routes/upgrade-broadcast-routes.ts +26 -2
  346. package/src/runtime/routes/workspace-commit-routes.ts +62 -0
  347. package/src/runtime/routes/workspace-routes.test.ts +22 -1
  348. package/src/runtime/routes/workspace-routes.ts +1 -1
  349. package/src/runtime/routes/workspace-utils.ts +86 -2
  350. package/src/security/ces-credential-client.ts +75 -29
  351. package/src/security/ces-rpc-credential-backend.ts +86 -0
  352. package/src/security/credential-backend.ts +22 -92
  353. package/src/security/keychain-broker-client.ts +10 -2
  354. package/src/security/secure-keys.ts +113 -115
  355. package/src/skills/catalog-install.ts +6 -32
  356. package/src/skills/skill-memory.ts +1 -0
  357. package/src/subagent/manager.ts +2 -5
  358. package/src/telemetry/usage-telemetry-reporter.ts +4 -2
  359. package/src/tools/acp/spawn.ts +78 -1
  360. package/src/tools/calls/call-start.ts +1 -0
  361. package/src/tools/credentials/vault.ts +5 -3
  362. package/src/tools/executor.ts +0 -4
  363. package/src/tools/memory/definitions.ts +3 -2
  364. package/src/tools/memory/handlers.ts +10 -7
  365. package/src/tools/network/script-proxy/session-manager.ts +19 -4
  366. package/src/tools/network/web-fetch.ts +3 -1
  367. package/src/tools/skills/execute.ts +1 -1
  368. package/src/tools/terminal/safe-env.ts +1 -0
  369. package/src/tools/types.ts +0 -8
  370. package/src/util/browser.ts +15 -0
  371. package/src/util/errors.ts +0 -12
  372. package/src/util/platform.ts +4 -51
  373. package/src/workspace/git-service.ts +5 -2
  374. package/src/workspace/migrations/001-avatar-rename.ts +15 -0
  375. package/src/workspace/migrations/003-seed-device-id.ts +17 -1
  376. package/src/workspace/migrations/004-extract-collect-usage-data.ts +33 -0
  377. package/src/workspace/migrations/005-add-send-diagnostics.ts +3 -0
  378. package/src/workspace/migrations/006-services-config.ts +49 -0
  379. package/src/workspace/migrations/007-web-search-provider-rename.ts +27 -0
  380. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +3 -0
  381. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +4 -0
  382. package/src/workspace/migrations/010-app-dir-rename.ts +78 -0
  383. package/src/workspace/migrations/011-backfill-installation-id.ts +11 -0
  384. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +44 -0
  385. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +5 -0
  386. package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +153 -0
  387. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +156 -0
  388. package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +150 -0
  389. package/src/workspace/migrations/017-seed-persona-dirs.ts +96 -0
  390. package/src/workspace/migrations/018-rekey-compound-credential-keys.ts +184 -0
  391. package/src/workspace/migrations/019-scope-journal-to-guardian.ts +103 -0
  392. package/src/workspace/migrations/migrate-to-workspace-volume.ts +27 -5
  393. package/src/workspace/migrations/registry.ts +12 -0
  394. package/src/workspace/migrations/runner.ts +106 -2
  395. package/src/workspace/migrations/types.ts +4 -0
  396. package/src/workspace/provider-commit-message-generator.ts +12 -21
  397. package/src/__tests__/claude-code-skill-regression.test.ts +0 -206
  398. package/src/__tests__/claude-code-tool-profiles.test.ts +0 -99
  399. package/src/__tests__/diagnostics-export.test.ts +0 -288
  400. package/src/__tests__/local-gateway-health.test.ts +0 -209
  401. package/src/__tests__/provider-fail-open-selection.test.ts +0 -271
  402. package/src/__tests__/provider-failover-actual-provider.test.ts +0 -66
  403. package/src/__tests__/secret-ingress-handler.test.ts +0 -120
  404. package/src/__tests__/swarm-conversation-integration.test.ts +0 -358
  405. package/src/__tests__/swarm-dag-pathological.test.ts +0 -547
  406. package/src/__tests__/swarm-orchestrator.test.ts +0 -463
  407. package/src/__tests__/swarm-plan-validator.test.ts +0 -384
  408. package/src/__tests__/swarm-recursion.test.ts +0 -197
  409. package/src/__tests__/swarm-router-planner.test.ts +0 -234
  410. package/src/__tests__/swarm-tool.test.ts +0 -185
  411. package/src/__tests__/swarm-worker-backend.test.ts +0 -144
  412. package/src/__tests__/swarm-worker-runner.test.ts +0 -288
  413. package/src/commands/__tests__/cc-command-registry.test.ts +0 -396
  414. package/src/commands/cc-command-registry.ts +0 -248
  415. package/src/config/bundled-skills/claude-code/SKILL.md +0 -53
  416. package/src/config/bundled-skills/claude-code/TOOLS.json +0 -47
  417. package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -12
  418. package/src/config/bundled-skills/orchestration/SKILL.md +0 -33
  419. package/src/config/bundled-skills/orchestration/TOOLS.json +0 -35
  420. package/src/config/bundled-skills/orchestration/tools/swarm-delegate.ts +0 -12
  421. package/src/config/schemas/swarm.ts +0 -82
  422. package/src/logfire.ts +0 -135
  423. package/src/memory/search/lexical.ts +0 -48
  424. package/src/providers/failover.ts +0 -186
  425. package/src/runtime/local-gateway-health.ts +0 -275
  426. package/src/security/secret-ingress.ts +0 -68
  427. package/src/swarm/backend-claude-code.ts +0 -225
  428. package/src/swarm/checkpoint.ts +0 -137
  429. package/src/swarm/graph-utils.ts +0 -53
  430. package/src/swarm/index.ts +0 -55
  431. package/src/swarm/limits.ts +0 -66
  432. package/src/swarm/orchestrator.ts +0 -424
  433. package/src/swarm/plan-validator.ts +0 -117
  434. package/src/swarm/router-planner.ts +0 -162
  435. package/src/swarm/router-prompts.ts +0 -39
  436. package/src/swarm/synthesizer.ts +0 -81
  437. package/src/swarm/types.ts +0 -72
  438. package/src/swarm/worker-backend.ts +0 -131
  439. package/src/swarm/worker-prompts.ts +0 -80
  440. package/src/swarm/worker-runner.ts +0 -170
  441. package/src/tools/claude-code/claude-code.ts +0 -610
  442. package/src/tools/swarm/delegate.ts +0 -205
@@ -1,424 +0,0 @@
1
- import type { ModelIntent, Provider } from "../providers/types.js";
2
- import { getLogger } from "../util/logger.js";
3
- import {
4
- isCheckpointCompatible,
5
- loadCheckpoint,
6
- removeCheckpoint,
7
- writeCheckpoint,
8
- } from "./checkpoint.js";
9
- import { detectCycles } from "./graph-utils.js";
10
- import type { SwarmLimits } from "./limits.js";
11
- import { getTimeoutForRole } from "./limits.js";
12
- import { synthesizeResults } from "./synthesizer.js";
13
- import type {
14
- SwarmExecutionSummary,
15
- SwarmPlan,
16
- SwarmTaskNode,
17
- SwarmTaskResult,
18
- } from "./types.js";
19
- import type { SwarmWorkerBackend } from "./worker-backend.js";
20
- import { runWorkerTask } from "./worker-runner.js";
21
-
22
- const log = getLogger("swarm-orchestrator");
23
-
24
- export type OrchestratorEventKind =
25
- | "plan_created"
26
- | "task_started"
27
- | "task_completed"
28
- | "task_failed"
29
- | "task_blocked"
30
- | "synthesis_started"
31
- | "done";
32
-
33
- export interface OrchestratorEvent {
34
- kind: OrchestratorEventKind;
35
- taskId?: string;
36
- message?: string;
37
- }
38
-
39
- export type OrchestratorStatusCallback = (event: OrchestratorEvent) => void;
40
-
41
- export interface ExecuteSwarmOptions {
42
- plan: SwarmPlan;
43
- limits: SwarmLimits;
44
- backend: SwarmWorkerBackend;
45
- workingDir: string;
46
- modelIntent?: string;
47
- /** Provider + model intent for final synthesis. */
48
- synthesisProvider?: Provider;
49
- synthesisModelIntent?: ModelIntent;
50
- onStatus?: OrchestratorStatusCallback;
51
- signal?: AbortSignal;
52
- /** Stable identifier for this swarm run, used for checkpoint persistence. */
53
- runId?: string;
54
- /** When true, attempt to resume from the last checkpoint for this runId. */
55
- resume?: boolean;
56
- }
57
-
58
- /**
59
- * Execute a validated swarm plan with parallel DAG scheduling,
60
- * bounded concurrency, and per-task retries.
61
- */
62
- export async function executeSwarm(
63
- opts: ExecuteSwarmOptions,
64
- ): Promise<SwarmExecutionSummary> {
65
- const {
66
- plan,
67
- limits,
68
- backend,
69
- workingDir,
70
- modelIntent,
71
- onStatus,
72
- signal,
73
- runId,
74
- resume,
75
- } = opts;
76
- const startTime = Date.now();
77
-
78
- // Safety net: reject cyclic plans even if the caller skipped validation
79
- const cycleNodes = detectCycles(plan.tasks);
80
- if (cycleNodes) {
81
- throw new Error(
82
- `Swarm plan contains a dependency cycle: ${cycleNodes.join(" -> ")}`,
83
- );
84
- }
85
-
86
- onStatus?.({
87
- kind: "plan_created",
88
- message: `Plan with ${plan.tasks.length} tasks`,
89
- });
90
-
91
- const results = new Map<string, SwarmTaskResult>();
92
- const blocked = new Set<string>();
93
-
94
- // Restore from checkpoint if resuming
95
- if (runId && resume) {
96
- const checkpoint = loadCheckpoint(runId);
97
- if (checkpoint && isCheckpointCompatible(checkpoint, plan)) {
98
- for (const result of checkpoint.results) {
99
- results.set(result.taskId, result);
100
- }
101
- for (const taskId of checkpoint.blockedTaskIds) {
102
- blocked.add(taskId);
103
- }
104
- const restored = checkpoint.results.length;
105
- const restoredBlocked = checkpoint.blockedTaskIds.length;
106
- log.info({ runId, restored, restoredBlocked }, "Resumed from checkpoint");
107
- onStatus?.({
108
- kind: "plan_created",
109
- message: `Resumed ${restored} completed tasks from checkpoint`,
110
- });
111
- } else if (checkpoint) {
112
- log.warn(
113
- { runId },
114
- "Checkpoint incompatible with current plan, starting fresh",
115
- );
116
- }
117
- }
118
-
119
- // Build adjacency for dependency tracking
120
- const dependents = new Map<string, string[]>();
121
- for (const task of plan.tasks) {
122
- dependents.set(task.id, []);
123
- }
124
- for (const task of plan.tasks) {
125
- for (const dep of task.dependencies) {
126
- dependents.get(dep)?.push(task.id);
127
- }
128
- }
129
-
130
- // Determine initial ready tasks — skip tasks already completed or blocked
131
- // from a restored checkpoint
132
- const ready: SwarmTaskNode[] = [];
133
- const remaining = new Map<string, SwarmTaskNode>();
134
- const pendingDeps = new Map<string, Set<string>>();
135
-
136
- for (const task of plan.tasks) {
137
- if (results.has(task.id) || blocked.has(task.id)) continue;
138
- remaining.set(task.id, task);
139
- // Remove already-completed dependencies from this task's pending set
140
- const unresolvedDeps = task.dependencies.filter((d) => !results.has(d));
141
- if (unresolvedDeps.length === 0) {
142
- ready.push(task);
143
- } else {
144
- pendingDeps.set(task.id, new Set(unresolvedDeps));
145
- }
146
- }
147
-
148
- // Concurrent DAG executor — schedule tasks as soon as their prerequisites
149
- // finish, bounded by maxWorkers. Unlike wave/batch execution, a newly
150
- // unblocked task can start immediately when a worker slot opens up rather
151
- // than waiting for the entire previous batch to complete.
152
- let activeCount = 0;
153
- let resolve: (() => void) | null = null;
154
-
155
- // Resolves whenever a running task completes (or the ready queue is refilled)
156
- // so the main loop can re-evaluate whether to launch more work.
157
- function signalProgress(): void {
158
- if (resolve) {
159
- const r = resolve;
160
- resolve = null;
161
- r();
162
- }
163
- }
164
-
165
- function waitForProgress(): Promise<void> {
166
- return new Promise<void>((r) => {
167
- resolve = r;
168
- });
169
- }
170
-
171
- function processResult(result: SwarmTaskResult): void {
172
- results.set(result.taskId, result);
173
- remaining.delete(result.taskId);
174
-
175
- if (result.status === "completed") {
176
- onStatus?.({ kind: "task_completed", taskId: result.taskId });
177
- // Immediately unblock dependents so they enter the ready queue
178
- for (const depId of dependents.get(result.taskId) ?? []) {
179
- const pending = pendingDeps.get(depId);
180
- if (pending) {
181
- pending.delete(result.taskId);
182
- if (pending.size === 0) {
183
- pendingDeps.delete(depId);
184
- const task = plan.tasks.find((t) => t.id === depId);
185
- if (task && !blocked.has(depId)) {
186
- ready.push(task);
187
- }
188
- }
189
- }
190
- }
191
- } else {
192
- onStatus?.({ kind: "task_failed", taskId: result.taskId });
193
- blockDependents(result.taskId, dependents, blocked, onStatus);
194
- }
195
-
196
- if (runId) {
197
- writeCheckpoint(runId, plan, results, blocked);
198
- }
199
- }
200
-
201
- async function runTask(task: SwarmTaskNode): Promise<void> {
202
- onStatus?.({ kind: "task_started", taskId: task.id });
203
-
204
- const depOutputs = task.dependencies
205
- .map((depId) => {
206
- const r = results.get(depId);
207
- return r ? { taskId: depId, summary: r.summary } : null;
208
- })
209
- .filter((d): d is { taskId: string; summary: string } => d != null);
210
-
211
- let result: SwarmTaskResult;
212
- try {
213
- const taskTimeoutMs = getTimeoutForRole(limits, task.role) * 1000;
214
-
215
- result = await runWorkerTask({
216
- task,
217
- upstreamContext: plan.objective,
218
- dependencyOutputs: depOutputs,
219
- backend,
220
- workingDir,
221
- modelIntent,
222
- timeoutMs: taskTimeoutMs,
223
- signal,
224
- });
225
-
226
- let retries = 0;
227
- while (
228
- result.status === "failed" &&
229
- retries < limits.maxRetriesPerTask &&
230
- !signal?.aborted
231
- ) {
232
- retries++;
233
- // Exponential backoff with ±25% jitter to prevent thundering herd
234
- const baseDelayMs = 1000 * Math.pow(2, retries - 1);
235
- const jitter = baseDelayMs * 0.25 * (2 * Math.random() - 1);
236
- await abortableSleep(baseDelayMs + jitter, signal);
237
- if (signal?.aborted) break;
238
- result = await runWorkerTask({
239
- task,
240
- upstreamContext: plan.objective,
241
- dependencyOutputs: depOutputs,
242
- backend,
243
- workingDir,
244
- modelIntent,
245
- timeoutMs: taskTimeoutMs,
246
- signal,
247
- });
248
- }
249
- result.retryCount = retries;
250
-
251
- if (result.status === "failed") {
252
- log.error(
253
- { taskId: task.id, error: result.summary, retries },
254
- "Swarm task execution failed",
255
- );
256
- }
257
- } catch (err) {
258
- const error = err instanceof Error ? err : new Error(String(err));
259
- log.error(
260
- { taskId: task.id, error: error.message, stack: error.stack },
261
- "Swarm task execution failed unexpectedly",
262
- );
263
- result = {
264
- taskId: task.id,
265
- status: "failed",
266
- summary: `Unexpected error: ${error.message}`,
267
- artifacts: [],
268
- issues: [error.message],
269
- nextSteps: [],
270
- rawOutput: "",
271
- durationMs: 0,
272
- retryCount: 0,
273
- };
274
- }
275
-
276
- processResult(result);
277
- }
278
-
279
- while (ready.length > 0 || activeCount > 0) {
280
- if (signal?.aborted) break;
281
-
282
- // Launch as many ready tasks as worker slots allow
283
- while (ready.length > 0 && activeCount < limits.maxWorkers) {
284
- const task = ready.shift()!;
285
- activeCount++;
286
- // Fire-and-forget — completion is handled inside runTask.
287
- // .finally() ensures activeCount is decremented exactly once
288
- // regardless of where an error occurs (e.g. a throwing onStatus
289
- // callback inside processResult). .catch() suppresses the
290
- // unhandled rejection for fire-and-forget usage.
291
- runTask(task)
292
- .finally(() => {
293
- activeCount--;
294
- signalProgress();
295
- })
296
- .catch((err) => {
297
- const error = err instanceof Error ? err : new Error(String(err));
298
- log.error(
299
- { taskId: task.id, error: error.message, stack: error.stack },
300
- "Swarm task runner failed",
301
- );
302
- });
303
- }
304
-
305
- // Nothing left to launch and nothing running — we're done
306
- if (activeCount === 0 && ready.length === 0) break;
307
-
308
- // Wait until a running task completes (or a new task becomes ready)
309
- await waitForProgress();
310
- }
311
-
312
- // Let in-flight workers settle before finalizing — if we broke out of the
313
- // loop due to abort, workers may still be running and would otherwise emit
314
- // events (task_completed / task_failed) after the 'done' event.
315
- while (activeCount > 0) {
316
- await waitForProgress();
317
- }
318
-
319
- // Mark any remaining tasks that were never reached as blocked
320
- for (const [taskId] of remaining) {
321
- if (!results.has(taskId) && !blocked.has(taskId)) {
322
- blocked.add(taskId);
323
- onStatus?.({ kind: "task_blocked", taskId });
324
- }
325
- }
326
-
327
- // Synthesize final answer (skip LLM synthesis when aborted, but still
328
- // build the markdown fallback so partial results are preserved)
329
- const allResults = Array.from(results.values());
330
-
331
- let finalAnswer: string;
332
- if (opts.synthesisProvider && !signal?.aborted) {
333
- onStatus?.({ kind: "synthesis_started" });
334
- finalAnswer = await synthesizeResults({
335
- objective: plan.objective,
336
- results: allResults,
337
- provider: opts.synthesisProvider,
338
- modelIntent: opts.synthesisModelIntent ?? "quality-optimized",
339
- });
340
- } else {
341
- if (!signal?.aborted) onStatus?.({ kind: "synthesis_started" });
342
- finalAnswer = buildMarkdownFallback(plan.objective, allResults);
343
- }
344
-
345
- const totalDurationMs = Date.now() - startTime;
346
-
347
- // Clean up checkpoint on successful completion (not on abort, so it can be resumed)
348
- if (runId && !signal?.aborted) {
349
- removeCheckpoint(runId);
350
- }
351
-
352
- onStatus?.({ kind: "done", message: `Completed in ${totalDurationMs}ms` });
353
-
354
- return {
355
- objective: plan.objective,
356
- plan,
357
- results: allResults,
358
- finalAnswer,
359
- stats: {
360
- totalTasks: plan.tasks.length,
361
- completed: allResults.filter((r) => r.status === "completed").length,
362
- failed: allResults.filter((r) => r.status === "failed").length,
363
- blocked: blocked.size,
364
- totalDurationMs,
365
- },
366
- };
367
- }
368
-
369
- function blockDependents(
370
- taskId: string,
371
- dependents: Map<string, string[]>,
372
- blocked: Set<string>,
373
- onStatus?: OrchestratorStatusCallback,
374
- ): void {
375
- for (const depId of dependents.get(taskId) ?? []) {
376
- if (!blocked.has(depId)) {
377
- blocked.add(depId);
378
- onStatus?.({ kind: "task_blocked", taskId: depId });
379
- blockDependents(depId, dependents, blocked, onStatus);
380
- }
381
- }
382
- }
383
-
384
- /** Resolves after `ms` milliseconds, or immediately if the signal fires first. */
385
- function abortableSleep(ms: number, signal?: AbortSignal): Promise<void> {
386
- if (signal?.aborted) return Promise.resolve();
387
- return new Promise<void>((resolve) => {
388
- const timer = setTimeout(done, ms);
389
- signal?.addEventListener("abort", done, { once: true });
390
- function done() {
391
- clearTimeout(timer);
392
- signal?.removeEventListener("abort", done);
393
- resolve();
394
- }
395
- });
396
- }
397
-
398
- function buildMarkdownFallback(
399
- objective: string,
400
- results: SwarmTaskResult[],
401
- ): string {
402
- const lines: string[] = [`## Swarm Results: ${objective}`, ""];
403
-
404
- const completed = results.filter((r) => r.status === "completed");
405
- const failed = results.filter((r) => r.status === "failed");
406
-
407
- if (completed.length > 0) {
408
- lines.push("### Completed Tasks");
409
- for (const r of completed) {
410
- lines.push(`- **${r.taskId}**: ${r.summary}`);
411
- }
412
- lines.push("");
413
- }
414
-
415
- if (failed.length > 0) {
416
- lines.push("### Failed Tasks");
417
- for (const r of failed) {
418
- lines.push(`- **${r.taskId}**: ${r.summary}`);
419
- }
420
- lines.push("");
421
- }
422
-
423
- return lines.join("\n");
424
- }
@@ -1,117 +0,0 @@
1
- import { getLogger } from "../util/logger.js";
2
- import { detectCycles } from "./graph-utils.js";
3
- import type { SwarmLimits } from "./limits.js";
4
- import type { SwarmPlan, SwarmTaskNode } from "./types.js";
5
- import { VALID_SWARM_ROLES } from "./types.js";
6
-
7
- const log = getLogger("swarm:plan-validator");
8
-
9
- export class SwarmPlanValidationError extends Error {
10
- constructor(
11
- message: string,
12
- public readonly issues: string[],
13
- ) {
14
- super(message);
15
- this.name = "SwarmPlanValidationError";
16
- }
17
- }
18
-
19
- /**
20
- * Validate and normalize a swarm plan. Returns the validated plan or throws
21
- * SwarmPlanValidationError with all detected issues.
22
- */
23
- export function validateAndNormalizePlan(
24
- plan: SwarmPlan,
25
- limits: SwarmLimits,
26
- ): SwarmPlan {
27
- const issues: string[] = [];
28
-
29
- // --- Basic structure ---
30
- if (!plan.objective || typeof plan.objective !== "string") {
31
- issues.push("Plan must have a non-empty objective string.");
32
- }
33
- if (!Array.isArray(plan.tasks) || plan.tasks.length === 0) {
34
- issues.push("Plan must have at least one task.");
35
- throw new SwarmPlanValidationError("Plan validation failed", issues);
36
- }
37
-
38
- // --- Normalize dependencies early (before any checks that iterate them) ---
39
- let tasks: SwarmTaskNode[] = plan.tasks.map((t) => ({
40
- ...t,
41
- dependencies: Array.isArray(t.dependencies) ? t.dependencies : [],
42
- }));
43
- const originalIds = new Set(tasks.map((task) => task.id));
44
-
45
- // --- Task count limit ---
46
- if (tasks.length > limits.maxTasks) {
47
- log.warn(
48
- { original: tasks.length, max: limits.maxTasks },
49
- "Plan truncated to task limit",
50
- );
51
- tasks = tasks.slice(0, limits.maxTasks);
52
- }
53
-
54
- // --- Unique IDs ---
55
- const ids = new Set<string>();
56
- for (const task of tasks) {
57
- if (!task.id || typeof task.id !== "string") {
58
- issues.push(`Task has invalid or empty id.`);
59
- continue;
60
- }
61
- if (ids.has(task.id)) {
62
- issues.push(`Duplicate task id: "${task.id}".`);
63
- }
64
- ids.add(task.id);
65
- }
66
-
67
- // --- Valid roles ---
68
- for (const task of tasks) {
69
- if (!VALID_SWARM_ROLES.includes(task.role)) {
70
- issues.push(
71
- `Task "${task.id}" has invalid role "${
72
- task.role
73
- }". Valid roles: ${VALID_SWARM_ROLES.join(", ")}.`,
74
- );
75
- }
76
- }
77
-
78
- // --- Strip orphaned dependency references (from truncation) and report real errors ---
79
- for (const task of tasks) {
80
- const valid: string[] = [];
81
- for (const dep of task.dependencies) {
82
- if (!ids.has(dep)) {
83
- if (!originalIds.has(dep)) {
84
- issues.push(`Task "${task.id}" depends on unknown task "${dep}".`);
85
- }
86
- } else {
87
- valid.push(dep);
88
- }
89
- }
90
- task.dependencies = valid;
91
- }
92
-
93
- // --- Cycle detection (Kahn's algorithm) ---
94
- if (!hasDuplicateIds(tasks) && issues.length === 0) {
95
- const cycleNodes = detectCycles(tasks);
96
- if (cycleNodes) {
97
- issues.push(
98
- `Plan contains a dependency cycle: ${cycleNodes.join(" -> ")}.`,
99
- );
100
- }
101
- }
102
-
103
- if (issues.length > 0) {
104
- throw new SwarmPlanValidationError("Plan validation failed", issues);
105
- }
106
-
107
- return { ...plan, tasks };
108
- }
109
-
110
- function hasDuplicateIds(tasks: SwarmTaskNode[]): boolean {
111
- const seen = new Set<string>();
112
- for (const t of tasks) {
113
- if (seen.has(t.id)) return true;
114
- seen.add(t.id);
115
- }
116
- return false;
117
- }
@@ -1,162 +0,0 @@
1
- import type { Message, ModelIntent, Provider } from "../providers/types.js";
2
- import { parseJsonSafe } from "../util/json.js";
3
- import type { SwarmLimits } from "./limits.js";
4
- import { validateAndNormalizePlan } from "./plan-validator.js";
5
- import {
6
- buildPlannerUserMessage,
7
- ROUTER_SYSTEM_PROMPT,
8
- } from "./router-prompts.js";
9
- import type { SwarmPlan, SwarmTaskNode } from "./types.js";
10
- import { VALID_SWARM_ROLES } from "./types.js";
11
-
12
- /**
13
- * Generate a validated swarm plan from a user objective using an LLM.
14
- * Falls back to a single-coder plan if generation or validation fails.
15
- */
16
- export async function generatePlan(opts: {
17
- objective: string;
18
- provider: Provider;
19
- modelIntent: ModelIntent;
20
- limits: SwarmLimits;
21
- }): Promise<SwarmPlan> {
22
- const { objective, provider, modelIntent, limits } = opts;
23
-
24
- try {
25
- const messages: Message[] = [
26
- {
27
- role: "user",
28
- content: [
29
- {
30
- type: "text",
31
- text: buildPlannerUserMessage(objective, limits.maxTasks),
32
- },
33
- ],
34
- },
35
- ];
36
-
37
- const response = await provider.sendMessage(
38
- messages,
39
- undefined,
40
- ROUTER_SYSTEM_PROMPT,
41
- { config: { max_tokens: 2048, modelIntent } },
42
- );
43
-
44
- // Extract text from response
45
- const textBlock = response.content.find((b) => b.type === "text");
46
- if (!textBlock || textBlock.type !== "text") {
47
- return makeFallbackPlan(objective);
48
- }
49
-
50
- const rawPlan = parsePlanJSON(textBlock.text);
51
- if (!rawPlan) {
52
- return makeFallbackPlan(objective);
53
- }
54
-
55
- const validatedTasks = validateRawTasks(rawPlan.tasks);
56
- if (validatedTasks.length === 0) {
57
- return makeFallbackPlan(objective);
58
- }
59
-
60
- const plan: SwarmPlan = {
61
- objective,
62
- tasks: validatedTasks,
63
- };
64
-
65
- return validateAndNormalizePlan(plan, limits);
66
- } catch {
67
- return makeFallbackPlan(objective);
68
- }
69
- }
70
-
71
- /**
72
- * Parse the LLM output as a plan JSON. Handles bare JSON objects and
73
- * fenced code blocks (tries all fenced blocks, not just the first).
74
- */
75
- export function parsePlanJSON(raw: string): {
76
- tasks: Array<{
77
- id: string;
78
- role: string;
79
- objective: string;
80
- dependencies: string[];
81
- }>;
82
- } | null {
83
- // Try all fenced code blocks — LLMs sometimes emit non-JSON blocks before the plan
84
- const fencedRegex = /```(?:json)?\s*\n?([\s\S]*?)\n?```/g;
85
- let match;
86
- while ((match = fencedRegex.exec(raw)) != null) {
87
- const result = tryParsePlan(match[1]);
88
- if (result) return result;
89
- }
90
-
91
- // Fall back to parsing the raw string as bare JSON
92
- return tryParsePlan(raw);
93
- }
94
-
95
- function tryParsePlan(jsonStr: string): {
96
- tasks: Array<{
97
- id: string;
98
- role: string;
99
- objective: string;
100
- dependencies: string[];
101
- }>;
102
- } | null {
103
- const parsed = parseJsonSafe<{ tasks?: unknown }>(jsonStr.trim());
104
- if (parsed && Array.isArray(parsed.tasks)) {
105
- return parsed as {
106
- tasks: Array<{
107
- id: string;
108
- role: string;
109
- objective: string;
110
- dependencies: string[];
111
- }>;
112
- };
113
- }
114
- return null;
115
- }
116
-
117
- /**
118
- * Validate that raw parsed task objects have the required fields and correct types
119
- * before treating them as SwarmTaskNode[]. Filters out malformed entries so a
120
- * partially valid LLM response can still produce a usable plan.
121
- */
122
- function validateRawTasks(raw: unknown[]): SwarmTaskNode[] {
123
- const validRoles = new Set<string>(VALID_SWARM_ROLES);
124
- const validated: SwarmTaskNode[] = [];
125
-
126
- for (const item of raw) {
127
- if (item == null || typeof item !== "object") continue;
128
- const t = item as Record<string, unknown>;
129
-
130
- if (typeof t.id !== "string" || t.id === "") continue;
131
- if (typeof t.role !== "string" || !validRoles.has(t.role)) continue;
132
- if (typeof t.objective !== "string" || t.objective === "") continue;
133
-
134
- validated.push({
135
- id: t.id,
136
- role: t.role as SwarmTaskNode["role"],
137
- objective: t.objective,
138
- dependencies: Array.isArray(t.dependencies)
139
- ? t.dependencies.filter((d): d is string => typeof d === "string")
140
- : [],
141
- });
142
- }
143
-
144
- return validated;
145
- }
146
-
147
- /**
148
- * Deterministic fallback: a single coder task for the full objective.
149
- */
150
- export function makeFallbackPlan(objective: string): SwarmPlan {
151
- return {
152
- objective,
153
- tasks: [
154
- {
155
- id: "fallback-coder",
156
- role: "coder",
157
- objective,
158
- dependencies: [],
159
- },
160
- ],
161
- };
162
- }