@vellumai/assistant 0.10.2-dev.202606250318.5e7cfb0 → 0.10.2

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 (430) hide show
  1. package/bun.lock +0 -20
  2. package/docs/workspace-tools.md +33 -42
  3. package/eslint-rules/cli-no-daemon-internals.js +0 -6
  4. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +0 -31
  5. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +0 -44
  6. package/node_modules/@vellumai/gateway-client/src/index.ts +0 -14
  7. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +0 -17
  8. package/node_modules/@vellumai/service-contracts/package.json +0 -1
  9. package/node_modules/@vellumai/service-contracts/src/index.ts +0 -1
  10. package/openapi.yaml +0 -155
  11. package/package.json +1 -4
  12. package/scripts/test.sh +15 -36
  13. package/src/__tests__/actor-token-service.test.ts +14 -36
  14. package/src/__tests__/agent-loop-override-profile.test.ts +0 -1
  15. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +0 -2
  16. package/src/__tests__/agent-wake-override-profile.test.ts +0 -2
  17. package/src/__tests__/annotate-activity-metadata.test.ts +0 -2
  18. package/src/__tests__/annotate-risk-options.test.ts +0 -2
  19. package/src/__tests__/approval-cascade.test.ts +0 -2
  20. package/src/__tests__/assistant-attachments.test.ts +0 -42
  21. package/src/__tests__/background-workers-disk-pressure.test.ts +0 -2
  22. package/src/__tests__/btw-routes.test.ts +0 -2
  23. package/src/__tests__/build-persisted-content.test.ts +0 -2
  24. package/src/__tests__/call-controller.test.ts +0 -19
  25. package/src/__tests__/channel-guardian.test.ts +58 -94
  26. package/src/__tests__/channel-reply-delivery.test.ts +0 -2
  27. package/src/__tests__/compaction-events.test.ts +0 -2
  28. package/src/__tests__/compaction.benchmark.test.ts +0 -2
  29. package/src/__tests__/compactor-call-site-logging.test.ts +0 -2
  30. package/src/__tests__/compactor-low-watermark-cut.test.ts +0 -2
  31. package/src/__tests__/compactor-preserved-tail-count.test.ts +0 -2
  32. package/src/__tests__/compactor-summary-call-truncation.test.ts +0 -2
  33. package/src/__tests__/compactor-web-search-strip.test.ts +0 -2
  34. package/src/__tests__/config-loader-backfill.test.ts +10 -123
  35. package/src/__tests__/config-schema.test.ts +0 -1
  36. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +29 -31
  37. package/src/__tests__/contacts-relay-reads.test.ts +15 -13
  38. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -2
  39. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +0 -2
  40. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +0 -2
  41. package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -2
  42. package/src/__tests__/conversation-agent-loop.test.ts +0 -134
  43. package/src/__tests__/conversation-analysis-routes.test.ts +0 -2
  44. package/src/__tests__/conversation-app-control-lifecycle.test.ts +0 -2
  45. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -2
  46. package/src/__tests__/conversation-history-web-search.test.ts +0 -2
  47. package/src/__tests__/conversation-load-history-repair.test.ts +0 -2
  48. package/src/__tests__/conversation-load-history-stripped.test.ts +0 -2
  49. package/src/__tests__/conversation-pairing.test.ts +0 -2
  50. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +0 -2
  51. package/src/__tests__/conversation-process-callsite.test.ts +0 -2
  52. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -2
  53. package/src/__tests__/conversation-queue.test.ts +0 -91
  54. package/src/__tests__/conversation-routes-guardian-reply.test.ts +0 -14
  55. package/src/__tests__/conversation-routes-slash-commands.test.ts +0 -14
  56. package/src/__tests__/conversation-slash-queue.test.ts +0 -2
  57. package/src/__tests__/conversation-slash-unknown.test.ts +0 -2
  58. package/src/__tests__/conversation-speed-override.test.ts +0 -2
  59. package/src/__tests__/conversation-surfaces-task-progress.test.ts +0 -29
  60. package/src/__tests__/conversation-title-service.test.ts +0 -2
  61. package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -47
  62. package/src/__tests__/conversation-usage.test.ts +0 -2
  63. package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -2
  64. package/src/__tests__/conversation-workspace-injection.test.ts +0 -2
  65. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -2
  66. package/src/__tests__/credential-security-invariants.test.ts +1 -1
  67. package/src/__tests__/db-migration-rollback.test.ts +171 -205
  68. package/src/__tests__/db-test-helpers.ts +4 -5
  69. package/src/__tests__/deterministic-verification-control-plane.test.ts +2 -4
  70. package/src/__tests__/disk-pressure-guard.test.ts +0 -41
  71. package/src/__tests__/dm-persistence.test.ts +0 -2
  72. package/src/__tests__/emit-signal-routing-intent.test.ts +5 -10
  73. package/src/__tests__/events-dev-bypass-actor.test.ts +1 -7
  74. package/src/__tests__/exploration-drift-hook.test.ts +2 -3
  75. package/src/__tests__/filing-service.test.ts +0 -2
  76. package/src/__tests__/guardian-binding-drift-heal.test.ts +10 -75
  77. package/src/__tests__/guardian-dispatch.test.ts +1 -95
  78. package/src/__tests__/guardian-outbound-http.test.ts +0 -13
  79. package/src/__tests__/heartbeat-disk-pressure.test.ts +0 -2
  80. package/src/__tests__/heartbeat-service.test.ts +0 -2
  81. package/src/__tests__/helpers/channel-test-adapter.ts +7 -1
  82. package/src/__tests__/host-app-control-routes.test.ts +30 -24
  83. package/src/__tests__/host-bash-routes.test.ts +41 -31
  84. package/src/__tests__/host-browser-routes.test.ts +32 -26
  85. package/src/__tests__/host-cu-routes-targeted.test.ts +33 -25
  86. package/src/__tests__/host-file-routes-targeted.test.ts +52 -40
  87. package/src/__tests__/host-transfer-routes-targeted.test.ts +43 -31
  88. package/src/__tests__/http-user-message-parity.test.ts +8 -290
  89. package/src/__tests__/inbound-invite-redemption.test.ts +0 -28
  90. package/src/__tests__/inbound-slack-persistence.test.ts +0 -2
  91. package/src/__tests__/invite-redemption-service.test.ts +0 -198
  92. package/src/__tests__/llm-context-normalization.test.ts +0 -105
  93. package/src/__tests__/llm-request-log-error-payload.test.ts +9 -71
  94. package/src/__tests__/llm-usage-store.test.ts +0 -25
  95. package/src/__tests__/mcp-health-check.test.ts +1 -2
  96. package/src/__tests__/media-stream-server-integration.test.ts +0 -127
  97. package/src/__tests__/memory-retrieval-hook.test.ts +0 -2
  98. package/src/__tests__/messaging-send-tool.test.ts +0 -2
  99. package/src/__tests__/migration-import-from-url.test.ts +2 -2
  100. package/src/__tests__/mtime-cache.test.ts +5 -146
  101. package/src/__tests__/native-web-search.test.ts +0 -2
  102. package/src/__tests__/non-member-access-request.test.ts +17 -189
  103. package/src/__tests__/notification-broadcaster.test.ts +0 -4
  104. package/src/__tests__/notification-decision-recipient-context.test.ts +32 -33
  105. package/src/__tests__/notification-deep-link.test.ts +0 -6
  106. package/src/__tests__/notification-guardian-path.test.ts +0 -19
  107. package/src/__tests__/openai-provider.test.ts +12 -22
  108. package/src/__tests__/openai-responses-provider.test.ts +2 -12
  109. package/src/__tests__/outbound-slack-persistence.test.ts +0 -2
  110. package/src/__tests__/pending-interactions-resolved-event.test.ts +4 -7
  111. package/src/__tests__/persistence-secret-redaction.test.ts +0 -2
  112. package/src/__tests__/plugin-bootstrap.test.ts +73 -3
  113. package/src/__tests__/plugin-route-contribution.test.ts +17 -4
  114. package/src/__tests__/plugin-tool-contribution.test.ts +18 -3
  115. package/src/__tests__/plugin-types.test.ts +2 -0
  116. package/src/__tests__/process-message-background-slack.test.ts +0 -2
  117. package/src/__tests__/process-message-display-content.test.ts +0 -2
  118. package/src/__tests__/provider-error-scenarios.test.ts +4 -5
  119. package/src/__tests__/provider-usage-tracking.test.ts +0 -39
  120. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +0 -2
  121. package/src/__tests__/registry.test.ts +1 -4
  122. package/src/__tests__/relay-server.test.ts +25 -694
  123. package/src/__tests__/runtime-attachment-metadata.test.ts +1 -0
  124. package/src/__tests__/secret-ingress-http.test.ts +0 -14
  125. package/src/__tests__/send-endpoint-busy.test.ts +8 -30
  126. package/src/__tests__/skills.test.ts +0 -44
  127. package/src/__tests__/slack-inbound-verification.test.ts +2 -47
  128. package/src/__tests__/stt-hints.test.ts +13 -44
  129. package/src/__tests__/subagent-detail.test.ts +0 -27
  130. package/src/__tests__/subagent-disposal.test.ts +0 -65
  131. package/src/__tests__/subagent-notify-parent.test.ts +0 -2
  132. package/src/__tests__/subagent-role-registry.test.ts +2 -7
  133. package/src/__tests__/subagent-spawn-tool-fork.test.ts +0 -2
  134. package/src/__tests__/subagent-tools.test.ts +0 -2
  135. package/src/__tests__/suggestion-routes.test.ts +0 -2
  136. package/src/__tests__/title-generate-hook.test.ts +0 -2
  137. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -2
  138. package/src/__tests__/tool-executor.test.ts +11 -16
  139. package/src/__tests__/tool-preview-lifecycle.test.ts +0 -2
  140. package/src/__tests__/tool-result-metadata-plumbing.test.ts +0 -2
  141. package/src/__tests__/tool-start-timestamp.test.ts +0 -2
  142. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +10 -10
  143. package/src/__tests__/twilio-routes.test.ts +0 -96
  144. package/src/__tests__/ui-file-upload-surface.test.ts +0 -86
  145. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  146. package/src/__tests__/voice-invite-redemption.test.ts +0 -33
  147. package/src/__tests__/web-search-backend-failure.test.ts +0 -2
  148. package/src/__tests__/workspace-migration-remove-hooks.test.ts +35 -14
  149. package/src/__tests__/workspace-tool-loader.test.ts +2 -195
  150. package/src/__tests__/workspace-tools-watcher-flag.test.ts +70 -0
  151. package/src/agent/loop.ts +0 -56
  152. package/src/api/index.ts +1 -19
  153. package/src/api/responses/llm-request-log-entry.ts +0 -29
  154. package/src/api/responses/subagent-detail.ts +0 -17
  155. package/src/api/surfaces.ts +3 -39
  156. package/src/approvals/guardian-request-resolvers.ts +11 -1
  157. package/src/calls/__tests__/relay-setup-router.test.ts +4 -262
  158. package/src/calls/call-domain.ts +3 -3
  159. package/src/calls/guardian-dispatch.ts +8 -10
  160. package/src/calls/inbound-trust-reader.ts +1 -17
  161. package/src/calls/media-stream-server.ts +0 -21
  162. package/src/calls/relay-server.ts +50 -167
  163. package/src/calls/relay-setup-router.ts +7 -37
  164. package/src/calls/relay-verification.ts +4 -4
  165. package/src/calls/stt-hints.ts +12 -9
  166. package/src/calls/twilio-routes.ts +4 -14
  167. package/src/channels/types.ts +20 -10
  168. package/src/cli/commands/__tests__/cache.test.ts +1 -8
  169. package/src/cli/commands/cache.ts +181 -194
  170. package/src/cli/commands/db/__tests__/repair.test.ts +5 -6
  171. package/src/cli/commands/db/status.ts +1 -37
  172. package/src/cli/commands/mcp.ts +218 -252
  173. package/src/cli/commands/memory/index.ts +0 -2
  174. package/src/cli/commands/plugins.ts +3 -75
  175. package/src/cli/lib/__tests__/install-from-github.test.ts +0 -102
  176. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +1 -160
  177. package/src/cli/lib/list-installed-plugins.ts +1 -179
  178. package/src/config/__tests__/sync-gated-profiles.test.ts +3 -11
  179. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +17 -27
  180. package/src/config/bundled-skills/contacts/tools/contact-search.ts +3 -13
  181. package/src/config/bundled-skills/subagent/SKILL.md +1 -1
  182. package/src/config/bundled-skills/subagent/TOOLS.json +1 -1
  183. package/src/config/feature-flag-registry.json +13 -5
  184. package/src/config/loader.ts +5 -38
  185. package/src/config/schemas/__tests__/memory-v3.test.ts +0 -1
  186. package/src/config/schemas/memory-lifecycle.ts +0 -12
  187. package/src/config/schemas/memory-v3.ts +0 -7
  188. package/src/config/schemas/memory.ts +0 -4
  189. package/src/config/schemas/timeouts.ts +0 -8
  190. package/src/config/seed-inference-profiles.ts +11 -21
  191. package/src/config/skills.ts +5 -27
  192. package/src/config/sync-gated-profiles.ts +13 -12
  193. package/src/contacts/contacts-write.ts +0 -3
  194. package/src/daemon/assistant-attachments.ts +4 -27
  195. package/src/daemon/conversation-agent-loop.ts +0 -28
  196. package/src/daemon/conversation-process.ts +16 -35
  197. package/src/daemon/conversation-surfaces.ts +38 -111
  198. package/src/daemon/conversation-tool-setup.ts +16 -50
  199. package/src/daemon/conversation.ts +1 -13
  200. package/src/daemon/disk-pressure-guard.ts +2 -12
  201. package/src/daemon/event-loop-watchdog.ts +1 -28
  202. package/src/daemon/external-plugins-bootstrap.ts +34 -4
  203. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -25
  204. package/src/daemon/handlers/config-a2a.ts +14 -6
  205. package/src/daemon/handlers/config-channels.ts +22 -78
  206. package/src/daemon/handlers/conversations.ts +0 -77
  207. package/src/daemon/lifecycle.ts +0 -4
  208. package/src/daemon/mcp-reload-service.ts +0 -10
  209. package/src/daemon/memory-v2-startup.test.ts +0 -72
  210. package/src/daemon/memory-v2-startup.ts +19 -87
  211. package/src/daemon/message-types/conversations.ts +0 -2
  212. package/src/daemon/message-types/surfaces.ts +12 -12
  213. package/src/daemon/server.ts +4 -0
  214. package/src/daemon/shutdown-handlers.ts +0 -20
  215. package/src/daemon/tool-setup-types.ts +0 -9
  216. package/src/daemon/workspace-tools-watcher.ts +328 -0
  217. package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
  218. package/src/ipc/assistant-server.ts +2 -2
  219. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
  220. package/src/mcp/client.ts +1 -15
  221. package/src/mcp/mcp-auth-orchestrator.ts +1 -6
  222. package/src/mcp/mcp-oauth-provider.ts +8 -19
  223. package/src/memory/__tests__/memory-retrospective-job.test.ts +0 -8
  224. package/src/memory/conversation-crud.ts +0 -38
  225. package/src/memory/db-connection.ts +3 -22
  226. package/src/memory/db-init.ts +502 -36
  227. package/src/memory/db-singleton.ts +4 -6
  228. package/src/memory/jobs-worker.ts +0 -58
  229. package/src/memory/llm-request-log-store.ts +1 -26
  230. package/src/memory/llm-usage-store.ts +20 -48
  231. package/src/memory/memory-retrospective-job.ts +8 -9
  232. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +56 -130
  233. package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
  234. package/src/memory/migrations/registry.ts +573 -0
  235. package/src/memory/migrations/run-migrations.ts +6 -90
  236. package/src/memory/migrations/validate-migration-state.ts +66 -101
  237. package/src/memory/schema/conversations.ts +0 -9
  238. package/src/memory/schema/infrastructure.ts +0 -20
  239. package/src/memory/v2/__tests__/cli-command-store.test.ts +0 -25
  240. package/src/memory/v2/__tests__/skill-store.test.ts +0 -80
  241. package/src/memory/v2/cli-command-store.ts +38 -75
  242. package/src/memory/v2/prompts/consolidation.ts +82 -13
  243. package/src/memory/v2/prompts/router.ts +93 -21
  244. package/src/memory/v2/skill-store.ts +31 -68
  245. package/src/notifications/__tests__/broadcaster.test.ts +8 -16
  246. package/src/notifications/__tests__/decision-engine.test.ts +9 -78
  247. package/src/notifications/broadcaster.ts +1 -8
  248. package/src/notifications/decision-engine.ts +7 -15
  249. package/src/notifications/destination-resolver.ts +24 -68
  250. package/src/notifications/emit-signal.ts +14 -39
  251. package/src/permissions/question-prompter.test.ts +1 -1
  252. package/src/permissions/question-prompter.ts +4 -7
  253. package/src/plugin-api/index.ts +6 -6
  254. package/src/plugin-api/types.ts +5 -3
  255. package/src/plugin-api/vision-support.test.ts +4 -28
  256. package/src/plugin-api/vision-support.ts +31 -66
  257. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -161
  258. package/src/plugins/defaults/advisor/consult.ts +6 -110
  259. package/src/plugins/defaults/advisor/steering.ts +2 -14
  260. package/src/plugins/defaults/advisor/tools/advisor.ts +5 -32
  261. package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +1 -2
  262. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +7 -47
  263. package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +11 -10
  264. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +20 -12
  265. package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +11 -42
  266. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +3 -33
  267. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +4 -48
  268. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +8 -4
  269. package/src/plugins/defaults/memory-v3-shadow/injector.ts +15 -43
  270. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +2 -11
  271. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +13 -77
  272. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +11 -12
  273. package/src/plugins/mtime-cache.ts +291 -76
  274. package/src/plugins/pipeline.ts +13 -111
  275. package/src/plugins/types.ts +2 -0
  276. package/src/providers/anthropic/client.ts +0 -5
  277. package/src/providers/call-site-routing.ts +0 -4
  278. package/src/providers/model-catalog.ts +0 -16
  279. package/src/providers/openai/__tests__/api-error-detail.test.ts +120 -0
  280. package/src/providers/openai/chat-completions-provider.ts +83 -37
  281. package/src/providers/openai/responses-provider.ts +46 -50
  282. package/src/providers/openrouter/client.ts +0 -5
  283. package/src/providers/provider-send-message.ts +0 -4
  284. package/src/providers/ratelimit.ts +0 -4
  285. package/src/providers/retry.ts +0 -4
  286. package/src/providers/types.ts +0 -9
  287. package/src/providers/usage-tracking.ts +0 -4
  288. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +3 -335
  289. package/src/runtime/access-request-helper.ts +39 -19
  290. package/src/runtime/actor-trust-resolver.ts +2 -2
  291. package/src/runtime/assistant-event-hub.ts +1 -1
  292. package/src/runtime/assistant-stream-state.ts +2 -9
  293. package/src/runtime/auth/require-bound-guardian.ts +11 -21
  294. package/src/runtime/channel-verification-service.ts +31 -56
  295. package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
  296. package/src/runtime/guardian-vellum-migration.ts +7 -66
  297. package/src/runtime/invite-redemption-service.ts +187 -198
  298. package/src/runtime/local-actor-identity.ts +11 -76
  299. package/src/runtime/pending-interactions.ts +1 -11
  300. package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +5 -56
  301. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
  302. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +0 -187
  303. package/src/runtime/routes/browser-routes.ts +1 -1
  304. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +5 -13
  305. package/src/runtime/routes/channel-verification-routes.ts +3 -3
  306. package/src/runtime/routes/contact-routes.ts +32 -8
  307. package/src/runtime/routes/conversation-cli-routes.ts +5 -4
  308. package/src/runtime/routes/conversation-list-routes.ts +7 -4
  309. package/src/runtime/routes/conversation-query-routes.ts +0 -72
  310. package/src/runtime/routes/conversation-routes.ts +85 -84
  311. package/src/runtime/routes/events-routes.ts +2 -2
  312. package/src/runtime/routes/global-search-routes.ts +1 -3
  313. package/src/runtime/routes/guardian-action-routes.ts +5 -4
  314. package/src/runtime/routes/host-app-control-routes.ts +4 -5
  315. package/src/runtime/routes/host-bash-routes.ts +4 -5
  316. package/src/runtime/routes/host-browser-routes.ts +11 -9
  317. package/src/runtime/routes/host-cu-routes.ts +4 -5
  318. package/src/runtime/routes/host-file-routes.ts +4 -5
  319. package/src/runtime/routes/host-transfer-routes.ts +6 -6
  320. package/src/runtime/routes/http-adapter.ts +1 -1
  321. package/src/runtime/routes/identity-routes.ts +2 -3
  322. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  323. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +5 -97
  324. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +49 -61
  325. package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -16
  326. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
  327. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +8 -21
  328. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +3 -14
  329. package/src/runtime/routes/index.ts +0 -2
  330. package/src/runtime/routes/llm-context-normalization.ts +0 -83
  331. package/src/runtime/routes/mcp-auth-routes.ts +19 -171
  332. package/src/runtime/routes/migration-rollback-routes.ts +3 -4
  333. package/src/runtime/routes/migration-routes.ts +1 -4
  334. package/src/runtime/routes/subagents-routes.ts +0 -5
  335. package/src/runtime/routes/surface-action-routes.ts +56 -42
  336. package/src/runtime/services/__tests__/conversation-serializer.test.ts +0 -1
  337. package/src/runtime/services/conversation-serializer.ts +9 -7
  338. package/src/runtime/tool-grant-request-helper.ts +3 -3
  339. package/src/runtime/trust-verdict-consumer.ts +9 -85
  340. package/src/runtime/verification-outbound-actions.ts +18 -18
  341. package/src/signals/user-message.ts +0 -16
  342. package/src/subagent/manager.ts +0 -9
  343. package/src/subagent/types.ts +3 -3
  344. package/src/telemetry/types.ts +1 -34
  345. package/src/telemetry/usage-telemetry-reporter.test.ts +2 -3
  346. package/src/telemetry/usage-telemetry-reporter.ts +3 -87
  347. package/src/tools/ask-question/ask-question-tool.test.ts +0 -29
  348. package/src/tools/ask-question/ask-question-tool.ts +0 -13
  349. package/src/tools/executor.ts +4 -4
  350. package/src/tools/registry.ts +0 -18
  351. package/src/tools/shared/filesystem/path-policy.ts +5 -12
  352. package/src/tools/tool-approval-handler.ts +1 -1
  353. package/src/tools/tool-defaults.ts +2 -9
  354. package/src/tools/tool-manifest.ts +0 -3
  355. package/src/tools/types.ts +2 -17
  356. package/src/tools/workspace-tools/loader.ts +244 -348
  357. package/src/util/errors.ts +1 -26
  358. package/src/util/platform.ts +0 -5
  359. package/src/workflows/library.test.ts +0 -140
  360. package/src/workflows/library.ts +28 -82
  361. package/src/workspace/migrations/017-seed-persona-dirs.ts +34 -3
  362. package/src/workspace/migrations/019-scope-journal-to-guardian.ts +24 -3
  363. package/src/workspace/migrations/048-remove-workspace-hooks.ts +66 -14
  364. package/src/workspace/migrations/registry.ts +0 -2
  365. package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +0 -91
  366. package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +0 -48
  367. package/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +0 -28
  368. package/node_modules/@vellumai/service-contracts/src/channels.ts +0 -41
  369. package/src/__tests__/code-search-tool.test.ts +0 -585
  370. package/src/__tests__/guardian-expiry-notifier.test.ts +0 -282
  371. package/src/__tests__/mcp-config-secret-boundary.test.ts +0 -390
  372. package/src/__tests__/plugin-pipeline.test.ts +0 -96
  373. package/src/__tests__/sse-actor-principal-guardian-source.test.ts +0 -102
  374. package/src/__tests__/steer-on-enqueue-question.test.ts +0 -181
  375. package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +0 -208
  376. package/src/agent/loop-exclusive-tool.test.ts +0 -150
  377. package/src/api/constants/sse-replay.ts +0 -41
  378. package/src/api/events/conversation-notice.ts +0 -26
  379. package/src/approvals/guardian-channel-delivery.ts +0 -30
  380. package/src/approvals/guardian-expiry-notifier.ts +0 -148
  381. package/src/cli/commands/memory/__tests__/worker.test.ts +0 -302
  382. package/src/cli/commands/memory/worker.ts +0 -175
  383. package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +0 -143
  384. package/src/config/prune-seeded-callsite-defaults.ts +0 -110
  385. package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +0 -129
  386. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +0 -312
  387. package/src/contacts/__tests__/member-write-relay.test.ts +0 -202
  388. package/src/contacts/guardian-delivery-reader.ts +0 -223
  389. package/src/contacts/member-write-relay.ts +0 -189
  390. package/src/daemon/conversation-notices.ts +0 -60
  391. package/src/daemon/handlers/__tests__/config-channels.test.ts +0 -225
  392. package/src/hooks/hook-loader.ts +0 -341
  393. package/src/mcp/mcp-header-store.ts +0 -134
  394. package/src/memory/__tests__/301-create-watchdog-events.test.ts +0 -110
  395. package/src/memory/__tests__/prompt-override.test.ts +0 -192
  396. package/src/memory/__tests__/watchdog-events-store.test.ts +0 -161
  397. package/src/memory/migrations/300-add-processing-started-at.ts +0 -30
  398. package/src/memory/migrations/301-create-watchdog-events.ts +0 -45
  399. package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +0 -224
  400. package/src/memory/prompt-override.ts +0 -129
  401. package/src/memory/steps.ts +0 -573
  402. package/src/memory/watchdog-events-store.ts +0 -87
  403. package/src/memory/worker-control.ts +0 -118
  404. package/src/memory/worker-process.ts +0 -72
  405. package/src/notifications/__tests__/connected-channels.test.ts +0 -114
  406. package/src/notifications/__tests__/destination-resolver.test.ts +0 -256
  407. package/src/onboarding/checkin-event.test.ts +0 -222
  408. package/src/onboarding/checkin-event.ts +0 -321
  409. package/src/onboarding/schedule-checkin.ts +0 -190
  410. package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +0 -106
  411. package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +0 -60
  412. package/src/plugins/defaults/advisor/context-pack.ts +0 -288
  413. package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +0 -146
  414. package/src/plugins/surface-import.ts +0 -121
  415. package/src/providers/openai/__tests__/api-error-normalization.test.ts +0 -321
  416. package/src/providers/openai/api-error-normalization.ts +0 -270
  417. package/src/runtime/__tests__/channel-verification-service.test.ts +0 -133
  418. package/src/runtime/__tests__/guardian-vellum-migration.test.ts +0 -181
  419. package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +0 -66
  420. package/src/runtime/__tests__/local-principal-trust.test.ts +0 -164
  421. package/src/runtime/anchored-guardian.test.ts +0 -156
  422. package/src/runtime/anchored-guardian.ts +0 -135
  423. package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +0 -99
  424. package/src/runtime/local-principal-trust.ts +0 -52
  425. package/src/runtime/routes/__tests__/contact-routes.test.ts +0 -212
  426. package/src/runtime/routes/__tests__/global-search-routes.test.ts +0 -93
  427. package/src/runtime/routes/onboarding-checkin-routes.ts +0 -86
  428. package/src/tools/filesystem/search.ts +0 -543
  429. package/src/util/telemetry-db-path.ts +0 -24
  430. package/src/workspace/migrations/111-prune-seeded-callsite-defaults.ts +0 -134
