@vellumai/assistant 0.4.48 → 0.4.50

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 (423) hide show
  1. package/ARCHITECTURE.md +26 -35
  2. package/README.md +5 -26
  3. package/docs/architecture/integrations.md +45 -41
  4. package/docs/architecture/keychain-broker.md +3 -3
  5. package/docs/architecture/memory.md +180 -119
  6. package/docs/runbook-trusted-contacts.md +3 -8
  7. package/hook-templates/debug-prompt-logger/hook.json +1 -1
  8. package/hook-templates/debug-prompt-logger/run.sh +1 -3
  9. package/package.json +2 -2
  10. package/src/__tests__/actor-token-service.test.ts +0 -1
  11. package/src/__tests__/agent-loop.test.ts +3 -1
  12. package/src/__tests__/anthropic-provider.test.ts +249 -2
  13. package/src/__tests__/approval-cascade.test.ts +796 -0
  14. package/src/__tests__/approval-primitive.test.ts +0 -1
  15. package/src/__tests__/approval-routes-http.test.ts +4 -0
  16. package/src/__tests__/assistant-attachments.test.ts +12 -34
  17. package/src/__tests__/assistant-feature-flag-guard.test.ts +0 -23
  18. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +76 -0
  19. package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -1
  20. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
  21. package/src/__tests__/canonical-guardian-store.test.ts +95 -0
  22. package/src/__tests__/channel-guardian.test.ts +0 -2
  23. package/src/__tests__/channel-readiness-routes.test.ts +15 -6
  24. package/src/__tests__/channel-readiness-service.test.ts +10 -9
  25. package/src/__tests__/checker.test.ts +13 -20
  26. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
  27. package/src/__tests__/computer-use-tools.test.ts +2 -19
  28. package/src/__tests__/config-schema.test.ts +1 -68
  29. package/src/__tests__/config-watcher.test.ts +0 -1
  30. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
  31. package/src/__tests__/context-image-dimensions.test.ts +332 -0
  32. package/src/__tests__/context-memory-e2e.test.ts +11 -100
  33. package/src/__tests__/context-token-estimator.test.ts +196 -13
  34. package/src/__tests__/conversation-attention-store.test.ts +0 -1
  35. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  36. package/src/__tests__/conversation-routes-guardian-reply.test.ts +152 -0
  37. package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -0
  38. package/src/__tests__/credential-metadata-store.test.ts +64 -73
  39. package/src/__tests__/credential-security-e2e.test.ts +1 -0
  40. package/src/__tests__/credential-security-invariants.test.ts +13 -7
  41. package/src/__tests__/credential-vault-unit.test.ts +284 -49
  42. package/src/__tests__/credential-vault.test.ts +150 -16
  43. package/src/__tests__/credentials-cli.test.ts +71 -0
  44. package/src/__tests__/cu-unified-flow.test.ts +532 -0
  45. package/src/__tests__/date-context.test.ts +93 -77
  46. package/src/__tests__/deterministic-verification-control-plane.test.ts +64 -0
  47. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  48. package/src/__tests__/ephemeral-permissions.test.ts +3 -3
  49. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  50. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -1
  51. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +0 -1
  52. package/src/__tests__/guardian-routing-invariants.test.ts +93 -1
  53. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
  54. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -39
  55. package/src/__tests__/heartbeat-service.test.ts +0 -1
  56. package/src/__tests__/history-repair.test.ts +245 -0
  57. package/src/__tests__/host-cu-proxy.test.ts +791 -0
  58. package/src/__tests__/host-shell-tool.test.ts +27 -15
  59. package/src/__tests__/http-user-message-parity.test.ts +2 -0
  60. package/src/__tests__/ingress-url-consistency.test.ts +14 -21
  61. package/src/__tests__/integration-status.test.ts +32 -51
  62. package/src/__tests__/intent-routing.test.ts +0 -1
  63. package/src/__tests__/invite-redemption-service.test.ts +65 -1
  64. package/src/__tests__/invite-routes-http.test.ts +10 -9
  65. package/src/__tests__/keychain-broker-client.test.ts +14 -46
  66. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +56 -18
  67. package/src/__tests__/memory-lifecycle-e2e.test.ts +244 -387
  68. package/src/__tests__/memory-recall-quality.test.ts +244 -407
  69. package/src/__tests__/memory-regressions.experimental.test.ts +126 -101
  70. package/src/__tests__/memory-regressions.test.ts +477 -2841
  71. package/src/__tests__/memory-retrieval.benchmark.test.ts +33 -150
  72. package/src/__tests__/memory-upsert-concurrency.test.ts +5 -244
  73. package/src/__tests__/mime-builder.test.ts +28 -0
  74. package/src/__tests__/native-web-search.test.ts +1 -0
  75. package/src/__tests__/notification-routing-intent.test.ts +0 -1
  76. package/src/__tests__/oauth-cli.test.ts +941 -15
  77. package/src/__tests__/oauth-provider-profiles.test.ts +9 -9
  78. package/src/__tests__/oauth-scope-policy.test.ts +4 -6
  79. package/src/__tests__/oauth-store.test.ts +870 -0
  80. package/src/__tests__/onboarding-starter-tasks.test.ts +0 -1
  81. package/src/__tests__/provider-error-scenarios.test.ts +0 -1
  82. package/src/__tests__/provider-streaming.benchmark.test.ts +0 -1
  83. package/src/__tests__/public-ingress-urls.test.ts +15 -21
  84. package/src/__tests__/qdrant-collection-migration.test.ts +53 -8
  85. package/src/__tests__/recording-handler.test.ts +3 -4
  86. package/src/__tests__/registry.test.ts +2 -3
  87. package/src/__tests__/relay-server.test.ts +46 -1
  88. package/src/__tests__/runtime-events-sse.test.ts +55 -7
  89. package/src/__tests__/schedule-store.test.ts +0 -1
  90. package/src/__tests__/schedule-tools.test.ts +32 -0
  91. package/src/__tests__/scheduler-recurrence.test.ts +0 -1
  92. package/src/__tests__/scoped-approval-grants.test.ts +0 -1
  93. package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -1
  94. package/src/__tests__/script-proxy-certs.test.ts +1 -1
  95. package/src/__tests__/secret-ingress-handler.test.ts +0 -1
  96. package/src/__tests__/secret-onetime-send.test.ts +1 -0
  97. package/src/__tests__/secure-keys.test.ts +7 -2
  98. package/src/__tests__/send-endpoint-busy.test.ts +24 -6
  99. package/src/__tests__/sequence-store.test.ts +0 -1
  100. package/src/__tests__/session-abort-tool-results.test.ts +1 -14
  101. package/src/__tests__/session-agent-loop-overflow.test.ts +1583 -0
  102. package/src/__tests__/session-agent-loop.test.ts +19 -15
  103. package/src/__tests__/session-confirmation-signals.test.ts +1 -15
  104. package/src/__tests__/session-error.test.ts +124 -2
  105. package/src/__tests__/session-history-web-search.test.ts +918 -0
  106. package/src/__tests__/session-init.benchmark.test.ts +4 -5
  107. package/src/__tests__/session-pre-run-repair.test.ts +1 -14
  108. package/src/__tests__/session-provider-retry-repair.test.ts +25 -28
  109. package/src/__tests__/session-queue.test.ts +37 -27
  110. package/src/__tests__/session-runtime-assembly.test.ts +54 -0
  111. package/src/__tests__/session-slash-known.test.ts +1 -15
  112. package/src/__tests__/session-slash-queue.test.ts +1 -15
  113. package/src/__tests__/session-slash-unknown.test.ts +1 -15
  114. package/src/__tests__/session-workspace-cache-state.test.ts +3 -33
  115. package/src/__tests__/session-workspace-injection.test.ts +3 -37
  116. package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -37
  117. package/src/__tests__/skill-include-graph.test.ts +66 -0
  118. package/src/__tests__/skill-load-feature-flag.test.ts +0 -1
  119. package/src/__tests__/skill-load-tool.test.ts +149 -1
  120. package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
  121. package/src/__tests__/skills-install-extract.test.ts +93 -0
  122. package/src/__tests__/skills-uninstall.test.ts +1 -1
  123. package/src/__tests__/skills.test.ts +3 -3
  124. package/src/__tests__/skillssh-registry.test.ts +451 -0
  125. package/src/__tests__/slack-channel-config.test.ts +67 -3
  126. package/src/__tests__/slack-share-routes.test.ts +17 -19
  127. package/src/__tests__/system-prompt.test.ts +0 -1
  128. package/src/__tests__/telegram-invite-adapter.test.ts +18 -22
  129. package/src/__tests__/terminal-tools.test.ts +4 -3
  130. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  131. package/src/__tests__/tool-approval-handler.test.ts +0 -1
  132. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -1
  133. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  134. package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
  135. package/src/__tests__/tool-executor.test.ts +0 -1
  136. package/src/__tests__/tool-grant-request-escalation.test.ts +0 -1
  137. package/src/__tests__/trust-store-pattern-matches.test.ts +29 -0
  138. package/src/__tests__/trust-store.test.ts +7 -13
  139. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  140. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
  141. package/src/__tests__/twilio-routes.test.ts +0 -16
  142. package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
  143. package/src/__tests__/voice-invite-redemption.test.ts +32 -1
  144. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
  145. package/src/agent/ax-tree-compaction.test.ts +286 -0
  146. package/src/agent/loop.ts +104 -131
  147. package/src/approvals/AGENTS.md +1 -1
  148. package/src/approvals/guardian-request-resolvers.ts +14 -2
  149. package/src/bundler/compiler-tools.ts +66 -2
  150. package/src/calls/call-domain.ts +133 -6
  151. package/src/calls/call-store.ts +6 -0
  152. package/src/calls/relay-server.ts +52 -18
  153. package/src/calls/relay-setup-router.ts +17 -1
  154. package/src/calls/twilio-config.ts +3 -8
  155. package/src/calls/twilio-routes.ts +1 -2
  156. package/src/calls/types.ts +3 -1
  157. package/src/calls/voice-ingress-preflight.ts +1 -1
  158. package/src/cli/commands/browser-relay.ts +18 -12
  159. package/src/cli/commands/completions.ts +0 -3
  160. package/src/cli/commands/credentials.ts +101 -15
  161. package/src/cli/commands/doctor.ts +4 -3
  162. package/src/cli/commands/mcp.ts +46 -59
  163. package/src/cli/commands/memory.ts +16 -165
  164. package/src/cli/commands/oauth/apps.ts +284 -0
  165. package/src/cli/commands/oauth/connections.ts +633 -0
  166. package/src/cli/commands/oauth/index.ts +52 -0
  167. package/src/cli/commands/oauth/providers.ts +256 -0
  168. package/src/cli/commands/sessions.ts +5 -2
  169. package/src/cli/commands/skills.ts +177 -339
  170. package/src/cli/http-client.ts +0 -20
  171. package/src/cli/main-screen.tsx +2 -2
  172. package/src/cli/program.ts +6 -11
  173. package/src/cli/reference.ts +1 -3
  174. package/src/cli.ts +4 -10
  175. package/src/config/assistant-feature-flags.ts +0 -3
  176. package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
  177. package/src/config/bundled-skills/computer-use/SKILL.md +3 -6
  178. package/src/config/bundled-skills/computer-use/TOOLS.json +23 -5
  179. package/src/config/bundled-skills/computer-use/tools/{computer-use-request-control.ts → computer-use-observe.ts} +1 -5
  180. package/src/config/bundled-skills/google-calendar/calendar-client.ts +21 -16
  181. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -4
  182. package/src/config/bundled-skills/settings/SKILL.md +1 -1
  183. package/src/config/bundled-skills/settings/TOOLS.json +2 -8
  184. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +5 -33
  185. package/src/config/bundled-tool-registry.ts +2 -5
  186. package/src/config/env-registry.ts +14 -83
  187. package/src/config/env.ts +11 -50
  188. package/src/config/feature-flag-registry.json +16 -16
  189. package/src/config/loader.ts +0 -6
  190. package/src/config/schema.ts +4 -13
  191. package/src/config/schemas/memory-lifecycle.ts +0 -9
  192. package/src/config/schemas/memory-processing.ts +0 -180
  193. package/src/config/schemas/memory-retrieval.ts +32 -104
  194. package/src/config/schemas/memory.ts +0 -10
  195. package/src/config/skills.ts +21 -2
  196. package/src/config/types.ts +0 -4
  197. package/src/context/image-dimensions.ts +229 -0
  198. package/src/context/token-estimator.ts +75 -12
  199. package/src/context/window-manager.ts +53 -11
  200. package/src/daemon/assistant-attachments.ts +1 -13
  201. package/src/daemon/config-watcher.ts +61 -3
  202. package/src/daemon/daemon-control.ts +1 -1
  203. package/src/daemon/date-context.ts +114 -31
  204. package/src/daemon/handlers/config-ingress.ts +8 -33
  205. package/src/daemon/handlers/config-slack-channel.ts +49 -46
  206. package/src/daemon/handlers/config-telegram.ts +32 -16
  207. package/src/daemon/handlers/sessions.ts +27 -36
  208. package/src/daemon/handlers/shared.ts +0 -130
  209. package/src/daemon/handlers/skills.ts +20 -1
  210. package/src/daemon/history-repair.ts +72 -8
  211. package/src/daemon/host-cu-proxy.ts +430 -0
  212. package/src/daemon/lifecycle.ts +67 -71
  213. package/src/daemon/mcp-reload-service.ts +2 -2
  214. package/src/daemon/message-protocol.ts +3 -0
  215. package/src/daemon/message-types/computer-use.ts +1 -129
  216. package/src/daemon/message-types/host-cu.ts +19 -0
  217. package/src/daemon/message-types/memory.ts +4 -16
  218. package/src/daemon/message-types/messages.ts +4 -0
  219. package/src/daemon/message-types/sessions.ts +4 -0
  220. package/src/daemon/server.ts +25 -21
  221. package/src/daemon/session-agent-loop-handlers.ts +40 -0
  222. package/src/daemon/session-agent-loop.ts +334 -48
  223. package/src/daemon/session-attachments.ts +1 -2
  224. package/src/daemon/session-error.ts +89 -6
  225. package/src/daemon/session-history.ts +17 -7
  226. package/src/daemon/session-media-retry.ts +6 -2
  227. package/src/daemon/session-memory.ts +69 -149
  228. package/src/daemon/session-process.ts +10 -1
  229. package/src/daemon/session-runtime-assembly.ts +49 -19
  230. package/src/daemon/session-slash.ts +1 -1
  231. package/src/daemon/session-surfaces.ts +43 -28
  232. package/src/daemon/session-tool-setup.ts +9 -10
  233. package/src/daemon/session.ts +150 -17
  234. package/src/daemon/tool-side-effects.ts +2 -8
  235. package/src/daemon/watch-handler.ts +2 -2
  236. package/src/events/tool-metrics-listener.ts +2 -2
  237. package/src/hooks/manager.ts +1 -4
  238. package/src/inbound/public-ingress-urls.ts +7 -7
  239. package/src/instrument.ts +61 -1
  240. package/src/logfire.ts +16 -5
  241. package/src/memory/admin.ts +2 -191
  242. package/src/memory/canonical-guardian-store.ts +38 -2
  243. package/src/memory/conversation-crud.ts +0 -33
  244. package/src/memory/conversation-key-store.ts +21 -0
  245. package/src/memory/conversation-queries.ts +22 -3
  246. package/src/memory/db-init.ts +32 -0
  247. package/src/memory/embedding-backend.ts +84 -8
  248. package/src/memory/embedding-types.ts +9 -1
  249. package/src/memory/indexer.ts +7 -46
  250. package/src/memory/items-extractor.ts +274 -76
  251. package/src/memory/job-handlers/backfill.ts +2 -127
  252. package/src/memory/job-handlers/cleanup.ts +2 -16
  253. package/src/memory/job-handlers/extraction.ts +2 -138
  254. package/src/memory/job-handlers/index-maintenance.ts +1 -6
  255. package/src/memory/job-handlers/summarization.ts +3 -148
  256. package/src/memory/job-utils.ts +21 -59
  257. package/src/memory/jobs-store.ts +1 -159
  258. package/src/memory/jobs-worker.ts +9 -52
  259. package/src/memory/migrations/104-core-indexes.ts +3 -3
  260. package/src/memory/migrations/149-oauth-tables.ts +62 -0
  261. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +98 -0
  262. package/src/memory/migrations/151-oauth-providers-ping-url.ts +11 -0
  263. package/src/memory/migrations/152-memory-item-supersession.ts +44 -0
  264. package/src/memory/migrations/153-drop-entity-tables.ts +15 -0
  265. package/src/memory/migrations/154-drop-fts.ts +20 -0
  266. package/src/memory/migrations/155-drop-conflicts.ts +7 -0
  267. package/src/memory/migrations/156-call-session-invite-metadata.ts +24 -0
  268. package/src/memory/migrations/index.ts +8 -0
  269. package/src/memory/qdrant-client.ts +148 -51
  270. package/src/memory/raw-query.ts +1 -1
  271. package/src/memory/retriever.test.ts +294 -273
  272. package/src/memory/retriever.ts +421 -645
  273. package/src/memory/schema/calls.ts +2 -0
  274. package/src/memory/schema/index.ts +1 -0
  275. package/src/memory/schema/memory-core.ts +3 -48
  276. package/src/memory/schema/oauth.ts +67 -0
  277. package/src/memory/search/formatting.ts +263 -176
  278. package/src/memory/search/lexical.ts +1 -254
  279. package/src/memory/search/ranking.ts +0 -455
  280. package/src/memory/search/semantic.ts +100 -14
  281. package/src/memory/search/staleness.ts +47 -0
  282. package/src/memory/search/tier-classifier.ts +21 -0
  283. package/src/memory/search/types.ts +15 -77
  284. package/src/memory/task-memory-cleanup.ts +4 -6
  285. package/src/messaging/provider.ts +4 -4
  286. package/src/messaging/providers/gmail/client.ts +82 -2
  287. package/src/messaging/providers/gmail/mime-builder.ts +17 -7
  288. package/src/messaging/providers/gmail/people-client.ts +10 -10
  289. package/src/messaging/providers/telegram-bot/adapter.ts +17 -17
  290. package/src/messaging/providers/whatsapp/adapter.ts +11 -8
  291. package/src/messaging/registry.ts +2 -32
  292. package/src/notifications/copy-composer.ts +0 -5
  293. package/src/notifications/signal.ts +4 -5
  294. package/src/oauth/byo-connection.test.ts +133 -25
  295. package/src/oauth/byo-connection.ts +22 -6
  296. package/src/oauth/connect-orchestrator.ts +113 -57
  297. package/src/oauth/connect-types.ts +17 -23
  298. package/src/oauth/connection-resolver.ts +35 -11
  299. package/src/oauth/connection.ts +1 -1
  300. package/src/oauth/manual-token-connection.ts +104 -0
  301. package/src/oauth/oauth-store.ts +582 -0
  302. package/src/oauth/platform-connection.test.ts +29 -0
  303. package/src/oauth/platform-connection.ts +6 -5
  304. package/src/oauth/provider-behaviors.ts +124 -0
  305. package/src/oauth/scope-policy.ts +9 -2
  306. package/src/oauth/seed-providers.ts +167 -0
  307. package/src/oauth/token-persistence.ts +81 -77
  308. package/src/permissions/checker.ts +3 -3
  309. package/src/permissions/defaults.ts +1 -1
  310. package/src/permissions/prompter.ts +10 -1
  311. package/src/permissions/trust-store.ts +36 -1
  312. package/src/playbooks/playbook-compiler.ts +1 -1
  313. package/src/prompts/__tests__/build-cli-reference-section.test.ts +3 -1
  314. package/src/prompts/system-prompt.ts +46 -42
  315. package/src/providers/anthropic/client.ts +59 -20
  316. package/src/providers/retry.ts +1 -27
  317. package/src/providers/types.ts +7 -1
  318. package/src/runtime/AGENTS.md +9 -0
  319. package/src/runtime/auth/route-policy.ts +6 -6
  320. package/src/runtime/channel-reply-delivery.ts +0 -40
  321. package/src/runtime/gateway-client.ts +0 -7
  322. package/src/runtime/guardian-reply-router.ts +24 -22
  323. package/src/runtime/http-server.ts +10 -8
  324. package/src/runtime/http-types.ts +2 -2
  325. package/src/runtime/invite-redemption-service.ts +19 -1
  326. package/src/runtime/invite-service.ts +25 -0
  327. package/src/runtime/middleware/twilio-validation.ts +1 -11
  328. package/src/runtime/pending-interactions.ts +14 -12
  329. package/src/runtime/routes/brain-graph-routes.ts +10 -90
  330. package/src/runtime/routes/channel-delivery-routes.ts +0 -1
  331. package/src/runtime/routes/conversation-routes.ts +81 -19
  332. package/src/runtime/routes/events-routes.ts +21 -11
  333. package/src/runtime/routes/host-cu-routes.ts +97 -0
  334. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
  335. package/src/runtime/routes/inbound-stages/background-dispatch.ts +12 -111
  336. package/src/runtime/routes/integrations/slack/share.ts +6 -7
  337. package/src/runtime/routes/log-export-routes.ts +126 -8
  338. package/src/runtime/routes/memory-item-routes.test.ts +754 -0
  339. package/src/runtime/routes/memory-item-routes.ts +503 -0
  340. package/src/runtime/routes/session-management-routes.ts +3 -3
  341. package/src/runtime/routes/settings-routes.ts +55 -48
  342. package/src/runtime/routes/surface-action-routes.ts +1 -1
  343. package/src/runtime/routes/trust-rules-routes.ts +14 -0
  344. package/src/runtime/routes/watch-routes.ts +128 -0
  345. package/src/runtime/routes/workspace-routes.ts +2 -1
  346. package/src/schedule/integration-status.ts +10 -9
  347. package/src/security/credential-key.ts +0 -156
  348. package/src/security/keychain-broker-client.ts +22 -10
  349. package/src/security/oauth2.ts +1 -1
  350. package/src/security/secure-keys.ts +25 -3
  351. package/src/security/token-manager.ts +137 -64
  352. package/src/skills/catalog-install.ts +414 -0
  353. package/src/skills/include-graph.ts +32 -0
  354. package/src/skills/skillssh-registry.ts +503 -0
  355. package/src/telegram/bot-username.ts +2 -3
  356. package/src/tools/assets/search.ts +5 -1
  357. package/src/tools/browser/network-recorder.ts +1 -1
  358. package/src/tools/browser/network-recording-types.ts +1 -1
  359. package/src/tools/computer-use/definitions.ts +36 -11
  360. package/src/tools/computer-use/registry.ts +5 -6
  361. package/src/tools/credentials/broker.ts +1 -2
  362. package/src/tools/credentials/metadata-store.ts +17 -121
  363. package/src/tools/credentials/vault.ts +92 -167
  364. package/src/tools/memory/definitions.ts +4 -13
  365. package/src/tools/memory/handlers.test.ts +83 -103
  366. package/src/tools/memory/handlers.ts +50 -85
  367. package/src/tools/registry.ts +2 -7
  368. package/src/tools/schedule/create.ts +8 -1
  369. package/src/tools/schedule/update.ts +8 -1
  370. package/src/tools/skills/load.ts +85 -3
  371. package/src/tools/watch/watch-state.ts +0 -12
  372. package/src/util/logger.ts +7 -41
  373. package/src/util/platform.ts +9 -28
  374. package/src/watcher/providers/google-calendar.ts +2 -1
  375. package/src/__tests__/clarification-resolver.test.ts +0 -193
  376. package/src/__tests__/computer-use-session-compaction.test.ts +0 -143
  377. package/src/__tests__/computer-use-session-lifecycle.test.ts +0 -322
  378. package/src/__tests__/computer-use-session-working-dir.test.ts +0 -166
  379. package/src/__tests__/computer-use-skill-baseline.test.ts +0 -78
  380. package/src/__tests__/computer-use-skill-endstate.test.ts +0 -105
  381. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +0 -249
  382. package/src/__tests__/conflict-intent-tokenization.test.ts +0 -160
  383. package/src/__tests__/conflict-policy.test.ts +0 -269
  384. package/src/__tests__/conflict-store.test.ts +0 -372
  385. package/src/__tests__/contradiction-checker.test.ts +0 -361
  386. package/src/__tests__/entity-extractor.test.ts +0 -211
  387. package/src/__tests__/entity-search.test.ts +0 -1117
  388. package/src/__tests__/profile-compiler.test.ts +0 -392
  389. package/src/__tests__/ride-shotgun-handler.test.ts +0 -452
  390. package/src/__tests__/session-conflict-gate.test.ts +0 -1228
  391. package/src/__tests__/session-profile-injection.test.ts +0 -557
  392. package/src/cli/commands/dev.ts +0 -129
  393. package/src/cli/commands/map.ts +0 -391
  394. package/src/cli/commands/oauth.ts +0 -77
  395. package/src/config/bundled-skills/knowledge-graph/SKILL.md +0 -25
  396. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +0 -66
  397. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +0 -211
  398. package/src/daemon/computer-use-session.ts +0 -1026
  399. package/src/daemon/ride-shotgun-handler.ts +0 -569
  400. package/src/daemon/session-conflict-gate.ts +0 -167
  401. package/src/daemon/session-dynamic-profile.ts +0 -77
  402. package/src/memory/clarification-resolver.ts +0 -417
  403. package/src/memory/conflict-intent.ts +0 -205
  404. package/src/memory/conflict-policy.ts +0 -127
  405. package/src/memory/conflict-store.ts +0 -410
  406. package/src/memory/contradiction-checker.ts +0 -508
  407. package/src/memory/entity-extractor.ts +0 -535
  408. package/src/memory/format-recall.ts +0 -47
  409. package/src/memory/fts-reconciler.ts +0 -165
  410. package/src/memory/job-handlers/conflict.ts +0 -200
  411. package/src/memory/profile-compiler.ts +0 -195
  412. package/src/memory/recall-cache.ts +0 -117
  413. package/src/memory/search/entity.ts +0 -535
  414. package/src/memory/search/query-expansion.test.ts +0 -70
  415. package/src/memory/search/query-expansion.ts +0 -118
  416. package/src/oauth/provider-base-urls.ts +0 -21
  417. package/src/oauth/provider-profiles.ts +0 -192
  418. package/src/prompts/computer-use-prompt.ts +0 -98
  419. package/src/runtime/routes/computer-use-routes.ts +0 -641
  420. package/src/runtime/routes/mcp-routes.ts +0 -20
  421. package/src/runtime/telegram-streaming-delivery.test.ts +0 -729
  422. package/src/runtime/telegram-streaming-delivery.ts +0 -393
  423. package/src/tools/computer-use/request-computer-control.ts +0 -56
