@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,64 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ import type { WorkspaceMigration } from "./types.js";
5
+
6
+ /**
7
+ * Strip a persisted `llm.callSites.advisor` entry from existing config files.
8
+ *
9
+ * `advisor` is not a valid `LLMCallSiteEnum` key, so a saved
10
+ * `llm.callSites.advisor.profile` is rejected on parse by the
11
+ * `z.partialRecord(LLMCallSiteEnum, ...)` schema. The loader recovers (logs
12
+ * `Invalid config at "llm.callSites.advisor"...`, deletes the key, re-parses),
13
+ * so it is not a crash — but the warning is logged on every boot, and because
14
+ * `GET /config` serves the raw file the web "Overrides" badge keeps counting
15
+ * the invalid key with no reset path.
16
+ *
17
+ * This migration strips the key once. The now-empty `llm.callSites` object is
18
+ * pruned if `advisor` was its only key (the schema defaults `callSites` to
19
+ * `{}`, so an absent key is equivalent to an empty map). Other call-site keys
20
+ * and the unrelated top-level `llm.advisorProfile` selection are left intact.
21
+ *
22
+ * No-op for configs that never had the key. Idempotent.
23
+ */
24
+ export const removeAdvisorCallsiteOverrideMigration: WorkspaceMigration = {
25
+ id: "112-remove-advisor-callsite-override",
26
+ description:
27
+ "Remove the stale advisor entry from llm.callSites (advisor call site removed)",
28
+ run(workspaceDir: string): void {
29
+ const configPath = join(workspaceDir, "config.json");
30
+ if (!existsSync(configPath)) return;
31
+
32
+ let config: Record<string, unknown>;
33
+ try {
34
+ const raw = JSON.parse(readFileSync(configPath, "utf-8"));
35
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
36
+ config = raw as Record<string, unknown>;
37
+ } catch {
38
+ return;
39
+ }
40
+
41
+ const llm = config.llm;
42
+ if (!llm || typeof llm !== "object" || Array.isArray(llm)) return;
43
+
44
+ const callSites = (llm as Record<string, unknown>).callSites;
45
+ if (!callSites || typeof callSites !== "object" || Array.isArray(callSites))
46
+ return;
47
+
48
+ const sites = callSites as Record<string, unknown>;
49
+ if (!("advisor" in sites)) return;
50
+
51
+ delete sites.advisor;
52
+
53
+ // Prune the now-empty callSites map; an absent key is equivalent to the
54
+ // schema's `{}` default.
55
+ if (Object.keys(sites).length === 0) {
56
+ delete (llm as Record<string, unknown>).callSites;
57
+ }
58
+
59
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
60
+ },
61
+ down(_workspaceDir: string): void {
62
+ // Forward-only — the advisor call site no longer exists.
63
+ },
64
+ };
@@ -108,6 +108,8 @@ import { dropSendDiagnosticsMigration } from "./107-drop-send-diagnostics.js";
108
108
  import { dropBalancedEconomyProfileMigration } from "./108-drop-balanced-economy-profile.js";
109
109
  import { swapQualityProfileToGlm52Migration } from "./109-swap-quality-profile-to-glm-5p2.js";
110
110
  import { flipBalancedProfileToTogetherMigration } from "./110-flip-balanced-profile-to-together.js";
111
+ import { pruneSeededCallsiteDefaultsMigration } from "./111-prune-seeded-callsite-defaults.js";
112
+ import { removeAdvisorCallsiteOverrideMigration } from "./112-remove-advisor-callsite-override.js";
111
113
  import { migrateToWorkspaceVolumeMigration } from "./migrate-to-workspace-volume.js";
112
114
  import type { WorkspaceMigration } from "./types.js";
113
115
 
@@ -227,4 +229,6 @@ export const WORKSPACE_MIGRATIONS: WorkspaceMigration[] = [
227
229
  dropBalancedEconomyProfileMigration,
228
230
  swapQualityProfileToGlm52Migration,
229
231
  flipBalancedProfileToTogetherMigration,
232
+ pruneSeededCallsiteDefaultsMigration,
233
+ removeAdvisorCallsiteOverrideMigration,
230
234
  ];
