@vellumai/assistant 0.5.6 → 0.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (305) hide show
  1. package/.env.example +16 -2
  2. package/ARCHITECTURE.md +6 -75
  3. package/Dockerfile +1 -1
  4. package/README.md +0 -2
  5. package/bun.lock +0 -414
  6. package/docs/architecture/keychain-broker.md +45 -240
  7. package/docs/architecture/security.md +0 -17
  8. package/docs/credential-execution-service.md +2 -2
  9. package/node_modules/@vellumai/ces-contracts/package.json +1 -0
  10. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +119 -0
  11. package/node_modules/@vellumai/credential-storage/package.json +1 -0
  12. package/node_modules/@vellumai/egress-proxy/package.json +1 -0
  13. package/package.json +2 -3
  14. package/src/__tests__/actor-token-service.test.ts +0 -114
  15. package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
  16. package/src/__tests__/browser-skill-endstate.test.ts +6 -5
  17. package/src/__tests__/btw-routes.test.ts +0 -39
  18. package/src/__tests__/call-domain.test.ts +0 -128
  19. package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -0
  20. package/src/__tests__/channel-approval-routes.test.ts +0 -5
  21. package/src/__tests__/channel-readiness-service.test.ts +1 -60
  22. package/src/__tests__/checker.test.ts +4 -2
  23. package/src/__tests__/cli-command-risk-guard.test.ts +112 -0
  24. package/src/__tests__/config-schema-cmd.test.ts +0 -1
  25. package/src/__tests__/config-schema.test.ts +1 -1
  26. package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
  27. package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
  28. package/src/__tests__/conversation-skill-tools.test.ts +0 -54
  29. package/src/__tests__/conversation-title-service.test.ts +87 -0
  30. package/src/__tests__/credential-execution-feature-gates.test.ts +28 -14
  31. package/src/__tests__/credential-execution-managed-contract.test.ts +33 -18
  32. package/src/__tests__/credential-security-e2e.test.ts +0 -66
  33. package/src/__tests__/credential-security-invariants.test.ts +4 -45
  34. package/src/__tests__/credentials-cli.test.ts +78 -0
  35. package/src/__tests__/db-migration-rollback.test.ts +2015 -1
  36. package/src/__tests__/docker-signing-key-bootstrap.test.ts +34 -143
  37. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
  38. package/src/__tests__/guardian-routing-state.test.ts +0 -5
  39. package/src/__tests__/host-shell-tool.test.ts +6 -7
  40. package/src/__tests__/http-user-message-parity.test.ts +3 -103
  41. package/src/__tests__/inbound-invite-redemption.test.ts +0 -4
  42. package/src/__tests__/inline-skill-load-permissions.test.ts +6 -8
  43. package/src/__tests__/intent-routing.test.ts +0 -13
  44. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +178 -0
  45. package/src/__tests__/keychain-broker-client.test.ts +161 -22
  46. package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
  47. package/src/__tests__/migration-export-http.test.ts +2 -2
  48. package/src/__tests__/migration-import-commit-http.test.ts +2 -2
  49. package/src/__tests__/migration-import-preflight-http.test.ts +2 -2
  50. package/src/__tests__/migration-validate-http.test.ts +2 -2
  51. package/src/__tests__/non-member-access-request.test.ts +0 -5
  52. package/src/__tests__/notification-decision-fallback.test.ts +4 -0
  53. package/src/__tests__/notification-decision-identity.test.ts +4 -0
  54. package/src/__tests__/permission-types.test.ts +1 -0
  55. package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
  56. package/src/__tests__/qdrant-manager.test.ts +28 -2
  57. package/src/__tests__/registry.test.ts +0 -6
  58. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
  59. package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
  60. package/src/__tests__/secure-keys.test.ts +83 -263
  61. package/src/__tests__/shell-identity.test.ts +96 -6
  62. package/src/__tests__/skill-feature-flags-integration.test.ts +22 -14
  63. package/src/__tests__/skill-feature-flags.test.ts +46 -45
  64. package/src/__tests__/skill-load-feature-flag.test.ts +7 -10
  65. package/src/__tests__/skill-load-inline-command.test.ts +8 -12
  66. package/src/__tests__/skill-load-inline-includes.test.ts +6 -10
  67. package/src/__tests__/skill-load-tool.test.ts +0 -2
  68. package/src/__tests__/skill-projection-feature-flag.test.ts +33 -29
  69. package/src/__tests__/skills.test.ts +0 -2
  70. package/src/__tests__/slack-inbound-verification.test.ts +0 -4
  71. package/src/__tests__/suggestion-routes.test.ts +1 -32
  72. package/src/__tests__/system-prompt.test.ts +0 -1
  73. package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
  74. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
  75. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
  76. package/src/__tests__/update-bulletin.test.ts +0 -2
  77. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +6 -9
  78. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -6
  79. package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +252 -0
  80. package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +218 -0
  81. package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
  82. package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
  83. package/src/calls/audio-store.test.ts +97 -0
  84. package/src/calls/audio-store.ts +205 -0
  85. package/src/calls/call-controller.ts +85 -7
  86. package/src/calls/call-domain.ts +3 -0
  87. package/src/calls/call-store.ts +10 -3
  88. package/src/calls/fish-audio-client.ts +117 -0
  89. package/src/calls/relay-server.ts +27 -0
  90. package/src/calls/twilio-routes.ts +2 -1
  91. package/src/calls/types.ts +1 -0
  92. package/src/calls/voice-ingress-preflight.ts +0 -42
  93. package/src/calls/voice-quality.ts +26 -5
  94. package/src/calls/voice-session-bridge.ts +6 -12
  95. package/src/cli/commands/config.ts +1 -4
  96. package/src/cli/commands/credentials.ts +34 -4
  97. package/src/cli/commands/oauth/index.ts +7 -0
  98. package/src/cli/commands/oauth/platform.ts +179 -0
  99. package/src/cli/commands/platform.ts +3 -3
  100. package/src/config/assistant-feature-flags.ts +186 -5
  101. package/src/config/bundled-skills/messaging/SKILL.md +5 -5
  102. package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
  103. package/src/config/bundled-skills/settings/TOOLS.json +2 -2
  104. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
  105. package/src/config/bundled-tool-registry.ts +1 -11
  106. package/src/config/env-registry.ts +1 -1
  107. package/src/config/env.ts +8 -14
  108. package/src/config/feature-flag-registry.json +48 -8
  109. package/src/config/loader.ts +98 -31
  110. package/src/config/schema.ts +4 -13
  111. package/src/config/schemas/calls.ts +13 -0
  112. package/src/config/schemas/fish-audio.ts +39 -0
  113. package/src/config/schemas/security.ts +0 -4
  114. package/src/config/types.ts +0 -1
  115. package/src/contacts/contact-store.ts +39 -0
  116. package/src/contacts/types.ts +2 -0
  117. package/src/credential-execution/approval-bridge.ts +1 -0
  118. package/src/credential-execution/executable-discovery.ts +28 -4
  119. package/src/credential-execution/feature-gates.ts +16 -0
  120. package/src/credential-execution/process-manager.ts +38 -0
  121. package/src/daemon/assistant-attachments.ts +9 -0
  122. package/src/daemon/config-watcher.ts +5 -0
  123. package/src/daemon/conversation-tool-setup.ts +0 -105
  124. package/src/daemon/conversation.ts +10 -1
  125. package/src/daemon/handlers/config-vercel.ts +92 -0
  126. package/src/daemon/handlers/skills.ts +2 -15
  127. package/src/daemon/install-symlink.ts +195 -0
  128. package/src/daemon/lifecycle.ts +227 -51
  129. package/src/daemon/message-types/conversations.ts +3 -4
  130. package/src/daemon/message-types/diagnostics.ts +3 -22
  131. package/src/daemon/message-types/messages.ts +0 -2
  132. package/src/daemon/message-types/upgrades.ts +8 -0
  133. package/src/daemon/server.ts +30 -92
  134. package/src/events/domain-events.ts +2 -1
  135. package/src/inbound/platform-callback-registration.ts +3 -3
  136. package/src/instrument.ts +8 -5
  137. package/src/memory/conversation-title-service.ts +50 -1
  138. package/src/memory/db-init.ts +12 -0
  139. package/src/memory/items-extractor.ts +15 -1
  140. package/src/memory/job-handlers/conversation-starters.ts +4 -1
  141. package/src/memory/jobs-store.ts +30 -5
  142. package/src/memory/jobs-worker.ts +31 -7
  143. package/src/memory/migrations/001-job-deferrals.ts +19 -0
  144. package/src/memory/migrations/004-entity-relation-dedup.ts +10 -0
  145. package/src/memory/migrations/005-fingerprint-scope-unique.ts +76 -0
  146. package/src/memory/migrations/006-scope-salted-fingerprints.ts +50 -0
  147. package/src/memory/migrations/007-assistant-id-to-self.ts +10 -0
  148. package/src/memory/migrations/008-remove-assistant-id-columns.ts +34 -0
  149. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +26 -0
  150. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +10 -0
  151. package/src/memory/migrations/015-drop-active-search-index.ts +17 -0
  152. package/src/memory/migrations/019-notification-tables-schema-migration.ts +12 -0
  153. package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +121 -0
  154. package/src/memory/migrations/024-embedding-vector-blob.ts +74 -0
  155. package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +82 -0
  156. package/src/memory/migrations/036-normalize-phone-identities.ts +11 -0
  157. package/src/memory/migrations/116-messages-fts.ts +106 -1
  158. package/src/memory/migrations/126-backfill-guardian-principal-id.ts +52 -0
  159. package/src/memory/migrations/127-guardian-principal-id-not-null.ts +77 -0
  160. package/src/memory/migrations/134-contacts-notes-column.ts +13 -0
  161. package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +20 -0
  162. package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -0
  163. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +13 -0
  164. package/src/memory/migrations/141-rename-verification-table.ts +54 -0
  165. package/src/memory/migrations/142-rename-verification-session-id-column.ts +25 -0
  166. package/src/memory/migrations/143-rename-guardian-verification-values.ts +35 -0
  167. package/src/memory/migrations/144-rename-voice-to-phone.ts +136 -0
  168. package/src/memory/migrations/145-drop-accounts-table.ts +32 -0
  169. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +14 -1
  170. package/src/memory/migrations/148-drop-reminders-table.ts +35 -1
  171. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +69 -1
  172. package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +290 -0
  173. package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +51 -1
  174. package/src/memory/migrations/174-rename-thread-starters-table.ts +47 -1
  175. package/src/memory/migrations/176-drop-capability-card-state.ts +13 -0
  176. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +16 -0
  177. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +28 -1
  178. package/src/memory/migrations/190-call-session-skip-disclosure.ts +15 -0
  179. package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +64 -0
  180. package/src/memory/migrations/192-contacts-user-file-column.ts +15 -0
  181. package/src/memory/migrations/index.ts +4 -0
  182. package/src/memory/migrations/registry.ts +90 -0
  183. package/src/memory/migrations/validate-migration-state.ts +137 -11
  184. package/src/memory/qdrant-circuit-breaker.ts +9 -0
  185. package/src/memory/qdrant-manager.ts +64 -7
  186. package/src/memory/schema/calls.ts +1 -0
  187. package/src/memory/schema/contacts.ts +1 -0
  188. package/src/notifications/decision-engine.ts +4 -1
  189. package/src/oauth/connection-resolver.ts +6 -4
  190. package/src/permissions/checker.ts +0 -38
  191. package/src/permissions/shell-identity.ts +76 -22
  192. package/src/permissions/types.ts +4 -2
  193. package/src/platform/client.ts +35 -7
  194. package/src/prompts/persona-resolver.ts +138 -0
  195. package/src/prompts/system-prompt.ts +36 -4
  196. package/src/prompts/templates/users/default.md +1 -0
  197. package/src/providers/registry.ts +27 -40
  198. package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
  199. package/src/runtime/auth/__tests__/external-assistant-id.test.ts +13 -68
  200. package/src/runtime/auth/external-assistant-id.ts +13 -59
  201. package/src/runtime/auth/route-policy.ts +15 -1
  202. package/src/runtime/auth/token-service.ts +43 -138
  203. package/src/runtime/channel-readiness-service.ts +1 -16
  204. package/src/runtime/http-server.ts +27 -2
  205. package/src/runtime/middleware/error-handler.ts +1 -9
  206. package/src/runtime/routes/audio-routes.ts +40 -0
  207. package/src/runtime/routes/btw-routes.ts +0 -17
  208. package/src/runtime/routes/conversation-query-routes.ts +63 -1
  209. package/src/runtime/routes/conversation-routes.ts +4 -44
  210. package/src/runtime/routes/diagnostics-routes.ts +1 -477
  211. package/src/runtime/routes/identity-routes.ts +18 -29
  212. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -33
  213. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
  214. package/src/runtime/routes/integrations/vercel.ts +89 -0
  215. package/src/runtime/routes/log-export-routes.ts +5 -0
  216. package/src/runtime/routes/memory-item-routes.ts +24 -6
  217. package/src/runtime/routes/migration-rollback-routes.ts +209 -0
  218. package/src/runtime/routes/migration-routes.ts +17 -1
  219. package/src/runtime/routes/notification-routes.ts +58 -0
  220. package/src/runtime/routes/schedule-routes.ts +65 -0
  221. package/src/runtime/routes/settings-routes.ts +41 -1
  222. package/src/runtime/routes/tts-routes.ts +86 -0
  223. package/src/runtime/routes/upgrade-broadcast-routes.ts +26 -2
  224. package/src/runtime/routes/workspace-commit-routes.ts +62 -0
  225. package/src/runtime/routes/workspace-routes.test.ts +22 -1
  226. package/src/runtime/routes/workspace-routes.ts +1 -1
  227. package/src/runtime/routes/workspace-utils.ts +86 -2
  228. package/src/security/ces-credential-client.ts +59 -22
  229. package/src/security/ces-rpc-credential-backend.ts +85 -0
  230. package/src/security/credential-backend.ts +12 -88
  231. package/src/security/keychain-broker-client.ts +10 -2
  232. package/src/security/secure-keys.ts +94 -113
  233. package/src/skills/catalog-install.ts +13 -7
  234. package/src/telemetry/usage-telemetry-reporter.ts +4 -2
  235. package/src/tools/calls/call-start.ts +1 -0
  236. package/src/tools/executor.ts +0 -4
  237. package/src/tools/network/script-proxy/session-manager.ts +19 -4
  238. package/src/tools/network/web-fetch.ts +3 -1
  239. package/src/tools/skills/execute.ts +1 -1
  240. package/src/tools/types.ts +0 -8
  241. package/src/util/errors.ts +0 -12
  242. package/src/util/platform.ts +3 -50
  243. package/src/workspace/git-service.ts +5 -2
  244. package/src/workspace/migrations/001-avatar-rename.ts +15 -0
  245. package/src/workspace/migrations/003-seed-device-id.ts +17 -1
  246. package/src/workspace/migrations/004-extract-collect-usage-data.ts +33 -0
  247. package/src/workspace/migrations/005-add-send-diagnostics.ts +3 -0
  248. package/src/workspace/migrations/006-services-config.ts +49 -0
  249. package/src/workspace/migrations/007-web-search-provider-rename.ts +27 -0
  250. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +3 -0
  251. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +4 -0
  252. package/src/workspace/migrations/010-app-dir-rename.ts +78 -0
  253. package/src/workspace/migrations/011-backfill-installation-id.ts +11 -0
  254. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +44 -0
  255. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +5 -0
  256. package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +153 -0
  257. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +156 -0
  258. package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +150 -0
  259. package/src/workspace/migrations/017-seed-persona-dirs.ts +95 -0
  260. package/src/workspace/migrations/migrate-to-workspace-volume.ts +23 -1
  261. package/src/workspace/migrations/registry.ts +8 -0
  262. package/src/workspace/migrations/runner.ts +106 -2
  263. package/src/workspace/migrations/types.ts +4 -0
  264. package/src/__tests__/claude-code-skill-regression.test.ts +0 -206
  265. package/src/__tests__/claude-code-tool-profiles.test.ts +0 -99
  266. package/src/__tests__/diagnostics-export.test.ts +0 -288
  267. package/src/__tests__/local-gateway-health.test.ts +0 -209
  268. package/src/__tests__/secret-ingress-handler.test.ts +0 -120
  269. package/src/__tests__/swarm-conversation-integration.test.ts +0 -358
  270. package/src/__tests__/swarm-dag-pathological.test.ts +0 -547
  271. package/src/__tests__/swarm-orchestrator.test.ts +0 -463
  272. package/src/__tests__/swarm-plan-validator.test.ts +0 -384
  273. package/src/__tests__/swarm-recursion.test.ts +0 -197
  274. package/src/__tests__/swarm-router-planner.test.ts +0 -234
  275. package/src/__tests__/swarm-tool.test.ts +0 -185
  276. package/src/__tests__/swarm-worker-backend.test.ts +0 -144
  277. package/src/__tests__/swarm-worker-runner.test.ts +0 -288
  278. package/src/commands/__tests__/cc-command-registry.test.ts +0 -396
  279. package/src/commands/cc-command-registry.ts +0 -248
  280. package/src/config/bundled-skills/claude-code/SKILL.md +0 -53
  281. package/src/config/bundled-skills/claude-code/TOOLS.json +0 -47
  282. package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -12
  283. package/src/config/bundled-skills/orchestration/SKILL.md +0 -33
  284. package/src/config/bundled-skills/orchestration/TOOLS.json +0 -35
  285. package/src/config/bundled-skills/orchestration/tools/swarm-delegate.ts +0 -12
  286. package/src/config/schemas/swarm.ts +0 -82
  287. package/src/logfire.ts +0 -135
  288. package/src/runtime/local-gateway-health.ts +0 -275
  289. package/src/security/secret-ingress.ts +0 -68
  290. package/src/swarm/backend-claude-code.ts +0 -225
  291. package/src/swarm/checkpoint.ts +0 -137
  292. package/src/swarm/graph-utils.ts +0 -53
  293. package/src/swarm/index.ts +0 -55
  294. package/src/swarm/limits.ts +0 -66
  295. package/src/swarm/orchestrator.ts +0 -424
  296. package/src/swarm/plan-validator.ts +0 -117
  297. package/src/swarm/router-planner.ts +0 -162
  298. package/src/swarm/router-prompts.ts +0 -39
  299. package/src/swarm/synthesizer.ts +0 -81
  300. package/src/swarm/types.ts +0 -72
  301. package/src/swarm/worker-backend.ts +0 -131
  302. package/src/swarm/worker-prompts.ts +0 -80
  303. package/src/swarm/worker-runner.ts +0 -170
  304. package/src/tools/claude-code/claude-code.ts +0 -610
  305. package/src/tools/swarm/delegate.ts +0 -205
