@vellumai/assistant 0.10.1 → 0.10.2-dev.202606241651.2d2b40d

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 (367) hide show
  1. package/docs/workspace-tools.md +42 -33
  2. package/eslint-rules/cli-no-daemon-internals.js +6 -0
  3. package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +91 -0
  4. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +31 -0
  5. package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +48 -0
  6. package/node_modules/@vellumai/gateway-client/src/index.ts +14 -0
  7. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +17 -0
  8. package/openapi.yaml +74 -1
  9. package/package.json +1 -1
  10. package/scripts/test.sh +36 -15
  11. package/src/__tests__/actor-token-service.test.ts +36 -14
  12. package/src/__tests__/agent-loop-override-profile.test.ts +1 -0
  13. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  14. package/src/__tests__/agent-wake-override-profile.test.ts +2 -0
  15. package/src/__tests__/annotate-activity-metadata.test.ts +2 -0
  16. package/src/__tests__/annotate-risk-options.test.ts +2 -0
  17. package/src/__tests__/approval-cascade.test.ts +2 -0
  18. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -0
  19. package/src/__tests__/btw-routes.test.ts +2 -0
  20. package/src/__tests__/build-persisted-content.test.ts +2 -0
  21. package/src/__tests__/call-controller.test.ts +19 -0
  22. package/src/__tests__/channel-guardian.test.ts +94 -58
  23. package/src/__tests__/channel-reply-delivery.test.ts +2 -0
  24. package/src/__tests__/compaction-events.test.ts +2 -0
  25. package/src/__tests__/compaction.benchmark.test.ts +2 -0
  26. package/src/__tests__/compactor-call-site-logging.test.ts +2 -0
  27. package/src/__tests__/compactor-low-watermark-cut.test.ts +2 -0
  28. package/src/__tests__/compactor-preserved-tail-count.test.ts +2 -0
  29. package/src/__tests__/compactor-summary-call-truncation.test.ts +2 -0
  30. package/src/__tests__/compactor-web-search-strip.test.ts +2 -0
  31. package/src/__tests__/computer-use-tools.test.ts +13 -0
  32. package/src/__tests__/config-loader-backfill.test.ts +5 -1
  33. package/src/__tests__/config-schema.test.ts +1 -0
  34. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +31 -29
  35. package/src/__tests__/contacts-relay-reads.test.ts +13 -15
  36. package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
  37. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +2 -0
  38. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
  39. package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -0
  40. package/src/__tests__/conversation-agent-loop.test.ts +7 -0
  41. package/src/__tests__/conversation-analysis-routes.test.ts +2 -0
  42. package/src/__tests__/conversation-app-control-lifecycle.test.ts +2 -0
  43. package/src/__tests__/conversation-confirmation-signals.test.ts +2 -0
  44. package/src/__tests__/conversation-history-web-search.test.ts +2 -0
  45. package/src/__tests__/conversation-load-history-repair.test.ts +2 -0
  46. package/src/__tests__/conversation-load-history-stripped.test.ts +2 -0
  47. package/src/__tests__/conversation-pairing.test.ts +2 -0
  48. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
  49. package/src/__tests__/conversation-process-callsite.test.ts +2 -0
  50. package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
  51. package/src/__tests__/conversation-queue.test.ts +91 -0
  52. package/src/__tests__/conversation-routes-guardian-reply.test.ts +14 -0
  53. package/src/__tests__/conversation-routes-slash-commands.test.ts +14 -0
  54. package/src/__tests__/conversation-slash-queue.test.ts +2 -0
  55. package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
  56. package/src/__tests__/conversation-speed-override.test.ts +2 -0
  57. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +65 -0
  58. package/src/__tests__/conversation-title-service.test.ts +2 -0
  59. package/src/__tests__/conversation-tool-setup-attribution.test.ts +47 -0
  60. package/src/__tests__/conversation-usage.test.ts +2 -0
  61. package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -0
  62. package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
  63. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
  64. package/src/__tests__/credential-security-invariants.test.ts +0 -1
  65. package/src/__tests__/db-migration-rollback.test.ts +205 -171
  66. package/src/__tests__/db-test-helpers.ts +5 -4
  67. package/src/__tests__/deterministic-verification-control-plane.test.ts +4 -2
  68. package/src/__tests__/disk-pressure-guard.test.ts +41 -0
  69. package/src/__tests__/dm-persistence.test.ts +2 -0
  70. package/src/__tests__/emit-signal-routing-intent.test.ts +10 -5
  71. package/src/__tests__/events-dev-bypass-actor.test.ts +7 -1
  72. package/src/__tests__/filing-service.test.ts +2 -0
  73. package/src/__tests__/guardian-binding-drift-heal.test.ts +75 -10
  74. package/src/__tests__/guardian-dispatch.test.ts +95 -1
  75. package/src/__tests__/guardian-outbound-http.test.ts +13 -0
  76. package/src/__tests__/heartbeat-disk-pressure.test.ts +2 -0
  77. package/src/__tests__/heartbeat-service.test.ts +2 -0
  78. package/src/__tests__/helpers/channel-test-adapter.ts +1 -7
  79. package/src/__tests__/host-app-control-routes.test.ts +24 -30
  80. package/src/__tests__/host-bash-routes.test.ts +31 -41
  81. package/src/__tests__/host-browser-routes.test.ts +26 -32
  82. package/src/__tests__/host-cu-proxy.test.ts +299 -0
  83. package/src/__tests__/host-cu-routes-targeted.test.ts +25 -33
  84. package/src/__tests__/host-file-routes-targeted.test.ts +40 -52
  85. package/src/__tests__/host-transfer-routes-targeted.test.ts +31 -43
  86. package/src/__tests__/http-user-message-parity.test.ts +167 -8
  87. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  88. package/src/__tests__/invite-redemption-service.test.ts +43 -0
  89. package/src/__tests__/llm-context-normalization.test.ts +105 -0
  90. package/src/__tests__/llm-usage-store.test.ts +25 -0
  91. package/src/__tests__/media-stream-server-integration.test.ts +127 -0
  92. package/src/__tests__/memory-retrieval-hook.test.ts +2 -0
  93. package/src/__tests__/messaging-send-tool.test.ts +2 -0
  94. package/src/__tests__/migration-import-from-url.test.ts +2 -2
  95. package/src/__tests__/native-web-search.test.ts +2 -0
  96. package/src/__tests__/non-member-access-request.test.ts +189 -17
  97. package/src/__tests__/notification-broadcaster.test.ts +4 -0
  98. package/src/__tests__/notification-decision-recipient-context.test.ts +33 -32
  99. package/src/__tests__/notification-deep-link.test.ts +6 -0
  100. package/src/__tests__/notification-guardian-path.test.ts +19 -0
  101. package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
  102. package/src/__tests__/pending-interactions-resolved-event.test.ts +7 -4
  103. package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
  104. package/src/__tests__/plugin-bootstrap.test.ts +3 -73
  105. package/src/__tests__/plugin-route-contribution.test.ts +4 -17
  106. package/src/__tests__/plugin-tool-contribution.test.ts +3 -18
  107. package/src/__tests__/plugin-types.test.ts +0 -2
  108. package/src/__tests__/process-message-background-slack.test.ts +2 -0
  109. package/src/__tests__/process-message-display-content.test.ts +2 -0
  110. package/src/__tests__/provider-usage-tracking.test.ts +39 -0
  111. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +2 -0
  112. package/src/__tests__/registry.test.ts +3 -0
  113. package/src/__tests__/relay-server.test.ts +694 -25
  114. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  115. package/src/__tests__/secret-ingress-http.test.ts +14 -0
  116. package/src/__tests__/send-endpoint-busy.test.ts +30 -8
  117. package/src/__tests__/skills.test.ts +44 -0
  118. package/src/__tests__/slack-inbound-verification.test.ts +47 -2
  119. package/src/__tests__/sse-actor-principal-guardian-source.test.ts +102 -0
  120. package/src/__tests__/steer-on-enqueue-question.test.ts +181 -0
  121. package/src/__tests__/stt-hints.test.ts +44 -13
  122. package/src/__tests__/subagent-detail.test.ts +27 -0
  123. package/src/__tests__/subagent-disposal.test.ts +65 -0
  124. package/src/__tests__/subagent-notify-parent.test.ts +2 -0
  125. package/src/__tests__/subagent-spawn-tool-fork.test.ts +2 -0
  126. package/src/__tests__/subagent-tools.test.ts +2 -0
  127. package/src/__tests__/suggestion-routes.test.ts +2 -0
  128. package/src/__tests__/title-generate-hook.test.ts +2 -0
  129. package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -0
  130. package/src/__tests__/tool-executor.test.ts +16 -11
  131. package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
  132. package/src/__tests__/tool-result-metadata-plumbing.test.ts +2 -0
  133. package/src/__tests__/tool-start-timestamp.test.ts +2 -0
  134. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +10 -10
  135. package/src/__tests__/twilio-routes.test.ts +96 -0
  136. package/src/__tests__/verification-control-plane-policy.test.ts +2 -0
  137. package/src/__tests__/web-search-backend-failure.test.ts +2 -0
  138. package/src/__tests__/workspace-tool-loader.test.ts +195 -2
  139. package/src/agent/loop-exclusive-tool.test.ts +150 -0
  140. package/src/agent/loop.ts +56 -0
  141. package/src/api/constants/sse-replay.ts +41 -0
  142. package/src/api/index.ts +6 -0
  143. package/src/api/responses/llm-request-log-entry.ts +25 -0
  144. package/src/api/responses/subagent-detail.ts +17 -0
  145. package/src/calls/__tests__/relay-setup-router.test.ts +262 -4
  146. package/src/calls/call-domain.ts +3 -3
  147. package/src/calls/guardian-dispatch.ts +10 -8
  148. package/src/calls/inbound-trust-reader.ts +17 -1
  149. package/src/calls/media-stream-server.ts +21 -0
  150. package/src/calls/relay-server.ts +167 -50
  151. package/src/calls/relay-setup-router.ts +37 -7
  152. package/src/calls/relay-verification.ts +4 -4
  153. package/src/calls/stt-hints.ts +9 -12
  154. package/src/calls/twilio-routes.ts +14 -4
  155. package/src/cli/commands/__tests__/cache.test.ts +8 -1
  156. package/src/cli/commands/cache.ts +194 -181
  157. package/src/cli/commands/db/__tests__/repair.test.ts +6 -5
  158. package/src/cli/commands/db/status.ts +37 -1
  159. package/src/cli/commands/mcp.ts +252 -218
  160. package/src/cli/commands/memory/__tests__/worker.test.ts +302 -0
  161. package/src/cli/commands/memory/index.ts +2 -0
  162. package/src/cli/commands/memory/worker.ts +175 -0
  163. package/src/cli/commands/plugins.ts +75 -3
  164. package/src/cli/lib/__tests__/install-from-github.test.ts +102 -0
  165. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +160 -1
  166. package/src/cli/lib/list-installed-plugins.ts +179 -1
  167. package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +143 -0
  168. package/src/config/bundled-skills/computer-use/TOOLS.json +6 -1
  169. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +27 -17
  170. package/src/config/bundled-skills/contacts/tools/contact-search.ts +13 -3
  171. package/src/config/feature-flag-registry.json +0 -8
  172. package/src/config/loader.ts +36 -5
  173. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  174. package/src/config/schemas/memory-lifecycle.ts +12 -0
  175. package/src/config/schemas/memory-v3.ts +7 -0
  176. package/src/config/schemas/memory.ts +4 -0
  177. package/src/config/schemas/timeouts.ts +8 -0
  178. package/src/config/seed-inference-profiles.ts +14 -5
  179. package/src/config/skills.ts +27 -5
  180. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +312 -0
  181. package/src/contacts/contacts-write.ts +3 -0
  182. package/src/contacts/guardian-delivery-reader.ts +223 -0
  183. package/src/daemon/conversation-agent-loop.ts +9 -0
  184. package/src/daemon/conversation-process.ts +39 -17
  185. package/src/daemon/conversation-surfaces.ts +8 -0
  186. package/src/daemon/conversation-tool-setup.ts +49 -16
  187. package/src/daemon/conversation.ts +21 -2
  188. package/src/daemon/disk-pressure-guard.ts +12 -2
  189. package/src/daemon/event-loop-watchdog.ts +28 -1
  190. package/src/daemon/external-plugins-bootstrap.ts +4 -34
  191. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +25 -0
  192. package/src/daemon/handlers/__tests__/config-channels.test.ts +225 -0
  193. package/src/daemon/handlers/config-a2a.ts +6 -14
  194. package/src/daemon/handlers/config-channels.ts +78 -22
  195. package/src/daemon/handlers/conversations.ts +77 -0
  196. package/src/daemon/host-cu-proxy.ts +102 -11
  197. package/src/daemon/lifecycle.ts +4 -0
  198. package/src/daemon/memory-v2-startup.test.ts +72 -0
  199. package/src/daemon/memory-v2-startup.ts +87 -19
  200. package/src/daemon/server.ts +0 -4
  201. package/src/daemon/shutdown-handlers.ts +20 -0
  202. package/src/daemon/tool-setup-types.ts +9 -0
  203. package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
  204. package/src/ipc/assistant-server.ts +2 -2
  205. package/src/memory/__tests__/301-create-watchdog-events.test.ts +110 -0
  206. package/src/memory/__tests__/memory-retrospective-job.test.ts +8 -0
  207. package/src/memory/__tests__/prompt-override.test.ts +192 -0
  208. package/src/memory/__tests__/watchdog-events-store.test.ts +161 -0
  209. package/src/memory/conversation-crud.ts +38 -0
  210. package/src/memory/db-connection.ts +22 -3
  211. package/src/memory/db-init.ts +36 -502
  212. package/src/memory/db-singleton.ts +6 -4
  213. package/src/memory/jobs-worker.ts +58 -0
  214. package/src/memory/llm-usage-store.ts +48 -20
  215. package/src/memory/memory-retrospective-job.ts +9 -8
  216. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +13 -3
  217. package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -27
  218. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +130 -56
  219. package/src/memory/migrations/300-add-processing-started-at.ts +30 -0
  220. package/src/memory/migrations/301-create-watchdog-events.ts +45 -0
  221. package/src/memory/migrations/__tests__/014-backfill-inbox-thread-state.test.ts +108 -0
  222. package/src/memory/migrations/__tests__/136-drop-assistant-id-columns.test.ts +82 -0
  223. package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +224 -0
  224. package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
  225. package/src/memory/migrations/run-migrations.ts +90 -6
  226. package/src/memory/migrations/schema-introspection.ts +14 -0
  227. package/src/memory/migrations/validate-migration-state.ts +101 -66
  228. package/src/memory/prompt-override.ts +129 -0
  229. package/src/memory/schema/conversations.ts +9 -0
  230. package/src/memory/schema/infrastructure.ts +20 -0
  231. package/src/memory/steps.ts +573 -0
  232. package/src/memory/v2/__tests__/cli-command-store.test.ts +25 -0
  233. package/src/memory/v2/__tests__/skill-store.test.ts +80 -0
  234. package/src/memory/v2/cli-command-store.ts +75 -38
  235. package/src/memory/v2/prompts/consolidation.ts +13 -82
  236. package/src/memory/v2/prompts/router.ts +21 -93
  237. package/src/memory/v2/skill-store.ts +68 -31
  238. package/src/memory/watchdog-events-store.ts +87 -0
  239. package/src/memory/worker-control.ts +118 -0
  240. package/src/memory/worker-process.ts +72 -0
  241. package/src/notifications/__tests__/broadcaster.test.ts +16 -8
  242. package/src/notifications/__tests__/connected-channels.test.ts +114 -0
  243. package/src/notifications/__tests__/decision-engine.test.ts +78 -9
  244. package/src/notifications/__tests__/destination-resolver.test.ts +256 -0
  245. package/src/notifications/broadcaster.ts +8 -1
  246. package/src/notifications/decision-engine.ts +15 -7
  247. package/src/notifications/destination-resolver.ts +68 -24
  248. package/src/notifications/emit-signal.ts +39 -14
  249. package/src/onboarding/checkin-event.test.ts +220 -0
  250. package/src/onboarding/checkin-event.ts +321 -0
  251. package/src/onboarding/schedule-checkin.ts +190 -0
  252. package/src/permissions/question-prompter.test.ts +1 -1
  253. package/src/permissions/question-prompter.ts +7 -4
  254. package/src/plugin-api/index.ts +6 -6
  255. package/src/plugin-api/types.ts +3 -5
  256. package/src/plugin-api/vision-support.test.ts +28 -4
  257. package/src/plugin-api/vision-support.ts +66 -31
  258. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +161 -0
  259. package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +106 -0
  260. package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +60 -0
  261. package/src/plugins/defaults/advisor/consult.ts +110 -6
  262. package/src/plugins/defaults/advisor/context-pack.ts +288 -0
  263. package/src/plugins/defaults/advisor/steering.ts +14 -2
  264. package/src/plugins/defaults/advisor/tools/advisor.ts +32 -5
  265. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +47 -7
  266. package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +10 -11
  267. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +12 -20
  268. package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +42 -11
  269. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +11 -2
  270. package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +146 -0
  271. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +29 -1
  272. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +8 -1
  273. package/src/plugins/mtime-cache.ts +7 -2
  274. package/src/plugins/types.ts +0 -2
  275. package/src/providers/anthropic/client.ts +5 -0
  276. package/src/providers/call-site-routing.ts +4 -0
  277. package/src/providers/model-catalog.ts +16 -0
  278. package/src/providers/openai/responses-provider.ts +5 -0
  279. package/src/providers/openrouter/client.ts +5 -0
  280. package/src/providers/provider-send-message.ts +4 -0
  281. package/src/providers/ratelimit.ts +4 -0
  282. package/src/providers/retry.ts +4 -0
  283. package/src/providers/types.ts +9 -0
  284. package/src/providers/usage-tracking.ts +4 -0
  285. package/src/runtime/__tests__/channel-verification-service.test.ts +133 -0
  286. package/src/runtime/__tests__/guardian-vellum-migration.test.ts +181 -0
  287. package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +66 -0
  288. package/src/runtime/__tests__/local-principal-trust.test.ts +164 -0
  289. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +335 -3
  290. package/src/runtime/access-request-helper.ts +19 -39
  291. package/src/runtime/actor-trust-resolver.ts +2 -2
  292. package/src/runtime/anchored-guardian.test.ts +156 -0
  293. package/src/runtime/anchored-guardian.ts +135 -0
  294. package/src/runtime/assistant-event-hub.ts +1 -1
  295. package/src/runtime/assistant-stream-state.ts +9 -2
  296. package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +99 -0
  297. package/src/runtime/auth/require-bound-guardian.ts +21 -11
  298. package/src/runtime/channel-verification-service.ts +56 -31
  299. package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
  300. package/src/runtime/guardian-vellum-migration.ts +66 -7
  301. package/src/runtime/invite-redemption-service.ts +50 -18
  302. package/src/runtime/local-actor-identity.ts +76 -11
  303. package/src/runtime/local-principal-trust.ts +52 -0
  304. package/src/runtime/pending-interactions.ts +11 -1
  305. package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +56 -5
  306. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
  307. package/src/runtime/routes/__tests__/contact-routes.test.ts +212 -0
  308. package/src/runtime/routes/__tests__/global-search-routes.test.ts +93 -0
  309. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +215 -1
  310. package/src/runtime/routes/browser-routes.ts +1 -1
  311. package/src/runtime/routes/channel-verification-routes.ts +3 -3
  312. package/src/runtime/routes/contact-routes.ts +8 -32
  313. package/src/runtime/routes/conversation-cli-routes.ts +4 -5
  314. package/src/runtime/routes/conversation-list-routes.ts +4 -7
  315. package/src/runtime/routes/conversation-routes.ts +74 -81
  316. package/src/runtime/routes/events-routes.ts +2 -2
  317. package/src/runtime/routes/global-search-routes.ts +3 -1
  318. package/src/runtime/routes/guardian-action-routes.ts +4 -5
  319. package/src/runtime/routes/host-app-control-routes.ts +5 -4
  320. package/src/runtime/routes/host-bash-routes.ts +5 -4
  321. package/src/runtime/routes/host-browser-routes.ts +9 -11
  322. package/src/runtime/routes/host-cu-routes.ts +5 -4
  323. package/src/runtime/routes/host-file-routes.ts +5 -4
  324. package/src/runtime/routes/host-transfer-routes.ts +6 -6
  325. package/src/runtime/routes/http-adapter.ts +1 -1
  326. package/src/runtime/routes/identity-routes.ts +3 -2
  327. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  328. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +97 -5
  329. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +61 -49
  330. package/src/runtime/routes/inbound-stages/background-dispatch.ts +16 -4
  331. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
  332. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +21 -8
  333. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -3
  334. package/src/runtime/routes/index.ts +2 -0
  335. package/src/runtime/routes/llm-context-normalization.ts +71 -0
  336. package/src/runtime/routes/mcp-auth-routes.ts +38 -15
  337. package/src/runtime/routes/migration-rollback-routes.ts +4 -3
  338. package/src/runtime/routes/migration-routes.ts +4 -1
  339. package/src/runtime/routes/onboarding-checkin-routes.ts +86 -0
  340. package/src/runtime/routes/subagents-routes.ts +5 -0
  341. package/src/runtime/routes/surface-action-routes.ts +51 -55
  342. package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
  343. package/src/runtime/services/conversation-serializer.ts +7 -9
  344. package/src/runtime/tool-grant-request-helper.ts +3 -3
  345. package/src/runtime/trust-verdict-consumer.ts +85 -9
  346. package/src/runtime/verification-outbound-actions.ts +18 -18
  347. package/src/signals/user-message.ts +16 -0
  348. package/src/subagent/manager.ts +9 -0
  349. package/src/telemetry/types.ts +34 -1
  350. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
  351. package/src/telemetry/usage-telemetry-reporter.ts +87 -3
  352. package/src/tools/ask-question/ask-question-tool.test.ts +29 -0
  353. package/src/tools/ask-question/ask-question-tool.ts +13 -0
  354. package/src/tools/computer-use/definitions.ts +8 -2
  355. package/src/tools/executor.ts +4 -4
  356. package/src/tools/registry.ts +18 -0
  357. package/src/tools/tool-approval-handler.ts +1 -1
  358. package/src/tools/tool-defaults.ts +9 -2
  359. package/src/tools/types.ts +17 -2
  360. package/src/tools/workspace-tools/loader.ts +348 -244
  361. package/src/util/platform.ts +5 -0
  362. package/src/util/telemetry-db-path.ts +24 -0
  363. package/src/workspace/migrations/017-seed-persona-dirs.ts +3 -34
  364. package/src/workspace/migrations/019-scope-journal-to-guardian.ts +3 -24
  365. package/src/__tests__/workspace-tools-watcher-flag.test.ts +0 -70
  366. package/src/daemon/workspace-tools-watcher.ts +0 -328
  367. package/src/memory/migrations/registry.ts +0 -573
