@vellumai/assistant 0.10.2 → 0.10.3-dev.202606252046.9075fd5

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 (546) hide show
  1. package/bun.lock +20 -0
  2. package/docs/workspace-tools.md +42 -33
  3. package/eslint-rules/cli-no-daemon-internals.js +6 -0
  4. package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +91 -0
  5. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +31 -0
  6. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +44 -0
  7. package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +48 -0
  8. package/node_modules/@vellumai/gateway-client/src/index.ts +14 -0
  9. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +17 -0
  10. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  11. package/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +28 -0
  12. package/node_modules/@vellumai/service-contracts/src/channels.ts +41 -0
  13. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  14. package/openapi.yaml +196 -56
  15. package/package.json +4 -1
  16. package/scripts/test.sh +36 -15
  17. package/src/__tests__/actor-token-service.test.ts +36 -14
  18. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +83 -31
  19. package/src/__tests__/agent-loop-override-profile.test.ts +1 -0
  20. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  21. package/src/__tests__/agent-wake-override-profile.test.ts +2 -0
  22. package/src/__tests__/annotate-activity-metadata.test.ts +2 -0
  23. package/src/__tests__/annotate-risk-options.test.ts +2 -0
  24. package/src/__tests__/approval-cascade.test.ts +2 -0
  25. package/src/__tests__/assistant-attachments.test.ts +42 -0
  26. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -0
  27. package/src/__tests__/btw-routes.test.ts +2 -0
  28. package/src/__tests__/build-persisted-content.test.ts +2 -0
  29. package/src/__tests__/call-controller.test.ts +19 -0
  30. package/src/__tests__/channel-approval-routes.test.ts +21 -26
  31. package/src/__tests__/channel-delivery-store.test.ts +28 -0
  32. package/src/__tests__/channel-guardian.test.ts +164 -78
  33. package/src/__tests__/channel-inbound-disk-pressure.test.ts +11 -19
  34. package/src/__tests__/channel-reply-delivery.test.ts +2 -0
  35. package/src/__tests__/code-search-tool.test.ts +585 -0
  36. package/src/__tests__/compaction-events.test.ts +2 -0
  37. package/src/__tests__/compaction-ledger-store.test.ts +128 -0
  38. package/src/__tests__/compaction.benchmark.test.ts +2 -0
  39. package/src/__tests__/compactor-call-site-logging.test.ts +2 -0
  40. package/src/__tests__/compactor-low-watermark-cut.test.ts +2 -0
  41. package/src/__tests__/compactor-preserved-tail-count.test.ts +2 -0
  42. package/src/__tests__/compactor-summary-call-truncation.test.ts +2 -0
  43. package/src/__tests__/compactor-web-search-strip.test.ts +2 -0
  44. package/src/__tests__/config-loader-backfill.test.ts +123 -10
  45. package/src/__tests__/config-schema.test.ts +1 -0
  46. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +31 -29
  47. package/src/__tests__/consult-deadline.test.ts +60 -0
  48. package/src/__tests__/contact-store-interaction-info.test.ts +156 -0
  49. package/src/__tests__/contact-store-user-file.test.ts +7 -10
  50. package/src/__tests__/contacts-relay-reads.test.ts +19 -24
  51. package/src/__tests__/contacts-write.test.ts +0 -2
  52. package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
  53. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +2 -0
  54. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
  55. package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -0
  56. package/src/__tests__/conversation-agent-loop.test.ts +134 -0
  57. package/src/__tests__/conversation-analysis-routes.test.ts +2 -0
  58. package/src/__tests__/conversation-app-control-lifecycle.test.ts +2 -0
  59. package/src/__tests__/conversation-attention-telegram.test.ts +9 -11
  60. package/src/__tests__/conversation-confirmation-signals.test.ts +2 -0
  61. package/src/__tests__/conversation-fork-crud.test.ts +354 -24
  62. package/src/__tests__/conversation-history-web-search.test.ts +2 -0
  63. package/src/__tests__/conversation-load-history-repair.test.ts +2 -0
  64. package/src/__tests__/conversation-load-history-stripped.test.ts +2 -0
  65. package/src/__tests__/conversation-pairing.test.ts +2 -0
  66. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
  67. package/src/__tests__/conversation-process-callsite.test.ts +2 -0
  68. package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
  69. package/src/__tests__/conversation-queue.test.ts +91 -0
  70. package/src/__tests__/conversation-routes-guardian-reply.test.ts +14 -0
  71. package/src/__tests__/conversation-routes-slash-commands.test.ts +14 -0
  72. package/src/__tests__/conversation-slash-queue.test.ts +2 -0
  73. package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
  74. package/src/__tests__/conversation-speed-override.test.ts +2 -0
  75. package/src/__tests__/conversation-surfaces-task-progress.test.ts +29 -0
  76. package/src/__tests__/conversation-title-service.test.ts +2 -0
  77. package/src/__tests__/conversation-tool-setup-attribution.test.ts +47 -0
  78. package/src/__tests__/conversation-usage.test.ts +2 -0
  79. package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -0
  80. package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
  81. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
  82. package/src/__tests__/credential-security-invariants.test.ts +1 -1
  83. package/src/__tests__/db-compaction-events-migration.test.ts +129 -0
  84. package/src/__tests__/db-migration-rollback.test.ts +205 -171
  85. package/src/__tests__/db-test-helpers.ts +5 -4
  86. package/src/__tests__/delete-propagation.test.ts +5 -3
  87. package/src/__tests__/deterministic-verification-control-plane.test.ts +4 -2
  88. package/src/__tests__/disk-pressure-guard.test.ts +41 -0
  89. package/src/__tests__/dm-backfill.test.ts +6 -4
  90. package/src/__tests__/dm-persistence.test.ts +2 -0
  91. package/src/__tests__/emit-signal-routing-intent.test.ts +7 -6
  92. package/src/__tests__/events-dev-bypass-actor.test.ts +7 -1
  93. package/src/__tests__/exploration-drift-hook.test.ts +3 -2
  94. package/src/__tests__/filing-service.test.ts +2 -0
  95. package/src/__tests__/guardian-binding-drift-heal.test.ts +104 -19
  96. package/src/__tests__/guardian-dispatch.test.ts +140 -1
  97. package/src/__tests__/guardian-expiry-notifier.test.ts +282 -0
  98. package/src/__tests__/guardian-outbound-http.test.ts +13 -0
  99. package/src/__tests__/guardian-routing-state.test.ts +6 -10
  100. package/src/__tests__/heartbeat-disk-pressure.test.ts +2 -0
  101. package/src/__tests__/heartbeat-service.test.ts +2 -0
  102. package/src/__tests__/helpers/channel-test-adapter.ts +46 -19
  103. package/src/__tests__/helpers/create-guardian-binding.ts +15 -23
  104. package/src/__tests__/helpers/mock-logger.ts +1 -0
  105. package/src/__tests__/helpers/seed-contact-channel.ts +96 -0
  106. package/src/__tests__/host-app-control-routes.test.ts +24 -30
  107. package/src/__tests__/host-bash-routes.test.ts +31 -41
  108. package/src/__tests__/host-browser-routes.test.ts +26 -32
  109. package/src/__tests__/host-cu-routes-targeted.test.ts +25 -33
  110. package/src/__tests__/host-file-routes-targeted.test.ts +40 -52
  111. package/src/__tests__/host-transfer-routes-targeted.test.ts +31 -43
  112. package/src/__tests__/http-user-message-parity.test.ts +290 -8
  113. package/src/__tests__/inbound-invite-redemption.test.ts +115 -10
  114. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  115. package/src/__tests__/invite-redemption-service.test.ts +471 -53
  116. package/src/__tests__/invite-routes-http.test.ts +34 -0
  117. package/src/__tests__/invite-service-ipc.test.ts +65 -2
  118. package/src/__tests__/llm-context-normalization.test.ts +105 -0
  119. package/src/__tests__/llm-request-log-error-payload.test.ts +71 -9
  120. package/src/__tests__/llm-usage-store.test.ts +25 -0
  121. package/src/__tests__/mcp-config-secret-boundary.test.ts +392 -0
  122. package/src/__tests__/mcp-health-check.test.ts +2 -1
  123. package/src/__tests__/media-stream-server-integration.test.ts +127 -0
  124. package/src/__tests__/memory-retrieval-hook.test.ts +2 -0
  125. package/src/__tests__/messaging-send-tool.test.ts +2 -0
  126. package/src/__tests__/migration-import-from-url.test.ts +2 -2
  127. package/src/__tests__/mtime-cache.test.ts +275 -5
  128. package/src/__tests__/native-web-search.test.ts +2 -0
  129. package/src/__tests__/non-member-access-request.test.ts +191 -17
  130. package/src/__tests__/notification-broadcaster.test.ts +4 -0
  131. package/src/__tests__/notification-decision-recipient-context.test.ts +33 -32
  132. package/src/__tests__/notification-deep-link.test.ts +6 -0
  133. package/src/__tests__/notification-guardian-path.test.ts +19 -0
  134. package/src/__tests__/onboarding-persona-write.test.ts +52 -22
  135. package/src/__tests__/openai-provider.test.ts +22 -12
  136. package/src/__tests__/openai-responses-provider.test.ts +12 -2
  137. package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
  138. package/src/__tests__/pending-interactions-resolved-event.test.ts +7 -4
  139. package/src/__tests__/persist-onboarding-artifacts.test.ts +1 -0
  140. package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
  141. package/src/__tests__/persona-resolver.test.ts +75 -45
  142. package/src/__tests__/plugin-bootstrap.test.ts +21 -83
  143. package/src/__tests__/plugin-disabled-state.test.ts +190 -0
  144. package/src/__tests__/plugin-pipeline.test.ts +96 -0
  145. package/src/__tests__/plugin-route-contribution.test.ts +6 -19
  146. package/src/__tests__/plugin-tool-contribution.test.ts +5 -20
  147. package/src/__tests__/plugin-types.test.ts +2 -4
  148. package/src/__tests__/process-message-background-slack.test.ts +2 -0
  149. package/src/__tests__/process-message-display-content.test.ts +2 -0
  150. package/src/__tests__/provider-error-scenarios.test.ts +5 -4
  151. package/src/__tests__/provider-usage-tracking.test.ts +39 -0
  152. package/src/__tests__/reaction-intercept-cold-cache-warm.test.ts +135 -0
  153. package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +158 -0
  154. package/src/__tests__/reaction-persistence.test.ts +51 -4
  155. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +2 -0
  156. package/src/__tests__/registry.test.ts +4 -1
  157. package/src/__tests__/relay-server.test.ts +758 -32
  158. package/src/__tests__/runtime-attachment-metadata.test.ts +9 -12
  159. package/src/__tests__/secret-ingress-http.test.ts +14 -0
  160. package/src/__tests__/send-endpoint-busy.test.ts +30 -8
  161. package/src/__tests__/settings-routes.test.ts +32 -0
  162. package/src/__tests__/skills.test.ts +44 -0
  163. package/src/__tests__/slack-inbound-verification.test.ts +47 -2
  164. package/src/__tests__/sse-actor-principal-guardian-source.test.ts +79 -0
  165. package/src/__tests__/steer-on-enqueue-question.test.ts +181 -0
  166. package/src/__tests__/stt-hints.test.ts +49 -15
  167. package/src/__tests__/subagent-detail.test.ts +27 -0
  168. package/src/__tests__/subagent-disposal.test.ts +65 -0
  169. package/src/__tests__/subagent-fork-prompt-role.test.ts +195 -0
  170. package/src/__tests__/subagent-fork-spawn.test.ts +6 -7
  171. package/src/__tests__/subagent-notify-parent.test.ts +2 -0
  172. package/src/__tests__/subagent-role-registry.test.ts +24 -6
  173. package/src/__tests__/subagent-spawn-and-await.test.ts +546 -0
  174. package/src/__tests__/subagent-spawn-tool-fork.test.ts +2 -0
  175. package/src/__tests__/subagent-tools.test.ts +398 -1
  176. package/src/__tests__/suggestion-routes.test.ts +2 -0
  177. package/src/__tests__/thread-backfill.test.ts +3 -3
  178. package/src/__tests__/title-generate-hook.test.ts +2 -0
  179. package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -0
  180. package/src/__tests__/tool-executor.test.ts +16 -11
  181. package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
  182. package/src/__tests__/tool-result-metadata-plumbing.test.ts +2 -0
  183. package/src/__tests__/tool-start-timestamp.test.ts +2 -0
  184. package/src/__tests__/trusted-contact-approval-notifier.test.ts +37 -51
  185. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +12 -12
  186. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +9 -7
  187. package/src/__tests__/trusted-contact-multichannel.test.ts +16 -7
  188. package/src/__tests__/trusted-contact-verification.test.ts +79 -54
  189. package/src/__tests__/twilio-routes.test.ts +96 -0
  190. package/src/__tests__/ui-file-upload-surface.test.ts +86 -0
  191. package/src/__tests__/verification-control-plane-policy.test.ts +2 -0
  192. package/src/__tests__/voice-guardian-cold-cache-warm.test.ts +137 -0
  193. package/src/__tests__/voice-invite-redemption.test.ts +216 -20
  194. package/src/__tests__/web-search-backend-failure.test.ts +2 -0
  195. package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +3 -3
  196. package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +208 -0
  197. package/src/__tests__/workspace-migration-112-remove-advisor-callsite-override.test.ts +170 -0
  198. package/src/__tests__/workspace-migration-drop-user-md.test.ts +196 -238
  199. package/src/__tests__/workspace-migration-remove-hooks.test.ts +14 -35
  200. package/src/__tests__/workspace-tool-loader.test.ts +195 -2
  201. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +35 -47
  202. package/src/agent/loop-exclusive-tool.test.ts +154 -0
  203. package/src/agent/loop-native-web-search.test.ts +200 -0
  204. package/src/agent/loop.ts +164 -1
  205. package/src/api/constants/sse-replay.ts +41 -0
  206. package/src/api/events/conversation-notice.ts +26 -0
  207. package/src/api/index.ts +19 -1
  208. package/src/api/responses/llm-request-log-entry.ts +29 -0
  209. package/src/api/responses/subagent-detail.ts +17 -0
  210. package/src/api/surfaces.ts +39 -3
  211. package/src/approvals/guardian-channel-delivery.ts +30 -0
  212. package/src/approvals/guardian-expiry-notifier.ts +148 -0
  213. package/src/approvals/guardian-request-resolvers.ts +17 -15
  214. package/src/calls/__tests__/relay-setup-router.test.ts +267 -17
  215. package/src/calls/call-domain.ts +3 -3
  216. package/src/calls/guardian-dispatch.ts +14 -9
  217. package/src/calls/inbound-trust-reader.ts +24 -2
  218. package/src/calls/media-stream-server.ts +21 -0
  219. package/src/calls/relay-access-wait.ts +6 -6
  220. package/src/calls/relay-server.ts +188 -51
  221. package/src/calls/relay-setup-router.ts +47 -17
  222. package/src/calls/relay-verification.ts +4 -4
  223. package/src/calls/stt-hints.ts +9 -12
  224. package/src/calls/twilio-routes.ts +14 -4
  225. package/src/channels/types.ts +10 -20
  226. package/src/cli/commands/__tests__/cache.test.ts +8 -1
  227. package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
  228. package/src/cli/commands/cache.ts +194 -181
  229. package/src/cli/commands/contacts.ts +10 -7
  230. package/src/cli/commands/db/__tests__/repair.test.ts +6 -5
  231. package/src/cli/commands/db/status.ts +37 -1
  232. package/src/cli/commands/mcp.ts +252 -218
  233. package/src/cli/commands/memory/__tests__/worker.test.ts +432 -0
  234. package/src/cli/commands/memory/index.ts +2 -0
  235. package/src/cli/commands/memory/worker.ts +242 -0
  236. package/src/cli/commands/plugins.ts +125 -13
  237. package/src/cli/lib/__tests__/install-from-github.test.ts +102 -0
  238. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +160 -1
  239. package/src/cli/lib/__tests__/publish-plugin.test.ts +404 -0
  240. package/src/cli/lib/list-installed-plugins.ts +179 -1
  241. package/src/cli/lib/publish-plugin.ts +633 -0
  242. package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +143 -0
  243. package/src/config/__tests__/sync-gated-profiles.test.ts +16 -10
  244. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +27 -17
  245. package/src/config/bundled-skills/contacts/tools/contact-search.ts +13 -3
  246. package/src/config/bundled-skills/subagent/SKILL.md +17 -2
  247. package/src/config/bundled-skills/subagent/TOOLS.json +5 -4
  248. package/src/config/call-site-defaults.ts +0 -6
  249. package/src/config/feature-flag-registry.json +5 -13
  250. package/src/config/llm-resolver.ts +0 -3
  251. package/src/config/loader.ts +38 -5
  252. package/src/config/prune-seeded-callsite-defaults.ts +110 -0
  253. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  254. package/src/config/schemas/call-site-catalog.ts +0 -7
  255. package/src/config/schemas/heartbeat.ts +2 -5
  256. package/src/config/schemas/llm.ts +3 -12
  257. package/src/config/schemas/memory-lifecycle.ts +12 -0
  258. package/src/config/schemas/memory-v3.ts +7 -0
  259. package/src/config/schemas/memory.ts +4 -0
  260. package/src/config/schemas/timeouts.ts +8 -0
  261. package/src/config/seed-inference-profiles.ts +30 -34
  262. package/src/config/skills.ts +27 -5
  263. package/src/config/sync-gated-profiles.ts +12 -16
  264. package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +128 -0
  265. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +312 -0
  266. package/src/contacts/__tests__/member-write-relay.test.ts +226 -0
  267. package/src/contacts/contact-store.ts +27 -237
  268. package/src/contacts/contacts-write.ts +18 -55
  269. package/src/contacts/gateway-channel-read.ts +51 -0
  270. package/src/contacts/guardian-delivery-reader.ts +223 -0
  271. package/src/contacts/member-write-relay.ts +183 -0
  272. package/src/contacts/types.ts +3 -15
  273. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +35 -0
  274. package/src/daemon/__tests__/conversation-tool-setup.test.ts +0 -44
  275. package/src/daemon/assistant-attachments.ts +27 -4
  276. package/src/daemon/conversation-agent-loop.ts +28 -0
  277. package/src/daemon/conversation-notices.ts +60 -0
  278. package/src/daemon/conversation-process.ts +35 -16
  279. package/src/daemon/conversation-surfaces.ts +111 -38
  280. package/src/daemon/conversation-tool-setup.ts +71 -30
  281. package/src/daemon/conversation.ts +23 -1
  282. package/src/daemon/disk-pressure-guard.ts +12 -2
  283. package/src/daemon/event-loop-watchdog.ts +28 -1
  284. package/src/daemon/external-plugins-bootstrap.ts +17 -41
  285. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +0 -1
  286. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -2
  287. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +25 -2
  288. package/src/daemon/handlers/__tests__/config-channels.test.ts +220 -0
  289. package/src/daemon/handlers/config-a2a.ts +6 -14
  290. package/src/daemon/handlers/config-channels.ts +67 -26
  291. package/src/daemon/handlers/conversations.ts +77 -0
  292. package/src/daemon/lifecycle.ts +16 -0
  293. package/src/daemon/mcp-reload-service.ts +10 -0
  294. package/src/daemon/memory-v2-startup.test.ts +72 -0
  295. package/src/daemon/memory-v2-startup.ts +87 -19
  296. package/src/daemon/message-types/conversations.ts +2 -0
  297. package/src/daemon/message-types/surfaces.ts +12 -12
  298. package/src/daemon/server.ts +0 -4
  299. package/src/daemon/shutdown-handlers.ts +20 -0
  300. package/src/daemon/tool-setup-types.ts +9 -0
  301. package/src/heartbeat/heartbeat-service.ts +5 -0
  302. package/src/home/relationship-state-writer.ts +5 -0
  303. package/src/hooks/hook-loader.ts +341 -0
  304. package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
  305. package/src/ipc/assistant-server.ts +2 -2
  306. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +1 -0
  307. package/src/mcp/client.ts +15 -1
  308. package/src/mcp/mcp-auth-orchestrator.ts +6 -1
  309. package/src/mcp/mcp-header-store.ts +134 -0
  310. package/src/mcp/mcp-oauth-provider.ts +19 -8
  311. package/src/memory/__tests__/301-create-watchdog-events.test.ts +110 -0
  312. package/src/memory/__tests__/memory-retrospective-job.test.ts +8 -0
  313. package/src/memory/__tests__/prompt-override.test.ts +192 -0
  314. package/src/memory/__tests__/watchdog-events-store.test.ts +161 -0
  315. package/src/memory/compaction-ledger-store.ts +107 -0
  316. package/src/memory/conversation-crud.ts +129 -61
  317. package/src/memory/db-connection.ts +22 -3
  318. package/src/memory/db-init.ts +37 -503
  319. package/src/memory/db-singleton.ts +6 -4
  320. package/src/memory/jobs-worker.ts +104 -0
  321. package/src/memory/llm-request-log-store.ts +26 -1
  322. package/src/memory/llm-usage-store.ts +48 -20
  323. package/src/memory/memory-retrospective-job.ts +14 -8
  324. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +152 -56
  325. package/src/memory/migrations/300-add-processing-started-at.ts +30 -0
  326. package/src/memory/migrations/301-create-watchdog-events.ts +45 -0
  327. package/src/memory/migrations/302-create-compaction-events.ts +107 -0
  328. package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +297 -0
  329. package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
  330. package/src/memory/migrations/run-migrations.ts +106 -10
  331. package/src/memory/migrations/validate-migration-state.ts +105 -67
  332. package/src/memory/prompt-override.ts +129 -0
  333. package/src/memory/schema/contacts.ts +6 -2
  334. package/src/memory/schema/conversations.ts +37 -0
  335. package/src/memory/schema/infrastructure.ts +20 -0
  336. package/src/memory/steps.ts +1294 -0
  337. package/src/memory/v2/__tests__/cli-command-store.test.ts +25 -0
  338. package/src/memory/v2/__tests__/skill-store.test.ts +80 -0
  339. package/src/memory/v2/cli-command-store.ts +75 -38
  340. package/src/memory/v2/prompts/consolidation.ts +13 -82
  341. package/src/memory/v2/prompts/router.ts +21 -93
  342. package/src/memory/v2/skill-store.ts +68 -31
  343. package/src/memory/watchdog-events-store.ts +87 -0
  344. package/src/memory/worker-control.ts +204 -0
  345. package/src/memory/worker-process.ts +89 -0
  346. package/src/messaging/channel-binding-metadata.ts +31 -0
  347. package/src/messaging/provider-types.ts +8 -0
  348. package/src/messaging/providers/slack/binding-metadata.ts +60 -0
  349. package/src/notifications/__tests__/broadcaster.test.ts +8 -8
  350. package/src/notifications/__tests__/connected-channels.test.ts +86 -0
  351. package/src/notifications/__tests__/decision-engine.test.ts +78 -9
  352. package/src/notifications/__tests__/destination-resolver.test.ts +151 -0
  353. package/src/notifications/broadcaster.ts +8 -1
  354. package/src/notifications/decision-engine.ts +15 -7
  355. package/src/notifications/destination-resolver.ts +53 -25
  356. package/src/notifications/emit-signal.ts +34 -15
  357. package/src/onboarding/checkin-event.test.ts +222 -0
  358. package/src/onboarding/checkin-event.ts +321 -0
  359. package/src/onboarding/schedule-checkin.ts +190 -0
  360. package/src/permissions/question-prompter.test.ts +1 -1
  361. package/src/permissions/question-prompter.ts +7 -4
  362. package/src/plugin-api/index.ts +12 -12
  363. package/src/plugin-api/types.ts +12 -14
  364. package/src/plugin-api/vision-support.test.ts +28 -4
  365. package/src/plugin-api/vision-support.ts +66 -31
  366. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +2 -2
  367. package/src/plugins/defaults/empty-response/hooks/stop.ts +2 -2
  368. package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +4 -3
  369. package/src/plugins/defaults/history-repair/hooks/post-model-call.ts +2 -2
  370. package/src/plugins/defaults/history-repair/hooks/stop.ts +2 -2
  371. package/src/plugins/defaults/history-repair/hooks/user-prompt-submit.ts +2 -2
  372. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +47 -7
  373. package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +12 -13
  374. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +14 -22
  375. package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +42 -11
  376. package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +2 -2
  377. package/src/plugins/defaults/image-recovery/hooks/stop.ts +2 -2
  378. package/src/plugins/defaults/index.ts +0 -35
  379. package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +2 -2
  380. package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +2 -2
  381. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +2 -2
  382. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  383. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +33 -3
  384. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +48 -4
  385. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +4 -8
  386. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +2 -2
  387. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +2 -2
  388. package/src/plugins/defaults/memory-v3-shadow/injector.ts +43 -15
  389. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +11 -2
  390. package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +146 -0
  391. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +77 -13
  392. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +12 -11
  393. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +2 -2
  394. package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +2 -2
  395. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +2 -2
  396. package/src/plugins/defaults/title-generate/hooks/stop.ts +2 -2
  397. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +2 -2
  398. package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +2 -2
  399. package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +2 -2
  400. package/src/plugins/disabled-state.ts +31 -0
  401. package/src/plugins/external-plugin-loader.ts +2 -2
  402. package/src/plugins/mtime-cache.ts +186 -330
  403. package/src/plugins/pipeline.ts +111 -13
  404. package/src/plugins/registry.ts +59 -16
  405. package/src/plugins/surface-import.ts +121 -0
  406. package/src/plugins/types.ts +7 -9
  407. package/src/prompts/persona-resolver.ts +43 -11
  408. package/src/providers/anthropic/client.ts +5 -0
  409. package/src/providers/call-site-routing.ts +45 -0
  410. package/src/providers/model-catalog.ts +16 -0
  411. package/src/providers/openai/__tests__/api-error-normalization.test.ts +321 -0
  412. package/src/providers/openai/api-error-normalization.ts +270 -0
  413. package/src/providers/openai/chat-completions-provider.ts +37 -83
  414. package/src/providers/openai/responses-provider.ts +50 -46
  415. package/src/providers/openrouter/client.ts +5 -0
  416. package/src/providers/provider-send-message.ts +10 -0
  417. package/src/providers/ratelimit.ts +10 -0
  418. package/src/providers/registry.ts +1 -1
  419. package/src/providers/retry.ts +10 -0
  420. package/src/providers/types.ts +22 -0
  421. package/src/providers/usage-tracking.ts +10 -0
  422. package/src/runtime/__tests__/channel-verification-service.test.ts +133 -0
  423. package/src/runtime/__tests__/guardian-vellum-migration.test.ts +184 -0
  424. package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +66 -0
  425. package/src/runtime/__tests__/local-principal-trust.test.ts +162 -0
  426. package/src/runtime/__tests__/member-verdict-cache.test.ts +119 -0
  427. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +402 -123
  428. package/src/runtime/access-request-helper.ts +18 -39
  429. package/src/runtime/actor-trust-resolver.ts +46 -19
  430. package/src/runtime/anchored-guardian.test.ts +109 -0
  431. package/src/runtime/anchored-guardian.ts +86 -0
  432. package/src/runtime/assistant-event-hub.ts +1 -1
  433. package/src/runtime/assistant-stream-state.ts +9 -2
  434. package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +99 -0
  435. package/src/runtime/auth/require-bound-guardian.ts +21 -11
  436. package/src/runtime/channel-verification-service.ts +56 -31
  437. package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
  438. package/src/runtime/guardian-vellum-migration.ts +69 -8
  439. package/src/runtime/invite-redemption-service.ts +213 -187
  440. package/src/runtime/local-actor-identity.test.ts +108 -0
  441. package/src/runtime/local-actor-identity.ts +85 -13
  442. package/src/runtime/local-principal-trust.ts +52 -0
  443. package/src/runtime/member-verdict-cache.ts +0 -0
  444. package/src/runtime/pending-interactions.ts +11 -1
  445. package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +56 -5
  446. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
  447. package/src/runtime/routes/__tests__/contact-routes.test.ts +305 -0
  448. package/src/runtime/routes/__tests__/global-search-routes.test.ts +92 -0
  449. package/src/runtime/routes/__tests__/http-adapter-actor-header.test.ts +129 -0
  450. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +188 -0
  451. package/src/runtime/routes/browser-routes.ts +1 -1
  452. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +13 -5
  453. package/src/runtime/routes/channel-verification-routes.ts +3 -3
  454. package/src/runtime/routes/contact-routes.ts +48 -57
  455. package/src/runtime/routes/conversation-cli-routes.ts +4 -5
  456. package/src/runtime/routes/conversation-list-routes.ts +4 -7
  457. package/src/runtime/routes/conversation-query-routes.ts +72 -0
  458. package/src/runtime/routes/conversation-routes.ts +84 -85
  459. package/src/runtime/routes/events-routes.ts +2 -2
  460. package/src/runtime/routes/global-search-routes.ts +3 -1
  461. package/src/runtime/routes/guardian-action-routes.ts +4 -5
  462. package/src/runtime/routes/host-app-control-routes.ts +5 -4
  463. package/src/runtime/routes/host-bash-routes.ts +5 -4
  464. package/src/runtime/routes/host-browser-routes.ts +9 -11
  465. package/src/runtime/routes/host-cu-routes.ts +5 -4
  466. package/src/runtime/routes/host-file-routes.ts +5 -4
  467. package/src/runtime/routes/host-transfer-routes.ts +6 -6
  468. package/src/runtime/routes/http-adapter.ts +16 -1
  469. package/src/runtime/routes/identity-routes.ts +3 -2
  470. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  471. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +97 -5
  472. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +61 -59
  473. package/src/runtime/routes/inbound-stages/background-dispatch.ts +13 -5
  474. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
  475. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +21 -8
  476. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -3
  477. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +19 -0
  478. package/src/runtime/routes/index.ts +2 -0
  479. package/src/runtime/routes/llm-context-normalization.ts +83 -0
  480. package/src/runtime/routes/mcp-auth-routes.ts +171 -19
  481. package/src/runtime/routes/migration-rollback-routes.ts +4 -3
  482. package/src/runtime/routes/migration-routes.ts +4 -1
  483. package/src/runtime/routes/onboarding-checkin-routes.ts +86 -0
  484. package/src/runtime/routes/settings-routes.ts +8 -3
  485. package/src/runtime/routes/subagents-routes.ts +5 -0
  486. package/src/runtime/routes/surface-action-routes.ts +42 -56
  487. package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
  488. package/src/runtime/services/conversation-serializer.ts +13 -58
  489. package/src/runtime/tool-grant-request-helper.ts +3 -3
  490. package/src/runtime/trust-verdict-consumer.ts +111 -40
  491. package/src/runtime/verification-outbound-actions.ts +18 -18
  492. package/src/signals/user-message.ts +16 -0
  493. package/src/subagent/__tests__/consult-prompt.test.ts +35 -0
  494. package/src/{plugins/defaults/advisor/__tests__/transcript.test.ts → subagent/__tests__/consult-transcript.test.ts} +47 -10
  495. package/src/{plugins/defaults/advisor/steering.ts → subagent/consult-prompt.ts} +22 -32
  496. package/src/{plugins/defaults/advisor/transcript.ts → subagent/consult-transcript.ts} +18 -8
  497. package/src/subagent/index.ts +1 -1
  498. package/src/subagent/manager.ts +254 -33
  499. package/src/subagent/types.ts +11 -4
  500. package/src/telemetry/types.ts +34 -1
  501. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
  502. package/src/telemetry/usage-telemetry-reporter.ts +87 -3
  503. package/src/tools/ask-question/ask-question-tool.test.ts +29 -0
  504. package/src/tools/ask-question/ask-question-tool.ts +13 -0
  505. package/src/tools/executor.ts +4 -4
  506. package/src/tools/filesystem/search.ts +543 -0
  507. package/src/tools/registry.ts +38 -0
  508. package/src/tools/shared/filesystem/path-policy.ts +12 -5
  509. package/src/tools/subagent/consult-deadline.ts +49 -0
  510. package/src/tools/subagent/spawn.ts +234 -5
  511. package/src/tools/tool-approval-handler.ts +1 -1
  512. package/src/tools/tool-defaults.ts +9 -2
  513. package/src/tools/tool-manifest.ts +3 -0
  514. package/src/tools/types.ts +131 -25
  515. package/src/tools/workspace-tools/loader.ts +348 -244
  516. package/src/util/errors.ts +26 -1
  517. package/src/util/logger.ts +9 -0
  518. package/src/util/platform.ts +19 -0
  519. package/src/util/telemetry-db-path.ts +24 -0
  520. package/src/workflows/library.test.ts +140 -0
  521. package/src/workflows/library.ts +82 -28
  522. package/src/workspace/migrations/017-seed-persona-dirs.ts +3 -34
  523. package/src/workspace/migrations/019-scope-journal-to-guardian.ts +3 -24
  524. package/src/workspace/migrations/031-drop-user-md.ts +232 -148
  525. package/src/workspace/migrations/048-remove-workspace-hooks.ts +14 -66
  526. package/src/workspace/migrations/111-prune-seeded-callsite-defaults.ts +134 -0
  527. package/src/workspace/migrations/112-remove-advisor-callsite-override.ts +64 -0
  528. package/src/workspace/migrations/registry.ts +4 -0
  529. package/src/__tests__/workspace-tools-watcher-flag.test.ts +0 -70
  530. package/src/daemon/workspace-tools-watcher.ts +0 -328
  531. package/src/memory/migrations/registry.ts +0 -573
  532. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +0 -56
  533. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +0 -43
  534. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +0 -137
  535. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -153
  536. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +0 -138
  537. package/src/plugins/defaults/advisor/advisor-gate.ts +0 -29
  538. package/src/plugins/defaults/advisor/advisor-state-store.ts +0 -94
  539. package/src/plugins/defaults/advisor/config.ts +0 -21
  540. package/src/plugins/defaults/advisor/consult.ts +0 -93
  541. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +0 -34
  542. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +0 -30
  543. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +0 -19
  544. package/src/plugins/defaults/advisor/package.json +0 -14
  545. package/src/plugins/defaults/advisor/tools/advisor.ts +0 -65
  546. package/src/providers/openai/__tests__/api-error-detail.test.ts +0 -120