@@ -1,53 +0,0 @@
1
- /**
2
- * Shared graph utilities for swarm DAG operations.
3
- */
4
-
5
- export interface GraphNode {
6
- id: string;
7
- dependencies: string[];
8
- }
9
-
10
- /**
11
- * Detect cycles in a directed graph using Kahn's algorithm.
12
- * Returns the IDs of nodes involved in cycles, or null if the graph is acyclic.
13
- */
14
- export function detectCycles(nodes: GraphNode[]): string[] | null {
15
- const inDegree = new Map<string, number>();
16
- const adj = new Map<string, string[]>();
17
-
18
- for (const node of nodes) {
19
- inDegree.set(node.id, 0);
20
- adj.set(node.id, []);
21
- }
22
-
23
- for (const node of nodes) {
24
- for (const dep of node.dependencies) {
25
- if (adj.has(dep)) {
26
- adj.get(dep)!.push(node.id);
27
- inDegree.set(node.id, (inDegree.get(node.id) ?? 0) + 1);
28
- }
29
- }
30
- }
31
-
32
- const queue: string[] = [];
33
- for (const [id, deg] of inDegree) {
34
- if (deg === 0) queue.push(id);
35
- }
36
-
37
- let processed = 0;
38
- while (queue.length > 0) {
39
- const current = queue.shift()!;
40
- processed++;
41
- for (const neighbor of adj.get(current)!) {
42
- const newDeg = (inDegree.get(neighbor) ?? 0) - 1;
43
- inDegree.set(neighbor, newDeg);
44
- if (newDeg === 0) queue.push(neighbor);
45
- }
46
- }
47
-
48
- if (processed < nodes.length) {
49
- return nodes.filter((n) => (inDegree.get(n.id) ?? 0) > 0).map((n) => n.id);
50
- }
51
-
52
- return null;
53
- }
@@ -1,55 +0,0 @@
1
- export type { SwarmCheckpoint } from "./checkpoint.js";
2
- export {
3
- isCheckpointCompatible,
4
- loadCheckpoint,
5
- removeCheckpoint,
6
- writeCheckpoint,
7
- } from "./checkpoint.js";
8
- export type { SwarmLimits } from "./limits.js";
9
- export {
10
- getTimeoutForRole,
11
- resolveSwarmLimits,
12
- SWARM_HARD_LIMITS,
13
- } from "./limits.js";
14
- export type {
15
- ExecuteSwarmOptions,
16
- OrchestratorEvent,
17
- OrchestratorEventKind,
18
- OrchestratorStatusCallback,
19
- } from "./orchestrator.js";
20
- export { executeSwarm } from "./orchestrator.js";
21
- export {
22
- SwarmPlanValidationError,
23
- validateAndNormalizePlan,
24
- } from "./plan-validator.js";
25
- export {
26
- generatePlan,
27
- makeFallbackPlan,
28
- parsePlanJSON,
29
- } from "./router-planner.js";
30
- export { synthesizeResults } from "./synthesizer.js";
31
- export type {
32
- SwarmExecutionSummary,
33
- SwarmPlan,
34
- SwarmRole,
35
- SwarmTaskNode,
36
- SwarmTaskResult,
37
- SwarmTaskStatus,
38
- } from "./types.js";
39
- export { VALID_SWARM_ROLES } from "./types.js";
40
- export type {
41
- ProfilePolicy,
42
- SwarmWorkerBackend,
43
- SwarmWorkerBackendInput,
44
- SwarmWorkerBackendResult,
45
- WorkerFailureReason,
46
- WorkerProfile,
47
- } from "./worker-backend.js";
48
- export { getProfilePolicy, roleToProfile } from "./worker-backend.js";
49
- export { buildWorkerPrompt, parseWorkerOutput } from "./worker-prompts.js";
50
- export type {
51
- RunWorkerTaskOptions,
52
- WorkerStatusCallback,
53
- WorkerStatusKind,
54
- } from "./worker-runner.js";
55
- export { runWorkerTask } from "./worker-runner.js";
@@ -1,66 +0,0 @@
1
- /**
2
- * Runtime limits for swarm execution, resolved from config.
3
- */
4
-
5
- import type { SwarmRole } from "./types.js";
6
-
7
- export interface SwarmLimits {
8
- maxWorkers: number;
9
- maxTasks: number;
10
- maxRetriesPerTask: number;
11
- workerTimeoutSec: number;
12
- /** Per-role timeout overrides. When set, takes precedence over workerTimeoutSec for that role. */
13
- roleTimeoutsSec: Partial<Record<SwarmRole, number>>;
14
- }
15
-
16
- /** Hard ceilings that config values are clamped to. */
17
- export const SWARM_HARD_LIMITS = {
18
- maxWorkers: 6,
19
- maxTasks: 20,
20
- maxRetriesPerTask: 3,
21
- } as const;
22
-
23
- /**
24
- * Resolve effective limits from config, clamping to hard ceilings.
25
- */
26
- export function resolveSwarmLimits(config: {
27
- maxWorkers: number;
28
- maxTasks: number;
29
- maxRetriesPerTask: number;
30
- workerTimeoutSec: number;
31
- roleTimeoutsSec?: Partial<Record<SwarmRole, number>>;
32
- }): SwarmLimits {
33
- const resolvedRoleTimeouts: Partial<Record<SwarmRole, number>> = {};
34
- if (config.roleTimeoutsSec) {
35
- for (const [role, timeout] of Object.entries(config.roleTimeoutsSec)) {
36
- if (timeout != null) {
37
- resolvedRoleTimeouts[role as SwarmRole] = Math.max(1, timeout);
38
- }
39
- }
40
- }
41
-
42
- return {
43
- maxWorkers: Math.min(
44
- Math.max(1, config.maxWorkers),
45
- SWARM_HARD_LIMITS.maxWorkers,
46
- ),
47
- maxTasks: Math.min(
48
- Math.max(1, config.maxTasks),
49
- SWARM_HARD_LIMITS.maxTasks,
50
- ),
51
- maxRetriesPerTask: Math.min(
52
- Math.max(0, config.maxRetriesPerTask),
53
- SWARM_HARD_LIMITS.maxRetriesPerTask,
54
- ),
55
- workerTimeoutSec: Math.max(1, config.workerTimeoutSec),
56
- roleTimeoutsSec: resolvedRoleTimeouts,
57
- };
58
- }
59
-
60
- /** Get the effective timeout for a given role, falling back to the global workerTimeoutSec. */
61
- export function getTimeoutForRole(
62
- limits: SwarmLimits,
63
- role: SwarmRole,
64
- ): number {
65
- return limits.roleTimeoutsSec[role] ?? limits.workerTimeoutSec;
66
- }
@@ -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
- }