@@ -1,10 +1,12 @@
1
1
  import { v4 as uuid } from "uuid";
2
2
 
3
3
  import { peekAcpSessionManager } from "../../acp/index.js";
4
+ import { resolveCanonicalGuardianRequest } from "../../memory/canonical-guardian-store.js";
4
5
  import { clearAll, getConversation } from "../../memory/conversation-crud.js";
5
6
  import { resolveConversationId } from "../../memory/conversation-key-store.js";
6
7
  import { broadcastMessage } from "../../runtime/assistant-event-hub.js";
7
8
  import { resolveCapabilities } from "../../runtime/capabilities.js";
9
+ import * as pendingInteractions from "../../runtime/pending-interactions.js";
8
10
  import { getSubagentManager } from "../../subagent/index.js";
9
11
  import { createAbortReason } from "../../util/abort-reasons.js";
10
12
  import { UserError } from "../../util/errors.js";
@@ -389,6 +391,81 @@ export function steerToMessage(
389
391
  return { steered: true };
390
392
  }
391
393
 
394
+ /**
395
+ * Supersede an open `ask_question` prompt when a new chat message is enqueued
396
+ * for the same conversation.
397
+ *
398
+ * A queued message while a clarification question is open means the user chose
399
+ * to move on rather than answer it. Steering to that message aborts the parked
400
+ * turn — which settles the open question via its turn-abort signal — repairs
401
+ * the dangling `tool_use`, and drains the message, instead of stranding it
402
+ * behind a prompt no one is going to answer. Only `ask_question` prompts
403
+ * (`kind: "question"`) trigger this; pending confirmations are handled
404
+ * separately by the enqueue path's auto-deny.
405
+ *
406
+ * Returns `true` when a parked question was found and a steer was issued.
407
+ */
408
+ export function steerOnEnqueuedMessageIfQuestionParked(
409
+ conversationId: string,
410
+ enqueuedRequestId: string,
411
+ ): boolean {
412
+ const hasParkedQuestion = pendingInteractions
413
+ .getByConversation(conversationId)
414
+ .some((interaction) => interaction.kind === "question");
415
+ if (!hasParkedQuestion) return false;
416
+ steerToMessage(conversationId, enqueuedRequestId);
417
+ return true;
418
+ }
419
+
420
+ /**
421
+ * Supersede interactions left pending by an in-flight turn when a new message
422
+ * is enqueued for a busy conversation. Centralized so every ingress path (the
423
+ * HTTP send handler and the CLI signal path) gets identical handling:
424
+ *
425
+ * 1. Auto-deny pending confirmations — notify the client and sync the
426
+ * canonical guardian record *before* clearing the prompter-owned
427
+ * confirmations, so a later guardian reply can't match a stale "pending"
428
+ * record and fail with `pending_interaction_not_found`.
429
+ * 2. Supersede a parked ask_question by steering to the enqueued message.
430
+ *
431
+ * Order matters: the steer aborts the turn, which denies the prompter's
432
+ * confirmations as a side effect, so the canonical/notification sync must run
433
+ * first. `removeByConversation` preserves `question` entries, so the parked
434
+ * question is still registered for the steer even after the confirmation sweep.
435
+ */
436
+ export function supersedePendingInteractionsOnEnqueue(
437
+ conversationId: string,
438
+ enqueuedRequestId: string,
439
+ ): void {
440
+ const conversation = findConversation(conversationId);
441
+ if (!conversation) return;
442
+
443
+ if (conversation.hasAnyPendingConfirmation()) {
444
+ for (const interaction of pendingInteractions.getByConversation(
445
+ conversationId,
446
+ )) {
447
+ if (interaction.kind === "confirmation") {
448
+ // sendToClient (wired to the SSE hub) delivers the denial to clients.
449
+ conversation.emitConfirmationStateChanged({
450
+ conversationId,
451
+ requestId: interaction.requestId,
452
+ state: "denied" as const,
453
+ source: "auto_deny" as const,
454
+ });
455
+ // Sync the canonical guardian record so stale "pending" rows aren't
456
+ // matched by later guardian reply routing.
457
+ resolveCanonicalGuardianRequest(interaction.requestId, "pending", {
458
+ status: "denied",
459
+ });
460
+ }
461
+ }
462
+ conversation.denyAllPendingConfirmations();
463
+ pendingInteractions.removeByConversation(conversationId);
464
+ }
465
+
466
+ steerOnEnqueuedMessageIfQuestionParked(conversationId, enqueuedRequestId);
467
+ }
468
+
392
469
  // ---------------------------------------------------------------------------