@@ -0,0 +1,432 @@
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 the worker process state, `memory.worker.enabled`, and
7
+ * the synchronous in-process runner via PID/marker-file liveness.
8
+ * - `stop` sends SIGTERM to a live worker and disables the config flag, and
9
+ * still disables the flag (success) when no worker is running.
10
+ * - `start` spawns/reuses the worker process and enables the config flag.
11
+ */
12
+
13
+ import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
14
+ import { tmpdir } from "node:os";
15
+ import { join } from "node:path";
16
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
17
+
18
+ import { Command } from "commander";
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // Mock state
22
+ // ---------------------------------------------------------------------------
23
+
24
+ let tmpDir: string;
25
+ let pidPath: string;
26
+ let markerPath: string;
27
+ let logOutput: string[] = [];
28
+
29
+ /** In-memory stand-in for the on-disk raw config the CLI reads/writes. */
30
+ let rawConfig: Record<string, unknown> = {};
31
+
32
+ /** Records (pid, signal) pairs passed to the mocked process.kill. */
33
+ let killCalls: Array<{ pid: number; signal: string | number }> = [];
34
+
35
+ function workerEnabledFromRaw(): boolean {
36
+ const memory = rawConfig.memory as { worker?: { enabled?: unknown } };
37
+ return memory?.worker?.enabled === true;
38
+ }
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // Mocks
42
+ // ---------------------------------------------------------------------------
43
+
44
+ mock.module("../../../../util/platform.js", () => ({
45
+ getMemoryWorkerPidPath: () => pidPath,
46
+ getMemorySyncRunnerMarkerPath: () => markerPath,
47
+ }));
48
+
49
+ mock.module("../../../../config/loader.js", () => ({
50
+ loadRawConfig: () => structuredClone(rawConfig),
51
+ saveRawConfig: (config: Record<string, unknown>) => {
52
+ rawConfig = structuredClone(config);
53
+ },
54
+ getConfigReadOnly: () => ({
55
+ memory: { worker: { enabled: workerEnabledFromRaw() } },
56
+ }),
57
+ setNestedValue: (
58
+ obj: Record<string, unknown>,
59
+ path: string,
60
+ value: unknown,
61
+ ) => {
62
+ const keys = path.split(".");
63
+ let cur = obj;
64
+ for (let i = 0; i < keys.length - 1; i++) {
65
+ const k = keys[i];
66
+ if (cur[k] == null || typeof cur[k] !== "object") cur[k] = {};
67
+ cur = cur[k] as Record<string, unknown>;
68
+ }
69
+ cur[keys[keys.length - 1]] = value;
70
+ },
71
+ }));
72
+
73
+ const capture = (...args: unknown[]) => {
74
+ logOutput.push(args.map(String).join(" "));
75
+ };
76
+ const fakeLogger = {
77
+ info: capture,
78
+ warn: capture,
79
+ error: capture,
80
+ debug: () => {},
81
+ };
82
+ mock.module("../../../../util/logger.js", () => ({
83
+ getLogger: () => fakeLogger,
84
+ getCliLogger: () => fakeLogger,
85
+ getCurrentLogFilePath: () => `${tmpDir}/assistant-test-mock.log`,
86
+ }));
87
+
88
+ // ---------------------------------------------------------------------------
89
+ // Import module under test (after mocks)
90
+ // ---------------------------------------------------------------------------
91
+
92
+ const { registerMemoryWorkerCommand } = await import("../worker.js");
93
+
94
+ // ---------------------------------------------------------------------------
95
+ // Test helpers
96
+ // ---------------------------------------------------------------------------
97
+
98
+ function buildProgram(): Command {
99
+ const program = new Command();
100
+ program.exitOverride();
101
+ program.configureOutput({
102
+ writeErr: () => {},
103
+ writeOut: () => {},
104
+ });
105
+ const memory = program.command("memory");
106
+ registerMemoryWorkerCommand(memory);
107
+ return program;
108
+ }
109
+
110
+ async function runCommand(
111
+ args: string[],
112
+ ): Promise<{ stdout: string; exitCode: number }> {
113
+ const originalStdoutWrite = process.stdout.write.bind(process.stdout);
114
+ const originalStderrWrite = process.stderr.write.bind(process.stderr);
115
+ const stdoutChunks: string[] = [];
116
+
117
+ process.stdout.write = ((chunk: unknown) => {
118
+ stdoutChunks.push(typeof chunk === "string" ? chunk : String(chunk));
119
+ return true;
120
+ }) as typeof process.stdout.write;
121
+
122
+ process.stderr.write = (() => true) as typeof process.stderr.write;
123
+
124
+ process.exitCode = 0;
125
+
126
+ try {
127
+ const program = buildProgram();
128
+ await program.parseAsync(["node", "assistant", ...args]);
129
+ } catch {
130
+ if (process.exitCode === 0) process.exitCode = 1;
131
+ } finally {
132
+ process.stdout.write = originalStdoutWrite;
133
+ process.stderr.write = originalStderrWrite;
134
+ }
135
+
136
+ const exitCode = process.exitCode ?? 0;
137
+ process.exitCode = 0;
138
+
139
+ return { exitCode, stdout: stdoutChunks.join("") };
140
+ }
141
+
142
+ /**
143
+ * Replace process.kill with a recording stub. `signal 0` is the liveness
144
+ * probe: it resolves for `livePids` and throws ESRCH otherwise. Other signals
145
+ * are recorded and no-oped so the test runner is never actually signalled.
146
+ */
147
+ function stubProcessKill(livePids: Set<number>): () => void {
148
+ const original = process.kill.bind(process);
149
+ killCalls = [];
150
+ process.kill = ((pid: number, signal?: string | number) => {
151
+ const sig = signal ?? 0;
152
+ if (sig === 0) {
153
+ if (livePids.has(pid)) return true;
154
+ const err = new Error("kill ESRCH") as NodeJS.ErrnoException;
155
+ err.code = "ESRCH";
156
+ throw err;
157
+ }
158
+ killCalls.push({ pid, signal: sig });
159
+ return true;
160
+ }) as typeof process.kill;
161
+ return () => {
162
+ process.kill = original;
163
+ };
164
+ }
165
+
166
+ // ---------------------------------------------------------------------------
167
+ // Setup
168
+ // ---------------------------------------------------------------------------
169
+
170
+ beforeEach(() => {
171
+ tmpDir = mkdtempSync(join(tmpdir(), "memory-worker-test-"));
172
+ pidPath = join(tmpDir, "memory-worker.pid");
173
+ markerPath = join(tmpDir, "memory-sync-runner.pid");
174
+ logOutput = [];
175
+ killCalls = [];
176
+ rawConfig = {};
177
+ process.exitCode = 0;
178
+ });
179
+
180
+ afterEach(() => {
181
+ rmSync(tmpDir, { recursive: true, force: true });
182
+ });
183
+
184
+ // ---------------------------------------------------------------------------
185
+ // Subcommand registration
186
+ // ---------------------------------------------------------------------------
187
+
188
+ describe("subcommand registration", () => {
189
+ test("registers worker under memory with start/stop/status", () => {
190
+ const program = buildProgram();
191
+ const memory = program.commands.find((c) => c.name() === "memory");
192
+ expect(memory).toBeDefined();
193
+ const worker = memory!.commands.find((c) => c.name() === "worker");
194
+ expect(worker).toBeDefined();
195
+ const subcommandNames = worker!.commands.map((c) => c.name()).sort();
196
+ expect(subcommandNames).toEqual(["start", "status", "stop"]);
197
+ });
198
+ });
199
+
200
+ // ---------------------------------------------------------------------------
201
+ // status
202
+ // ---------------------------------------------------------------------------
203
+
204
+ describe("memory worker status", () => {
205
+ test("reports not_running with workerEnabled and syncRunner when nothing is running", async () => {
206
+ const { exitCode, stdout } = await runCommand([
207
+ "memory",
208
+ "worker",
209
+ "status",
210
+ "--json",
211
+ ]);
212
+ expect(exitCode).toBe(0);
213
+ expect(JSON.parse(stdout)).toEqual({
214
+ status: "not_running",
215
+ workerEnabled: false,
216
+ syncRunner: { status: "not_running" },
217
+ });
218
+ });
219
+
220
+ test("reports running when PID file points at a live process", async () => {
221
+ writeFileSync(pidPath, String(process.pid));
222
+ const restore = stubProcessKill(new Set([process.pid]));
223
+ try {
224
+ const { exitCode, stdout } = await runCommand([
225
+ "memory",
226
+ "worker",
227
+ "status",
228
+ "--json",
229
+ ]);
230
+ expect(exitCode).toBe(0);
231
+ expect(JSON.parse(stdout)).toEqual({
232
+ status: "running",
233
+ pid: process.pid,
234
+ workerEnabled: false,
235
+ syncRunner: { status: "not_running" },
236
+ });
237
+ } finally {
238
+ restore();
239
+ }
240
+ });
241
+
242
+ test("reflects memory.worker.enabled from config", async () => {
243
+ rawConfig = { memory: { worker: { enabled: true } } };
244
+ const restore = stubProcessKill(new Set());
245
+ try {
246
+ const { exitCode, stdout } = await runCommand([
247
+ "memory",
248
+ "worker",
249
+ "status",
250
+ "--json",
251
+ ]);
252
+ expect(exitCode).toBe(0);
253
+ expect(JSON.parse(stdout)).toMatchObject({ workerEnabled: true });
254
+ } finally {
255
+ restore();
256
+ }
257
+ });
258
+
259
+ test("reports the synchronous runner as running when its marker is live", async () => {
260
+ writeFileSync(markerPath, String(process.pid));
261
+ const restore = stubProcessKill(new Set([process.pid]));
262
+ try {
263
+ const { exitCode, stdout } = await runCommand([
264
+ "memory",
265
+ "worker",
266
+ "status",
267
+ "--json",
268
+ ]);
269
+ expect(exitCode).toBe(0);
270
+ expect(JSON.parse(stdout)).toMatchObject({
271
+ status: "not_running",
272
+ syncRunner: { status: "running", pid: process.pid },
273
+ });
274
+ } finally {
275
+ restore();
276
+ }
277
+ });
278
+
279
+ test("treats a stale PID file as not_running and cleans it up", async () => {
280
+ writeFileSync(pidPath, "999999");
281
+ const restore = stubProcessKill(new Set());
282
+ try {
283
+ const { exitCode, stdout } = await runCommand([
284
+ "memory",
285
+ "worker",
286
+ "status",
287
+ "--json",
288
+ ]);
289
+ expect(exitCode).toBe(0);
290
+ expect(JSON.parse(stdout)).toEqual({
291
+ status: "not_running",
292
+ workerEnabled: false,
293
+ syncRunner: { status: "not_running" },
294
+ });
295
+ expect(existsSync(pidPath)).toBe(false);
296
+ } finally {
297
+ restore();
298
+ }
299
+ });
300
+ });
301
+
302
+ // ---------------------------------------------------------------------------
303
+ // stop
304
+ // ---------------------------------------------------------------------------
305
+
306
+ describe("memory worker stop", () => {
307
+ test("disables the config flag and succeeds when no worker is running", async () => {
308
+ rawConfig = { memory: { worker: { enabled: true } } };
309
+ const { exitCode, stdout } = await runCommand([
310
+ "memory",
311
+ "worker",
312
+ "stop",
313
+ "--json",
314
+ ]);
315
+ expect(exitCode).toBe(0);
316
+ expect(JSON.parse(stdout)).toMatchObject({
317
+ ok: true,
318
+ workerWasRunning: false,
319
+ workerEnabled: false,
320
+ });
321
+ expect(workerEnabledFromRaw()).toBe(false);
322
+ });
323
+
324
+ test("sends SIGTERM to a running worker and disables the flag", async () => {
325
+ rawConfig = { memory: { worker: { enabled: true } } };
326
+ writeFileSync(pidPath, String(process.pid));
327
+ const restore = stubProcessKill(new Set([process.pid]));
328
+ try {
329
+ const { exitCode, stdout } = await runCommand([
330
+ "memory",
331
+ "worker",
332
+ "stop",
333
+ "--json",
334
+ ]);
335
+ expect(exitCode).toBe(0);
336
+ expect(JSON.parse(stdout)).toEqual({
337
+ ok: true,
338
+ pid: process.pid,
339
+ workerEnabled: false,
340
+ });
341
+ expect(killCalls).toContainEqual({
342
+ pid: process.pid,
343
+ signal: "SIGTERM",
344
+ });
345
+ expect(workerEnabledFromRaw()).toBe(false);
346
+ } finally {
347
+ restore();
348
+ }
349
+ });
350
+ });
351
+
352
+ // ---------------------------------------------------------------------------
353
+ // start
354
+ // ---------------------------------------------------------------------------
355
+
356
+ describe("memory worker start", () => {
357
+ test("reuses an already-running worker and enables the flag", async () => {
358
+ writeFileSync(pidPath, String(process.pid));
359
+ const restore = stubProcessKill(new Set([process.pid]));
360
+ try {
361
+ const { exitCode, stdout } = await runCommand([
362
+ "memory",
363
+ "worker",
364
+ "start",
365
+ "--json",
366
+ ]);
367
+ expect(exitCode).toBe(0);
368
+ expect(JSON.parse(stdout)).toMatchObject({
369
+ ok: true,
370
+ pid: process.pid,
371
+ alreadyRunning: true,
372
+ workerEnabled: true,
373
+ });
374
+ expect(workerEnabledFromRaw()).toBe(true);
375
+ } finally {
376
+ restore();
377
+ }
378
+ });
379
+
380
+ test("spawns the worker, reports its PID, and enables the flag", async () => {
381
+ const restore = stubProcessKill(new Set());
382
+ const originalSpawn = Bun.spawn;
383
+ // Simulate the spawned worker writing its PID file on startup.
384
+ (Bun as { spawn: typeof Bun.spawn }).spawn = (() => {
385
+ writeFileSync(pidPath, "424242");
386
+ return { unref: () => {}, pid: 424242 };
387
+ }) as unknown as typeof Bun.spawn;
388
+ try {
389
+ const { exitCode, stdout } = await runCommand([
390
+ "memory",
391
+ "worker",
392
+ "start",
393
+ "--json",
394
+ ]);
395
+ expect(exitCode).toBe(0);
396
+ expect(JSON.parse(stdout)).toMatchObject({
397
+ ok: true,
398
+ pid: 424242,
399
+ workerEnabled: true,
400
+ });
401
+ expect(workerEnabledFromRaw()).toBe(true);
402
+ } finally {
403
+ (Bun as { spawn: typeof Bun.spawn }).spawn = originalSpawn;
404
+ restore();
405
+ }
406
+ });
407
+
408
+ test("leaves the config flag untouched when the spawn fails", async () => {
409
+ const restore = stubProcessKill(new Set());
410
+ const originalSpawn = Bun.spawn;
411
+ // Spawn returns but never writes a PID file → spawnMemoryWorkerProcess
412
+ // throws after its wait loop, so the flag must stay disabled.
413
+ (Bun as { spawn: typeof Bun.spawn }).spawn = (() => ({
414
+ unref: () => {},
415
+ pid: 0,
416
+ })) as unknown as typeof Bun.spawn;
417
+ try {
418
+ const { exitCode, stdout } = await runCommand([
419
+ "memory",
420
+ "worker",
421
+ "start",
422
+ "--json",
423
+ ]);
424
+ expect(exitCode).toBe(1);
425
+ expect(JSON.parse(stdout)).toMatchObject({ ok: false });
426
+ expect(workerEnabledFromRaw()).toBe(false);
427
+ } finally {
428
+ (Bun as { spawn: typeof Bun.spawn }).spawn = originalSpawn;
429
+ restore();
430
+ }
431
+ });
432
+ });
@@ -4,6 +4,7 @@ import { registerCommand } from "../../lib/register-command.js";
4
4
  import { registerMemoryRetrospectiveCommand } from "./memory-retrospective.js";
