@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,234 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
-
3
- import type { Provider, ProviderResponse } from "../providers/types.js";
4
- import { resolveSwarmLimits } from "../swarm/limits.js";
5
- import {
6
- generatePlan,
7
- makeFallbackPlan,
8
- parsePlanJSON,
9
- } from "../swarm/router-planner.js";
10
-
11
- const DEFAULT_LIMITS = resolveSwarmLimits({
12
- maxWorkers: 3,
13
- maxTasks: 8,
14
- maxRetriesPerTask: 1,
15
- workerTimeoutSec: 900,
16
- });
17
-
18
- function makeProvider(responseText: string): Provider {
19
- return {
20
- name: "test",
21
- async sendMessage(): Promise<ProviderResponse> {
22
- return {
23
- content: [{ type: "text", text: responseText }],
24
- model: "test-model",
25
- usage: { inputTokens: 100, outputTokens: 50 },
26
- stopReason: "end_turn",
27
- };
28
- },
29
- };
30
- }
31
-
32
- function makeFailingProvider(): Provider {
33
- return {
34
- name: "test",
35
- async sendMessage(): Promise<ProviderResponse> {
36
- throw new Error("API error");
37
- },
38
- };
39
- }
40
-
41
- describe("parsePlanJSON", () => {
42
- test("parses bare JSON", () => {
43
- const result = parsePlanJSON(
44
- '{"tasks":[{"id":"a","role":"coder","objective":"do stuff","dependencies":[]}]}',
45
- );
46
- expect(result).not.toBeNull();
47
- expect(result!.tasks).toHaveLength(1);
48
- expect(result!.tasks[0].id).toBe("a");
49
- });
50
-
51
- test("parses fenced JSON", () => {
52
- const raw =
53
- '```json\n{"tasks":[{"id":"a","role":"coder","objective":"do stuff","dependencies":[]}]}\n```';
54
- const result = parsePlanJSON(raw);
55
- expect(result).not.toBeNull();
56
- expect(result!.tasks).toHaveLength(1);
57
- });
58
-
59
- test("parses fenced code block without json tag", () => {
60
- const raw =
61
- '```\n{"tasks":[{"id":"a","role":"coder","objective":"do stuff","dependencies":[]}]}\n```';
62
- const result = parsePlanJSON(raw);
63
- expect(result).not.toBeNull();
64
- });
65
-
66
- test("returns null for invalid JSON", () => {
67
- expect(parsePlanJSON("this is not json")).toBeNull();
68
- });
69
-
70
- test("returns null for JSON without tasks array", () => {
71
- expect(parsePlanJSON('{"plan":"something"}')).toBeNull();
72
- });
73
-
74
- test("returns null for empty string", () => {
75
- expect(parsePlanJSON("")).toBeNull();
76
- });
77
-
78
- test("finds plan JSON in second fenced block when first is not valid JSON", () => {
79
- const raw =
80
- '```\nHere is my thinking about the plan\n```\n\n```json\n{"tasks":[{"id":"a","role":"coder","objective":"do stuff","dependencies":[]}]}\n```';
81
- const result = parsePlanJSON(raw);
82
- expect(result).not.toBeNull();
83
- expect(result!.tasks).toHaveLength(1);
84
- expect(result!.tasks[0].id).toBe("a");
85
- });
86
-
87
- test("finds plan JSON in second fenced block when first has no tasks array", () => {
88
- const raw =
89
- '```json\n{"note":"not a plan"}\n```\n\n```json\n{"tasks":[{"id":"b","role":"researcher","objective":"research","dependencies":[]}]}\n```';
90
- const result = parsePlanJSON(raw);
91
- expect(result).not.toBeNull();
92
- expect(result!.tasks[0].id).toBe("b");
93
- });
94
- });
95
-
96
- describe("makeFallbackPlan", () => {
97
- test("creates single coder task", () => {
98
- const plan = makeFallbackPlan("Build a feature");
99
- expect(plan.objective).toBe("Build a feature");
100
- expect(plan.tasks).toHaveLength(1);
101
- expect(plan.tasks[0].role).toBe("coder");
102
- expect(plan.tasks[0].id).toBe("fallback-coder");
103
- expect(plan.tasks[0].dependencies).toEqual([]);
104
- });
105
- });
106
-
107
- describe("generatePlan", () => {
108
- test("generates a valid plan from LLM response", async () => {
109
- const responseText = JSON.stringify({
110
- tasks: [
111
- {
112
- id: "research",
113
- role: "researcher",
114
- objective: "Find docs",
115
- dependencies: [],
116
- },
117
- {
118
- id: "code",
119
- role: "coder",
120
- objective: "Implement",
121
- dependencies: ["research"],
122
- },
123
- ],
124
- });
125
- const plan = await generatePlan({
126
- objective: "Build feature X",
127
- provider: makeProvider(responseText),
128
- modelIntent: "latency-optimized",
129
- limits: DEFAULT_LIMITS,
130
- });
131
- expect(plan.tasks).toHaveLength(2);
132
- expect(plan.tasks[0].id).toBe("research");
133
- expect(plan.tasks[1].dependencies).toContain("research");
134
- });
135
-
136
- test("falls back on invalid LLM JSON", async () => {
137
- const plan = await generatePlan({
138
- objective: "Build feature X",
139
- provider: makeProvider("Sorry, I cannot help with that."),
140
- modelIntent: "latency-optimized",
141
- limits: DEFAULT_LIMITS,
142
- });
143
- expect(plan.tasks).toHaveLength(1);
144
- expect(plan.tasks[0].id).toBe("fallback-coder");
145
- });
146
-
147
- test("falls back when provider throws", async () => {
148
- const plan = await generatePlan({
149
- objective: "Build feature X",
150
- provider: makeFailingProvider(),
151
- modelIntent: "latency-optimized",
152
- limits: DEFAULT_LIMITS,
153
- });
154
- expect(plan.tasks).toHaveLength(1);
155
- expect(plan.tasks[0].id).toBe("fallback-coder");
156
- });
157
-
158
- test("falls back when LLM plan has invalid roles", async () => {
159
- const responseText = JSON.stringify({
160
- tasks: [
161
- {
162
- id: "hack",
163
- role: "hacker",
164
- objective: "Hack stuff",
165
- dependencies: [],
166
- },
167
- ],
168
- });
169
- const plan = await generatePlan({
170
- objective: "Build feature X",
171
- provider: makeProvider(responseText),
172
- modelIntent: "latency-optimized",
173
- limits: DEFAULT_LIMITS,
174
- });
175
- // Plan validator rejects invalid roles, so generatePlan catches and falls back
176
- expect(plan.tasks).toHaveLength(1);
177
- expect(plan.tasks[0].id).toBe("fallback-coder");
178
- });
179
-
180
- test("falls back when LLM plan has cycles", async () => {
181
- const responseText = JSON.stringify({
182
- tasks: [
183
- { id: "a", role: "coder", objective: "A", dependencies: ["b"] },
184
- { id: "b", role: "coder", objective: "B", dependencies: ["a"] },
185
- ],
186
- });
187
- const plan = await generatePlan({
188
- objective: "Build feature X",
189
- provider: makeProvider(responseText),
190
- modelIntent: "latency-optimized",
191
- limits: DEFAULT_LIMITS,
192
- });
193
- expect(plan.tasks).toHaveLength(1);
194
- expect(plan.tasks[0].id).toBe("fallback-coder");
195
- });
196
-
197
- test("truncates plans exceeding maxTasks", async () => {
198
- const tasks = Array.from({ length: 15 }, (_, i) => ({
199
- id: `t${i}`,
200
- role: "coder",
201
- objective: `Task ${i}`,
202
- dependencies: [],
203
- }));
204
- const plan = await generatePlan({
205
- objective: "Big feature",
206
- provider: makeProvider(JSON.stringify({ tasks })),
207
- modelIntent: "latency-optimized",
208
- limits: DEFAULT_LIMITS,
209
- });
210
- expect(plan.tasks.length).toBeLessThanOrEqual(DEFAULT_LIMITS.maxTasks);
211
- });
212
-
213
- test("handles response with no text content", async () => {
214
- const provider: Provider = {
215
- name: "test",
216
- async sendMessage(): Promise<ProviderResponse> {
217
- return {
218
- content: [],
219
- model: "test-model",
220
- usage: { inputTokens: 0, outputTokens: 0 },
221
- stopReason: "end_turn",
222
- };
223
- },
224
- };
225
- const plan = await generatePlan({
226
- objective: "Build feature",
227
- provider,
228
- modelIntent: "latency-optimized",
229
- limits: DEFAULT_LIMITS,
230
- });
231
- expect(plan.tasks).toHaveLength(1);
232
- expect(plan.tasks[0].id).toBe("fallback-coder");
233
- });
234
- });
@@ -1,185 +0,0 @@
1
- import { beforeEach, describe, expect, mock, test } from "bun:test";
2
-
3
- // ---------------------------------------------------------------------------
4
- // Mocks — declared before imports that depend on them
5
- // ---------------------------------------------------------------------------
6
-
7
- mock.module("../util/logger.js", () => ({
8
- getLogger: () =>
9
- new Proxy({} as Record<string, unknown>, {
10
- get: () => () => {},
11
- }),
12
- }));
13
-
14
- mock.module("../config/loader.js", () => ({
15
- getConfig: () => ({
16
- ui: {},
17
-
18
- provider: "anthropic",
19
- providerOrder: ["anthropic"],
20
- swarm: {
21
- enabled: true,
22
- maxWorkers: 3,
23
- maxTasks: 8,
24
- maxRetriesPerTask: 1,
25
- workerTimeoutSec: 900,
26
- roleTimeoutsSec: {},
27
- plannerModelIntent: "latency-optimized",
28
- synthesizerModelIntent: "quality-optimized",
29
- },
30
- services: {
31
- inference: {
32
- mode: "your-own",
33
- provider: "anthropic",
34
- model: "claude-opus-4-6",
35
- },
36
- "image-generation": {
37
- mode: "your-own",
38
- provider: "gemini",
39
- model: "gemini-3.1-flash-image-preview",
40
- },
41
- "web-search": { mode: "your-own", provider: "inference-provider-native" },
42
- },
43
- }),
44
- getSwarmDisabledConfig: () => ({
45
- provider: "anthropic",
46
- providerOrder: ["anthropic"],
47
- swarm: {
48
- enabled: false,
49
- maxWorkers: 3,
50
- maxTasks: 8,
51
- maxRetriesPerTask: 1,
52
- workerTimeoutSec: 900,
53
- roleTimeoutsSec: {},
54
- plannerModelIntent: "latency-optimized",
55
- synthesizerModelIntent: "quality-optimized",
56
- },
57
- }),
58
- }));
59
-
60
- // Mock provider registry — returns a mock provider
61
- const mockProvider = {
62
- name: "test",
63
- async sendMessage() {
64
- return {
65
- content: [
66
- {
67
- type: "text",
68
- text: '{"tasks":[{"id":"t1","role":"coder","objective":"Do it","dependencies":[]}]}',
69
- },
70
- ],
71
- model: "test",
72
- usage: { inputTokens: 10, outputTokens: 10 },
73
- stopReason: "end_turn",
74
- };
75
- },
76
- };
77
- mock.module("../security/secure-keys.js", () => ({
78
- getSecureKeyAsync: async () => "test-api-key",
79
- getProviderKeyAsync: async () => "test-api-key",
80
- }));
81
-
82
- mock.module("../providers/registry.js", () => ({
83
- getProvider: () => mockProvider,
84
- getFailoverProvider: () => mockProvider,
85
- }));
86
-
87
- // Mock the Agent SDK to prevent real subprocess spawning
88
- mock.module("@anthropic-ai/claude-agent-sdk", () => ({
89
- query: () => ({
90
- async *[Symbol.asyncIterator]() {
91
- yield {
92
- type: "result" as const,
93
- session_id: "test-session",
94
- subtype: "success" as const,
95
- result:
96
- '```json\n{"summary":"Done","artifacts":[],"issues":[],"nextSteps":[]}\n```',
97
- };
98
- },
99
- }),
100
- }));
101
-
102
- import {
103
- _resetSwarmActive,
104
- swarmDelegateTool,
105
- } from "../tools/swarm/delegate.js";
106
- import type { ToolContext } from "../tools/types.js";
107
-
108
- function makeContext(overrides?: Partial<ToolContext>): ToolContext {
109
- return {
110
- conversationId: "test-session",
111
- workingDir: "/tmp/test",
112
- trustClass: "guardian",
113
- onOutput: () => {},
114
- ...overrides,
115
- } as ToolContext;
116
- }
117
-
118
- describe("swarm_delegate tool", () => {
119
- beforeEach(() => {
120
- _resetSwarmActive();
121
- });
122
-
123
- test("getDefinition returns valid schema", () => {
124
- const def = swarmDelegateTool.getDefinition();
125
- expect(def.name).toBe("swarm_delegate");
126
- const props = (def.input_schema as Record<string, unknown>)
127
- .properties as Record<string, unknown>;
128
- expect(props.objective).toBeDefined();
129
- expect(props.context).toBeDefined();
130
- expect(props.max_workers).toBeDefined();
131
- });
132
-
133
- test("executes successfully with a simple objective", async () => {
134
- const outputs: string[] = [];
135
- const result = await swarmDelegateTool.execute(
136
- { objective: "Build a simple feature" },
137
- makeContext({ onOutput: (text: string) => outputs.push(text) }),
138
- );
139
-
140
- expect(result.isError).toBeFalsy();
141
- expect(result.content).toBeTruthy();
142
- expect(outputs.length).toBeGreaterThan(0);
143
- });
144
-
145
- test("blocks nested swarm invocation", async () => {
146
- // Simulate active swarm by calling _resetSwarmActive then manually setting it
147
- // We test this by running two sequential calls where the first doesn't finish
148
- // Actually, we can test by checking the recursion guard directly
149
- const result1Promise = swarmDelegateTool.execute(
150
- { objective: "First task" },
151
- makeContext(),
152
- );
153
-
154
- // While first is running, try a second
155
- // Since the mock backend resolves instantly, we need to be creative
156
- // Let's just verify the guard works by testing post-execution
157
- await result1Promise;
158
-
159
- // After completion, the flag should be reset
160
- const result2 = await swarmDelegateTool.execute(
161
- { objective: "Second task" },
162
- makeContext(),
163
- );
164
- expect(result2.isError).toBeFalsy();
165
- });
166
-
167
- test("handles objective with context", async () => {
168
- const result = await swarmDelegateTool.execute(
169
- { objective: "Build feature", context: "This is a React project" },
170
- makeContext(),
171
- );
172
- expect(result.isError).toBeFalsy();
173
- });
174
-
175
- test("short-circuits when signal is already aborted", async () => {
176
- const controller = new AbortController();
177
- controller.abort();
178
- const result = await swarmDelegateTool.execute(
179
- { objective: "Should not run" },
180
- makeContext({ signal: controller.signal }),
181
- );
182
- expect(result.isError).toBe(true);
183
- expect(result.content).toBe("Cancelled");
184
- });
185
- });
@@ -1,144 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
-
3
- import type { WorkerProfile } from "../swarm/worker-backend.js";
4
- import { getProfilePolicy, roleToProfile } from "../swarm/worker-backend.js";
5
-
6
- describe("roleToProfile", () => {
7
- test("maps researcher role to researcher profile", () => {
8
- expect(roleToProfile("researcher")).toBe("researcher");
9
- });
10
-
11
- test("maps coder role to coder profile", () => {
12
- expect(roleToProfile("coder")).toBe("coder");
13
- });
14
-
15
- test("maps reviewer role to reviewer profile", () => {
16
- expect(roleToProfile("reviewer")).toBe("reviewer");
17
- });
18
-
19
- test("maps router role to general profile", () => {
20
- expect(roleToProfile("router")).toBe("general");
21
- });
22
- });
23
-
24
- describe("getProfilePolicy", () => {
25
- const READ_ONLY_TOOLS = [
26
- "Read",
27
- "Glob",
28
- "Grep",
29
- "WebSearch",
30
- "WebFetch",
31
- "LS",
32
- "Bash(grep *)",
33
- "Bash(rg *)",
34
- "Bash(find *)",
35
- ];
36
-
37
- const WRITE_TOOLS = ["Edit", "Write", "MultiEdit", "NotebookEdit"];
38
-
39
- describe("general profile", () => {
40
- const policy = getProfilePolicy("general");
41
-
42
- test("allows read-only tools", () => {
43
- for (const tool of READ_ONLY_TOOLS) {
44
- expect(policy.allow.has(tool)).toBe(true);
45
- }
46
- });
47
-
48
- test("has no hard denies", () => {
49
- expect(policy.deny.size).toBe(0);
50
- });
51
-
52
- test("requires approval for write tools and Bash", () => {
53
- for (const tool of WRITE_TOOLS) {
54
- expect(policy.approvalRequired.has(tool)).toBe(true);
55
- }
56
- expect(policy.approvalRequired.has("Bash")).toBe(true);
57
- });
58
- });
59
-
60
- describe("researcher profile", () => {
61
- const policy = getProfilePolicy("researcher");
62
-
63
- test("allows read-only tools", () => {
64
- for (const tool of READ_ONLY_TOOLS) {
65
- expect(policy.allow.has(tool)).toBe(true);
66
- }
67
- });
68
-
69
- test("denies write tools and Bash", () => {
70
- for (const tool of WRITE_TOOLS) {
71
- expect(policy.deny.has(tool)).toBe(true);
72
- }
73
- expect(policy.deny.has("Bash")).toBe(true);
74
- });
75
- });
76
-
77
- describe("coder profile", () => {
78
- const policy = getProfilePolicy("coder");
79
-
80
- test("allows read-only tools", () => {
81
- for (const tool of READ_ONLY_TOOLS) {
82
- expect(policy.allow.has(tool)).toBe(true);
83
- }
84
- });
85
-
86
- test("has no hard denies", () => {
87
- expect(policy.deny.size).toBe(0);
88
- });
89
-
90
- test("requires approval for write tools and Bash", () => {
91
- for (const tool of WRITE_TOOLS) {
92
- expect(policy.approvalRequired.has(tool)).toBe(true);
93
- }
94
- expect(policy.approvalRequired.has("Bash")).toBe(true);
95
- });
96
- });
97
-
98
- describe("reviewer profile", () => {
99
- const policy = getProfilePolicy("reviewer");
100
-
101
- test("allows read-only tools", () => {
102
- for (const tool of READ_ONLY_TOOLS) {
103
- expect(policy.allow.has(tool)).toBe(true);
104
- }
105
- });
106
-
107
- test("denies write tools and Bash", () => {
108
- for (const tool of WRITE_TOOLS) {
109
- expect(policy.deny.has(tool)).toBe(true);
110
- }
111
- expect(policy.deny.has("Bash")).toBe(true);
112
- });
113
- });
114
-
115
- test("all profiles allow the same read-only tool set", () => {
116
- const profiles: WorkerProfile[] = [
117
- "general",
118
- "researcher",
119
- "coder",
120
- "reviewer",
121
- ];
122
- for (const profile of profiles) {
123
- const policy = getProfilePolicy(profile);
124
- for (const tool of READ_ONLY_TOOLS) {
125
- expect(policy.allow.has(tool)).toBe(true);
126
- }
127
- }
128
- });
129
-
130
- test("denied tools are never also in allow set", () => {
131
- const profiles: WorkerProfile[] = [
132
- "general",
133
- "researcher",
134
- "coder",
135
- "reviewer",
136
- ];
137
- for (const profile of profiles) {
138
- const policy = getProfilePolicy(profile);
139
- for (const tool of policy.deny) {
140
- expect(policy.allow.has(tool)).toBe(false);
141
- }
142
- }
143
- });
144
- });