@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,547 +0,0 @@
1
- /**
2
- * Integration tests for swarm DAG scheduling under pathological graph shapes.
3
- *
4
- * These tests exercise the orchestrator and plan validator together to verify
5
- * correct behaviour when the dependency graph is adversarial:
6
- * 1. Deep dependency chains (20+ sequential tasks)
7
- * 2. Near-circular dependencies (partial cycles that stop just short of
8
- * forming a full cycle, plus detection of actual cycles)
9
- * 3. Very wide fan-outs (one root task with 50+ immediate dependents)
10
- */
11
-
12
- import { describe, expect, test } from "bun:test";
13
-
14
- import { resolveSwarmLimits } from "../swarm/limits.js";
15
- import type { OrchestratorEvent } from "../swarm/orchestrator.js";
16
- import { executeSwarm } from "../swarm/orchestrator.js";
17
- import {
18
- SwarmPlanValidationError,
19
- validateAndNormalizePlan,
20
- } from "../swarm/plan-validator.js";
21
- import type { SwarmPlan, SwarmTaskNode } from "../swarm/types.js";
22
- import type { SwarmWorkerBackend } from "../swarm/worker-backend.js";
23
-
24
- // ---------------------------------------------------------------------------
25
- // Shared helpers
26
- // ---------------------------------------------------------------------------
27
-
28
- const SUCCESS_OUTPUT =
29
- '```json\n{"summary":"Done","artifacts":[],"issues":[],"nextSteps":[]}\n```';
30
-
31
- function makeBackend(
32
- overrides?: Partial<SwarmWorkerBackend>,
33
- ): SwarmWorkerBackend {
34
- return {
35
- name: "test-backend",
36
- isAvailable: () => true,
37
- runTask: async () => ({
38
- success: true,
39
- output: SUCCESS_OUTPUT,
40
- durationMs: 5,
41
- }),
42
- ...overrides,
43
- };
44
- }
45
-
46
- /** Limits generous enough for large pathological plans. */
47
- const LARGE_LIMITS = {
48
- maxWorkers: 6,
49
- // Bypass the maxTasks hard-ceiling inside resolveSwarmLimits by using a
50
- // value at the ceiling (20). For the deep-chain test we build exactly 20
51
- // tasks, and for the fan-out we pass the oversized plan directly to
52
- // executeSwarm (skipping validation) to test the orchestrator in isolation.
53
- maxTasks: 20,
54
- maxRetriesPerTask: 0,
55
- workerTimeoutSec: 30,
56
- };
57
-
58
- const RESOLVED_LIMITS = resolveSwarmLimits(LARGE_LIMITS);
59
-
60
- // ---------------------------------------------------------------------------
61
- // Helpers for building specific graph shapes
62
- // ---------------------------------------------------------------------------
63
-
64
- /** Build a linear chain: t0 -> t1 -> t2 -> ... -> t(n-1) */
65
- function buildChain(n: number): SwarmTaskNode[] {
66
- return Array.from({ length: n }, (_, i) => ({
67
- id: `t${i}`,
68
- role: "coder" as const,
69
- objective: `Task ${i}`,
70
- dependencies: i === 0 ? [] : [`t${i - 1}`],
71
- }));
72
- }
73
-
74
- /** Build a star: one root + n leaf tasks that all depend on the root. */
75
- function buildStar(leafCount: number): SwarmTaskNode[] {
76
- const root: SwarmTaskNode = {
77
- id: "root",
78
- role: "coder",
79
- objective: "Root task",
80
- dependencies: [],
81
- };
82
- const leaves: SwarmTaskNode[] = Array.from({ length: leafCount }, (_, i) => ({
83
- id: `leaf${i}`,
84
- role: "coder" as const,
85
- objective: `Leaf ${i}`,
86
- dependencies: ["root"],
87
- }));
88
- return [root, ...leaves];
89
- }
90
-
91
- // ---------------------------------------------------------------------------
92
- // 1. Deep dependency chains
93
- // ---------------------------------------------------------------------------
94
-
95
- describe("deep dependency chains", () => {
96
- test("validates a 20-task linear chain without errors", () => {
97
- const tasks = buildChain(20);
98
- const plan: SwarmPlan = { objective: "Deep chain", tasks };
99
- // Should not throw — a pure chain is a valid DAG with no cycles.
100
- const validated = validateAndNormalizePlan(plan, RESOLVED_LIMITS);
101
- expect(validated.tasks).toHaveLength(20);
102
- });
103
-
104
- test("executes a 20-task linear chain completing all tasks in order", async () => {
105
- const tasks = buildChain(20);
106
- // Drive through executeSwarm directly — bypassing the maxTasks truncation
107
- // inside the validator so we can test the orchestrator with 20 tasks.
108
- const plan: SwarmPlan = { objective: "Deep chain", tasks };
109
-
110
- const completionOrder: string[] = [];
111
- const backend = makeBackend({
112
- runTask: async (input) => {
113
- // Extract task id from prompt — the prompt embeds the task objective.
114
- const match = input.prompt.match(/Task (\d+)/);
115
- if (match) completionOrder.push(`t${match[1]}`);
116
- return { success: true, output: SUCCESS_OUTPUT, durationMs: 2 };
117
- },
118
- });
119
-
120
- const limits = resolveSwarmLimits({ ...LARGE_LIMITS, maxWorkers: 2 });
121
- const summary = await executeSwarm({
122
- plan,
123
- limits,
124
- backend,
125
- workingDir: "/tmp",
126
- });
127
-
128
- expect(summary.stats.totalTasks).toBe(20);
129
- expect(summary.stats.completed).toBe(20);
130
- expect(summary.stats.failed).toBe(0);
131
- expect(summary.stats.blocked).toBe(0);
132
- });
133
-
134
- test("blocks the entire tail when the head of a deep chain fails", async () => {
135
- const tasks = buildChain(20);
136
- const plan: SwarmPlan = { objective: "Deep chain failure", tasks };
137
-
138
- // Only the very first task (t0) fails; all subsequent ones should be blocked.
139
- const backend = makeBackend({
140
- runTask: async (input) => {
141
- if (input.prompt.includes("Task 0")) {
142
- return {
143
- success: false,
144
- output: "fail",
145
- failureReason: "timeout" as const,
146
- durationMs: 2,
147
- };
148
- }
149
- return { success: true, output: SUCCESS_OUTPUT, durationMs: 2 };
150
- },
151
- });
152
-
153
- const summary = await executeSwarm({
154
- plan,
155
- limits: resolveSwarmLimits({ ...LARGE_LIMITS, maxRetriesPerTask: 0 }),
156
- backend,
157
- workingDir: "/tmp",
158
- });
159
-
160
- expect(summary.stats.failed).toBe(1);
161
- // Every downstream task (t1..t19) must be blocked, not completed.
162
- expect(summary.stats.blocked).toBe(19);
163
- expect(summary.stats.completed).toBe(0);
164
- });
165
-
166
- test("passes dependency output down the full chain", async () => {
167
- const tasks = buildChain(5);
168
- const plan: SwarmPlan = { objective: "Context propagation", tasks };
169
-
170
- // Track how many dependency-output entries each task sees.
171
- // buildWorkerPrompt uses the section header "Outputs from prerequisite tasks:"
172
- // followed by "- [taskId]: summary" lines (one per upstream dependency).
173
- const depCounts: Record<string, number> = {};
174
- const backend = makeBackend({
175
- runTask: async (input) => {
176
- // Count how many "- [<id>]:" lines appear in the upstream-output section.
177
- const prereqSection = input.prompt.match(
178
- /Outputs from prerequisite tasks:([\s\S]*?)(?:\n\n|$)/,
179
- );
180
- const count = prereqSection
181
- ? (prereqSection[1].match(/^- \[/gm) ?? []).length
182
- : 0;
183
- // The prompt embeds the objective ("Task N") verbatim.
184
- const match = input.prompt.match(/Task (\d+)/);
185
- if (match) depCounts[`t${match[1]}`] = count;
186
- return { success: true, output: SUCCESS_OUTPUT, durationMs: 2 };
187
- },
188
- });
189
-
190
- await executeSwarm({
191
- plan,
192
- limits: resolveSwarmLimits({ ...LARGE_LIMITS, maxWorkers: 1 }),
193
- backend,
194
- workingDir: "/tmp",
195
- });
196
-
197
- // t0 has no dependencies; each subsequent task has exactly one upstream dep.
198
- expect(depCounts["t0"] ?? 0).toBe(0);
199
- expect(depCounts["t1"]).toBe(1);
200
- expect(depCounts["t2"]).toBe(1);
201
- expect(depCounts["t3"]).toBe(1);
202
- expect(depCounts["t4"]).toBe(1);
203
- });
204
-
205
- test("emits task events for every node in a deep chain", async () => {
206
- const tasks = buildChain(10);
207
- const plan: SwarmPlan = { objective: "Event coverage", tasks };
208
-
209
- const events: OrchestratorEvent[] = [];
210
- await executeSwarm({
211
- plan,
212
- limits: resolveSwarmLimits({ ...LARGE_LIMITS, maxWorkers: 2 }),
213
- backend: makeBackend(),
214
- workingDir: "/tmp",
215
- onStatus: (e) => events.push(e),
216
- });
217
-
218
- const startedIds = events
219
- .filter((e) => e.kind === "task_started")
220
- .map((e) => e.taskId);
221
- const completedIds = events
222
- .filter((e) => e.kind === "task_completed")
223
- .map((e) => e.taskId);
224
-
225
- // Every task must have been started and completed exactly once.
226
- expect(new Set(startedIds).size).toBe(10);
227
- expect(new Set(completedIds).size).toBe(10);
228
- });
229
- });
230
-
231
- // ---------------------------------------------------------------------------
232
- // 2. Near-circular dependencies
233
- // ---------------------------------------------------------------------------
234
-
235
- describe("near-circular dependencies", () => {
236
- /**
237
- * "Near-circular" is a chain where the last node almost loops back to the
238
- * first but instead stops one edge short. These are valid DAGs and must
239
- * execute without error. Contrast with actual cycles, which must be
240
- * rejected.
241
- */
242
-
243
- test("validates and executes a near-circular chain (A→B→C, C does NOT link back to A)", async () => {
244
- // A→B→C is valid; the test confirms it finishes without a cycle error.
245
- const tasks: SwarmTaskNode[] = [
246
- { id: "A", role: "coder", objective: "Step A", dependencies: [] },
247
- { id: "B", role: "coder", objective: "Step B", dependencies: ["A"] },
248
- { id: "C", role: "coder", objective: "Step C", dependencies: ["B"] },
249
- ];
250
- const plan: SwarmPlan = { objective: "Near-circular chain", tasks };
251
- expect(() => validateAndNormalizePlan(plan, RESOLVED_LIMITS)).not.toThrow();
252
-
253
- const summary = await executeSwarm({
254
- plan,
255
- limits: RESOLVED_LIMITS,
256
- backend: makeBackend(),
257
- workingDir: "/tmp",
258
- });
259
- expect(summary.stats.completed).toBe(3);
260
- expect(summary.stats.blocked).toBe(0);
261
- });
262
-
263
- test("rejects a minimal 2-node cycle (A→B→A)", () => {
264
- const plan: SwarmPlan = {
265
- objective: "Two-node cycle",
266
- tasks: [
267
- { id: "A", role: "coder", objective: "A", dependencies: ["B"] },
268
- { id: "B", role: "coder", objective: "B", dependencies: ["A"] },
269
- ],
270
- };
271
- let err: unknown;
272
- try {
273
- validateAndNormalizePlan(plan, RESOLVED_LIMITS);
274
- } catch (e) {
275
- err = e;
276
- }
277
- expect(err).toBeInstanceOf(SwarmPlanValidationError);
278
- expect((err as SwarmPlanValidationError).issues).toContainEqual(
279
- expect.stringContaining("cycle"),
280
- );
281
- });
282
-
283
- test("rejects a 3-node cycle (A→B→C→A)", () => {
284
- const plan: SwarmPlan = {
285
- objective: "Three-node cycle",
286
- tasks: [
287
- { id: "A", role: "coder", objective: "A", dependencies: ["C"] },
288
- { id: "B", role: "coder", objective: "B", dependencies: ["A"] },
289
- { id: "C", role: "coder", objective: "C", dependencies: ["B"] },
290
- ],
291
- };
292
- expect(() => validateAndNormalizePlan(plan, RESOLVED_LIMITS)).toThrow(
293
- SwarmPlanValidationError,
294
- );
295
- });
296
-
297
- test("rejects a cycle embedded in an otherwise-acyclic graph", () => {
298
- // D is an independent node; only A/B/C form the cycle.
299
- const plan: SwarmPlan = {
300
- objective: "Mixed graph with embedded cycle",
301
- tasks: [
302
- { id: "D", role: "coder", objective: "Independent", dependencies: [] },
303
- { id: "A", role: "coder", objective: "A", dependencies: ["C"] },
304
- { id: "B", role: "coder", objective: "B", dependencies: ["A"] },
305
- { id: "C", role: "coder", objective: "C", dependencies: ["B"] },
306
- ],
307
- };
308
- let err: unknown;
309
- try {
310
- validateAndNormalizePlan(plan, RESOLVED_LIMITS);
311
- } catch (e) {
312
- err = e;
313
- }
314
- expect(err).toBeInstanceOf(SwarmPlanValidationError);
315
- expect((err as SwarmPlanValidationError).issues).toContainEqual(
316
- expect.stringContaining("cycle"),
317
- );
318
- });
319
-
320
- test("rejects a self-loop (A depends on itself)", () => {
321
- const plan: SwarmPlan = {
322
- objective: "Self-loop",
323
- tasks: [{ id: "A", role: "coder", objective: "A", dependencies: ["A"] }],
324
- };
325
- expect(() => validateAndNormalizePlan(plan, RESOLVED_LIMITS)).toThrow(
326
- SwarmPlanValidationError,
327
- );
328
- });
329
-
330
- test("validates a long chain that almost cycles — last node stops one step short", () => {
331
- // t0→t1→...→t9; t9 does NOT depend on t0 (so it is NOT a cycle).
332
- const tasks: SwarmTaskNode[] = Array.from({ length: 10 }, (_, i) => ({
333
- id: `t${i}`,
334
- role: "coder" as const,
335
- objective: `Step ${i}`,
336
- dependencies: i === 0 ? [] : [`t${i - 1}`],
337
- }));
338
- const plan: SwarmPlan = { objective: "Long near-circular chain", tasks };
339
- // Should not throw.
340
- const validated = validateAndNormalizePlan(plan, RESOLVED_LIMITS);
341
- expect(validated.tasks).toHaveLength(10);
342
- });
343
-
344
- test("rejects a long chain that does complete the cycle — last node links back to first", () => {
345
- // t0→t1→...→t8→t9, and t0 also depends on t9 — this closes the loop
346
- // making the entire chain one big cycle.
347
- const tasks: SwarmTaskNode[] = Array.from({ length: 10 }, (_, i) => ({
348
- id: `t${i}`,
349
- role: "coder" as const,
350
- objective: `Step ${i}`,
351
- // t0 depends on t9 (closing the loop); every other node depends on its predecessor.
352
- dependencies: i === 0 ? ["t9"] : [`t${i - 1}`],
353
- }));
354
- const plan: SwarmPlan = { objective: "Closed long cycle", tasks };
355
- expect(() => validateAndNormalizePlan(plan, RESOLVED_LIMITS)).toThrow(
356
- SwarmPlanValidationError,
357
- );
358
- });
359
-
360
- test("error message identifies at least one node involved in the cycle", () => {
361
- const plan: SwarmPlan = {
362
- objective: "Cycle identification",
363
- tasks: [
364
- { id: "X", role: "coder", objective: "X", dependencies: ["Z"] },
365
- { id: "Y", role: "coder", objective: "Y", dependencies: ["X"] },
366
- { id: "Z", role: "coder", objective: "Z", dependencies: ["Y"] },
367
- ],
368
- };
369
- try {
370
- validateAndNormalizePlan(plan, RESOLVED_LIMITS);
371
- expect(true).toBe(false); // unreachable
372
- } catch (e) {
373
- const err = e as SwarmPlanValidationError;
374
- const cycleMessage = err.issues.find((i) => i.includes("cycle")) ?? "";
375
- // The cycle message must name at least one of the nodes.
376
- const mentionsANode = ["X", "Y", "Z"].some((id) =>
377
- cycleMessage.includes(id),
378
- );
379
- expect(mentionsANode).toBe(true);
380
- }
381
- });
382
- });
383
-
384
- // ---------------------------------------------------------------------------
385
- // 3. Very wide fan-outs
386
- // ---------------------------------------------------------------------------
387
-
388
- describe("very wide fan-outs", () => {
389
- /**
390
- * These tests bypass validateAndNormalizePlan (which caps maxTasks at 20)
391
- * and drive executeSwarm directly. The orchestrator has no hard cap on
392
- * tasks — the cap lives only in the validator. Testing the orchestrator
393
- * directly lets us verify scheduling correctness at scale without needing
394
- * to change the hard limit constants.
395
- */
396
-
397
- test("completes a star graph with 51 leaf tasks (1 root + 50 dependents)", async () => {
398
- const tasks = buildStar(50);
399
- const plan: SwarmPlan = { objective: "Wide fan-out", tasks };
400
-
401
- const summary = await executeSwarm({
402
- plan,
403
- limits: RESOLVED_LIMITS,
404
- backend: makeBackend(),
405
- workingDir: "/tmp",
406
- });
407
-
408
- expect(summary.stats.totalTasks).toBe(51);
409
- expect(summary.stats.completed).toBe(51);
410
- expect(summary.stats.failed).toBe(0);
411
- expect(summary.stats.blocked).toBe(0);
412
- });
413
-
414
- test("no leaf starts before root finishes in a 51-task star", async () => {
415
- const tasks = buildStar(50);
416
- const plan: SwarmPlan = { objective: "Wide fan-out ordering", tasks };
417
-
418
- const completionOrder: string[] = [];
419
- const backend = makeBackend({
420
- runTask: async (input) => {
421
- const isRoot = input.prompt.includes("Root task");
422
- if (isRoot) {
423
- // Introduce a tiny delay so leaves clearly start after root ends.
424
- await new Promise((r) => setTimeout(r, 10));
425
- // Record completion only after the delay finishes so completionOrder
426
- // reflects actual completion time, not when execution started.
427
- completionOrder.push("root");
428
- }
429
- return { success: true, output: SUCCESS_OUTPUT, durationMs: 2 };
430
- },
431
- });
432
-
433
- await executeSwarm({
434
- plan,
435
- limits: RESOLVED_LIMITS,
436
- backend,
437
- workingDir: "/tmp",
438
- onStatus: (e) => {
439
- if (e.kind === "task_started" && e.taskId !== "root") {
440
- // Leaves must only start after root has been recorded as completed.
441
- expect(completionOrder).toContain("root");
442
- }
443
- },
444
- });
445
- });
446
-
447
- test("blocks all 50 leaves when the root task fails", async () => {
448
- const tasks = buildStar(50);
449
- const plan: SwarmPlan = { objective: "Fan-out failure propagation", tasks };
450
-
451
- const backend = makeBackend({
452
- runTask: async (input) => {
453
- if (input.prompt.includes("Root task")) {
454
- return {
455
- success: false,
456
- output: "root failed",
457
- failureReason: "timeout" as const,
458
- durationMs: 2,
459
- };
460
- }
461
- return { success: true, output: SUCCESS_OUTPUT, durationMs: 2 };
462
- },
463
- });
464
-
465
- const summary = await executeSwarm({
466
- plan,
467
- limits: resolveSwarmLimits({ ...LARGE_LIMITS, maxRetriesPerTask: 0 }),
468
- backend,
469
- workingDir: "/tmp",
470
- });
471
-
472
- expect(summary.stats.failed).toBe(1);
473
- expect(summary.stats.blocked).toBe(50);
474
- expect(summary.stats.completed).toBe(0);
475
- });
476
-
477
- test("honours maxWorkers concurrency ceiling during a wide fan-out", async () => {
478
- const leafCount = 20;
479
- const tasks = buildStar(leafCount);
480
- const plan: SwarmPlan = { objective: "Fan-out concurrency", tasks };
481
-
482
- let peak = 0;
483
- let current = 0;
484
- const backend = makeBackend({
485
- runTask: async () => {
486
- current++;
487
- if (current > peak) peak = current;
488
- // Hold the slot open briefly so concurrency actually builds up.
489
- await new Promise((r) => setTimeout(r, 5));
490
- current--;
491
- return { success: true, output: SUCCESS_OUTPUT, durationMs: 5 };
492
- },
493
- });
494
-
495
- const maxWorkers = 3;
496
- await executeSwarm({
497
- plan,
498
- limits: resolveSwarmLimits({ ...LARGE_LIMITS, maxWorkers }),
499
- backend,
500
- workingDir: "/tmp",
501
- });
502
-
503
- // Peak concurrency must never exceed the configured worker limit.
504
- expect(peak).toBeLessThanOrEqual(maxWorkers);
505
- // At least 2 workers should have run concurrently (shows leaves ran in
506
- // parallel rather than sequentially).
507
- expect(peak).toBeGreaterThanOrEqual(2);
508
- });
509
-
510
- test("all 50 leaf task_started events are emitted eventually in a wide fan-out", async () => {
511
- const tasks = buildStar(50);
512
- const plan: SwarmPlan = { objective: "Fan-out event coverage", tasks };
513
-
514
- const startedIds = new Set<string>();
515
- await executeSwarm({
516
- plan,
517
- limits: RESOLVED_LIMITS,
518
- backend: makeBackend(),
519
- workingDir: "/tmp",
520
- onStatus: (e) => {
521
- if (e.kind === "task_started" && e.taskId) startedIds.add(e.taskId);
522
- },
523
- });
524
-
525
- // root + 50 leaves = 51 started events
526
- expect(startedIds.size).toBe(51);
527
- expect(startedIds.has("root")).toBe(true);
528
- for (let i = 0; i < 50; i++) {
529
- expect(startedIds.has(`leaf${i}`)).toBe(true);
530
- }
531
- });
532
-
533
- test("validator correctly validates a 20-task star (within hard limits)", () => {
534
- const tasks = buildStar(19); // 1 root + 19 leaves = 20 tasks total
535
- const plan: SwarmPlan = { objective: "Validated star", tasks };
536
- const validated = validateAndNormalizePlan(plan, RESOLVED_LIMITS);
537
- expect(validated.tasks).toHaveLength(20);
538
- });
539
-
540
- test("validator truncates a 30-task star to maxTasks=20", () => {
541
- const tasks = buildStar(29); // 1 root + 29 leaves = 30 tasks total
542
- const plan: SwarmPlan = { objective: "Oversized star", tasks };
543
- const validated = validateAndNormalizePlan(plan, RESOLVED_LIMITS);
544
- // Truncated to 20; the root will still be present (it is first in the array).
545
- expect(validated.tasks.length).toBeLessThanOrEqual(20);
546
- });
547
- });