@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,275 +0,0 @@
1
- import { getGatewayInternalBaseUrl } from "../config/env.js";
2
- import { getBaseDataDir, getIsContainerized } from "../config/env-registry.js";
3
- import { readLockfile } from "../util/platform.js";
4
- import { sleep } from "../util/retry.js";
5
-
6
- const DEFAULT_PROBE_TIMEOUT_MS = 2_000;
7
- const DEFAULT_RECOVERY_POLL_TIMEOUT_MS = 30_000;
8
- const DEFAULT_RECOVERY_POLL_INTERVAL_MS = 250;
9
- const DEFAULT_WAKE_TIMEOUT_MS = 90_000;
10
-
11
- interface LockfileAssistantEntry {
12
- assistantId?: string;
13
- cloud?: string;
14
- hatchedAt?: string | number | Date;
15
- }
16
-
17
- export interface WakeCommandResult {
18
- exitCode: number;
19
- stdout: string;
20
- stderr: string;
21
- }
22
-
23
- export interface LocalGatewayHealthResult {
24
- target: string;
25
- healthy: boolean;
26
- localDeployment: boolean;
27
- error?: string;
28
- }
29
-
30
- export interface EnsureLocalGatewayReadyResult extends LocalGatewayHealthResult {
31
- recovered: boolean;
32
- recoveryAttempted: boolean;
33
- recoverySkipped: boolean;
34
- }
35
-
36
- export interface ProbeLocalGatewayHealthOptions {
37
- timeoutMs?: number;
38
- fetchImpl?: typeof fetch;
39
- }
40
-
41
- export interface EnsureLocalGatewayReadyOptions extends ProbeLocalGatewayHealthOptions {
42
- pollTimeoutMs?: number;
43
- pollIntervalMs?: number;
44
- wakeTimeoutMs?: number;
45
- runWakeCommand?: () => Promise<WakeCommandResult>;
46
- sleepImpl?: (ms: number) => Promise<void>;
47
- }
48
-
49
- function getLatestAssistantEntry(): LockfileAssistantEntry | null {
50
- try {
51
- const lockData = readLockfile();
52
- const assistants = lockData?.assistants;
53
- if (!Array.isArray(assistants) || assistants.length === 0) {
54
- return null;
55
- }
56
-
57
- const sorted = [...assistants].sort((a, b) => {
58
- const dateA = new Date(
59
- (a as LockfileAssistantEntry).hatchedAt || 0,
60
- ).getTime();
61
- const dateB = new Date(
62
- (b as LockfileAssistantEntry).hatchedAt || 0,
63
- ).getTime();
64
- return dateB - dateA;
65
- });
66
-
67
- return (sorted[0] as LockfileAssistantEntry) ?? null;
68
- } catch {
69
- return null;
70
- }
71
- }
72
-
73
- function resolveLocalDeployment(): boolean {
74
- if (getIsContainerized()) {
75
- return false;
76
- }
77
-
78
- const latestAssistant = getLatestAssistantEntry();
79
- if (typeof latestAssistant?.cloud === "string") {
80
- return latestAssistant.cloud === "local";
81
- }
82
-
83
- return true;
84
- }
85
-
86
- /**
87
- * Derive instance name from BASE_DATA_DIR which follows the multi-instance
88
- * path pattern (~/.local/share/vellum/assistants/<name>/).
89
- */
90
- function resolveInstanceNameFromBaseDataDir(): string | undefined {
91
- const base = getBaseDataDir();
92
- if (!base || typeof base !== "string") return undefined;
93
-
94
- const normalized = base.replace(/\\/g, "/").replace(/\/+$/, "");
95
- const match = normalized.match(/\/assistants\/([^/]+)$/);
96
- if (match) return match[1];
97
- return undefined;
98
- }
99
-
100
- function resolveLocalAssistantName(): string | undefined {
101
- const fromPath = resolveInstanceNameFromBaseDataDir();
102
- if (fromPath) return fromPath;
103
-
104
- const latestAssistant = getLatestAssistantEntry();
105
- if (
106
- latestAssistant &&
107
- typeof latestAssistant.assistantId === "string" &&
108
- latestAssistant.assistantId.trim().length > 0
109
- ) {
110
- return latestAssistant.assistantId.trim();
111
- }
112
-
113
- return undefined;
114
- }
115
-
116
- function formatError(err: unknown): string {
117
- return err instanceof Error ? err.message : String(err);
118
- }
119
-
120
- async function runDefaultWakeCommand(
121
- timeoutMs: number,
122
- ): Promise<WakeCommandResult> {
123
- const assistantName = resolveLocalAssistantName();
124
- const command = assistantName
125
- ? ["vellum", "wake", assistantName]
126
- : ["vellum", "wake"];
127
-
128
- // Only when the assistant name came from the instance path (e.g.
129
- // ~/.local/share/vellum/assistants/<name>/), unset BASE_DATA_DIR so the
130
- // spawned CLI reads the global lockfile. When the name came from the
131
- // lockfile, keep BASE_DATA_DIR — vellum wake resolves names through the
132
- // lockfile rooted at BASE_DATA_DIR, so clearing it would read the wrong
133
- // lockfile (e.g. $HOME) and fail or wake the wrong assistant.
134
- const fromInstancePath = resolveInstanceNameFromBaseDataDir();
135
- const env =
136
- fromInstancePath && getBaseDataDir()
137
- ? { ...process.env, BASE_DATA_DIR: undefined }
138
- : process.env;
139
-
140
- return new Promise((resolve, reject) => {
141
- const proc = Bun.spawn(command, {
142
- stdout: "pipe",
143
- stderr: "pipe",
144
- env: {
145
- ...env,
146
- PATH: [env.PATH, "/opt/homebrew/bin", "/usr/local/bin"]
147
- .filter(Boolean)
148
- .join(":"),
149
- },
150
- });
151
- const timer = setTimeout(() => {
152
- proc.kill();
153
- reject(
154
- new Error(`Process timed out after ${timeoutMs}ms: ${command[0]}`),
155
- );
156
- }, timeoutMs);
157
- proc.exited.then(async (exitCode) => {
158
- clearTimeout(timer);
159
- const stdout = await new Response(proc.stdout).text();
160
- const stderr = await new Response(proc.stderr).text();
161
- resolve({ exitCode, stdout, stderr });
162
- });
163
- });
164
- }
165
-
166
- export async function probeLocalGatewayHealth(
167
- options: ProbeLocalGatewayHealthOptions = {},
168
- ): Promise<LocalGatewayHealthResult> {
169
- const target = getGatewayInternalBaseUrl();
170
- const localDeployment = resolveLocalDeployment();
171
- const fetchImpl = options.fetchImpl ?? fetch;
172
- const timeoutMs = options.timeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS;
173
-
174
- try {
175
- const response = await fetchImpl(`${target}/healthz`, {
176
- method: "GET",
177
- signal: AbortSignal.timeout(timeoutMs),
178
- });
179
- if (!response.ok) {
180
- return {
181
- target,
182
- healthy: false,
183
- localDeployment,
184
- error: `Gateway health check returned HTTP ${response.status}`,
185
- };
186
- }
187
-
188
- return {
189
- target,
190
- healthy: true,
191
- localDeployment,
192
- };
193
- } catch (err) {
194
- return {
195
- target,
196
- healthy: false,
197
- localDeployment,
198
- error: formatError(err),
199
- };
200
- }
201
- }
202
-
203
- export async function ensureLocalGatewayReady(
204
- options: EnsureLocalGatewayReadyOptions = {},
205
- ): Promise<EnsureLocalGatewayReadyResult> {
206
- const initialProbe = await probeLocalGatewayHealth(options);
207
- if (initialProbe.healthy) {
208
- return {
209
- ...initialProbe,
210
- recovered: false,
211
- recoveryAttempted: false,
212
- recoverySkipped: false,
213
- };
214
- }
215
-
216
- if (!initialProbe.localDeployment) {
217
- return {
218
- ...initialProbe,
219
- recovered: false,
220
- recoveryAttempted: false,
221
- recoverySkipped: true,
222
- error:
223
- initialProbe.error ??
224
- "Skipped gateway recovery because this assistant is not locally managed",
225
- };
226
- }
227
-
228
- const runWakeCommand =
229
- options.runWakeCommand ??
230
- (() =>
231
- runDefaultWakeCommand(options.wakeTimeoutMs ?? DEFAULT_WAKE_TIMEOUT_MS));
232
- const sleepImpl = options.sleepImpl ?? sleep;
233
- const pollTimeoutMs =
234
- options.pollTimeoutMs ?? DEFAULT_RECOVERY_POLL_TIMEOUT_MS;
235
- const pollIntervalMs =
236
- options.pollIntervalMs ?? DEFAULT_RECOVERY_POLL_INTERVAL_MS;
237
-
238
- let wakeError: string | undefined;
239
- try {
240
- const wakeResult = await runWakeCommand();
241
- if (wakeResult.exitCode !== 0) {
242
- const detail = wakeResult.stderr.trim() || wakeResult.stdout.trim();
243
- wakeError = detail
244
- ? `vellum wake exited with code ${wakeResult.exitCode}: ${detail}`
245
- : `vellum wake exited with code ${wakeResult.exitCode}`;
246
- }
247
- } catch (err) {
248
- wakeError = `Failed to run vellum wake: ${formatError(err)}`;
249
- }
250
-
251
- const deadline = Date.now() + pollTimeoutMs;
252
- let probe = await probeLocalGatewayHealth(options);
253
- while (!probe.healthy && Date.now() < deadline) {
254
- await sleepImpl(pollIntervalMs);
255
- probe = await probeLocalGatewayHealth(options);
256
- }
257
-
258
- if (probe.healthy) {
259
- return {
260
- ...probe,
261
- recovered: true,
262
- recoveryAttempted: true,
263
- recoverySkipped: false,
264
- };
265
- }
266
-
267
- const combinedError = [wakeError, probe.error].filter(Boolean).join("; ");
268
- return {
269
- ...probe,
270
- recovered: false,
271
- recoveryAttempted: true,
272
- recoverySkipped: false,
273
- error: combinedError || undefined,
274
- };
275
- }
@@ -1,68 +0,0 @@
1
- import { getConfig } from "../config/loader.js";
2
- import { getLogger } from "../util/logger.js";
3
- import { compileCustomPatterns, scanText } from "./secret-scanner.js";
4
-
5
- const log = getLogger("secret-ingress");
6
-
7
- export interface IngressCheckResult {
8
- /** Whether the message should be blocked from entering the model context. */
9
- blocked: boolean;
10
- /** Secret types detected (empty if none). */
11
- detectedTypes: string[];
12
- /**
13
- * User-facing notice explaining why the message was blocked.
14
- * Does NOT echo the secret value — only describes what was found.
15
- */
16
- userNotice?: string;
17
- }
18
-
19
- /**
20
- * Scan inbound user text for secrets before it enters model context.
21
- *
22
- * When `secretDetection.blockIngress` is `true` (default), any message
23
- * containing a detected secret is rejected with a safe notice. This is
24
- * independent of `secretDetection.action`, which only controls how
25
- * secrets in tool *output* are handled.
26
- *
27
- * SECURITY: This function intentionally never logs the message content.
28
- */
29
- export function checkIngressForSecrets(content: string): IngressCheckResult {
30
- const config = getConfig();
31
- if (!config.secretDetection.enabled) {
32
- return { blocked: false, detectedTypes: [] };
33
- }
34
-
35
- if (!config.secretDetection.blockIngress) {
36
- return { blocked: false, detectedTypes: [] };
37
- }
38
-
39
- const entropyConfig = {
40
- enabled: true,
41
- base64Threshold: config.secretDetection.entropyThreshold,
42
- };
43
- const compiledCustom = config.secretDetection.customPatterns?.length
44
- ? compileCustomPatterns(config.secretDetection.customPatterns)
45
- : undefined;
46
- const matches = scanText(content, entropyConfig, compiledCustom);
47
-
48
- if (matches.length === 0) {
49
- return { blocked: false, detectedTypes: [] };
50
- }
51
-
52
- const detectedTypes = [...new Set(matches.map((m) => m.type))];
53
- log.warn(
54
- { detectedTypes, matchCount: matches.length },
55
- "Blocked inbound message containing secrets",
56
- );
57
-
58
- return {
59
- blocked: true,
60
- detectedTypes,
61
- userNotice:
62
- `Your message appears to contain sensitive information (${detectedTypes.join(
63
- ", ",
64
- )}). ` +
65
- `For security, it was not sent to the AI. ` +
66
- `Please use the secure credential prompt instead — the assistant will ask for secrets when it needs them.`,
67
- };
68
- }
@@ -1,225 +0,0 @@
1
- /**
2
- * Claude Code worker backend for swarm execution.
3
- *
4
- * Extracted from the swarm delegate tool so backend construction
5
- * is testable and swappable independently of the tool adapter.
6
- */
7
-
8
- import { resolveModelIntent } from "../providers/model-intents.js";
9
- import type { ModelIntent } from "../providers/types.js";
10
- import { getProviderKeyAsync } from "../security/secure-keys.js";
11
- import { getLogger } from "../util/logger.js";
12
- import type {
13
- SwarmWorkerBackend,
14
- SwarmWorkerBackendInput,
15
- } from "./worker-backend.js";
16
- import { getProfilePolicy } from "./worker-backend.js";
17
-
18
- const log = getLogger("swarm-backend-claude-code");
19
-
20
- const MAX_CLAUDE_CODE_DEPTH = 1;
21
- const DEPTH_ENV_VAR = "VELLUM_CLAUDE_CODE_DEPTH";
22
-
23
- /**
24
- * Create a Claude Code worker backend that enforces profile-based tool policies.
25
- * Uses the Claude Agent SDK to run autonomous worker tasks.
26
- */
27
- export function createClaudeCodeBackend(): SwarmWorkerBackend {
28
- return {
29
- name: "claude_code",
30
-
31
- async isAvailable(): Promise<boolean> {
32
- const apiKey = await getProviderKeyAsync("anthropic");
33
- return !!apiKey;
34
- },
35
-
36
- async runTask(input: SwarmWorkerBackendInput) {
37
- const start = Date.now();
38
- const stderrLines: string[] = [];
39
- try {
40
- const { query } = await import("@anthropic-ai/claude-agent-sdk");
41
- const apiKey = await getProviderKeyAsync("anthropic");
42
- if (!apiKey) {
43
- return {
44
- success: false,
45
- output: "No API key",
46
- failureReason: "backend_unavailable" as const,
47
- durationMs: 0,
48
- };
49
- }
50
-
51
- const profilePolicy = getProfilePolicy(input.profile);
52
-
53
- // Enforce profile restrictions — swarm workers run autonomously so
54
- // there is no user to prompt; denied tools are blocked, everything
55
- // else is allowed.
56
- const canUseTool: import("@anthropic-ai/claude-agent-sdk").CanUseTool =
57
- async (toolName) => {
58
- if (profilePolicy.deny.has(toolName)) {
59
- log.debug(
60
- { toolName, profile: input.profile },
61
- "Swarm worker tool denied by profile",
62
- );
63
- return {
64
- behavior: "deny" as const,
65
- message: `Tool "${toolName}" is denied by profile "${input.profile}"`,
66
- };
67
- }
68
- return { behavior: "allow" as const };
69
- };
70
-
71
- // Enforce nesting depth limit
72
- const currentDepth = parseInt(process.env[DEPTH_ENV_VAR] ?? "0", 10);
73
- if (currentDepth >= MAX_CLAUDE_CODE_DEPTH) {
74
- log.warn(
75
- { currentDepth, max: MAX_CLAUDE_CODE_DEPTH },
76
- "Swarm worker nesting depth exceeded",
77
- );
78
- return {
79
- success: false,
80
- output: `Nesting depth exceeded (depth ${currentDepth}, max ${MAX_CLAUDE_CODE_DEPTH})`,
81
- failureReason: "backend_unavailable" as const,
82
- durationMs: Date.now() - start,
83
- };
84
- }
85
-
86
- // Strip the SDK's nesting guard but set our own depth counter.
87
- const subprocessEnv: Record<string, string | undefined> = {
88
- ...process.env,
89
- ANTHROPIC_API_KEY: apiKey,
90
- [DEPTH_ENV_VAR]: String(currentDepth + 1),
91
- };
92
- delete subprocessEnv.CLAUDECODE;
93
- delete subprocessEnv.CLAUDE_CODE_ENTRYPOINT;
94
-
95
- const conversation = query({
96
- prompt: input.prompt,
97
- options: {
98
- cwd: input.workingDir,
99
- model: input.modelIntent
100
- ? resolveModelIntent(
101
- "anthropic",
102
- input.modelIntent as ModelIntent,
103
- )
104
- : "claude-sonnet-4-6",
105
- canUseTool,
106
- permissionMode: "default",
107
- maxTurns: 30,
108
- env: subprocessEnv,
109
- stderr: (data: string) => {
110
- const trimmed = data.trimEnd();
111
- if (trimmed) {
112
- stderrLines.push(trimmed);
113
- log.debug(
114
- { stderr: trimmed },
115
- "Swarm worker subprocess stderr",
116
- );
117
- }
118
- },
119
- },
120
- });
121
-
122
- let resultText = "";
123
- let hasError = false;
124
- for await (const message of conversation) {
125
- if (input.signal?.aborted) break;
126
- if (message.type === "assistant") {
127
- if (message.error) {
128
- log.error(
129
- { error: message.error, conversationId: message.session_id },
130
- "Swarm worker assistant message error",
131
- );
132
- hasError = true;
133
- resultText += `\n[Claude Code error: ${message.error}]`;
134
- }
135
- if (message.message?.content) {
136
- for (const block of message.message.content) {
137
- if (block.type === "text") resultText += block.text;
138
- }
139
- }
140
- } else if (message.type === "result") {
141
- if (message.subtype === "success") {
142
- log.info(
143
- {
144
- numTurns: message.num_turns,
145
- durationMs: message.duration_ms,
146
- costUsd: message.total_cost_usd,
147
- },
148
- "Swarm worker completed",
149
- );
150
- if (message.result && !resultText) {
151
- resultText = message.result;
152
- }
153
- } else {
154
- hasError = true;
155
- const errors = message.errors ?? [];
156
- const denials = message.permission_denials ?? [];
157
- log.error(
158
- {
159
- subtype: message.subtype,
160
- errors,
161
- permissionDenials: denials.length,
162
- numTurns: message.num_turns,
163
- durationMs: message.duration_ms,
164
- },
165
- "Swarm worker session failed",
166
- );
167
-
168
- const parts: string[] = [
169
- `[${message.subtype}] (${message.num_turns} turns, ${(
170
- message.duration_ms / 1000
171
- ).toFixed(1)}s)`,
172
- ];
173
- if (errors.length > 0) parts.push(`Errors: ${errors.join("; ")}`);
174
- if (denials.length > 0)
175
- parts.push(
176
- `Permission denied: ${denials
177
- .map((d: { tool_name: string }) => d.tool_name)
178
- .join(", ")}`,
179
- );
180
- resultText += `\n${parts.join("\n")}`;
181
- }
182
- }
183
- }
184
-
185
- // Treat abort as non-retryable cancellation, not a retryable timeout
186
- if (input.signal?.aborted) {
187
- return {
188
- success: false,
189
- output: "Cancelled (aborted)",
190
- failureReason: "cancelled" as const,
191
- durationMs: Date.now() - start,
192
- };
193
- }
194
-
195
- return {
196
- success: !hasError,
197
- output: resultText || "Completed",
198
- durationMs: Date.now() - start,
199
- };
200
- } catch (err) {
201
- const errMessage = err instanceof Error ? err.message : String(err);
202
- const recentStderr = stderrLines.slice(-20);
203
- log.error(
204
- { err, stderrTail: recentStderr },
205
- "Swarm worker execution failed",
206
- );
207
-
208
- const parts = [errMessage];
209
- if (recentStderr.length > 0) {
210
- parts.push(
211
- `\nSubprocess stderr (last ${
212
- recentStderr.length
213
- } lines):\n${recentStderr.join("\n")}`,
214
- );
215
- }
216
- return {
217
- success: false,
218
- output: parts.join(""),
219
- failureReason: "backend_unavailable" as const,
220
- durationMs: Date.now() - start,
221
- };
222
- }
223
- },
224
- };
225
- }
@@ -1,137 +0,0 @@
1
- import {
2
- existsSync,
3
- mkdirSync,
4
- readFileSync,
5
- renameSync,
6
- unlinkSync,
7
- writeFileSync,
8
- } from "node:fs";
9
- import { dirname, join } from "node:path";
10
-
11
- import { getLogger } from "../util/logger.js";
12
- import { getRootDir } from "../util/platform.js";
13
- import type { SwarmPlan, SwarmTaskResult } from "./types.js";
14
-
15
- const log = getLogger("swarm-checkpoint");
16
-
17
- /** Only allow safe token characters in runId (alphanumeric, hyphens, underscores, dots). */
18
- const SAFE_RUN_ID = /^[a-zA-Z0-9._-]+$/;
19
-
20
- function assertSafeRunId(runId: string): void {
21
- if (!SAFE_RUN_ID.test(runId)) {
22
- throw new Error(
23
- `Invalid runId: must match ${SAFE_RUN_ID} (got "${runId}")`,
24
- );
25
- }
26
- }
27
-
28
- export interface SwarmCheckpoint {
29
- runId: string;
30
- objective: string;
31
- /** Serialized plan for integrity verification on resume. */
32
- planTaskIds: string[];
33
- /** Stringified task dependency map for structural integrity on resume. */
34
- planHash: string;
35
- results: SwarmTaskResult[];
36
- /** Set of task IDs whose dependents were blocked due to failure. */
37
- blockedTaskIds: string[];
38
- updatedAt: string;
39
- }
40
-
41
- function getCheckpointDir(): string {
42
- return join(getRootDir(), "swarm-checkpoints");
43
- }
44
-
45
- function getCheckpointPath(runId: string): string {
46
- assertSafeRunId(runId);
47
- return join(getCheckpointDir(), `${runId}.json`);
48
- }
49
-
50
- /**
51
- * Deterministic fingerprint of a plan's structure: objective, task IDs,
52
- * roles, and dependency edges. Two plans with the same hash are structurally
53
- * identical and safe to resume from.
54
- */
55
- function computePlanHash(plan: SwarmPlan): string {
56
- const parts = plan.tasks.map(
57
- (t) => `${t.id}:${t.role}:${[...t.dependencies].sort().join(",")}`,
58
- );
59
- return `${plan.objective}|${parts.sort().join("|")}`;
60
- }
61
-
62
- /** Persist the current swarm progress to disk. */
63
- export function writeCheckpoint(
64
- runId: string,
65
- plan: SwarmPlan,
66
- results: Map<string, SwarmTaskResult>,
67
- blockedTaskIds: Set<string>,
68
- ): void {
69
- try {
70
- const path = getCheckpointPath(runId);
71
- const checkpoint: SwarmCheckpoint = {
72
- runId,
73
- objective: plan.objective,
74
- planTaskIds: plan.tasks.map((t) => t.id),
75
- planHash: computePlanHash(plan),
76
- results: Array.from(results.values()),
77
- blockedTaskIds: Array.from(blockedTaskIds),
78
- updatedAt: new Date().toISOString(),
79
- };
80
-
81
- mkdirSync(dirname(path), { recursive: true });
82
- // Atomic-ish write: write to temp then rename to avoid partial reads
83
- const tmpPath = path + ".tmp";
84
- writeFileSync(tmpPath, JSON.stringify(checkpoint, null, 2) + "\n");
85
- renameSync(tmpPath, path);
86
- } catch (err) {
87
- // Checkpoint failures should not crash the orchestrator
88
- log.warn(
89
- { runId, error: (err as Error).message },
90
- "Failed to write checkpoint",
91
- );
92
- }
93
- }
94
-
95
- /** Load a checkpoint from disk, or null if none exists. */
96
- export function loadCheckpoint(runId: string): SwarmCheckpoint | null {
97
- const path = getCheckpointPath(runId);
98
- if (!existsSync(path)) return null;
99
-
100
- try {
101
- const data = readFileSync(path, "utf-8");
102
- return JSON.parse(data) as SwarmCheckpoint;
103
- } catch (err) {
104
- log.warn(
105
- { runId, error: (err as Error).message },
106
- "Failed to read checkpoint, starting fresh",
107
- );
108
- return null;
109
- }
110
- }
111
-
112
- /** Remove a checkpoint file after successful completion. */
113
- export function removeCheckpoint(runId: string): void {
114
- const path = getCheckpointPath(runId);
115
- try {
116
- if (existsSync(path)) unlinkSync(path);
117
- } catch {
118
- // Best-effort cleanup
119
- }
120
- }
121
-
122
- /**
123
- * Validate that a checkpoint matches the current plan.
124
- * Compares objective, task IDs, roles, and dependency structure via planHash.
125
- * Falls back to subset check for checkpoints written before planHash existed.
126
- */
127
- export function isCheckpointCompatible(
128
- checkpoint: SwarmCheckpoint,
129
- plan: SwarmPlan,
130
- ): boolean {
131
- if (checkpoint.planHash) {
132
- return checkpoint.planHash === computePlanHash(plan);
133
- }
134
- // Legacy checkpoint without planHash — fall back to subset check
135
- const planTaskIds = new Set(plan.tasks.map((t) => t.id));
136
- return checkpoint.planTaskIds.every((id) => planTaskIds.has(id));
137
- }