@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,31 +1,20 @@
1
1
  /**
2
- * Integration tests for resolveSigningKey() covering the Docker bootstrap
3
- * lifecycle: fresh fetch from gateway, daemon restart (load from disk),
4
- * and local mode (file-based load/create).
2
+ * Tests for resolveSigningKey() covering env var injection (Docker)
3
+ * and file-based load/create (local mode).
5
4
  */
6
5
 
7
- import { mkdtempSync, readFileSync, realpathSync, rmSync } from "node:fs";
6
+ import { mkdirSync, mkdtempSync, realpathSync, rmSync } from "node:fs";
8
7
  import { tmpdir } from "node:os";
9
8
  import { join } from "node:path";
10
9
  import { afterAll, afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
11
10
 
12
- // ---------------------------------------------------------------------------
13
- // Temp directory for signing key persistence
14
- // ---------------------------------------------------------------------------
15
-
16
- const testDir = realpathSync(mkdtempSync(join(tmpdir(), "docker-signing-key-test-")));
17
-
18
- // ---------------------------------------------------------------------------
19
- // Mock platform to redirect signing key file to our temp directory
20
- // ---------------------------------------------------------------------------
11
+ const testDir = realpathSync(mkdtempSync(join(tmpdir(), "signing-key-test-")));
21
12
 
22
13
  mock.module("../util/platform.js", () => ({
23
14
  getRootDir: () => testDir,
24
15
  getDataDir: () => testDir,
25
16
  getDbPath: () => join(testDir, "test.db"),
26
17
  normalizeAssistantId: (id: string) => (id === "self" ? "self" : id),
27
- readLockfile: () => null,
28
- writeLockfile: () => {},
29
18
  isMacOS: () => process.platform === "darwin",
30
19
  isLinux: () => process.platform === "linux",
31
20
  isWindows: () => process.platform === "win32",
@@ -41,53 +30,23 @@ mock.module("../util/logger.js", () => ({
41
30
  }),
42
31
  }));
43
32
 
44
- // ---------------------------------------------------------------------------
45
- // Import the functions under test (after mocks are installed)
46
- // ---------------------------------------------------------------------------
47
-
48
- const {
49
- resolveSigningKey,
50
- loadOrCreateSigningKey: _loadOrCreateSigningKey,
51
- BootstrapAlreadyCompleted: _BootstrapAlreadyCompleted,
52
- } = await import("../runtime/auth/token-service.js");
53
-
54
- // ---------------------------------------------------------------------------
55
- // Test constants
56
- // ---------------------------------------------------------------------------
33
+ const { resolveSigningKey } = await import("../runtime/auth/token-service.js");
57
34
 
58
- const VALID_32_BYTE_KEY = "ab".repeat(32); // 64 hex chars = 32 bytes
59
- const SIGNING_KEY_PATH = join(testDir, "protected", "actor-token-signing-key");
35
+ const VALID_HEX_KEY = "ab".repeat(32); // 64 hex chars = 32 bytes
60
36
 
61
- // ---------------------------------------------------------------------------
62
- // Environment & fetch state management
63
- // ---------------------------------------------------------------------------
64
-
65
- const originalFetch = globalThis.fetch;
66
37
  const savedEnv: Record<string, string | undefined> = {};
67
38
 
68
- function saveEnv(...keys: string[]) {
69
- for (const key of keys) {
70
- savedEnv[key] = process.env[key];
71
- }
72
- }
73
-
74
- function restoreEnv() {
75
- for (const [key, val] of Object.entries(savedEnv)) {
76
- if (val === undefined) {
77
- delete process.env[key];
78
- } else {
79
- process.env[key] = val;
80
- }
81
- }
82
- }
83
-
84
39
  beforeEach(() => {
85
- saveEnv("IS_CONTAINERIZED", "GATEWAY_INTERNAL_URL");
40
+ savedEnv.ACTOR_TOKEN_SIGNING_KEY = process.env.ACTOR_TOKEN_SIGNING_KEY;
41
+ mkdirSync(join(testDir, "protected"), { recursive: true });
86
42
  });
87
43
 
88
44
  afterEach(() => {
89
- globalThis.fetch = originalFetch;
90
- restoreEnv();
45
+ if (savedEnv.ACTOR_TOKEN_SIGNING_KEY === undefined) {
46
+ delete process.env.ACTOR_TOKEN_SIGNING_KEY;
47
+ } else {
48
+ process.env.ACTOR_TOKEN_SIGNING_KEY = savedEnv.ACTOR_TOKEN_SIGNING_KEY;
49
+ }
91
50
  });
92
51
 
93
52
  afterAll(() => {
@@ -96,112 +55,44 @@ afterAll(() => {
96
55
  } catch {}
97
56
  });
98
57
 
99
- // ---------------------------------------------------------------------------
100
- // Docker mode tests resolveSigningKey() bootstrap lifecycle
101
- // ---------------------------------------------------------------------------
102
-
103
- describe("resolveSigningKey — Docker bootstrap lifecycle", () => {
104
- test("fresh bootstrap: fetches key from gateway and persists to disk", async () => {
105
- process.env.IS_CONTAINERIZED = "true";
106
- process.env.GATEWAY_INTERNAL_URL = "http://localhost:19876";
58
+ describe("resolveSigningKey", () => {
59
+ test("reads key from ACTOR_TOKEN_SIGNING_KEY env var", () => {
60
+ process.env.ACTOR_TOKEN_SIGNING_KEY = VALID_HEX_KEY;
107
61
 
108
- // Mock fetch to return a known 32-byte key on first call.
109
- globalThis.fetch = (async () =>
110
- new Response(JSON.stringify({ key: VALID_32_BYTE_KEY }), {
111
- status: 200,
112
- headers: { "Content-Type": "application/json" },
113
- })) as unknown as typeof fetch;
62
+ const key = resolveSigningKey();
114
63
 
115
- const key = await resolveSigningKey();
116
-
117
- // Verify the returned key is a 32-byte buffer with the expected content.
118
64
  expect(key).toBeInstanceOf(Buffer);
119
65
  expect(key.length).toBe(32);
120
- expect(key.toString("hex")).toBe(VALID_32_BYTE_KEY);
121
-
122
- // Verify the key was persisted to disk.
123
- const persisted = readFileSync(SIGNING_KEY_PATH);
124
- expect(persisted.length).toBe(32);
125
- expect(Buffer.from(persisted).equals(key)).toBe(true);
66
+ expect(key.toString("hex")).toBe(VALID_HEX_KEY);
126
67
  });
127
68
 
128
- test("daemon restart: gateway returns 403, loads persisted key from disk", async () => {
129
- process.env.IS_CONTAINERIZED = "true";
130
- process.env.GATEWAY_INTERNAL_URL = "http://localhost:19876";
69
+ test("rejects invalid ACTOR_TOKEN_SIGNING_KEY", () => {
70
+ process.env.ACTOR_TOKEN_SIGNING_KEY = "tooshort";
131
71
 
132
- // The previous test persisted the key. Simulate a daemon restart where
133
- // the gateway returns 403 (bootstrap already completed).
134
- globalThis.fetch = (async () =>
135
- new Response(JSON.stringify({ error: "Bootstrap already completed" }), {
136
- status: 403,
137
- })) as unknown as typeof fetch;
138
-
139
- const key = await resolveSigningKey();
140
-
141
- // Should have loaded the previously persisted key from disk.
142
- expect(key).toBeInstanceOf(Buffer);
143
- expect(key.length).toBe(32);
144
- expect(key.toString("hex")).toBe(VALID_32_BYTE_KEY);
145
- });
146
- });
147
-
148
- // ---------------------------------------------------------------------------
149
- // Local mode tests — resolveSigningKey() file-based path
150
- // ---------------------------------------------------------------------------
151
-
152
- describe("resolveSigningKey — local mode", () => {
153
- test("uses file-based loadOrCreateSigningKey without calling fetch", async () => {
154
- // Ensure Docker env vars are unset.
155
- delete process.env.IS_CONTAINERIZED;
156
- delete process.env.GATEWAY_INTERNAL_URL;
157
-
158
- let fetchCalled = false;
159
- globalThis.fetch = (async () => {
160
- fetchCalled = true;
161
- return new Response("should not be called", { status: 500 });
162
- }) as unknown as typeof fetch;
163
-
164
- const key = await resolveSigningKey();
165
-
166
- // Should return a valid 32-byte key (loaded from disk or newly created).
167
- expect(key).toBeInstanceOf(Buffer);
168
- expect(key.length).toBe(32);
169
-
170
- // Crucially, fetch should NOT have been called.
171
- expect(fetchCalled).toBe(false);
72
+ expect(() => resolveSigningKey()).toThrow("Invalid ACTOR_TOKEN_SIGNING_KEY");
172
73
  });
173
74
 
174
- test("IS_CONTAINERIZED=false does not trigger gateway fetch", async () => {
175
- process.env.IS_CONTAINERIZED = "false";
176
- process.env.GATEWAY_INTERNAL_URL = "http://localhost:19876";
75
+ test("falls back to file-based load/create when env var is not set", () => {
76
+ delete process.env.ACTOR_TOKEN_SIGNING_KEY;
177
77
 
178
- let fetchCalled = false;
179
- globalThis.fetch = (async () => {
180
- fetchCalled = true;
181
- return new Response("should not be called", { status: 500 });
182
- }) as unknown as typeof fetch;
183
-
184
- const key = await resolveSigningKey();
78
+ const key = resolveSigningKey();
185
79
 
186
80
  expect(key).toBeInstanceOf(Buffer);
187
81
  expect(key.length).toBe(32);
188
- expect(fetchCalled).toBe(false);
189
82
  });
190
83
 
191
- test("IS_CONTAINERIZED=true without GATEWAY_INTERNAL_URL uses local path", async () => {
192
- process.env.IS_CONTAINERIZED = "true";
193
- delete process.env.GATEWAY_INTERNAL_URL;
84
+ test("env var takes priority over file on disk", () => {
85
+ process.env.ACTOR_TOKEN_SIGNING_KEY = VALID_HEX_KEY;
194
86
 
195
- let fetchCalled = false;
196
- globalThis.fetch = (async () => {
197
- fetchCalled = true;
198
- return new Response("should not be called", { status: 500 });
199
- }) as unknown as typeof fetch;
87
+ // First call creates a file-based key
88
+ delete process.env.ACTOR_TOKEN_SIGNING_KEY;
89
+ const fileKey = resolveSigningKey();
200
90
 
201
- const key = await resolveSigningKey();
91
+ // Second call with env var should use the env var, not the file
92
+ process.env.ACTOR_TOKEN_SIGNING_KEY = "cd".repeat(32);
93
+ const envKey = resolveSigningKey();
202
94
 
203
- expect(key).toBeInstanceOf(Buffer);
204
- expect(key.length).toBe(32);
205
- expect(fetchCalled).toBe(false);
95
+ expect(envKey.toString("hex")).toBe("cd".repeat(32));
96
+ expect(envKey.toString("hex")).not.toBe(fileKey.toString("hex"));
206
97
  });
207
98
  });
@@ -53,9 +53,6 @@ mock.module("../util/logger.js", () => ({
53
53
 
54
54
  mock.module("../config/loader.js", () => ({
55
55
  getConfig: () => ({
56
- assistantFeatureFlagValues: {
57
- "feature_flags.browser.enabled": true,
58
- },
59
56
  services: {
60
57
  inference: {
61
58
  mode: "your-own",
@@ -77,17 +74,22 @@ mock.module("../config/loader.js", () => ({
77
74
  invalidateConfigCache: () => {},
78
75
  getNestedValue: () => undefined,
79
76
  setNestedValue: () => {},
80
- syncConfigToLockfile: () => {},
81
77
  }));
82
78
 
83
79
  const { buildSystemPrompt } = await import("../prompts/system-prompt.js");
80
+ const { _setOverridesForTesting } =
81
+ await import("../config/assistant-feature-flags.js");
84
82
 
85
83
  describe("Dynamic Skill Authoring Workflow moved to tool descriptions", () => {
86
84
  beforeEach(() => {
87
85
  mkdirSync(TEST_DIR, { recursive: true });
86
+ _setOverridesForTesting({
87
+ "feature_flags.browser.enabled": true,
88
+ });
88
89
  });
89
90
 
90
91
  afterEach(() => {
92
+ _setOverridesForTesting({});
91
93
  if (existsSync(TEST_DIR)) {
92
94
  rmSync(TEST_DIR, { recursive: true, force: true });
93
95
  }
@@ -31,11 +31,6 @@ mock.module("../util/logger.js", () => ({
31
31
  }),
32
32
  }));
33
33
 
34
- // Mock security check to always pass
35
- mock.module("../security/secret-ingress.js", () => ({
36
- checkIngressForSecrets: () => ({ blocked: false }),
37
- }));
38
-
39
34
  import { upsertContact } from "../contacts/contact-store.js";
40
35
  import { createGuardianBinding } from "../contacts/contacts-write.js";
41
36
  import type { TrustContext } from "../daemon/conversation-runtime-assembly.js";
@@ -846,12 +846,12 @@ describe("host_bash — proxy delegation", () => {
846
846
  });
847
847
 
848
848
  test("propagates VELLUM_UNTRUSTED_SHELL env to proxy under CES lockdown", async () => {
849
- // Enable CES shell lockdown
850
- const origFlags = (mockConfig as Record<string, unknown>)
851
- .assistantFeatureFlagValues;
852
- (mockConfig as Record<string, unknown>).assistantFeatureFlagValues = {
849
+ // Enable CES shell lockdown via the override cache
850
+ const { _setOverridesForTesting } =
851
+ await import("../config/assistant-feature-flags.js");
852
+ _setOverridesForTesting({
853
853
  "feature_flags.ces-shell-lockdown.enabled": true,
854
- };
854
+ });
855
855
 
856
856
  try {
857
857
  const proxyResult: ToolExecutionResult = {
@@ -875,8 +875,7 @@ describe("host_bash — proxy delegation", () => {
875
875
  expect(calls.length).toBe(1);
876
876
  expect(calls[0].input.env).toEqual({ VELLUM_UNTRUSTED_SHELL: "1" });
877
877
  } finally {
878
- (mockConfig as Record<string, unknown>).assistantFeatureFlagValues =
879
- origFlags;
878
+ _setOverridesForTesting({});
880
879
  }
881
880
  });
882
881
 
@@ -2,10 +2,9 @@
2
2
  * Tests for HTTP POST /v1/messages behavior after the legacy handleUserMessage
3
3
  * legacy entry point was retired.
4
4
  *
5
- * Secret ingress blocking has been ported to the HTTP path. Recording intent
6
- * interception has been deliberately retired the HTTP path has dedicated
7
- * /v1/recording/* endpoints and the model handles recording-related messages
8
- * through the agent loop.
5
+ * Recording intent interception has been deliberately retired the HTTP path
6
+ * has dedicated /v1/recording/* endpoints and the model handles
7
+ * recording-related messages through the agent loop.
9
8
  *
10
9
  * Approval reply interception has parity and is covered by
11
10
  * conversation-routes-guardian-reply.test.ts and send-endpoint-busy.test.ts.
@@ -121,12 +120,10 @@ mock.module("../runtime/trust-context-resolver.js", () => ({
121
120
  }),
122
121
  }));
123
122
 
124
- // Mock config to enable secret detection + ingress blocking
125
123
  mock.module("../config/loader.js", () => ({
126
124
  getConfig: () => ({
127
125
  secretDetection: {
128
126
  enabled: true,
129
- blockIngress: true,
130
127
  customPatterns: [],
131
128
  entropyThreshold: 3.5,
132
129
  },
@@ -238,103 +235,6 @@ async function sendMessage(
238
235
  );
239
236
  }
240
237
 
241
- // ============================================================================
242
- // SECRET INGRESS BLOCKING — now ported to HTTP path
243
- // ============================================================================
244
- describe("HTTP POST /v1/messages blocks secret ingress", () => {
245
- beforeEach(() => {
246
- routeGuardianReplyMock.mockClear();
247
- listPendingByDestinationMock.mockClear();
248
- listCanonicalMock.mockClear();
249
- addMessageMock.mockClear();
250
- });
251
-
252
- test("handleSendMessage rejects messages containing Telegram bot token patterns", async () => {
253
- const secretContent =
254
- "Set up Telegram with my bot token 123456789:ABCDefGHIJklmnopQRSTuvwxyz012345678";
255
- const persistUserMessage = mock(async () => "persisted-msg-id");
256
- const runAgentLoop = mock(async () => undefined);
257
- const conversation = makeConversation({ persistUserMessage, runAgentLoop });
258
-
259
- const res = await sendMessage(secretContent, conversation);
260
-
261
- expect(res.status).toBe(422);
262
- const body = (await res.json()) as {
263
- accepted: boolean;
264
- error: string;
265
- message: string;
266
- detectedTypes: string[];
267
- };
268
- expect(body.accepted).toBe(false);
269
- expect(body.error).toBe("secret_blocked");
270
- expect(body.detectedTypes.length).toBeGreaterThan(0);
271
-
272
- // The message should NOT reach the agent loop
273
- expect(persistUserMessage).toHaveBeenCalledTimes(0);
274
- expect(runAgentLoop).toHaveBeenCalledTimes(0);
275
- });
276
-
277
- test("handleSendMessage rejects messages containing AWS credentials", async () => {
278
- const secretContent =
279
- "Here is my AWS key AKIAQRSTUVWXYZ123456 and secret wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY";
280
- const persistUserMessage = mock(async () => "persisted-msg-id");
281
- const runAgentLoop = mock(async () => undefined);
282
- const conversation = makeConversation({ persistUserMessage, runAgentLoop });
283
-
284
- const res = await sendMessage(secretContent, conversation);
285
-
286
- expect(res.status).toBe(422);
287
- const body = (await res.json()) as {
288
- accepted: boolean;
289
- error: string;
290
- };
291
- expect(body.accepted).toBe(false);
292
- expect(body.error).toBe("secret_blocked");
293
-
294
- // The message should NOT reach the agent loop
295
- expect(persistUserMessage).toHaveBeenCalledTimes(0);
296
- expect(runAgentLoop).toHaveBeenCalledTimes(0);
297
- });
298
-
299
- test("handleSendMessage rejects messages containing Stripe live API keys", async () => {
300
- const secretContent = "My Stripe key is sk_live_4eC39HqLyjWDarjtT1zdp7dc";
301
- const persistUserMessage = mock(async () => "persisted-msg-id");
302
- const runAgentLoop = mock(async () => undefined);
303
- const conversation = makeConversation({ persistUserMessage, runAgentLoop });
304
-
305
- const res = await sendMessage(secretContent, conversation);
306
-
307
- expect(res.status).toBe(422);
308
- const body = (await res.json()) as {
309
- accepted: boolean;
310
- error: string;
311
- };
312
- expect(body.accepted).toBe(false);
313
- expect(body.error).toBe("secret_blocked");
314
-
315
- // The message should NOT reach the agent loop
316
- expect(persistUserMessage).toHaveBeenCalledTimes(0);
317
- expect(runAgentLoop).toHaveBeenCalledTimes(0);
318
- });
319
-
320
- test("handleSendMessage allows normal messages without secrets", async () => {
321
- const normalContent = "What is the weather today?";
322
- const persistUserMessage = mock(async () => "persisted-msg-id");
323
- const runAgentLoop = mock(async () => undefined);
324
- const conversation = makeConversation({ persistUserMessage, runAgentLoop });
325
-
326
- const res = await sendMessage(normalContent, conversation);
327
-
328
- expect(res.status).toBe(202);
329
- const body = (await res.json()) as { accepted: boolean };
330
- expect(body.accepted).toBe(true);
331
-
332
- // Normal messages proceed to the agent loop
333
- expect(persistUserMessage).toHaveBeenCalledTimes(1);
334
- expect(runAgentLoop).toHaveBeenCalledTimes(1);
335
- });
336
- });
337
-
338
238
  // ============================================================================
339
239
  // RECORDING INTENT — deliberately NOT intercepted on HTTP path
340
240
  // ============================================================================
@@ -35,10 +35,6 @@ mock.module("../util/logger.js", () => ({
35
35
  }),
36
36
  }));
37
37
 
38
- mock.module("../security/secret-ingress.js", () => ({
39
- checkIngressForSecrets: () => ({ blocked: false }),
40
- }));
41
-
42
38
  mock.module("../config/env.js", () => ({
43
39
  isHttpAuthDisabled: () => true,
44
40
  getGatewayInternalBaseUrl: () => "http://127.0.0.1:7830",
@@ -18,6 +18,8 @@ import { tmpdir } from "node:os";
18
18
  import { join } from "node:path";
19
19
  import { beforeEach, describe, expect, mock, test } from "bun:test";
20
20
 
21
+ import { _setOverridesForTesting } from "../config/assistant-feature-flags.js";
22
+
21
23
  // ── Mock setup (must be before any imports from the project) ──────────────
22
24
 
23
25
  const testDir = mkdtempSync(join(tmpdir(), "inline-skill-perm-test-"));
@@ -46,7 +48,6 @@ interface TestConfig {
46
48
  permissions: { mode: "strict" | "workspace" };
47
49
  skills: { load: { extraDirs: string[] } };
48
50
  sandbox: { enabled: boolean };
49
- assistantFeatureFlagValues?: Record<string, boolean>;
50
51
  [key: string]: unknown;
51
52
  }
52
53
 
@@ -54,9 +55,6 @@ const testConfig: TestConfig = {
54
55
  permissions: { mode: "workspace" },
55
56
  skills: { load: { extraDirs: [] } },
56
57
  sandbox: { enabled: true },
57
- assistantFeatureFlagValues: {
58
- "feature_flags.inline-skill-commands.enabled": true,
59
- },
60
58
  };
61
59
 
62
60
  mock.module("../config/loader.js", () => ({
@@ -118,9 +116,9 @@ describe("inline-command skill_load permissions", () => {
118
116
  clearCache();
119
117
  testConfig.permissions = { mode: "workspace" };
120
118
  testConfig.skills = { load: { extraDirs: [] } };
121
- testConfig.assistantFeatureFlagValues = {
119
+ _setOverridesForTesting({
122
120
  "feature_flags.inline-skill-commands.enabled": true,
123
- };
121
+ });
124
122
  try {
125
123
  rmSync(join(testDir, "protected", "trust.json"));
126
124
  } catch {
@@ -352,9 +350,9 @@ describe("inline-command skill_load permissions", () => {
352
350
  writeDynamicSkill("dynamic-flag-off", "Dynamic Flag Off Skill");
353
351
 
354
352
  // Disable the feature flag
355
- testConfig.assistantFeatureFlagValues = {
353
+ _setOverridesForTesting({
356
354
  "feature_flags.inline-skill-commands.enabled": false,
357
- };
355
+ });
358
356
 
359
357
  const result = await check(
360
358
  "skill_load",
@@ -53,8 +53,6 @@ mock.module("../util/logger.js", () => ({
53
53
  mock.module("../config/loader.js", () => ({
54
54
  getConfig: () => ({
55
55
  ui: {},
56
-
57
- assistantFeatureFlagValues: {},
58
56
  services: {
59
57
  inference: {
60
58
  mode: "your-own",
@@ -76,7 +74,6 @@ mock.module("../config/loader.js", () => ({
76
74
  invalidateConfigCache: () => {},
77
75
  getNestedValue: () => undefined,
78
76
  setNestedValue: () => {},
79
- syncConfigToLockfile: () => {},
80
77
  }));
81
78
 
82
79
  // ── Import after mocks ───────────────────────────────────────────────
@@ -250,16 +247,6 @@ describe("Activation hints in skills catalog", () => {
250
247
  expect(line).toContain("voice-setup");
251
248
  });
252
249
 
253
- test("orchestration skill includes hints and avoid-when in catalog line", () => {
254
- const prompt = buildSystemPrompt();
255
- const line = prompt
256
- .split("\n")
257
- .find((l) => l.includes("**orchestration**"));
258
- expect(line).toBeDefined();
259
- expect(line).toContain("parallel");
260
- expect(line).toContain("Single-focus");
261
- });
262
-
263
250
  test("browser skill includes hints in catalog line", () => {
264
251
  const prompt = buildSystemPrompt();
265
252
  const line = prompt.split("\n").find((l) => l.includes("**browser**"));