@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,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
- }
@@ -1,39 +0,0 @@
1
- /**
2
- * System prompt for the router planner LLM call.
3
- */
4
- export const ROUTER_SYSTEM_PROMPT = `You are a task decomposition planner. Given a user objective, break it down into a set of parallel and sequential tasks that can be executed by specialist workers.
5
-
6
- Available worker roles:
7
- - researcher: Can search the web, read files, and gather information. Cannot write or edit files.
8
- - coder: Can read, write, and edit files, run shell commands, and implement code changes.
9
- - reviewer: Can read and search files to review code. Cannot write or edit files.
10
-
11
- Rules:
12
- 1. Output ONLY a valid JSON object. No prose, no markdown, no explanation.
13
- 2. Each task must have a unique "id" (short slug), a "role", an "objective" (clear instruction), and "dependencies" (array of task IDs that must complete first).
14
- 3. Maximize parallelism: only add a dependency if the task truly needs output from another.
15
- 4. Keep the plan minimal — avoid unnecessary tasks. Prefer fewer, well-scoped tasks.
16
- 5. Do not create tasks for the "router" role.
17
- 6. The total number of tasks must not exceed the provided limit.
18
-
19
- Output schema:
20
- {
21
- "tasks": [
22
- {
23
- "id": "string",
24
- "role": "researcher" | "coder" | "reviewer",
25
- "objective": "string",
26
- "dependencies": ["task-id", ...]
27
- }
28
- ]
29
- }`;
30
-
31
- /**
32
- * Build the user message for the router planner.
33
- */
34
- export function buildPlannerUserMessage(
35
- objective: string,
36
- maxTasks: number,
37
- ): string {
38
- return `Objective: ${objective}\n\nMaximum tasks allowed: ${maxTasks}\n\nReturn ONLY the JSON plan.`;
39
- }
@@ -1,81 +0,0 @@
1
- import type { Message, ModelIntent, Provider } from "../providers/types.js";
2
- import type { SwarmTaskResult } from "./types.js";
3
-
4
- /**
5
- * Synthesize a final answer from all worker results using an LLM.
6
- * Falls back to a deterministic markdown summary if the LLM call fails.
7
- */
8
- export async function synthesizeResults(opts: {
9
- objective: string;
10
- results: SwarmTaskResult[];
11
- provider: Provider;
12
- modelIntent?: ModelIntent;
13
- }): Promise<string> {
14
- const { objective, results, provider, modelIntent } = opts;
15
-
16
- // Cap individual summaries and total input to avoid blowing up context on large plans
17
- const MAX_SUMMARY_CHARS = 500;
18
- const MAX_TOTAL_CHARS = 12_000;
19
-
20
- let taskSummaries = results
21
- .map((r) => {
22
- const status = r.status === "completed" ? "completed" : "FAILED";
23
- const summary =
24
- r.summary.length > MAX_SUMMARY_CHARS
25
- ? r.summary.slice(0, MAX_SUMMARY_CHARS) + "..."
26
- : r.summary;
27
- return `[${r.taskId}] (${status}): ${summary}`;
28
- })
29
- .join("\n");
30
-
31
- if (taskSummaries.length > MAX_TOTAL_CHARS) {
32
- taskSummaries =
33
- taskSummaries.slice(0, MAX_TOTAL_CHARS) + "\n... (truncated)";
34
- }
35
-
36
- const systemPrompt =
37
- "You are a synthesis assistant. Combine the outputs from multiple specialist workers into a coherent, concise final answer. Focus on the user's original objective.";
38
-
39
- const userMessage = `Original objective: ${objective}
40
-
41
- Worker results:
42
- ${taskSummaries}
43
-
44
- Synthesize these results into a clear, complete answer for the user.`;
45
-
46
- try {
47
- const messages: Message[] = [
48
- { role: "user", content: [{ type: "text", text: userMessage }] },
49
- ];
50
-
51
- const response = await provider.sendMessage(
52
- messages,
53
- undefined,
54
- systemPrompt,
55
- { config: { max_tokens: 4096, modelIntent } },
56
- );
57
-
58
- const textBlock = response.content.find((b) => b.type === "text");
59
- if (textBlock && textBlock.type === "text") {
60
- return textBlock.text;
61
- }
62
-
63
- return buildFallbackSynthesis(objective, results);
64
- } catch {
65
- return buildFallbackSynthesis(objective, results);
66
- }
67
- }
68
-
69
- function buildFallbackSynthesis(
70
- objective: string,
71
- results: SwarmTaskResult[],
72
- ): string {
73
- const lines: string[] = [`## Results: ${objective}`, ""];
74
-
75
- for (const r of results) {
76
- const icon = r.status === "completed" ? "OK" : "FAIL";
77
- lines.push(`- [${icon}] **${r.taskId}**: ${r.summary}`);
78
- }
79
-
80
- return lines.join("\n");
81
- }
@@ -1,72 +0,0 @@
1
- /**
2
- * Swarm runtime types for multi-worker task orchestration.
3
- */
4
-
5
- export type SwarmRole = "router" | "researcher" | "coder" | "reviewer";
6
-
7
- export const VALID_SWARM_ROLES: readonly SwarmRole[] = [
8
- "router",
9
- "researcher",
10
- "coder",
11
- "reviewer",
12
- ] as const;
13
-
14
- export interface SwarmTaskNode {
15
- /** Unique identifier within the plan. */
16
- id: string;
17
- /** Role that will execute this task. */
18
- role: SwarmRole;
19
- /** Human-readable objective for the worker. */
20
- objective: string;
21
- /** IDs of tasks that must complete before this one can start. */
22
- dependencies: string[];
23
- }
24
-
25
- export interface SwarmPlan {
26
- /** Top-level objective that was decomposed into tasks. */
27
- objective: string;
28
- /** Ordered list of tasks forming a DAG. */
29
- tasks: SwarmTaskNode[];
30
- }
31
-
32
- export type SwarmTaskStatus =
33
- | "queued"
34
- | "running"
35
- | "completed"
36
- | "failed"
37
- | "blocked";
38
-
39
- export interface SwarmTaskResult {
40
- taskId: string;
41
- status: "completed" | "failed";
42
- /** Structured summary from the worker. */
43
- summary: string;
44
- /** Artifacts produced (file paths, code snippets, etc.). */
45
- artifacts: string[];
46
- /** Issues encountered during execution. */
47
- issues: string[];
48
- /** Suggested follow-up steps. */
49
- nextSteps: string[];
50
- /** Raw unprocessed output from the worker backend. */
51
- rawOutput: string;
52
- /** Wall-clock duration in milliseconds. */
53
- durationMs: number;
54
- /** Number of retry attempts before final result. */
55
- retryCount: number;
56
- }
57
-
58
- export interface SwarmExecutionSummary {
59
- objective: string;
60
- plan: SwarmPlan;
61
- results: SwarmTaskResult[];
62
- /** Synthesized final answer combining all worker outputs. */
63
- finalAnswer: string;
64
- /** Aggregate stats. */
65
- stats: {
66
- totalTasks: number;
67
- completed: number;
68
- failed: number;
69
- blocked: number;
70
- totalDurationMs: number;
71
- };
72
- }
@@ -1,131 +0,0 @@
1
- import type { SwarmRole } from "./types.js";
2
-
3
- /**
4
- * Profile names that scope tool access for worker tasks.
5
- */
6
- export type WorkerProfile = "general" | "researcher" | "coder" | "reviewer";
7
-
8
- /**
9
- * Maps swarm roles to worker profiles.
10
- */
11
- export function roleToProfile(role: SwarmRole): WorkerProfile {
12
- switch (role) {
13
- case "researcher":
14
- return "researcher";
15
- case "coder":
16
- return "coder";
17
- case "reviewer":
18
- return "reviewer";
19
- case "router":
20
- return "general";
21
- default:
22
- return "general";
23
- }
24
- }
25
-
26
- /**
27
- * Per-profile tool access policy. The sets are checked in order:
28
- * 1. If tool is in `deny`, block unconditionally.
29
- * 2. If tool is in `allow`, auto-approve.
30
- * 3. If tool is in `approvalRequired`, prompt user.
31
- * 4. Otherwise follow default approval flow.
32
- */
33
- export interface ProfilePolicy {
34
- allow: Set<string>;
35
- deny: Set<string>;
36
- approvalRequired: Set<string>;
37
- }
38
-
39
- const READ_ONLY_TOOLS = new Set([
40
- "Read",
41
- "Glob",
42
- "Grep",
43
- "WebSearch",
44
- "WebFetch",
45
- "LS",
46
- "Bash(grep *)",
47
- "Bash(rg *)",
48
- "Bash(find *)",
49
- ]);
50
-
51
- const WRITE_TOOLS = new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
52
-
53
- /**
54
- * Return the tool access policy for a given profile.
55
- */
56
- export function getProfilePolicy(profile: WorkerProfile): ProfilePolicy {
57
- switch (profile) {
58
- case "general":
59
- return {
60
- allow: new Set(READ_ONLY_TOOLS),
61
- deny: new Set(),
62
- approvalRequired: new Set(["Bash", ...WRITE_TOOLS]),
63
- };
64
-
65
- case "researcher":
66
- return {
67
- allow: new Set(READ_ONLY_TOOLS),
68
- deny: new Set([...WRITE_TOOLS, "Bash"]),
69
- approvalRequired: new Set(),
70
- };
71
-
72
- case "coder":
73
- return {
74
- allow: new Set(READ_ONLY_TOOLS),
75
- deny: new Set(),
76
- approvalRequired: new Set(["Bash", ...WRITE_TOOLS]),
77
- };
78
-
79
- case "reviewer":
80
- return {
81
- allow: new Set(READ_ONLY_TOOLS),
82
- deny: new Set([...WRITE_TOOLS, "Bash"]),
83
- approvalRequired: new Set(),
84
- };
85
-
86
- default:
87
- return {
88
- allow: new Set(READ_ONLY_TOOLS),
89
- deny: new Set(),
90
- approvalRequired: new Set(["Bash", ...WRITE_TOOLS]),
91
- };
92
- }
93
- }
94
-
95
- // ---------------------------------------------------------------------------
96
- // Worker Backend
97
- // ---------------------------------------------------------------------------
98
-
99
- export type WorkerFailureReason =
100
- | "backend_unavailable"
101
- | "timeout"
102
- | "cancelled"
103
- | "malformed_output"
104
- | "tool_denied";
105
-
106
- export interface SwarmWorkerBackendResult {
107
- success: boolean;
108
- output: string;
109
- failureReason?: WorkerFailureReason;
110
- durationMs: number;
111
- }
112
-
113
- export interface SwarmWorkerBackendInput {
114
- prompt: string;
115
- profile: WorkerProfile;
116
- workingDir: string;
117
- modelIntent?: string;
118
- timeoutMs?: number;
119
- signal?: AbortSignal;
120
- }
121
-
122
- /**
123
- * Abstraction over execution backends for swarm workers.
124
- */
125
- export interface SwarmWorkerBackend {
126
- readonly name: string;
127
- /** Check whether the backend is available (e.g. API key present). */
128
- isAvailable(): boolean | Promise<boolean>;
129
- /** Run a task with the given input. */
130
- runTask(input: SwarmWorkerBackendInput): Promise<SwarmWorkerBackendResult>;
131
- }
@@ -1,80 +0,0 @@
1
- import { parseJsonSafe } from "../util/json.js";
2
- import { truncate } from "../util/truncate.js";
3
- import type { SwarmRole, SwarmTaskResult } from "./types.js";
4
-
5
- /**
6
- * Build a role-specific worker prompt for a swarm task.
7
- */
8
- export function buildWorkerPrompt(opts: {
9
- role: SwarmRole;
10
- objective: string;
11
- upstreamContext?: string;
12
- dependencyOutputs?: Array<{ taskId: string; summary: string }>;
13
- }): string {
14
- const parts: string[] = [];
15
-
16
- parts.push(
17
- `You are a ${opts.role} worker in a swarm. Your objective:\n${opts.objective}`,
18
- );
19
-
20
- if (opts.upstreamContext) {
21
- parts.push(`\nContext from the orchestrator:\n${opts.upstreamContext}`);
22
- }
23
-
24
- if (opts.dependencyOutputs && opts.dependencyOutputs.length > 0) {
25
- parts.push("\nOutputs from prerequisite tasks:");
26
- for (const dep of opts.dependencyOutputs) {
27
- parts.push(`- [${dep.taskId}]: ${dep.summary}`);
28
- }
29
- }
30
-
31
- parts.push(WORKER_OUTPUT_CONTRACT);
32
-
33
- return parts.join("\n");
34
- }
35
-
36
- const WORKER_OUTPUT_CONTRACT = `
37
-
38
- When you are finished, output your result as a single fenced JSON block:
39
-
40
- \`\`\`json
41
- {
42
- "summary": "Brief summary of what you accomplished",
43
- "artifacts": ["list of file paths, code snippets, or other outputs"],
44
- "issues": ["list of problems encountered, if any"],
45
- "nextSteps": ["suggested follow-up actions, if any"]
46
- }
47
- \`\`\`
48
-
49
- If you cannot produce valid JSON, just write a plain-text summary.`;
50
-
51
- /**
52
- * Parse the worker's raw output into a structured result shape.
53
- * Scans fenced JSON blocks from last to first, picking the last one that
54
- * matches the worker-result contract (has a `summary` string field).
55
- * Falls back to treating the entire output as a plain-text summary.
56
- */
57
- export function parseWorkerOutput(
58
- raw: string,
59
- ): Pick<SwarmTaskResult, "summary" | "artifacts" | "issues" | "nextSteps"> {
60
- const jsonBlocks = Array.from(raw.matchAll(/```json\s*\n([\s\S]*?)\n```/g));
61
-
62
- // Walk backwards to prefer the final valid contract block.
63
- for (let i = jsonBlocks.length - 1; i >= 0; i--) {
64
- const parsed = parseJsonSafe<Record<string, unknown>>(jsonBlocks[i][1]);
65
- if (!parsed || typeof parsed.summary !== "string") continue;
66
- return {
67
- summary: parsed.summary,
68
- artifacts: Array.isArray(parsed.artifacts) ? parsed.artifacts : [],
69
- issues: Array.isArray(parsed.issues) ? parsed.issues : [],
70
- nextSteps: Array.isArray(parsed.nextSteps) ? parsed.nextSteps : [],
71
- };
72
- }
73
-
74
- return {
75
- summary: truncate(raw, 500, ""),
76
- artifacts: [],
77
- issues: [],
78
- nextSteps: [],
79
- };
80
- }