5
5
  import { registerMemoryV2Command } from "./memory-v2.js";
6
6
  import { registerMemoryV3Command } from "./memory-v3.js";
7
+ import { registerMemoryWorkerCommand } from "./worker.js";
7
8
 
8
9
  export function registerMemoryCommand(program: Command): void {
9
10
  registerCommand(program, {
@@ -27,6 +28,7 @@ Examples:
27
28
  registerMemoryV2Command(memory);
28
29
  registerMemoryV3Command(memory);
29
30
  registerMemoryRetrospectiveCommand(memory);
31
+ registerMemoryWorkerCommand(memory);
30
32
  },
31
33
  });
32
34
  }
@@ -0,0 +1,242 @@
1
+ /**
2
+ * `assistant memory worker` CLI subgroup.
3
+ *
4
+ * Manages the memory jobs worker as its own OS process — separate from the
5
+ * daemon's main event loop. This prevents long-running embedding jobs from
6
+ * blocking user-facing HTTP traffic.
7
+ *
8
+ * Subcommands:
9
+ *
10
+ * - `start` — spawn the worker process and enable `memory.worker.enabled`,
11
+ * standing the daemon's synchronous in-process runner down.
12
+ * - `stop` — SIGTERM the worker process and disable `memory.worker.enabled`,
13
+ * handing the queue back to the synchronous in-process runner.
14
+ * - `status` — report the worker process state, the `memory.worker.enabled`
15
+ * config value, and whether the synchronous in-process runner is going.
16
+ *
17
+ * All three run directly in the CLI process (transport: "local") — no IPC
18
+ * round-trip to the daemon. The daemon's worker supervisor re-reads
19
+ * `memory.worker.enabled` each poll and stands its synchronous runner down (or
20
+ * resumes it) accordingly, so flipping the flag here switches the running
21
+ * daemon's mode without a restart.
22
+ */
23
+
24
+ import type { Command } from "commander";
25
+
26
+ import {
27
+ getConfigReadOnly,
28
+ loadRawConfig,
29
+ saveRawConfig,
30
+ setNestedValue,
31
+ } from "../../../config/loader.js";
32
+ import {
33
+ probeMemoryWorker,
34
+ probeSyncRunner,
35
+ spawnMemoryWorkerProcess,
36
+ } from "../../../memory/worker-control.js";
37
+ import { getMemoryWorkerPidPath } from "../../../util/platform.js";
38
+ import { registerCommand } from "../../lib/register-command.js";
39
+ import { log } from "../../logger.js";
40
+ import { shouldOutputJson, writeOutput } from "../../output.js";
41
+
42
+ /**
43
+ * Persist `memory.worker.enabled` to the on-disk config via the shared
44
+ * raw-config helpers, so only this leaf changes (schema defaults are not baked
45
+ * into the file). The assistant picks the change up on its next config read.
46
+ */
47
+ function setWorkerEnabled(enabled: boolean): void {
48
+ const raw = loadRawConfig();
49
+ setNestedValue(raw, "memory.worker.enabled", enabled);
50
+ saveRawConfig(raw);
51
+ }
52
+
53
+ // ---------------------------------------------------------------------------
54
+ // `start`
55
+ // ---------------------------------------------------------------------------
56
+
57
+ async function startWorker(
58
+ opts: { json?: boolean },
59
+ cmd: Command,
60
+ ): Promise<void> {
61
+ let result: { pid: number; alreadyRunning: boolean };
62
+ try {
63
+ result = await spawnMemoryWorkerProcess();
64
+ } catch (err) {
65
+ // Spawn failed — leave `memory.worker.enabled` untouched so the daemon's
66
+ // synchronous runner keeps draining the queue rather than standing down
67
+ // for a worker that never came up.
68
+ const msg = err instanceof Error ? err.message : String(err);
69
+ if (shouldOutputJson(cmd)) {
70
+ writeOutput(cmd, { ok: false, error: msg });
71
+ } else {
72
+ log.error(msg);
73
+ }
74
+ process.exitCode = 1;
75
+ return;
76
+ }
77
+
78
+ // The worker process is up (freshly spawned or already running). Enable the
79
+ // flag so the daemon's supervisor stands its synchronous runner down (and so
80
+ // the daemon spawns the worker again on the next restart).
81
+ setWorkerEnabled(true);
82
+
83
+ if (shouldOutputJson(cmd)) {
84
+ writeOutput(cmd, {
85
+ ok: true,
86
+ pid: result.pid,
87
+ alreadyRunning: result.alreadyRunning,
88
+ pidPath: getMemoryWorkerPidPath(),
89
+ workerEnabled: true,
90
+ });
91
+ } else {
92
+ log.info(
93
+ result.alreadyRunning
94
+ ? `Memory worker is already running (PID ${result.pid})`
95
+ : `Memory worker started (PID ${result.pid})`,
96
+ );
97
+ log.info(
98
+ "Enabled memory.worker.enabled; the synchronous in-process runner will stand down",
99
+ );
100
+ }
101
+ }
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // `stop`
105
+ // ---------------------------------------------------------------------------
106
+
107
+ function stopWorker(opts: { json?: boolean }, cmd: Command): void {
108
+ // Persist the preference first: `stop` means "hand the queue back to the
109
+ // synchronous in-process runner." Disabling the flag makes the daemon's
110
+ // supervisor resume processing in-process on its next poll and lines the next
111
+ // daemon restart up with synchronous mode; the SIGTERM below then stops the
112
+ // now-redundant worker process.
113
+ setWorkerEnabled(false);
114
+
115
+ const current = probeMemoryWorker();
116
+ if (current.status !== "running" || current.pid == null) {
117
+ // No worker process to signal — flipping the flag alone restores
118
+ // synchronous mode, so this is success, not an error.
119
+ if (shouldOutputJson(cmd)) {
120
+ writeOutput(cmd, {
121
+ ok: true,
122
+ workerWasRunning: false,
123
+ workerEnabled: false,
124
+ });
125
+ } else {
126
+ log.info(
127
+ "Memory worker process was not running; disabled memory.worker.enabled (synchronous runner active)",
128
+ );
129
+ }
130
+ return;
131
+ }
132
+
133
+ const pid = current.pid;
134
+ try {
135
+ process.kill(pid, "SIGTERM");
136
+ } catch (err) {
137
+ const msg = err instanceof Error ? err.message : String(err);
138
+ if (shouldOutputJson(cmd)) {
139
+ writeOutput(cmd, { ok: false, error: msg, pid, workerEnabled: false });
140
+ } else {
141
+ log.error(`Failed to stop memory worker (PID ${pid}): ${msg}`);
142
+ }
143
+ process.exitCode = 1;
144
+ return;
145
+ }
146
+
147
+ if (shouldOutputJson(cmd)) {
148
+ writeOutput(cmd, { ok: true, pid, workerEnabled: false });
149
+ } else {
150
+ log.info(`Memory worker stop signal sent (PID ${pid})`);
151
+ log.info(
152
+ "Disabled memory.worker.enabled; the synchronous in-process runner will take over",
153
+ );
154
+ }
155
+ }
156
+
157
+ // ---------------------------------------------------------------------------
158
+ // `status`
159
+ // ---------------------------------------------------------------------------
160
+
161
+ function statusWorker(opts: { json?: boolean }, cmd: Command): void {
162
+ const worker = probeMemoryWorker();
163
+ const syncRunner = probeSyncRunner();
164
+ const workerEnabled = getConfigReadOnly().memory.worker.enabled;
165
+
166
+ if (shouldOutputJson(cmd)) {
167
+ writeOutput(cmd, { ...worker, workerEnabled, syncRunner });
168
+ } else {
169
+ if (worker.status === "running") {
170
+ log.info(`Memory worker process is running (PID ${worker.pid})`);
171
+ } else {
172
+ log.info("Memory worker process is not running");
173
+ }
174
+ log.info(`memory.worker.enabled: ${workerEnabled}`);
175
+ if (syncRunner.status === "running") {
176
+ log.info(
177
+ `Synchronous in-process runner is running (PID ${syncRunner.pid})`,
178
+ );
179
+ } else {
180
+ log.info("Synchronous in-process runner is not running");
181
+ }
182
+ }
183
+ }
184
+
185
+ // ---------------------------------------------------------------------------
186
+ // Registration
187
+ // ---------------------------------------------------------------------------
188
+
189
+ export function registerMemoryWorkerCommand(memory: Command): void {
190
+ registerCommand(memory, {
191
+ name: "worker",
192
+ transport: "local",
193
+ description: "Manage the memory jobs worker process (start/stop/status)",
194
+ build: (worker) => {
195
+ worker.addHelpText(
196
+ "after",
197
+ `
198
+ The memory worker processes embedding, consolidation, and cleanup jobs in a
199
+ separate OS process so they do not block the assistant's main event loop.
200
+
201
+ \`start\` enables memory.worker.enabled and \`stop\` disables it, so the
202
+ assistant's synchronous in-process runner stands down (start) or takes back over
203
+ (stop) without a restart.
204
+
205
+ Examples:
206
+ $ assistant memory worker start
207
+ $ assistant memory worker status
208
+ $ assistant memory worker stop`,
209
+ );
210
+
211
+ worker
212
+ .command("start")
213
+ .description(
214
+ "Start the memory worker process and enable memory.worker.enabled",
215
+ )
216
+ .option("--json", "Emit raw JSON instead of a formatted summary")
217
+ .action(async (opts: { json?: boolean }, cmd: Command) => {
218
+ await startWorker(opts, cmd);
219
+ });
220
+
221
+ worker
222
+ .command("stop")
223
+ .description(
224
+ "Stop the memory worker process and disable memory.worker.enabled",
225
+ )
226
+ .option("--json", "Emit raw JSON instead of a formatted summary")
227
+ .action((opts: { json?: boolean }, cmd: Command) => {
228
+ stopWorker(opts, cmd);
229
+ });
230
+
231
+ worker
232
+ .command("status")
233
+ .description(
234
+ "Report worker process state, memory.worker.enabled, and the synchronous runner",
235
+ )
236
+ .option("--json", "Emit raw JSON instead of a formatted summary")
237
+ .action((opts: { json?: boolean }, cmd: Command) => {
238
+ statusWorker(opts, cmd);
239
+ });
240
+ },
241
+ });
242
+ }