@@ -501,8 +501,8 @@ describe("host_bash — environment setup", () => {
501
501
  });
502
502
 
503
503
  test("injects INTERNAL_GATEWAY_BASE_URL for host_bash commands", async () => {
504
- const originalGatewayBase = process.env.GATEWAY_INTERNAL_BASE_URL;
505
- process.env.GATEWAY_INTERNAL_BASE_URL = "http://gateway.internal:9000/";
504
+ const originalGatewayPort = process.env.GATEWAY_PORT;
505
+ process.env.GATEWAY_PORT = "9000";
506
506
  try {
507
507
  const result = await hostShellTool.execute(
508
508
  {
@@ -511,12 +511,12 @@ describe("host_bash — environment setup", () => {
511
511
  makeContext(),
512
512
  );
513
513
  expect(result.isError).toBe(false);
514
- expect(result.content.trim()).toBe("http://gateway.internal:9000");
514
+ expect(result.content.trim()).toBe("http://127.0.0.1:9000");
515
515
  } finally {
516
- if (originalGatewayBase === undefined) {
517
- delete process.env.GATEWAY_INTERNAL_BASE_URL;
516
+ if (originalGatewayPort === undefined) {
517
+ delete process.env.GATEWAY_PORT;
518
518
  } else {
519
- process.env.GATEWAY_INTERNAL_BASE_URL = originalGatewayBase;
519
+ process.env.GATEWAY_PORT = originalGatewayPort;
520
520
  }
521
521
  }
522
522
  });
@@ -695,7 +695,11 @@ describe("host_bash — spawn error handling", () => {
695
695
  describe("host_bash — proxy delegation", () => {
696
696
  function makeMockProxy(result: ToolExecutionResult) {
697
697
  const calls: Array<{
698
- input: { command: string; working_dir?: string; timeout_seconds?: number };
698
+ input: {
699
+ command: string;
700
+ working_dir?: string;
701
+ timeout_seconds?: number;
702
+ };
699
703
  sessionId: string;
700
704
  }> = [];
701
705
 
@@ -703,7 +707,11 @@ describe("host_bash — proxy delegation", () => {
703
707
  proxy: {
704
708
  isAvailable: () => true,
705
709
  request: async (
706
- input: { command: string; working_dir?: string; timeout_seconds?: number },
710
+ input: {
711
+ command: string;
712
+ working_dir?: string;
713
+ timeout_seconds?: number;
714
+ },
707
715
  sessionId: string,
708
716
  _signal?: AbortSignal,
709
717
  ) => {
@@ -748,7 +756,10 @@ describe("host_bash — proxy delegation", () => {
748
756
  });
749
757
 
750
758
  test("still validates input before proxying (null bytes in command)", async () => {
751
- const proxyResult: ToolExecutionResult = { content: "proxied", isError: false };
759
+ const proxyResult: ToolExecutionResult = {
760
+ content: "proxied",
761
+ isError: false,
762
+ };
752
763
  const { proxy, calls } = makeMockProxy(proxyResult);
753
764
 
754
765
  const ctx: ToolContext = {
@@ -756,10 +767,7 @@ describe("host_bash — proxy delegation", () => {
756
767
  hostBashProxy: proxy as unknown as ToolContext["hostBashProxy"],
757
768
  };
758
769
 
759
- const result = await hostShellTool.execute(
760
- { command: "echo \0evil" },
761
- ctx,
762
- );
770
+ const result = await hostShellTool.execute({ command: "echo \0evil" }, ctx);
763
771
 
764
772
  expect(result.isError).toBe(true);
765
773
  expect(result.content).toContain("null bytes");
@@ -768,7 +776,10 @@ describe("host_bash — proxy delegation", () => {
768
776
  });
769
777
 
770
778
  test("still validates input before proxying (relative working_dir)", async () => {
771
- const proxyResult: ToolExecutionResult = { content: "proxied", isError: false };
779
+ const proxyResult: ToolExecutionResult = {
780
+ content: "proxied",
781
+ isError: false,
782
+ };
772
783
  const { proxy, calls } = makeMockProxy(proxyResult);
773
784
 
774
785
  const ctx: ToolContext = {
@@ -803,7 +814,8 @@ describe("host_bash — proxy delegation", () => {
803
814
 
804
815
  const ctx: ToolContext = {
805
816
  ...makeContext(),
806
- hostBashProxy: unavailableProxy as unknown as ToolContext["hostBashProxy"],
817
+ hostBashProxy:
818
+ unavailableProxy as unknown as ToolContext["hostBashProxy"],
807
819
  };
808
820
 
809
821
  spawnCalls.length = 0;
@@ -169,6 +169,8 @@ function makeSession(overrides: Record<string, unknown> = {}) {
169
169
  updateClient: () => {},
170
170
  setHostBashProxy: () => {},
171
171
  setHostFileProxy: () => {},
172
+ setHostCuProxy: () => {},
173
+ addPreactivatedSkillId: () => {},
172
174
  emitConfirmationStateChanged: () => {},
173
175
  emitActivityState: () => {},
174
176
  setTurnChannelContext: () => {},
@@ -26,6 +26,7 @@ mock.module("../util/logger.js", () => ({
26
26
  getLogger: () => makeLoggerStub(),
27
27
  }));
28
28
 
29
+ import { setIngressPublicBaseUrl } from "../config/env.js";
29
30
  import {
30
31
  getPublicBaseUrl,
31
32
  getTwilioStatusCallbackUrl,
@@ -78,19 +79,12 @@ function computeTwilioSignature(
78
79
  // ---------------------------------------------------------------------------
79
80
 
80
81
  describe("Ingress URL consistency between assistant and gateway", () => {
81
- let savedIngressEnv: string | undefined;
82
-
83
82
  beforeEach(() => {
84
- savedIngressEnv = process.env.INGRESS_PUBLIC_BASE_URL;
85
- delete process.env.INGRESS_PUBLIC_BASE_URL;
83
+ setIngressPublicBaseUrl(undefined);
86
84
  });
87
85
 
88
86
  afterEach(() => {
89
- if (savedIngressEnv !== undefined) {
90
- process.env.INGRESS_PUBLIC_BASE_URL = savedIngressEnv;
91
- } else {
92
- delete process.env.INGRESS_PUBLIC_BASE_URL;
93
- }
87
+ setIngressPublicBaseUrl(undefined);
94
88
  });
95
89
 
96
90
  test("assistant callback URL and gateway signature reconstruction use same base when config is set", () => {
@@ -104,9 +98,8 @@ describe("Ingress URL consistency between assistant and gateway", () => {
104
98
  "session-abc",
105
99
  );
106
100
 
107
- // Simulate: when hatch.ts spawns the gateway, it reads config.ingress.publicBaseUrl
108
- // and passes it as INGRESS_PUBLIC_BASE_URL. The gateway stores this as
109
- // config.ingressPublicBaseUrl.
101
+ // The gateway reads config.ingress.publicBaseUrl from the workspace config
102
+ // file via ConfigFileCache.
110
103
  const gatewayIngressPublicBaseUrl = getPublicBaseUrl(config);
111
104
 
112
105
  // When Twilio calls the gateway, the gateway reconstructs the canonical URL
@@ -147,7 +140,7 @@ describe("Ingress URL consistency between assistant and gateway", () => {
147
140
  const localRequestUrl = "http://127.0.0.1:7830/webhooks/twilio/status";
148
141
 
149
142
  // Gateway reconstructs the canonical URL using its configured base
150
- // (which was passed from the assistant's config via INGRESS_PUBLIC_BASE_URL)
143
+ // (which was read from the workspace config file via ConfigFileCache)
151
144
  const gatewayIngressPublicBaseUrl = getPublicBaseUrl(config);
152
145
  const canonicalUrl = reconstructGatewayCanonicalUrl(
153
146
  gatewayIngressPublicBaseUrl,
@@ -208,20 +201,20 @@ describe("Ingress URL consistency between assistant and gateway", () => {
208
201
  expect(recomputedWith).toBe(twilioSignature);
209
202
  });
210
203
 
211
- test("env var fallback produces consistent URLs across assistant and gateway", () => {
212
- // When no config.ingress.publicBaseUrl is set, both assistant and gateway
213
- // fall back to the INGRESS_PUBLIC_BASE_URL env var.
214
- process.env.INGRESS_PUBLIC_BASE_URL = "https://env-tunnel.example.com";
204
+ test("module-level state fallback produces consistent URLs across assistant and gateway", () => {
205
+ // When no config.ingress.publicBaseUrl is set, the assistant falls back
206
+ // to the module-level ingress state.
207
+ setIngressPublicBaseUrl("https://env-tunnel.example.com");
215
208
 
216
209
  const config: IngressConfig = {};
217
210
 
218
- // Assistant resolves the base URL from env
211
+ // Assistant resolves the base URL from module state
219
212
  const assistantBase = getPublicBaseUrl(config);
220
213
  expect(assistantBase).toBe("https://env-tunnel.example.com");
221
214
 
222
- // Gateway would also read the same env var (process.env.INGRESS_PUBLIC_BASE_URL)
223
- // and store it as config.ingressPublicBaseUrl.
224
- const gatewayIngressPublicBaseUrl = process.env.INGRESS_PUBLIC_BASE_URL;
215
+ // Gateway would read the same value from the workspace config file
216
+ // via ConfigFileCache.getString("ingress", "publicBaseUrl").
217
+ const gatewayIngressPublicBaseUrl = "https://env-tunnel.example.com";
225
218
 
226
219
  // Callback URL generated by assistant
227
220
  const callbackUrl = getTwilioVoiceWebhookUrl(config, "session-xyz");
@@ -5,6 +5,9 @@ import { credentialKey } from "../security/credential-key.js";
5
5
  const secureKeyValues = new Map<string, string>();
6
6
  let mockTwilioAccountSid: string | undefined;
7
7
 
8
+ /** Set of providers that should report as connected via isProviderConnected(). */
9
+ const connectedProviders = new Set<string>();
10
+
8
11
  mock.module("../security/secure-keys.js", () => ({
9
12
  getSecureKey: (account: string) => secureKeyValues.get(account),
10
13
  }));
@@ -17,12 +20,27 @@ mock.module("../config/loader.js", () => ({
17
20
  }),
18
21
  }));
19
22
 
23
+ mock.module("../oauth/oauth-store.js", () => ({
24
+ isProviderConnected: (providerKey: string) =>
25
+ connectedProviders.has(providerKey),
26
+ getConnectionByProvider: (providerKey: string) =>
27
+ connectedProviders.has(providerKey)
28
+ ? { id: `conn-${providerKey}`, status: "active" }
29
+ : undefined,
30
+ }));
31
+
32
+ /** Mark a provider as fully connected (active row + access token). */
33
+ function setOAuthConnected(providerKey: string): void {
34
+ connectedProviders.add(providerKey);
35
+ }
36
+
20
37
  const { getIntegrationSummary, formatIntegrationSummary, hasCapability } =
21
38
  await import("../schedule/integration-status.js");
22
39
 
23
40
  describe("integration-status", () => {
24
41
  beforeEach(() => {
25
42
  secureKeyValues.clear();
43
+ connectedProviders.clear();
26
44
  mockTwilioAccountSid = undefined;
27
45
  });
28
46
 
@@ -38,21 +56,11 @@ describe("integration-status", () => {
38
56
  });
39
57
 
40
58
  test("returns all connected when all keys are set", () => {
41
- secureKeyValues.set(
42
- credentialKey("integration:gmail", "access_token"),
43
- "tok",
44
- );
45
- secureKeyValues.set(
46
- credentialKey("integration:slack", "access_token"),
47
- "tok",
48
- );
59
+ setOAuthConnected("integration:gmail");
60
+ setOAuthConnected("integration:slack");
49
61
  mockTwilioAccountSid = "sid";
50
62
  secureKeyValues.set(credentialKey("twilio", "auth_token"), "auth");
51
- secureKeyValues.set(credentialKey("telegram", "bot_token"), "tok");
52
- secureKeyValues.set(
53
- credentialKey("telegram", "webhook_secret"),
54
- "secret",
55
- );
63
+ setOAuthConnected("telegram");
56
64
 
57
65
  const summary = getIntegrationSummary();
58
66
  expect(summary.every((s: { connected: boolean }) => s.connected)).toBe(
@@ -63,11 +71,7 @@ describe("integration-status", () => {
63
71
  test("returns mixed status", () => {
64
72
  mockTwilioAccountSid = "sid";
65
73
  secureKeyValues.set(credentialKey("twilio", "auth_token"), "auth");
66
- secureKeyValues.set(credentialKey("telegram", "bot_token"), "tok");
67
- secureKeyValues.set(
68
- credentialKey("telegram", "webhook_secret"),
69
- "secret",
70
- );
74
+ setOAuthConnected("telegram");
71
75
 
72
76
  const summary = getIntegrationSummary();
73
77
  const connected = summary.filter(
@@ -95,9 +99,8 @@ describe("integration-status", () => {
95
99
  expect(twilio?.connected).toBe(false);
96
100
  });
97
101
 
98
- test("Telegram disconnected when only bot_token is set (missing webhook_secret)", () => {
99
- secureKeyValues.set(credentialKey("telegram", "bot_token"), "tok");
100
-
102
+ test("Telegram disconnected when no connection record exists", () => {
103
+ // No oauth_connection record for telegram should be disconnected
101
104
  const summary = getIntegrationSummary();
102
105
  const telegram = summary.find(
103
106
  (s: { name: string }) => s.name === "Telegram",
@@ -110,11 +113,7 @@ describe("integration-status", () => {
110
113
  test("shows checkmarks and crosses", () => {
111
114
  mockTwilioAccountSid = "sid";
112
115
  secureKeyValues.set(credentialKey("twilio", "auth_token"), "auth");
113
- secureKeyValues.set(credentialKey("telegram", "bot_token"), "tok");
114
- secureKeyValues.set(
115
- credentialKey("telegram", "webhook_secret"),
116
- "secret",
117
- );
116
+ setOAuthConnected("telegram");
118
117
 
119
118
  const result = formatIntegrationSummary();
120
119
  expect(result).toBe(
@@ -130,21 +129,11 @@ describe("integration-status", () => {
130
129
  });
131
130
 
132
131
  test("all connected", () => {
133
- secureKeyValues.set(
134
- credentialKey("integration:gmail", "access_token"),
135
- "tok",
136
- );
137
- secureKeyValues.set(
138
- credentialKey("integration:slack", "access_token"),
139
- "tok",
140
- );
132
+ setOAuthConnected("integration:gmail");
133
+ setOAuthConnected("integration:slack");
141
134
  mockTwilioAccountSid = "sid";
142
135
  secureKeyValues.set(credentialKey("twilio", "auth_token"), "auth");
143
- secureKeyValues.set(credentialKey("telegram", "bot_token"), "tok");
144
- secureKeyValues.set(
145
- credentialKey("telegram", "webhook_secret"),
146
- "secret",
147
- );
136
+ setOAuthConnected("telegram");
148
137
 
149
138
  const result = formatIntegrationSummary();
150
139
  expect(result).toBe(
@@ -160,17 +149,12 @@ describe("integration-status", () => {
160
149
  });
161
150
 
162
151
  test("returns true when any integration in category is connected", () => {
163
- secureKeyValues.set(credentialKey("telegram", "bot_token"), "tok");
164
- secureKeyValues.set(
165
- credentialKey("telegram", "webhook_secret"),
166
- "secret",
167
- );
152
+ setOAuthConnected("telegram");
168
153
  expect(hasCapability("messaging")).toBe(true);
169
154
  });
170
155
 
171
- test("returns false when only partial credentials exist for category integrations", () => {
172
- secureKeyValues.set(credentialKey("telegram", "bot_token"), "tok");
173
- // Missing webhook_secret — Telegram should not count as connected
156
+ test("returns false when no connection record exists for category integrations", () => {
157
+ // No oauth_connection record for telegram should not count as connected
174
158
  expect(hasCapability("messaging")).toBe(false);
175
159
  });
176
160
 
@@ -179,10 +163,7 @@ describe("integration-status", () => {
179
163
  });
180
164
 
181
165
  test("email category checks Gmail", () => {
182
- secureKeyValues.set(
183
- credentialKey("integration:gmail", "access_token"),
184
- "tok",
185
- );
166
+ setOAuthConnected("integration:gmail");
186
167
  expect(hasCapability("email")).toBe(true);
187
168
  });
188
169
  });
@@ -45,7 +45,6 @@ mock.module("../util/logger.js", () => ({
45
45
  ...realLogger,
46
46
  getLogger: () => noopLogger,
47
47
  getCliLogger: () => noopLogger,
48
- isDebug: () => false,
49
48
  truncateForLog: (v: string) => v,
50
49
  initLogger: () => {},
51
50
  pruneOldLogFiles: () => 0,
@@ -23,7 +23,10 @@ mock.module("../util/logger.js", () => ({
23
23
  }),
24
24
  }));
25
25
 
26
- import { findContactChannel } from "../contacts/contact-store.js";
26
+ import {
27
+ findContactChannel,
28
+ upsertContact,
29
+ } from "../contacts/contact-store.js";
27
30
  import { upsertContactChannel } from "../contacts/contacts-write.js";
28
31
  import { getSqlite, initializeDb, resetDb } from "../memory/db.js";
29
32
  import {
@@ -278,6 +281,67 @@ describe("invite-redemption-service", () => {
278
281
  expect(outcome).toEqual({ ok: false, reason: "invalid_token" });
279
282
  });
280
283
 
284
+ test("returns invalid_token for a revoked guardian to prevent invite-based reactivation", () => {
285
+ const { rawToken } = createInvite({
286
+ sourceChannel: "telegram",
287
+ maxUses: 5,
288
+ });
289
+
290
+ // Pre-create a guardian contact with a revoked telegram channel
291
+ upsertContact({
292
+ displayName: "Guardian",
293
+ role: "guardian",
294
+ channels: [
295
+ {
296
+ type: "telegram",
297
+ address: "guardian-tg-id",
298
+ externalUserId: "guardian-tg-id",
299
+ status: "revoked",
300
+ },
301
+ ],
302
+ });
303
+
304
+ const outcome = redeemInvite({
305
+ rawToken,
306
+ sourceChannel: "telegram",
307
+ externalUserId: "guardian-tg-id",
308
+ });
309
+
310
+ // Must reject — guardian channels are managed via the binding flow, not invites
311
+ expect(outcome).toEqual({ ok: false, reason: "invalid_token" });
312
+ });
313
+
314
+ test("returns invalid_token for a revoked guardian via 6-digit invite code", () => {
315
+ const code = "123456";
316
+ const inviteCodeHash = hashVoiceCode(code);
317
+ createInvite({
318
+ sourceChannel: "telegram",
319
+ maxUses: 5,
320
+ inviteCodeHash,
321
+ });
322
+
323
+ upsertContact({
324
+ displayName: "Guardian",
325
+ role: "guardian",
326
+ channels: [
327
+ {
328
+ type: "telegram",
329
+ address: "guardian-code-id",
330
+ externalUserId: "guardian-code-id",
331
+ status: "revoked",
332
+ },
333
+ ],
334
+ });
335
+
336
+ const outcome = redeemInviteByCode({
337
+ code,
338
+ sourceChannel: "telegram",
339
+ externalUserId: "guardian-code-id",
340
+ });
341
+
342
+ expect(outcome).toEqual({ ok: false, reason: "invalid_token" });
343
+ });
344
+
281
345
  test("does not return already_member for a revoked member", () => {
282
346
  const { rawToken } = createInvite({
283
347
  sourceChannel: "telegram",
@@ -24,8 +24,7 @@ mock.module("../util/logger.js", () => ({
24
24
  }));
25
25
 
26
26
  // Prevent ensureTelegramBotUsernameResolved() from reading real credentials
27
- // and calling the Telegram API, which would populate credential metadata
28
- // with the real bot username and shadow the env var override in tests.
27
+ // and calling the Telegram API.
29
28
  mock.module("../security/secure-keys.js", () => ({
30
29
  getSecureKey: () => undefined,
31
30
  setSecureKey: () => {},
@@ -35,6 +34,13 @@ mock.module("../security/secure-keys.js", () => ({
35
34
  deleteSecureKeyAsync: async () => {},
36
35
  }));
37
36
 
37
+ // Mock getTelegramBotUsername — the env var fallback was removed so we
38
+ // control the return value directly via a mutable variable.
39
+ let mockTelegramBotUsername: string | undefined;
40
+ mock.module("../telegram/bot-username.js", () => ({
41
+ getTelegramBotUsername: () => mockTelegramBotUsername,
42
+ }));
43
+
38
44
  import { getSqlite, initializeDb, resetDb } from "../memory/db.js";
39
45
  import {
40
46
  handleCreateInvite,
@@ -94,8 +100,7 @@ describe("ingress invite HTTP routes", () => {
94
100
  });
95
101
 
96
102
  test("POST /v1/contacts/invites — includes canonical share URL when bot username is configured", async () => {
97
- const prevBotUsername = process.env.TELEGRAM_BOT_USERNAME;
98
- process.env.TELEGRAM_BOT_USERNAME = "test_invite_bot";
103
+ mockTelegramBotUsername = "test_invite_bot";
99
104
 
100
105
  try {
101
106
  const req = new Request("http://localhost/v1/contacts/invites", {
@@ -121,11 +126,7 @@ describe("ingress invite HTTP routes", () => {
121
126
  expect(share.url).toBe(`https://t.me/test_invite_bot?start=iv_${token}`);
122
127
  expect(typeof share.displayText).toBe("string");
123
128
  } finally {
124
- if (prevBotUsername === undefined) {
125
- delete process.env.TELEGRAM_BOT_USERNAME;
126
- } else {
127
- process.env.TELEGRAM_BOT_USERNAME = prevBotUsername;
128
- }
129
+ mockTelegramBotUsername = undefined;
129
130
  }
130
131
  });
131
132
 
@@ -36,7 +36,7 @@ const TEST_DIR = join(
36
36
  );
37
37
  const TOKEN_DIR = join(TEST_DIR, ".vellum", "protected");
38
38
  const TOKEN_PATH = join(TOKEN_DIR, "keychain-broker.token");
39
- const SOCKET_PATH = join(TEST_DIR, "broker.sock");
39
+ const SOCKET_PATH = join(TEST_DIR, ".vellum", "keychain-broker.sock");
40
40
  const TEST_TOKEN = "test-auth-token-abc123";
41
41
 
42
42
  // ---------------------------------------------------------------------------
@@ -106,14 +106,11 @@ function createMockBroker(): {
106
106
  // Setup / teardown
107
107
  // ---------------------------------------------------------------------------
108
108
 
109
- let originalEnv: string | undefined;
110
-
111
109
  beforeAll(() => {
112
110
  mkdirSync(TOKEN_DIR, { recursive: true });
113
111
  });
114
112
 
115
113
  beforeEach(() => {
116
- originalEnv = process.env.VELLUM_KEYCHAIN_BROKER_SOCKET;
117
114
  // Clean up socket file from prior test
118
115
  try {
119
116
  rmSync(SOCKET_PATH, { force: true });
@@ -122,14 +119,6 @@ beforeEach(() => {
122
119
  }
123
120
  });
124
121
 
125
- afterEach(() => {
126
- if (originalEnv === undefined) {
127
- delete process.env.VELLUM_KEYCHAIN_BROKER_SOCKET;
128
- } else {
129
- process.env.VELLUM_KEYCHAIN_BROKER_SOCKET = originalEnv;
130
- }
131
- });
132
-
133
122
  afterAll(() => {
134
123
  rmSync(TEST_DIR, { recursive: true, force: true });
135
124
  });
@@ -157,15 +146,15 @@ describe("keychain-broker-client", () => {
157
146
  // isAvailable()
158
147
  // -----------------------------------------------------------------------
159
148
  describe("isAvailable", () => {
160
- test("returns false when env var is unset", () => {
161
- delete process.env.VELLUM_KEYCHAIN_BROKER_SOCKET;
149
+ test("returns false when socket file does not exist", () => {
162
150
  writeFileSync(TOKEN_PATH, TEST_TOKEN);
163
151
  const client = createBrokerClient();
164
152
  expect(client.isAvailable()).toBe(false);
165
153
  });
166
154
 
167
155
  test("returns false when token file does not exist", () => {
168
- process.env.VELLUM_KEYCHAIN_BROKER_SOCKET = SOCKET_PATH;
156
+ // Create the socket file so that check passes
157
+ writeFileSync(SOCKET_PATH, "");
169
158
  try {
170
159
  rmSync(TOKEN_PATH, { force: true });
171
160
  } catch {
@@ -175,8 +164,8 @@ describe("keychain-broker-client", () => {
175
164
  expect(client.isAvailable()).toBe(false);
176
165
  });
177
166
 
178
- test("returns true when both env var and token file exist", () => {
179
- process.env.VELLUM_KEYCHAIN_BROKER_SOCKET = SOCKET_PATH;
167
+ test("returns true when both socket file and token file exist", () => {
168
+ writeFileSync(SOCKET_PATH, "");
180
169
  writeFileSync(TOKEN_PATH, TEST_TOKEN);
181
170
  const client = createBrokerClient();
182
171
  expect(client.isAvailable()).toBe(true);
@@ -190,7 +179,6 @@ describe("keychain-broker-client", () => {
190
179
  let broker: ReturnType<typeof createMockBroker>;
191
180
 
192
181
  beforeEach(async () => {
193
- process.env.VELLUM_KEYCHAIN_BROKER_SOCKET = SOCKET_PATH;
194
182
  writeFileSync(TOKEN_PATH, TEST_TOKEN);
195
183
  broker = createMockBroker();
196
184
  });
@@ -276,7 +264,7 @@ describe("keychain-broker-client", () => {
276
264
 
277
265
  const client = createBrokerClient();
278
266
  const result = await client.set("my-key", "new-value");
279
- expect(result).toBe(true);
267
+ expect(result).toEqual({ status: "ok" });
280
268
  });
281
269
 
282
270
  test("del returns true on success", async () => {
@@ -343,7 +331,6 @@ describe("keychain-broker-client", () => {
343
331
  let broker: ReturnType<typeof createMockBroker>;
344
332
 
345
333
  beforeEach(async () => {
346
- process.env.VELLUM_KEYCHAIN_BROKER_SOCKET = SOCKET_PATH;
347
334
  writeFileSync(TOKEN_PATH, TEST_TOKEN);
348
335
  broker = createMockBroker();
349
336
  });
@@ -381,7 +368,6 @@ describe("keychain-broker-client", () => {
381
368
  let broker: ReturnType<typeof createMockBroker>;
382
369
 
383
370
  beforeEach(async () => {
384
- process.env.VELLUM_KEYCHAIN_BROKER_SOCKET = SOCKET_PATH;
385
371
  writeFileSync(TOKEN_PATH, "old-token");
386
372
  broker = createMockBroker();
387
373
  });
@@ -441,59 +427,42 @@ describe("keychain-broker-client", () => {
441
427
  // Graceful degradation
442
428
  // -----------------------------------------------------------------------
443
429
  describe("graceful degradation", () => {
444
- test("get returns null when broker is not running", async () => {
445
- process.env.VELLUM_KEYCHAIN_BROKER_SOCKET = SOCKET_PATH;
430
+ test("get returns null when socket file does not exist", async () => {
446
431
  writeFileSync(TOKEN_PATH, TEST_TOKEN);
447
432
  const client = createBrokerClient();
448
433
  const result = await client.get("test-key");
449
434
  expect(result).toBeNull();
450
435
  });
451
436
 
452
- test("set returns false when broker is not running", async () => {
453
- process.env.VELLUM_KEYCHAIN_BROKER_SOCKET = SOCKET_PATH;
437
+ test("set returns unreachable when socket file does not exist", async () => {
454
438
  writeFileSync(TOKEN_PATH, TEST_TOKEN);
455
439
  const client = createBrokerClient();
456
440
  const result = await client.set("test-key", "value");
457
- expect(result).toBe(false);
441
+ expect(result).toEqual({ status: "unreachable" });
458
442
  });
459
443
 
460
- test("del returns false when broker is not running", async () => {
461
- process.env.VELLUM_KEYCHAIN_BROKER_SOCKET = SOCKET_PATH;
444
+ test("del returns false when socket file does not exist", async () => {
462
445
  writeFileSync(TOKEN_PATH, TEST_TOKEN);
463
446
  const client = createBrokerClient();
464
447
  const result = await client.del("test-key");
465
448
  expect(result).toBe(false);
466
449
  });
467
450
 
468
- test("list returns empty array when broker is not running", async () => {
469
- process.env.VELLUM_KEYCHAIN_BROKER_SOCKET = SOCKET_PATH;
451
+ test("list returns empty array when socket file does not exist", async () => {
470
452
  writeFileSync(TOKEN_PATH, TEST_TOKEN);
471
453
  const client = createBrokerClient();
472
454
  const result = await client.list();
473
455
  expect(result).toEqual([]);
474
456
  });
475
457
 
476
- test("ping returns null when broker is not running", async () => {
477
- process.env.VELLUM_KEYCHAIN_BROKER_SOCKET = SOCKET_PATH;
458
+ test("ping returns null when socket file does not exist", async () => {
478
459
  writeFileSync(TOKEN_PATH, TEST_TOKEN);
479
460
  const client = createBrokerClient();
480
461
  const result = await client.ping();
481
462
  expect(result).toBeNull();
482
463
  });
483
464
 
484
- test("returns fallbacks when socket path env var is unset", async () => {
485
- delete process.env.VELLUM_KEYCHAIN_BROKER_SOCKET;
486
- writeFileSync(TOKEN_PATH, TEST_TOKEN);
487
- const client = createBrokerClient();
488
- expect(await client.get("key")).toBeNull();
489
- expect(await client.set("key", "val")).toBe(false);
490
- expect(await client.del("key")).toBe(false);
491
- expect(await client.list()).toEqual([]);
492
- expect(await client.ping()).toBeNull();
493
- });
494
-
495
465
  test("returns fallbacks when token file is missing", async () => {
496
- process.env.VELLUM_KEYCHAIN_BROKER_SOCKET = SOCKET_PATH;
497
466
  try {
498
467
  rmSync(TOKEN_PATH, { force: true });
499
468
  } catch {
@@ -501,7 +470,7 @@ describe("keychain-broker-client", () => {
501
470
  }
502
471
  const client = createBrokerClient();
503
472
  expect(await client.get("key")).toBeNull();
504
- expect(await client.set("key", "val")).toBe(false);
473
+ expect(await client.set("key", "val")).toEqual({ status: "unreachable" });
505
474
  expect(await client.del("key")).toBe(false);
506
475
  expect(await client.list()).toEqual([]);
507
476
  expect(await client.ping()).toBeNull();
@@ -515,7 +484,6 @@ describe("keychain-broker-client", () => {
515
484
  let broker: ReturnType<typeof createMockBroker>;
516
485
 
517
486
  beforeEach(async () => {
518
- process.env.VELLUM_KEYCHAIN_BROKER_SOCKET = SOCKET_PATH;
519
487
  writeFileSync(TOKEN_PATH, TEST_TOKEN);
520
488
  broker = createMockBroker();
521
489
  });