393
470
  // HTTP handler (delegates to shared logic)
394
471
  // ---------------------------------------------------------------------------
@@ -44,6 +44,56 @@ const MAX_HISTORY_ENTRIES = 10;
44
44
  const LOOP_DETECTION_WINDOW = 3;
45
45
  const CONSECUTIVE_UNCHANGED_WARNING_THRESHOLD = 2;
46
46
 
47
+ // computer_use_key combos that change only selection/cursor/clipboard state.
48
+ // The AX tree models none of these, so they always produce an empty diff —
49
+ // exempt them from the "NO VISIBLE EFFECT" signal (mirrors computer_use_wait).
50
+ // Stored in canonical form (see canonicalizeKeyCombo): modifier aliases
51
+ // normalized and ordered, so `cmd + a`, `command+a`, `alt+tab`, `tab+shift`
52
+ // all match.
53
+ const NO_AX_DIFF_KEY_COMBOS = new Set([
54
+ "cmd+a",
55
+ "cmd+c",
56
+ "up",
57
+ "down",
58
+ "left",
59
+ "right",
60
+ "shift+tab",
61
+ "option+tab",
62
+ ]);
63
+
64
+ // Modifier aliases mirror the mac helper's ActionExecutor.pressKey so the
65
+ // exemption check matches exactly what the helper will execute.
66
+ const KEY_MODIFIER_ALIASES: Record<string, string> = {
67
+ cmd: "cmd",
68
+ command: "cmd",
69
+ option: "option",
70
+ alt: "option",
71
+ ctrl: "ctrl",
72
+ control: "ctrl",
73
+ shift: "shift",
74
+ };
75
+ const KEY_MODIFIER_ORDER = ["cmd", "ctrl", "option", "shift"];
76
+
77
+ /**
78
+ * Normalize a key combo the way the mac helper does (lowercase, split on `+`,
79
+ * trim, alias modifiers) into a canonical `mods…+base` string with modifiers
80
+ * in a fixed order — so order/alias/whitespace variants compare equal.
81
+ */
82
+ function canonicalizeKeyCombo(key: string): string {
83
+ const mods = new Set<string>();
84
+ let base = "";
85
+ for (const raw of key.toLowerCase().split("+")) {
86
+ const part = raw.trim();
87
+ if (part.length === 0) continue;
88
+ const mod = KEY_MODIFIER_ALIASES[part];
89
+ if (mod) mods.add(mod);
90
+ else base = part; // last non-modifier wins, matching the executor
91
+ }
92
+ return [...KEY_MODIFIER_ORDER.filter((m) => mods.has(m)), base]
93
+ .filter((s) => s.length > 0)
94
+ .join("+");
95
+ }
96
+
47
97
  // ---------------------------------------------------------------------------