@@ -1,150 +0,0 @@
1
- /**
2
- * Verifies the agent loop's exclusive-tool dispatch: when a tool the loop is
3
- * told is exclusive (e.g. the advisor) appears in a multi-call turn, only that
4
- * tool runs and the siblings are deferred un-run with a benign result — so the
5
- * model incorporates the exclusive tool's output before acting on anything
6
- * else. Drives the REAL loop, mocking only the provider boundary.
7
- */
8
- import { describe, expect, test } from "bun:test";
9
-
10
- import { createMockProvider } from "../__tests__/helpers/mock-provider.js";
11
- import type { ContentBlock, ProviderResponse } from "../providers/types.js";
12
- import { AgentLoop } from "./loop.js";
13
-
14
- const endTurn = (text: string): ProviderResponse => ({
15
- content: [{ type: "text", text }],
16
- model: "mock-model",
17
- usage: { inputTokens: 1, outputTokens: 1 },
18
- stopReason: "end_turn",
19
- });
20
-
21
- const toolUseTurn = (
22
- blocks: Array<{ id: string; name: string }>,
23
- ): ProviderResponse => ({
24
- content: [
25
- { type: "text", text: "working" },
26
- ...blocks.map((b) => ({
27
- type: "tool_use" as const,
28
- id: b.id,
29
- name: b.name,
30
- input: {},
31
- })),
32
- ],
33
- model: "mock-model",
34
- usage: { inputTokens: 1, outputTokens: 1 },
35
- stopReason: "tool_use",
36
- });
37
-
38
- function toolResults(history: { content: ContentBlock[] }[]) {
39
- return history
40
- .flatMap((m) => m.content)
41
- .filter(
42
- (b): b is Extract<ContentBlock, { type: "tool_result" }> =>
43
- b.type === "tool_result",
44
- );
45
- }
46
-
47
- const baseRun = {
48
- requestId: "req-excl",
49
- onEvent: () => {},
50
- callSite: "mainAgent" as const,
51
- trust: { sourceChannel: "vellum" as const, trustClass: "unknown" as const },
52
- };
53
-
54
- describe("AgentLoop — exclusive tool deferral", () => {
55
- test("runs the exclusive tool alone and defers sibling calls un-run", async () => {
56
- const { provider } = createMockProvider([
57
- toolUseTurn([
58
- { id: "call-advisor", name: "advisor" },
59
- { id: "call-edit", name: "write_file" },
60
- ]),
61
- endTurn("done"),
62
- ]);
63
-
64
- const executed: string[] = [];
65
- const loop = new AgentLoop({
66
- provider,
67
- systemPrompt: "sys",
68
- conversationId: "excl-1",
69
- tools: [
70
- { name: "advisor", description: "", input_schema: { type: "object" } },
71
- {
72
- name: "write_file",
73
- description: "",
74
- input_schema: { type: "object" },
75
- },
76
- ],
77
- toolExecutor: async (name) => {
78
- executed.push(name);
79
- return { content: `ran ${name}`, isError: false };
80
- },
81
- isExclusiveTool: (name) => name === "advisor",
82
- });
83
-
84
- const { history } = await loop.run({
85
- ...baseRun,
86
- messages: [{ role: "user", content: [{ type: "text", text: "do it" }] }],
87
- });
88
-
89
- // Only the exclusive tool actually executed.
90
- expect(executed).toEqual(["advisor"]);
91
-
92
- const results = toolResults(history);
93
- const advisorResult = results.find(
94
- (b) => b.tool_use_id === "call-advisor",
95
- )!;
96
- const editResult = results.find((b) => b.tool_use_id === "call-edit")!;
97
-
98
- // The advisor ran; the sibling came back un-run (not an error) so the model
99
- // can re-issue it after reading the guidance.
100
- expect(advisorResult.content).toBe("ran advisor");
101
- expect(editResult.content).toContain("not run");
102
- expect(editResult.content).toContain("advisor");
103
- expect(editResult.is_error).toBe(false);
104
- });
105
-
106
- test("runs sibling tools normally when no exclusive tool is present", async () => {
107
- const { provider } = createMockProvider([
108
- toolUseTurn([
109
- { id: "call-read", name: "read_file" },
110
- { id: "call-write", name: "write_file" },
111
- ]),
112
- endTurn("done"),
113
- ]);
114
-
115
- const executed: string[] = [];
116
- const loop = new AgentLoop({
117
- provider,
118
- systemPrompt: "sys",
119
- conversationId: "excl-2",
120
- tools: [
121
- {
122
- name: "read_file",
123
- description: "",
124
- input_schema: { type: "object" },
125
- },
126
- {
127
- name: "write_file",
128
- description: "",
129
- input_schema: { type: "object" },
130
- },
131
- ],
132
- toolExecutor: async (name) => {
133
- executed.push(name);
134
- return { content: `ran ${name}`, isError: false };
135
- },
136
- isExclusiveTool: (name) => name === "advisor",
137
- });
138
-
139
- const { history } = await loop.run({
140
- ...baseRun,
141
- messages: [{ role: "user", content: [{ type: "text", text: "do it" }] }],
142
- });
143
-
144
- // Both non-exclusive tools ran; nothing was deferred.
145
- expect(executed.sort()).toEqual(["read_file", "write_file"]);
146
- for (const result of toolResults(history)) {
147
- expect(result.content).not.toContain("not run");
148
- }
149
- });
150
- });
@@ -1,41 +0,0 @@
1
- /**
2
- * Maximum number of events the daemon's per-process SSE replay ring
3
- * retains for `Last-Event-ID` resume. This is the ring's count bound; the
4
- * ring is also bounded by total bytes and entry age (whichever limit is
5
- * hit first wins), so the live ring can hold *fewer* than this many
6
- * events, never more. The daemon-side definition and eviction live in
7
- * `assistant/src/runtime/assistant-stream-state.ts`.
8
- *
9
- * Exposed on the API surface so the web client's SSE consumer can size
10
- * its seq-gap tolerance against the same number the daemon buffers
11
- * against, instead of hard-coding a duplicate.
12
- *
13
- * A live seq gap smaller than this is benign: the global per-assistant
14
- * `seq` counter is stamped before fanout, but the hub deliberately
15
- * withholds some events from a given subscriber — self-echo-suppressed
16
- * `sync_changed` (a client's own mutation echo) and capability-targeted
17
- * host-proxy events — so a subscriber legitimately sees its cursor skip a
18
- * few seqs it was never going to receive. Such a hole is not data loss
19
- * and must not trigger a destructive authoritative snapshot heal. Only a
20
- * gap that meets or exceeds this count proves the live suffix fell
21
- * outside the ring entirely and is genuinely non-contiguous.
22
- */
23
- export const SSE_REPLAY_RING_COUNT_LIMIT = 200;
24
-
25
- /**
26
- * Maximum age (in milliseconds) an event may reach in the daemon's SSE
27
- * replay ring before it is evicted, regardless of count or byte usage.
28
- * The daemon-side definition and eviction live in
29
- * `assistant/src/runtime/assistant-stream-state.ts`.
30
- *
31
- * Exposed alongside the count bound so the web client can reason about
32
- * the age dimension of the ring too. A small live seq gap is only safe to
33
- * treat as benign when the client has been continuously receiving events
34
- * — live delivery is concurrent with stamping, so a connected subscriber
35
- * never relies on the ring. Once the connection has been quiet for longer
36
- * than this window (a disconnect/resume), events the client missed may
37
- * have aged out of the ring and become unrecoverable by replay, so even a
38
- * small seq gap must trigger an authoritative reconcile rather than be
39
- * waved through.
40
- */
41
- export const SSE_REPLAY_RING_AGE_LIMIT_MS = 30_000;
@@ -1,26 +0,0 @@
1
- /**
2
- * `conversation_notice` SSE event.
3
- *
4
- * Non-terminal, conversation-scoped notice for actionable runtime conditions
5
- * that should not mark the turn as failed. The client may render CTA UI from
6
- * this event while preserving the current assistant response.
7
- */
8
-
9
- import { z } from "zod";
10
-
11
- import { ConversationErrorCodeSchema } from "./conversation-error.js";
12
-
13
- export const ConversationNoticeSourceSchema = z.enum(["memory_v3"]);
14
-
15
- export const ConversationNoticeEventSchema = z.object({
16
- type: z.literal("conversation_notice"),
17
- conversationId: z.string(),
18
- source: ConversationNoticeSourceSchema,
19
- code: ConversationErrorCodeSchema,
20
- userMessage: z.string(),
21
- errorCategory: z.string().optional(),
22
- });
23
-
24
- export type ConversationNoticeEvent = z.infer<
25
- typeof ConversationNoticeEventSchema
26
- >;
@@ -1,30 +0,0 @@
1
- /**
2
- * Shared addressing helpers for guardian requester-facing channel notices.
3
- *
4
- * Requester notices (approval, denial, expiry) are delivered straight to the
5
- * requester's chat via `deliverChannelReply` — independent of the
6
- * guardian-facing notification pipeline. Centralizing the addressing rules here
7
- * keeps the decision resolvers and the timer-driven expiry sweep from drifting
8
- * apart on how a requester is reached.
9
- */
10
-
11
- /**
12
- * Resolve the callback-less delivery route for a channel (e.g. `/deliver/slack`).
13
- *
14
- * Used when there is no inbound reply callback URL to post back to — the
15
- * guardian decided off-channel (desktop), or the expiry sweep fired on a timer
16
- * with no originating request in hand. Returns null for channels that have no
17
- * deliverable route (e.g. email, the in-app vellum surface).
18
- */
19
- export function resolveDeliverCallbackUrlForChannel(
20
- channel: string,
21
- ): string | null {
22
- switch (channel) {
23
- case "telegram":
24
- case "whatsapp":
25
- case "slack":
26
- return `/deliver/${channel}`;
27
- default:
28
- return null;
29
- }
30
- }
@@ -1,148 +0,0 @@
1
- /**
2
- * Expiry side effects for canonical guardian requests.
3
- *
4
- * When the canonical expiry sweep transitions a pending request to `expired`,
5
- * the request's cards are withdrawn — but nobody is told and any in-memory
6
- * interaction is left dangling. This module fills that gap:
7
- *
8
- * - the requester is told their request expired (for the persistent,
9
- * requester-facing kinds: `access_request`, `tool_grant_request`), and
10
- * - the in-memory pending interaction is released (for the interaction-bound
11
- * `tool_approval` kind).
12
- *
13
- * Delivery goes straight to the requester via `deliverChannelReply` on the
14
- * callback-less `/deliver/<channel>` route — NOT the notification pipeline,
15
- * which is guardian-facing (`emitNotificationSignal` resolves the *guardian's*
16
- * delivery channels). The guardian is intentionally left passive here: the
17
- * withdrawn card already reflects the expired state, so a fresh ping would be
18
- * noise.
19
- *
20
- * Best-effort by contract: the request is already resolved (CAS committed)
21
- * before this runs, so a failed notice or interaction release must never
22
- * surface as a sweep failure. Nothing here throws.
23
- */
24
-
25
- import type { CanonicalGuardianRequest } from "../memory/canonical-guardian-store.js";
26
- import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
27
- import { deliverChannelReply } from "../runtime/gateway-client.js";
28
- import * as pendingInteractions from "../runtime/pending-interactions.js";
29
- import { getLogger } from "../util/logger.js";
30
- import { resolveDeliverCallbackUrlForChannel } from "./guardian-channel-delivery.js";
31
-
32
- const log = getLogger("guardian-expiry-notifier");
33
-
34
- /**
35
- * Run the expiry side effects for a single canonical guardian request that the
36
- * sweep just transitioned to `expired`. Dispatches by kind; never throws.
37
- */
38
- export async function notifyExpiredGuardianRequest(
39
- request: CanonicalGuardianRequest,
40
- ): Promise<void> {
41
- try {
42
- switch (request.kind) {
43
- case "tool_approval":
44
- releaseExpiredInteraction(request);
45
- return;
46
- case "access_request":
47
- await notifyRequesterOfExpiry(
48
- request,
49
- "Your access request expired before it was reviewed. " +
50
- "Send a new message if you still need access.",
51
- );
52
- return;
53
- case "tool_grant_request":
54
- await notifyRequesterOfExpiry(
55
- request,
56
- `Your request to use "${request.toolName ?? "a tool"}" expired ` +
57
- "before it was reviewed. Ask again if you still need it.",
58
- );
59
- return;
60
- case "pending_question":
61
- // Voice call sessions own their own lifecycle and timeout. By the time
62
- // the canonical TTL lapses the call is long over and there is no
63
- // durable requester channel to notify, so there is nothing to do.
64
- return;
65
- default:
66
- return;
67
- }
68
- } catch (err) {
69
- log.warn(
70
- { err, requestId: request.id, kind: request.kind },
71
- "Expiry side effects failed for canonical guardian request (non-fatal)",
72
- );
73
- }
74
- }
75
-
76
- /**
77
- * Release the in-memory pending interaction for an expired `tool_approval`.
78
- *
79
- * `tool_approval` is the one interaction-bound kind the periodic sweep can
80
- * reach (it carries a 30-minute `expiresAt`). In practice the canonical request
81
- * id does not key a *blocking* prompter interaction: ingress escalations — the
82
- * sole producer of this kind — register no interaction at all, and
83
- * PermissionPrompter confirmations are keyed by their own request id with their
84
- * own (far shorter) timeout. So this is a safe cleanup: it no-ops when nothing
85
- * is registered under the request id, and otherwise drops a waiter-less async
86
- * entry and emits `interaction_resolved` so clients clear the attention
87
- * indicator. `cancelled` is the documented runtime-termination/timeout outcome.
88
- */
89
- function releaseExpiredInteraction(request: CanonicalGuardianRequest): void {
90
- const released = pendingInteractions.resolve(request.id, "cancelled");
91
- if (released) {
92
- log.info(
93
- { requestId: request.id, kind: request.kind },
94
- "Released pending interaction for expired guardian request",
95
- );
96
- }
97
- }
98
-
99
- /**
100
- * Deliver an expiry notice straight to the requester's chat.
101
- *
102
- * The sweep is timer-driven and holds no inbound reply callback URL, so this
103
- * mirrors the resolvers' off-channel (desktop) delivery path: post to the
104
- * callback-less `/deliver/<channel>` route. On Slack the notice is routed to
105
- * the requester's DM via their user id rather than the channel id, so it is
106
- * never posted into a shared channel. No-ops on channels without a deliverable
107
- * route (e.g. email, the in-app vellum surface) or when the requester chat is
108
- * unknown. Best-effort: a delivery failure is logged, never thrown.
109
- */
110
- async function notifyRequesterOfExpiry(
111
- request: CanonicalGuardianRequest,
112
- text: string,
113
- ): Promise<void> {
114
- const channel = request.sourceChannel ?? "";
115
- const deliverUrl = resolveDeliverCallbackUrlForChannel(channel);
116
- const requesterChatId =
117
- request.requesterChatId ?? request.requesterExternalUserId ?? "";
118
- const requesterExternalUserId = request.requesterExternalUserId ?? "";
119
-
120
- if (!deliverUrl || !requesterChatId) {
121
- return;
122
- }
123
-
124
- // On Slack, target the requester's DM (their `U…` user id) instead of the
125
- // channel id so the expiry notice stays private. Other channels deliver to
126
- // the requester chat directly.
127
- const targetChatId =
128
- channel === "slack" && requesterExternalUserId
129
- ? requesterExternalUserId
130
- : requesterChatId;
131
-
132
- try {
133
- await deliverChannelReply(deliverUrl, {
134
- chatId: targetChatId,
135
- text,
136
- assistantId: DAEMON_INTERNAL_ASSISTANT_ID,
137
- });
138
- log.info(
139
- { requestId: request.id, kind: request.kind, channel },
140
- "Notified requester that guardian request expired",
141
- );
142
- } catch (err) {
143
- log.warn(
144
- { err, requestId: request.id, channel },
145
- "Failed to notify requester of guardian request expiry (non-fatal)",
146
- );
147
- }
148
- }
@@ -1,302 +0,0 @@
1
- /**
2
- * Tests for the `assistant memory worker` CLI subgroup.
3
- *
4
- * Validates:
5
- * - Subcommand registration (start, stop, status) under `memory worker`.
6
- * - `status` reports running/not_running via PID-file liveness.
7
- * - `stop` sends SIGTERM to a live worker and errors when none is running.
8
- * - `start` refuses to spawn when a worker is already running, and reports
9
- * the PID once the spawned process writes its PID file.
10
- */
11
-
12
- import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
13
- import { tmpdir } from "node:os";
14
- import { join } from "node:path";
15
- import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
16
-
17
- import { Command } from "commander";
18
-
19
- // ---------------------------------------------------------------------------
20
- // Mock state
21
- // ---------------------------------------------------------------------------
22
-
23
- let tmpDir: string;
24
- let pidPath: string;
25
- let logOutput: string[] = [];
26
-
27
- /** Records (pid, signal) pairs passed to the mocked process.kill. */
28
- let killCalls: Array<{ pid: number; signal: string | number }> = [];
29
-
30
- // ---------------------------------------------------------------------------
31
- // Mocks
32
- // ---------------------------------------------------------------------------
33
-
34
- mock.module("../../../../util/platform.js", () => ({
35
- getMemoryWorkerPidPath: () => pidPath,
36
- }));
37
-
38
- const capture = (...args: unknown[]) => {
39
- logOutput.push(args.map(String).join(" "));
40
- };
41
- const fakeLogger = {
42
- info: capture,
43
- warn: capture,
44
- error: capture,
45
- debug: () => {},
46
- };
47
- mock.module("../../../../util/logger.js", () => ({
48
- getLogger: () => fakeLogger,
49
- getCliLogger: () => fakeLogger,
50
- }));
51
-
52
- // ---------------------------------------------------------------------------
53
- // Import module under test (after mocks)
54
- // ---------------------------------------------------------------------------
55
-
56
- const { registerMemoryWorkerCommand } = await import("../worker.js");
57
-
58
- // ---------------------------------------------------------------------------
59
- // Test helpers
60
- // ---------------------------------------------------------------------------
61
-
62
- function buildProgram(): Command {
63
- const program = new Command();
64
- program.exitOverride();
65
- program.configureOutput({
66
- writeErr: () => {},
67
- writeOut: () => {},
68
- });
69
- const memory = program.command("memory");
70
- registerMemoryWorkerCommand(memory);
71
- return program;
72
- }
73
-
74
- async function runCommand(
75
- args: string[],
76
- ): Promise<{ stdout: string; exitCode: number }> {
77
- const originalStdoutWrite = process.stdout.write.bind(process.stdout);
78
- const originalStderrWrite = process.stderr.write.bind(process.stderr);
79
- const stdoutChunks: string[] = [];
80
-
81
- process.stdout.write = ((chunk: unknown) => {
82
- stdoutChunks.push(typeof chunk === "string" ? chunk : String(chunk));
83
- return true;
84
- }) as typeof process.stdout.write;
85
-
86
- process.stderr.write = (() => true) as typeof process.stderr.write;
87
-
88
- process.exitCode = 0;
89
-
90
- try {
91
- const program = buildProgram();
92
- await program.parseAsync(["node", "assistant", ...args]);
93
- } catch {
94
- if (process.exitCode === 0) process.exitCode = 1;
95
- } finally {
96
- process.stdout.write = originalStdoutWrite;
97
- process.stderr.write = originalStderrWrite;
98
- }
99
-
100
- const exitCode = process.exitCode ?? 0;
101
- process.exitCode = 0;
102
-
103
- return { exitCode, stdout: stdoutChunks.join("") };
104
- }
105
-
106
- /**
107
- * Replace process.kill with a recording stub. `signal 0` is the liveness
108
- * probe: it resolves for `livePids` and throws ESRCH otherwise. Other signals
109
- * are recorded and no-oped so the test runner is never actually signalled.
110
- */
111
- function stubProcessKill(livePids: Set<number>): () => void {
112
- const original = process.kill.bind(process);
113
- killCalls = [];
114
- process.kill = ((pid: number, signal?: string | number) => {
115
- const sig = signal ?? 0;
116
- if (sig === 0) {
117
- if (livePids.has(pid)) return true;
118
- const err = new Error("kill ESRCH") as NodeJS.ErrnoException;
119
- err.code = "ESRCH";
120
- throw err;
121
- }
122
- killCalls.push({ pid, signal: sig });
123
- return true;
124
- }) as typeof process.kill;
125
- return () => {
126
- process.kill = original;
127
- };
128
- }
129
-
130
- // ---------------------------------------------------------------------------
131
- // Setup
132
- // ---------------------------------------------------------------------------
133
-
134
- beforeEach(() => {
135
- tmpDir = mkdtempSync(join(tmpdir(), "memory-worker-test-"));
136
- pidPath = join(tmpDir, "memory-worker.pid");
137
- logOutput = [];
138
- killCalls = [];
139
- process.exitCode = 0;
140
- });
141
-
142
- afterEach(() => {
143
- rmSync(tmpDir, { recursive: true, force: true });
144
- });
145
-
146
- // ---------------------------------------------------------------------------
147
- // Subcommand registration
148
- // ---------------------------------------------------------------------------
149
-
150
- describe("subcommand registration", () => {
151
- test("registers worker under memory with start/stop/status", () => {
152
- const program = buildProgram();
153
- const memory = program.commands.find((c) => c.name() === "memory");
154
- expect(memory).toBeDefined();
155
- const worker = memory!.commands.find((c) => c.name() === "worker");
156
- expect(worker).toBeDefined();
157
- const subcommandNames = worker!.commands.map((c) => c.name()).sort();
158
- expect(subcommandNames).toEqual(["start", "status", "stop"]);
159
- });
160
- });
161
-
162
- // ---------------------------------------------------------------------------
163
- // status
164
- // ---------------------------------------------------------------------------
165
-
166
- describe("memory worker status", () => {
167
- test("reports not_running when no PID file exists", async () => {
168
- const { exitCode, stdout } = await runCommand([
169
- "memory",
170
- "worker",
171
- "status",
172
- "--json",
173
- ]);
174
- expect(exitCode).toBe(0);
175
- expect(JSON.parse(stdout)).toEqual({ status: "not_running" });
176
- });
177
-
178
- test("reports running when PID file points at a live process", async () => {
179
- writeFileSync(pidPath, String(process.pid));
180
- const restore = stubProcessKill(new Set([process.pid]));
181
- try {
182
- const { exitCode, stdout } = await runCommand([
183
- "memory",
184
- "worker",
185
- "status",
186
- "--json",
187
- ]);
188
- expect(exitCode).toBe(0);
189
- expect(JSON.parse(stdout)).toEqual({
190
- status: "running",
191
- pid: process.pid,
192
- });
193
- } finally {
194
- restore();
195
- }
196
- });
197
-
198
- test("treats a stale PID file as not_running and cleans it up", async () => {
199
- writeFileSync(pidPath, "999999");
200
- const restore = stubProcessKill(new Set());
201
- try {
202
- const { exitCode, stdout } = await runCommand([
203
- "memory",
204
- "worker",
205
- "status",
206
- "--json",
207
- ]);
208
- expect(exitCode).toBe(0);
209
- expect(JSON.parse(stdout)).toEqual({ status: "not_running" });
210
- expect(existsSync(pidPath)).toBe(false);
211
- } finally {
212
- restore();
213
- }
214
- });
215
- });
216
-
217
- // ---------------------------------------------------------------------------
218
- // stop
219
- // ---------------------------------------------------------------------------
220
-
221
- describe("memory worker stop", () => {
222
- test("errors with exit 1 when no worker is running", async () => {
223
- const { exitCode, stdout } = await runCommand([
224
- "memory",
225
- "worker",
226
- "stop",
227
- "--json",
228
- ]);
229
- expect(exitCode).toBe(1);
230
- expect(JSON.parse(stdout)).toMatchObject({ ok: false });
231
- });
232
-
233
- test("sends SIGTERM to a running worker", async () => {
234
- writeFileSync(pidPath, String(process.pid));
235
- const restore = stubProcessKill(new Set([process.pid]));
236
- try {
237
- const { exitCode, stdout } = await runCommand([
238
- "memory",
239
- "worker",
240
- "stop",
241
- "--json",
242
- ]);
243
- expect(exitCode).toBe(0);
244
- expect(JSON.parse(stdout)).toEqual({ ok: true, pid: process.pid });
245
- expect(killCalls).toContainEqual({
246
- pid: process.pid,
247
- signal: "SIGTERM",
248
- });
249
- } finally {
250
- restore();
251
- }
252
- });
253
- });
254
-
255
- // ---------------------------------------------------------------------------
256
- // start
257
- // ---------------------------------------------------------------------------
258
-
259
- describe("memory worker start", () => {
260
- test("refuses to start when a worker is already running", async () => {
261
- writeFileSync(pidPath, String(process.pid));
262
- const restore = stubProcessKill(new Set([process.pid]));
263
- try {
264
- const { exitCode, stdout } = await runCommand([
265
- "memory",
266
- "worker",
267
- "start",
268
- "--json",
269
- ]);
270
- expect(exitCode).toBe(1);
271
- expect(JSON.parse(stdout)).toMatchObject({
272
- ok: false,
273
- pid: process.pid,
274
- });
275
- } finally {
276
- restore();
277
- }
278
- });
279
-
280
- test("spawns the worker and reports the PID it writes", async () => {
281
- const restore = stubProcessKill(new Set());
282
- const originalSpawn = Bun.spawn;
283
- // Simulate the spawned worker writing its PID file on startup.
284
- (Bun as { spawn: typeof Bun.spawn }).spawn = (() => {
285
- writeFileSync(pidPath, "424242");
286
- return { unref: () => {}, pid: 424242 };
287
- }) as unknown as typeof Bun.spawn;
288
- try {
289
- const { exitCode, stdout } = await runCommand([
290
- "memory",
291
- "worker",
292
- "start",
293
- "--json",
294
- ]);
295
- expect(exitCode).toBe(0);
296
- expect(JSON.parse(stdout)).toMatchObject({ ok: true, pid: 424242 });
297
- } finally {
298
- (Bun as { spawn: typeof Bun.spawn }).spawn = originalSpawn;
299
- restore();
300
- }
301
- });
302
- });