@@ -1,70 +0,0 @@
1
- /**
2
- * Tests that the `workspace-tools-watcher` feature flag gates the dynamic
3
- * hot-reload watcher in `WorkspaceToolsWatcher.start()`.
4
- *
5
- * The initial disk scan (`loadWorkspaceTools()`) is unconditional and lives
6
- * elsewhere; this suite only covers whether the live `fs.watch` loop mounts.
7
- */
8
- import { mkdirSync, rmSync } from "node:fs";
9
- import { tmpdir } from "node:os";
10
- import { join } from "node:path";
11
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
12
-
13
- import { clearFeatureFlagOverridesCache } from "../config/assistant-feature-flags.js";
14
- import { WorkspaceToolsWatcher } from "../daemon/workspace-tools-watcher.js";
15
- import { setOverridesForTesting } from "./feature-flag-test-helpers.js";
16
-
17
- const FLAG = "workspace-tools-watcher";
18
- const TEST_BASE_DIR = join(
19
- tmpdir(),
20
- `vellum-workspace-tools-watcher-flag-test-${process.pid}-${Date.now()}`,
21
- );
22
- let caseCounter = 0;
23
-
24
- function freshWorkspaceWithToolsDir(): void {
25
- caseCounter += 1;
26
- const dir = join(TEST_BASE_DIR, `case-${caseCounter}`);
27
- mkdirSync(join(dir, "tools"), { recursive: true });
28
- process.env.VELLUM_WORKSPACE_DIR = dir;
29
- }
30
-
31
- beforeEach(() => {
32
- WorkspaceToolsWatcher.resetForTests();
33
- clearFeatureFlagOverridesCache();
34
- freshWorkspaceWithToolsDir();
35
- });
36
-
37
- afterEach(() => {
38
- WorkspaceToolsWatcher.resetForTests();
39
- clearFeatureFlagOverridesCache();
40
- delete process.env.VELLUM_WORKSPACE_DIR;
41
- rmSync(TEST_BASE_DIR, { recursive: true, force: true });
42
- });
43
-
44
- describe("WorkspaceToolsWatcher feature-flag gate", () => {
45
- test("does not mount the watch loop when the flag is off", () => {
46
- // GIVEN the workspace tools directory exists
47
- // AND the watcher flag is disabled
48
- setOverridesForTesting({ [FLAG]: false });
49
-
50
- // WHEN the watcher starts
51
- const watcher = WorkspaceToolsWatcher.getInstance();
52
- watcher.start();
53
-
54
- // THEN no fs.watch loop is mounted
55
- expect(watcher.isWatchingForTests()).toBe(false);
56
- });
57
-
58
- test("mounts the watch loop when the flag is on", () => {
59
- // GIVEN the workspace tools directory exists
60
- // AND the watcher flag is enabled
61
- setOverridesForTesting({ [FLAG]: true });
62
-
63
- // WHEN the watcher starts
64
- const watcher = WorkspaceToolsWatcher.getInstance();
65
- watcher.start();
66
-
67
- // THEN a live fs.watch loop is mounted
68
- expect(watcher.isWatchingForTests()).toBe(true);
69
- });
70
- });
@@ -1,328 +0,0 @@
1
- /**
2
- * Filesystem watcher for `<workspaceDir>/tools/`.
3
- *
4
- * Watches the workspace-tools directory non-recursively using fs.watch.
5
- * On any add/change/delete event, debounces per filename stem (= tool
6
- * name) and reconciles registry state against on-disk state: registers
7
- * newly added tools, re-imports changed tools (cache-busting via the
8
- * loader's per-import URL query string), unregisters deleted tools,
9
- * strips core tools when a `.removed` sentinel appears, restores them
10
- * when the sentinel disappears.
11
- *
12
- * No assistant restart is required — the file watcher closes the
13
- * "edit a file, see the change" loop the same way the apps watcher and
14
- * plugin source watcher do for their respective directories.
15
- *
16
- * ## Why per-stem reconciliation
17
- *
18
- * The watcher receives `(eventType, filename)` from fs.watch but the
19
- * eventType ("rename" vs "change") is unreliable across editors and
20
- * platforms — vim atomic-save shows as a rename of the original file
21
- * plus an add of a new file, VS Code shows as a change in place, etc.
22
- * Rather than route on eventType, we debounce per stem and re-derive
23
- * the world: "given what's on disk right now for `<stem>.*`, what
24
- * registry state should the assistant be in?"
25
- *
26
- * This is the same eventual-consistency pattern the plugin mtime cache
27
- * uses — the watcher exists to KICK the reconciler, not to be the
28
- * source of truth about what changed.
29
- *
30
- * ## Lifecycle position
31
- *
32
- * Started after the initial `loadWorkspaceTools()` scan completes
33
- * during daemon startup, gated on the `workspace-tools-watcher` feature
34
- * flag — when the flag is off the initial scan still runs but no watch
35
- * loop is mounted, so workspace tools load from disk once and live edits
36
- * need a restart. Stopped on assistant shutdown alongside the
37
- * other long-lived watchers. Stoppage during shutdown does not
38
- * unregister tools — those go away with the process; the watcher's
39
- * only job is to keep the registry fresh while the assistant is up.
40
- */
41
-
42
- import { existsSync, type FSWatcher, watch } from "node:fs";
43
-
44
- import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
45
- import { getConfig } from "../config/loader.js";
46
- import {
47
- getCoreToolOverride,
48
- getTool,
49
- getToolOwner,
50
- removeCoreToolViaWorkspace,
51
- restoreStrippedCoreTool,
52
- unregisterWorkspaceTool,
53
- } from "../tools/registry.js";
54
- import {
55
- classifyWorkspaceToolEntry,
56
- findWinningWorkspaceToolPath,
57
- loadSingleWorkspaceTool,
58
- } from "../tools/workspace-tools/loader.js";
59
- import { DebouncerMap } from "../util/debounce.js";
60
- import { attachFsWatcherErrorHandler } from "../util/fs-watcher-error.js";
61
- import { getLogger } from "../util/logger.js";
62
- import { getWorkspaceToolsDir } from "../util/platform.js";
63
-
64
- const log = getLogger("workspace-tools-watcher");
65
-
66
- /**
67
- * Gates the dynamic hot-reload path. When disabled, workspace tools still
68
- * load from disk once at daemon startup via {@link loadWorkspaceTools}; only
69
- * the live watch → re-registration loop is suppressed. Read at its point of
70
- * use in {@link WorkspaceToolsWatcher.start} so a daemon restart picks up a
71
- * changed value.
72
- */
73
- const WORKSPACE_TOOLS_WATCHER_FLAG = "workspace-tools-watcher" as const;
74
-
75
- /**
76
- * Wait this long after the last fs event for a given stem before
77
- * reconciling. Editor saves often emit a burst of 2–4 events; the
78
- * debounce collapses them into a single load.
79
- */
80
- const RECONCILE_DEBOUNCE_MS = 250;
81
-
82
- export class WorkspaceToolsWatcher {
83
- /**
84
- * Process-wide singleton. Callers reach the watcher via
85
- * {@link WorkspaceToolsWatcher.getInstance} rather than instantiating
86
- * directly so the daemon `start()`/`stop()` lifecycle and any future
87
- * "trigger a manual reconcile" code paths share one watcher across the
88
- * assistant process lifetime.
89
- */
90
- private static singleton: WorkspaceToolsWatcher | null = null;
91
-
92
- static getInstance(): WorkspaceToolsWatcher {
93
- WorkspaceToolsWatcher.singleton ??= new WorkspaceToolsWatcher();
94
- return WorkspaceToolsWatcher.singleton;
95
- }
96
-
97
- /** Test-only — drops the singleton so the next `getInstance()` rebuilds. */
98
- static resetForTests(): void {
99
- WorkspaceToolsWatcher.singleton?.stop();
100
- WorkspaceToolsWatcher.singleton = null;
101
- }
102
-
103
- /** Test-only — whether a live `fs.watch` loop is currently mounted. */
104
- isWatchingForTests(): boolean {
105
- return this.watcher !== null;
106
- }
107
-
108
- private watcher: FSWatcher | null = null;
109
- private debouncer = new DebouncerMap({
110
- defaultDelayMs: RECONCILE_DEBOUNCE_MS,
111
- maxEntries: 100,
112
- });
113
- /**
114
- * Promise queue per stem — guarantees that two events for the same
115
- * stem can't run concurrently and corrupt the unregister/load
116
- * sequence. The queue is single-deep (the chained promise simply
117
- * awaits the in-flight one before running its own work), so the
118
- * debouncer's collapsing already does most of the deduplication; this
119
- * queue exists for the case where a second event lands during the
120
- * in-flight reconcile's `await loadSingleWorkspaceTool`.
121
- */
122
- private inflight = new Map<string, Promise<void>>();
123
-
124
- start(): void {
125
- if (this.watcher) return;
126
- if (
127
- !isAssistantFeatureFlagEnabled(WORKSPACE_TOOLS_WATCHER_FLAG, getConfig())
128
- ) {
129
- log.debug(
130
- "Workspace tools watcher disabled by feature flag; workspace tools load from disk at startup only (restart required to pick up edits)",
131
- );
132
- return;
133
- }
134
- const toolsDir = getWorkspaceToolsDir();
135
- if (!existsSync(toolsDir)) {
136
- log.debug(
137
- { toolsDir },
138
- "Workspace tools directory does not exist; watcher not started (will not auto-start on directory creation — restart required)",
139
- );
140
- return;
141
- }
142
- try {
143
- this.watcher = watch(
144
- toolsDir,
145
- { recursive: false },
146
- (_eventType, filename) => {
147
- if (!filename) return;
148
- const classified = classifyWorkspaceToolEntry(filename);
149
- if (!classified) {
150
- // Not a workspace-tool file — ignore (README.md, .DS_Store, etc.)
151
- return;
152
- }
153
- this.debouncer.schedule(`stem:${classified.stem}`, () => {
154
- this.scheduleReconcile(classified.stem);
155
- });
156
- },
157
- );
158
- // Async FSWatcher errors (e.g. ENOSPC, ENXIO) arrive as an 'error' event;
159
- // without a listener they crash the daemon. Degrade to a dead watcher.
160
- attachFsWatcherErrorHandler(this.watcher, log, toolsDir);
161
- log.info({ toolsDir }, "Workspace tools watcher started");
162
- } catch (err) {
163
- log.warn(
164
- { err, toolsDir },
165
- "Failed to start workspace tools watcher — workspace tools will only register at startup",
166
- );
167
- }
168
- }
169
-
170
- stop(): void {
171
- this.debouncer.cancelAll();
172
- if (this.watcher) {
173
- this.watcher.close();
174
- this.watcher = null;
175
- }
176
- }
177
-
178
- /**
179
- * Chain `reconcileStem(stem)` after any in-flight reconcile for the
180
- * same stem so we never run two `loadSingleWorkspaceTool` calls
181
- * concurrently for the same name.
182
- */
183
- private scheduleReconcile(stem: string): void {
184
- const prev = this.inflight.get(stem) ?? Promise.resolve();
185
- const next = prev
186
- .catch(() => {
187
- /* swallow — error already logged in the prior tick */
188
- })
189
- .then(() => this.reconcileStem(stem))
190
- .catch((err) => {
191
- const message = err instanceof Error ? err.message : String(err);
192
- log.error(
193
- { err, stem },
194
- `workspace-tools-watcher: reconcile for "${stem}" threw: ${message}`,
195
- );
196
- })
197
- .finally(() => {
198
- // Only clear if we're still the current in-flight promise.
199
- if (this.inflight.get(stem) === next) {
200
- this.inflight.delete(stem);
201
- }
202
- });
203
- this.inflight.set(stem, next);
204
- }
205
-
206
- /**
207
- * Reconcile a single stem (= tool name) with on-disk state.
208
- *
209
- * Possible (live, removed) tuples and their resolution:
210
- *
211
- * - `(present, absent)` → ensure workspace tool is registered using
212
- * the winning live file; re-import if a previous registration
213
- * pointed at a different path
214
- * - `(absent, present)` → ensure core tool is stripped (and any
215
- * previous workspace registration torn down first)
216
- * - `(absent, absent)` → ensure neither stripped state nor live
217
- * registration remains
218
- * - `(present, present)` → ambiguous; tear down both so neither
219
- * state survives (matches the initial-scan contract)
220
- */
221
- private async reconcileStem(stem: string): Promise<void> {
222
- const toolsDir = getWorkspaceToolsDir();
223
- if (!existsSync(toolsDir)) {
224
- this.teardownStem(stem);
225
- return;
226
- }
227
-
228
- const { livePath, hasRemovedSentinel } = findWinningWorkspaceToolPath(
229
- toolsDir,
230
- stem,
231
- );
232
-
233
- // Ambiguous: tear down everything for this stem.
234
- if (hasRemovedSentinel && livePath !== null) {
235
- log.error(
236
- { stem, toolsDir, livePath },
237
- `workspace-tools-watcher: "${stem}" has both a live file and a .removed sentinel — tearing down both`,
238
- );
239
- this.teardownStem(stem);
240
- return;
241
- }
242
-
243
- // Neither: tear down anything we own.
244
- if (!hasRemovedSentinel && livePath === null) {
245
- this.teardownStem(stem);
246
- return;
247
- }
248
-
249
- if (hasRemovedSentinel) {
250
- // Strip the core tool. Unregister any prior workspace registration
251
- // first so removeCoreToolViaWorkspace doesn't throw on the
252
- // workspace-owned-name check.
253
- if (getToolOwner(stem)?.kind === "workspace") {
254
- unregisterWorkspaceTool(stem);
255
- }
256
- try {
257
- removeCoreToolViaWorkspace(stem);
258
- } catch (err) {
259
- const message = err instanceof Error ? err.message : String(err);
260
- log.error(
261
- { err, stem },
262
- `workspace-tools-watcher: failed to strip "${stem}": ${message}`,
263
- );
264
- }
265
- return;
266
- }
267
-
268
- if (livePath !== null) {
269
- // If we previously stripped this core tool, restore it before the
270
- // workspace registration runs so the override path sees the
271
- // expected baseline (core tool present → stash + replace).
272
- if (getCoreToolOverride(stem) && !getTool(stem)) {
273
- restoreStrippedCoreTool(stem);
274
- }
275
-
276
- // If a workspace tool is already registered under this name,
277
- // unregister it so the loader can re-import cleanly. This covers
278
- // file-change events (same path, new contents) as well as
279
- // extension-precedence flips (e.g. user added foo.js next to
280
- // foo.ts — now .js wins and the registration must update).
281
- if (getToolOwner(stem)?.kind === "workspace") {
282
- unregisterWorkspaceTool(stem);
283
- }
284
-
285
- const registered = await loadSingleWorkspaceTool(livePath);
286
- if (registered) {
287
- log.info(
288
- { stem, livePath },
289
- `Workspace tool "${stem}" registered via watcher`,
290
- );
291
- }
292
- // loadSingleWorkspaceTool already logs failures with attribution.
293
- }
294
- }
295
-
296
- /**
297
- * Tear down any workspace-tool state we own for `stem`: unregister a
298
- * live workspace tool, restore a core tool we stripped. Both no-ops
299
- * when there's nothing to undo.
300
- */
301
- private teardownStem(stem: string): void {
302
- if (getToolOwner(stem)?.kind === "workspace") {
303
- unregisterWorkspaceTool(stem);
304
- log.info(
305
- { stem },
306
- `Workspace tool "${stem}" unregistered via watcher (file removed)`,
307
- );
308
- }
309
- if (getCoreToolOverride(stem) && !getTool(stem)) {
310
- restoreStrippedCoreTool(stem);
311
- }
312
- }
313
-
314
- // ── Test affordances ────────────────────────────────────────────────
315
- //
316
- // These are intentionally narrow — exposed for the watcher tests so
317
- // they can drive reconcile() deterministically without waiting on the
318
- // debouncer, and inspect whether an in-flight promise is settled.
319
- // Not part of the public lifecycle API.
320
-
321
- /**
322
- * Run a reconcile for `stem` synchronously (well, asynchronously, but
323
- * without going through the debouncer). For tests.
324
- */
325
- async _testReconcile(stem: string): Promise<void> {
326
- await this.reconcileStem(stem);
327
- }
328
- }