48
98
  // Types
49
99
  // ---------------------------------------------------------------------------
@@ -69,6 +119,43 @@ export interface ActionRecord {
69
119
  reasoning?: string;
70
120
  }
71
121
 
122
+ /**
123
+ * True when `action` is a computer_use_key press whose key only mutates
124
+ * selection/cursor/clipboard state — changes the AX tree cannot represent, so
125
+ * an empty diff is expected rather than a sign the action did nothing.
126
+ */
127
+ function isNoDiffKeyAction(action: ActionRecord | undefined): boolean {
128
+ if (action?.toolName !== "computer_use_key") return false;
129
+ const key = action.input.key;
130
+ return (
131
+ typeof key === "string" &&
132
+ NO_AX_DIFF_KEY_COMBOS.has(canonicalizeKeyCombo(key))
133
+ );
134
+ }
135
+
136
+ /**
137
+ * Canonical signature for loop detection. Key presses collapse equivalent
138
+ * spellings (`cmd+a`, `command+a`, `cmd + a`) of the same combo so a stuck
139
+ * session retrying it with alias/whitespace variants is still caught —
140
+ * important now that exempt keys no longer emit no-effect warnings. Only the
141
+ * `key` value is normalized; all other input fields (e.g. the routing
142
+ * `target_client_id`) are preserved, so the same combo sent to different
143
+ * desktop clients is not mistaken for a repeat.
144
+ */
145
+ function actionSignature(record: ActionRecord): string {
146
+ if (
147
+ record.toolName === "computer_use_key" &&
148
+ typeof record.input.key === "string"
149
+ ) {
150
+ const normalizedInput = {
151
+ ...record.input,
152
+ key: canonicalizeKeyCombo(record.input.key),
153
+ };
154
+ return `computer_use_key:${JSON.stringify(normalizedInput)}`;
155
+ }
156
+ return `${record.toolName}:${JSON.stringify(record.input)}`;
157
+ }
158
+
72
159
  // ---------------------------------------------------------------------------
73
160
  // HostCuProxy
74
161
  // ---------------------------------------------------------------------------
@@ -385,8 +472,9 @@ export class HostCuProxy {
385
472
  ? this._actionHistory[this._actionHistory.length - 1]
386
473
  : undefined;
387
474
  const isWaitAction = lastAction?.toolName === "computer_use_wait";
475
+ const isNoDiffKey = isNoDiffKeyAction(lastAction);
388
476
 
389
- if (!isWaitAction) {
477
+ if (!isWaitAction && !isNoDiffKey) {
390
478
  if (
391
479
  this._consecutiveUnchangedSteps >=
392
480
  CONSECUTIVE_UNCHANGED_WARNING_THRESHOLD
@@ -405,10 +493,9 @@ export class HostCuProxy {
405
493
 
406
494
  if (this._actionHistory.length >= LOOP_DETECTION_WINDOW) {
407
495
  const recent = this._actionHistory.slice(-LOOP_DETECTION_WINDOW);
496
+ const firstSignature = actionSignature(recent[0]);
408
497
  const allIdentical = recent.every(
409
- (r) =>
410
- r.toolName === recent[0].toolName &&
411
- JSON.stringify(r.input) === JSON.stringify(recent[0].input),
498
+ (r) => actionSignature(r) === firstSignature,
412
499
  );
413
500
  if (allIdentical) {
414
501
  parts.push(
@@ -505,14 +592,18 @@ export class HostCuProxy {
505
592
 
506
593
  private updateStateFromObservation(obs: CuObservationResult): void {
507
594
  if (this._stepCount > 0) {
508
- if (
509
- obs.axDiff == null &&
510
- this._previousAXTree != null &&
511
- obs.axTree != null
512
- ) {
513
- this._consecutiveUnchangedSteps++;
514
- } else if (obs.axDiff != null) {
595
+ const lastAction =
596
+ this._actionHistory.length > 0
597
+ ? this._actionHistory[this._actionHistory.length - 1]
598
+ : undefined;
599
+ if (obs.axDiff != null || isNoDiffKeyAction(lastAction)) {
600
+ // A real diff, or an exempt key whose effect is invisible by design,
601
+ // breaks the no-effect streak clear it rather than preserving a
602
+ // stale count so an intervening cmd+a can't bridge two no-op actions
603
+ // into a false "consecutive" escalation.
515
604
  this._consecutiveUnchangedSteps = 0;
605
+ } else if (this._previousAXTree != null && obs.axTree != null) {
606
+ this._consecutiveUnchangedSteps++;
516
607
  }
517
608
  }
518
609
 
@@ -966,6 +966,10 @@ export async function runDaemon(): Promise<void> {
966
966
  }
967
967
  }
968
968
 
969
+ // `startMemoryJobsWorker` selects the worker implementation based on
970
+ // `memory.worker.enabled` (in-process vs. a separate OS process).
971
+ // Shutdown stops whichever worker is actually running — see
972
+ // shutdown-handlers.ts.
969
973
  log.info("Daemon startup: starting memory worker");
970
974
  bgRefs.memoryWorker = startMemoryJobsWorker();
971
975
 
@@ -59,6 +59,18 @@ function configWithV2(enabled: boolean): AssistantConfig {
59
59
  return { memory: { v2: { enabled } } } as unknown as AssistantConfig;
60
60
  }
61
61
 
62
+ /** Poll until `m` has been called at least `n` times, or `timeoutMs` elapses. */
63
+ async function waitForCalls(
64
+ m: { mock: { calls: unknown[] } },
65
+ n: number,
66
+ timeoutMs = 1000,
67
+ ): Promise<void> {
68
+ const start = Date.now();
69
+ while (m.mock.calls.length < n && Date.now() - start < timeoutMs) {
70
+ await new Promise((resolve) => setTimeout(resolve, 5));
71
+ }
72
+ }
73
+
62
74
  afterEach(() => {
63
75
  seedSkill.mockClear();
64
76
  seedCli.mockClear();
@@ -128,4 +140,64 @@ describe("maybeReseedCapabilitiesAfterManagedCredential", () => {
128
140
 
129
141
  expect(seedCli).toHaveBeenCalledTimes(1);
130
142
  });
143
+
144
+ test("enqueues the v3 maintain pass even when one catalog reseed rejects", async () => {
145
+ proxyState.prereqs = true;
146
+ v3State.live = true;
147
+ seedSkill.mockImplementationOnce(async () => {
148
+ throw new Error('Embedding backend "gemini" is not configured');
149
+ });
150
+
151
+ await maybeReseedCapabilitiesAfterManagedCredential(configWithV2(true));
152
+
153
+ // The CLI catalog seeded, so v3 must still rebuild its lanes — a single
154
+ // catalog failure cannot suppress the maintain pass.
155
+ expect(seedCli).toHaveBeenCalledTimes(1);
156
+ expect(enqueueJob).toHaveBeenCalledTimes(1);
157
+ expect(enqueueJob).toHaveBeenCalledWith("memory_v3_maintain", {});
158
+ });
159
+
160
+ test("enqueues the v3 maintain pass without blocking when a catalog reseed exceeds the timeout", async () => {
161
+ proxyState.prereqs = true;
162
+ v3State.live = true;
163
+ // Skill reseed never settles — mirrors the wedged getCatalog()/embed seen in
164
+ // the field. The CLI reseed completes normally.
165
+ seedSkill.mockImplementationOnce(() => new Promise<void>(() => {}));
166
+
167
+ await maybeReseedCapabilitiesAfterManagedCredential(configWithV2(true), {
168
+ reseedTimeoutMs: 20,
169
+ });
170
+
171
+ // An unbounded `Promise.all` barrier would hang here forever; the bounded
172
+ // barrier lets the CLI catalog's maintain pass enqueue regardless.
173
+ expect(seedCli).toHaveBeenCalledTimes(1);
174
+ expect(enqueueJob).toHaveBeenCalledTimes(1);
175
+ expect(enqueueJob).toHaveBeenCalledWith("memory_v3_maintain", {});
176
+ });
177
+
178
+ test("re-enqueues the v3 maintain pass when a straggler catalog finishes after the timeout", async () => {
179
+ proxyState.prereqs = true;
180
+ v3State.live = true;
181
+ let resolveSkill!: () => void;
182
+ seedSkill.mockImplementationOnce(
183
+ () =>
184
+ new Promise<void>((resolve) => {
185
+ resolveSkill = resolve;
186
+ }),
187
+ );
188
+
189
+ await maybeReseedCapabilitiesAfterManagedCredential(configWithV2(true), {
190
+ reseedTimeoutMs: 10,
191
+ });
192
+
193
+ // Post-barrier enqueue fires once even though the skill catalog is still
194
+ // embedding.
195
+ expect(enqueueJob).toHaveBeenCalledTimes(1);
196
+
197
+ // The straggler lands; maintain re-enqueues so its late capability rows are
198
+ // reconciled without waiting out the 6h backstop.
199
+ resolveSkill();
200
+ await waitForCalls(enqueueJob, 2);
201
+ expect(enqueueJob).toHaveBeenCalledTimes(2);
202
+ });
131
203
  });
@@ -49,6 +49,36 @@ export function maybeSeedMemoryV2CliCommands(config: AssistantConfig): void {
49
49
  .catch((err) => log.warn({ err }, "Failed to seed v2 CLI-command entries"));
50
50
  }
51
51
 
52
+ /**
53
+ * Default upper bound on how long
54
+ * {@link maybeReseedCapabilitiesAfterManagedCredential} waits for the capability
55
+ * reseeds before enqueuing the v3 maintain pass. The reseeds keep running
56
+ * detached past this bound — it only stops the barrier from waiting on a wedged
57
+ * catalog (a stalled `getCatalog()` or a managed-proxy embed that never
58
+ * returns). A straggler that finishes later re-enqueues maintain.
59
+ */
60
+ const RESEED_BARRIER_TIMEOUT_MS = 120_000;
61
+
62
+ /**
63
+ * Resolve to `true` if `p` settles within `ms`, or `false` if the timeout wins.
64
+ * Always clears the timer, so a `p` that settles first leaves no pending timer
65
+ * keeping the event loop (or a test) alive.
66
+ */
67
+ async function settledWithin(
68
+ p: Promise<unknown>,
69
+ ms: number,
70
+ ): Promise<boolean> {
71
+ let timer: ReturnType<typeof setTimeout> | undefined;
72
+ const timedOut = new Promise<false>((resolve) => {
73
+ timer = setTimeout(() => resolve(false), ms);
74
+ });
75
+ try {
76
+ return await Promise.race([p.then(() => true), timedOut]);
77
+ } finally {
78
+ if (timer) clearTimeout(timer);
79
+ }
80
+ }
81
+
52
82
  /**
53
83
  * Re-seed the v2 skill and CLI-command capability entries once a managed-proxy
54
84
  * credential lands, closing the first-boot race where the daemon's startup seed
@@ -83,9 +113,23 @@ export function maybeSeedMemoryV2CliCommands(config: AssistantConfig): void {
83
113
  * dense store and its lane-invalidation stage forces a rebuild against the now-
84
114
  * populated index, so v3 surfaces the skill/CLI pages within seconds instead of
85
115
  * waiting out the backstop.
116
+ *
117
+ * The maintain enqueue must NOT be gated on both catalogs settling. The two
118
+ * embeds are independent, and a single wedged catalog (a stalled `getCatalog()`
119
+ * or a managed-proxy embed that never returns) would otherwise block the v3 lane
120
+ * rebuild indefinitely, so the catalog that DID seed never reaches the selector.
121
+ * The barrier is therefore bounded by `reseedTimeoutMs`: maintain is enqueued
122
+ * once the barrier resolves (the pass is idempotent and reconciles whatever the
123
+ * page index currently holds), and a straggler catalog that finishes after the
124
+ * timeout re-enqueues maintain so its late rows are picked up without waiting out
125
+ * the backstop.
126
+ *
127
+ * `reseedTimeoutMs` is injectable for tests; production uses
128
+ * {@link RESEED_BARRIER_TIMEOUT_MS}.
86
129
  */
87
130
  export async function maybeReseedCapabilitiesAfterManagedCredential(
88
131
  config: AssistantConfig,
132
+ opts: { reseedTimeoutMs?: number } = {},
89
133
  ): Promise<void> {
90
134
  if (!config.memory.v2.enabled) return;
91
135
 
@@ -115,35 +159,59 @@ export async function maybeReseedCapabilitiesAfterManagedCredential(
115
159
  ],
116
160
  ];
117
161
 
118
- await Promise.all(
119
- catalogs.map(async ([label, seed]) => {
120
- try {
121
- await seed();
162
+ // Each reseed is contained so one catalog's embed failure (or hang) never
163
+ // rejects the caller or aborts the other. Started here but not awaited as a
164
+ // single barrier — the bounded wait below decides when to stop waiting.
165
+ const reseeds = catalogs.map(([label, seed]) =>
166
+ seed().then(
167
+ () =>
122
168
  log.info(
123
169
  `Memory v2 ${label} entries seeded after managed proxy credential update`,
124
- );
125
- } catch (err) {
170
+ ),
171
+ (err: unknown) =>
126
172
  log.warn(
127
173
  { err },
128
174
  `Failed to seed v2 ${label} entries after managed proxy credential update`,
129
- );
130
- }
131
- }),
175
+ ),
176
+ ),
132
177
  );
133
178
 
134
- // The stores (and the page index) are now populated; when v3 is live, kick a
135
- // maintain pass so it embeds the capability rows into `memory_v3_sections` and
136
- // invalidates its lanes immediately rather than waiting out the 6h backstop.
179
+ // When v3 is live, a maintain pass embeds the freshly-seeded capability rows
180
+ // into `memory_v3_sections` and invalidates the lanes so v3 surfaces the
181
+ // skill/CLI pages within seconds instead of waiting out the 6h backstop.
182
+ // Resolve the gate + enqueuer once and reuse for the post-barrier enqueue and
183
+ // the straggler re-enqueue below.
137
184
  const { isMemoryV3Live } = await import("../config/memory-v3-gate.js");
138
- if (!isMemoryV3Live(config)) return;
139
- try {
140
- const { enqueueMemoryJob } = await import("../memory/jobs-store.js");
141
- enqueueMemoryJob("memory_v3_maintain", {});
142
- } catch (err) {
185
+ const v3Live = isMemoryV3Live(config);
186
+ const enqueueMaintain = async (): Promise<void> => {
187
+ if (!v3Live) return;
188
+ try {
189
+ const { enqueueMemoryJob } = await import("../memory/jobs-store.js");
190
+ enqueueMemoryJob("memory_v3_maintain", {});
191
+ } catch (err) {
192
+ log.warn(
193
+ { err },
194
+ "Failed to enqueue memory_v3_maintain after managed proxy credential update",
195
+ );
196
+ }
197
+ };
198
+
199
+ // Bound the barrier so a wedged catalog can't block the maintain enqueue
200
+ // indefinitely; the reseeds keep running detached past the timeout.
201
+ const timeoutMs = opts.reseedTimeoutMs ?? RESEED_BARRIER_TIMEOUT_MS;
202
+ const allReseeds = Promise.allSettled(reseeds);
203
+ const settledInTime = await settledWithin(allReseeds, timeoutMs);
204
+
205
+ await enqueueMaintain();
206
+
207
+ if (!settledInTime) {
143
208
  log.warn(
144
- { err },
145
- "Failed to enqueue memory_v3_maintain after managed proxy credential update",
209
+ { timeoutMs },
210
+ "Capability reseed still running after the barrier timeout — enqueued v3 maintain now; will re-enqueue when the straggler catalog finishes",
146
211
  );
212
+ // The straggler is still embedding; re-enqueue maintain once it lands so its
213
+ // late capability rows are reconciled without waiting out the 6h backstop.
214
+ void allReseeds.then(() => enqueueMaintain());
147
215
  }
148
216
  }
149
217
 
@@ -48,7 +48,6 @@ import { parseIdentityFields } from "./handlers/identity.js";
48
48
  import type { ConversationCreateOptions } from "./handlers/shared.js";
49
49
  import { setGlobalSkillIpcSender } from "./meet-host-supervisor.js";
50
50
  import { refreshSkillCapabilityMemories } from "./skill-memory-refresh.js";
51
- import { WorkspaceToolsWatcher } from "./workspace-tools-watcher.js";
52
51
 
53
52
  const log = getLogger("server");
54
53
 
@@ -308,8 +307,6 @@ export class DaemonServer {
308
307
 
309
308
  this.appSourceWatcher.start((appId) => this.handleAppSourceChange(appId));
310
309
 
311
- WorkspaceToolsWatcher.getInstance().start();
312
-
313
310
  // Broadcast contacts_changed to all clients when any contact mutation occurs.
314
311
  this.unsubscribeContactChange = onContactChange(() => {
315
312
  broadcastMessage({ type: "contacts_changed" });
@@ -324,7 +321,6 @@ export class DaemonServer {
324
321
  this.evictor.stop();
325
322
  this.configWatcher.stop();
326
323
  this.appSourceWatcher.stop();
327
- WorkspaceToolsWatcher.getInstance().stop();
328
324
  this.cliIpc.stop();
329
325
  this.skillIpc.stop();
330
326
  if (this.unsubscribeContactChange) {
@@ -5,6 +5,7 @@ import type { HeartbeatService } from "../heartbeat/heartbeat-service.js";
5
5
  import type { McpServerManager } from "../mcp/manager.js";
6
6
  import { getSqlite, resetDb } from "../memory/db-connection.js";
7
7
  import type { QdrantManager } from "../memory/qdrant-manager.js";
8
+ import { stopMemoryWorkerProcess } from "../memory/worker-control.js";
8
9
  import type { RuntimeHttpServer } from "../runtime/http-server.js";
9
10
  import { browserManager } from "../tools/browser/browser-manager.js";
10
11
  import { cleanupShellOutputTempFiles } from "../tools/shared/shell-output.js";
@@ -117,8 +118,27 @@ export function installShutdownHandlers(deps: ShutdownDeps): void {
117
118
  await browserManager.closeAllPages();
118
119
  cleanupShellOutputTempFiles();
119
120
  deps.scheduler.stop();
121
+
122
+ // Stop the in-process memory worker if one was started on the daemon's
123
+ // event loop (memory.worker.enabled = false).
120
124
  deps.getMemoryWorker()?.stop();
121
125
 
126
+ // Stop the out-of-process memory worker if it's actually running. This is
127
+ // keyed off live state rather than config: the worker may have been
128
+ // spawned at startup (memory.worker.enabled = true) or out of band via
129
+ // `assistant memory worker start`, so we stop whatever is actually there.
130
+ try {
131
+ const workerStatus = stopMemoryWorkerProcess();
132
+ if (workerStatus.status === "running") {
133
+ log.info(
134
+ { pid: workerStatus.pid },
135
+ "Sent SIGTERM to memory worker process",
136
+ );
137
+ }
138
+ } catch (err) {
139
+ log.warn({ err }, "Failed to stop memory worker process (non-fatal)");
140
+ }
141
+
122
142
  if (deps.mcpManager) {
123
143
  try {
124
144
  await deps.mcpManager.stop();
@@ -124,4 +124,13 @@ export interface ToolSetupContext extends SurfaceConversationContext {
124
124
  * return `undefined` for the in-flight (background) subagent.
125
125
  */
126
126
  currentTurnOverrideProfile?: string;
127
+ /**
128
+ * Whether the current turn has no human present to answer clarification
129
+ * prompts. Resolved once per turn by the agent loop — honoring an explicit
130
+ * per-run `isInteractive` option (e.g. scheduled/background turns) over the
131
+ * live client state — so tool execution sees turn-level interactivity rather
132
+ * than re-deriving it from `hasNoClient`/`headlessLock`, which would read a
133
+ * scheduled turn running on a client-attached conversation as interactive.
134
+ */
135
+ currentTurnIsNonInteractive?: boolean;
127
136
  }
@@ -36,7 +36,7 @@ mock.module("../../config/env.js", () => ({
36
36
  }));
37
37
 
38
38
  mock.module("../../runtime/local-actor-identity.js", () => ({
39
- findLocalGuardianPrincipalId: () => fakeLocalPrincipalId,
39
+ findLocalGuardianPrincipalIdFromStore: () => fakeLocalPrincipalId,
40
40
  }));
41
41
 
42
42
  // ── Real imports (after mocks) ────────────────────────────────────────────
@@ -33,7 +33,7 @@ import { createServer, type Server, type Socket } from "node:net";
33
33
 
34
34
  import { ensureSocketDir, SocketWatchdog } from "@vellumai/ipc-server-utils";
35
35
 
36
- import { findLocalGuardianPrincipalId } from "../runtime/local-actor-identity.js";
36
+ import { findLocalGuardianPrincipalIdFromStore } from "../runtime/local-actor-identity.js";
37
37
  import { RouteError } from "../runtime/routes/errors.js";
38
38
  import { ROUTES } from "../runtime/routes/index.js";
39
39
  import type {
@@ -637,7 +637,7 @@ function injectLocalActorHeader(
637
637
  // that require the header will fail-closed on their own.
638
638
  let localActor: string | undefined;
639
639
  try {
640
- localActor = findLocalGuardianPrincipalId();
640
+ localActor = findLocalGuardianPrincipalIdFromStore();
641
641
  } catch (err) {
642
642
  log.debug(
643
643
  { err },