@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
@@ -8,16 +8,8 @@ import {
8
8
  writeFileSync,
9
9
  } from "node:fs";
10
10
  import { basename, join } from "node:path";
11
+ import { Database } from "bun:sqlite";
11
12
 
12
- import { eq } from "drizzle-orm";
13
-
14
- import {
15
- findGuardianForChannel,
16
- generateUserFileSlug,
17
- listGuardianChannels,
18
- } from "../../contacts/contact-store.js";
19
- import { getDb } from "../../memory/db-connection.js";
20
- import { contacts } from "../../memory/schema/contacts.js";
21
13
  import { getLogger } from "../../util/logger.js";
22
14
  import type { WorkspaceMigration } from "./types.js";
23
15
 
@@ -25,11 +17,11 @@ const log = getLogger("workspace-migration-031-drop-user-md");
25
17
 
26
18
  // ── Inlined helpers ───────────────────────────────────────────────
27
19
  //
28
- // Per AGENTS.md, migrations should minimize cross-module imports so
29
- // they remain stable as code around them evolves. The helpers below
30
- // are duplicated inline (rather than imported from
31
- // `util/strip-comment-lines.js` and `prompts/system-prompt.js`) so
32
- // this migration does not regress if those modules change later.
20
+ // AGENTS.md requires each migration to be self-contained: it may import
21
+ // only `./types.js`, `./utils.js`, the logger, and runtime built-ins.
22
+ // The helpers below (including the guardian DB read and the userFile
23
+ // slug) are inlined so this migration does not regress if the modules
24
+ // they originate from change later.
33
25
 
34
26
  /**
35
27
  * Strip lines starting with `_` (comment convention for prompt .md files)
@@ -123,13 +115,59 @@ function isValidSlug(slug: string): boolean {
123
115
  return basename(slug) === slug && slug !== "." && slug !== "..";
124
116
  }
125
117
 
118
+ /**
119
+ * Strip LIKE metacharacters so the prefix match runs literally. SQLite has
120
+ * no default LIKE escape character, so strip rather than escape. Inlined
121
+ * from `contacts/contact-store.ts`.
122
+ */
123
+ function escapeLike(value: string): string {
124
+ return value.replace(/%/g, "").replace(/_/g, "");
125
+ }
126
+
127
+ /** Pure slug transform applied to a display name. */
128
+ function computeUserFileBaseSlug(displayName: string): string {
129
+ return (
130
+ displayName
131
+ .toLowerCase()
132
+ .replace(/[^a-z0-9]+/g, "-")
133
+ .replace(/^-+|-+$/g, "")
134
+ .slice(0, 100) || "user"
135
+ );
136
+ }
137
+
138
+ /**
139
+ * Generate a collision-free `users/<slug>.md` filename for a display name,
140
+ * inlined verbatim from `contacts/contact-store.ts` to keep this migration
141
+ * self-contained. Produces "alice.md", "alice-2.md", etc.
142
+ */
143
+ function generateUserFileSlug(db: Database, displayName: string): string {
144
+ const slug = computeUserFileBaseSlug(displayName);
145
+
146
+ const rows = db
147
+ .query<{ user_file: string | null }, [string]>(
148
+ `SELECT user_file FROM contacts WHERE user_file LIKE ?`,
149
+ )
150
+ .all(`${escapeLike(slug)}%`);
151
+
152
+ const taken = new Set(rows.map((r) => r.user_file?.toLowerCase()));
153
+
154
+ const base = `${slug}.md`;
155
+ if (!taken.has(base)) return base;
156
+
157
+ for (let i = 2; ; i++) {
158
+ const candidate = `${slug}-${i}.md`;
159
+ if (!taken.has(candidate)) return candidate;
160
+ }
161
+ }
162
+
126
163
  /**
127
164
  * Delete the legacy `USER.md` at the workspace root after migrating
128
165
  * any customized content into `users/<slug>.md`.
129
166
  *
130
- * Handles four relevant states:
131
- * 1. Fresh install, no guardian → no-op (nothing to migrate yet;
132
- * `USER.md` is no longer seeded post PR 11).
167
+ * Handles these relevant states:
168
+ * 1. No local guardian → preserve a customized `USER.md` (the local
169
+ * mirror can be stale, so a customized profile may still belong to a
170
+ * real guardian); delete only the unmodified template/empty file.
133
171
  * 2. Pre-017 customized `USER.md`, guardian has no `userFile` →
134
172
  * backfill the slug, copy `USER.md` → `users/<slug>.md`, delete `USER.md`.
135
173
  * 3. Post-017 state where `users/<slug>.md` already has content →
@@ -137,6 +175,12 @@ function isValidSlug(slug: string): boolean {
137
175
  * 4. Missing `users/<slug>.md` after guardian is resolved → seed a bare
138
176
  * `GUARDIAN_PERSONA_TEMPLATE` scaffold so downstream readers have a file.
139
177
  */
178
+ interface GuardianRow {
179
+ id: string;
180
+ display_name: string;
181
+ user_file: string | null;
182
+ }
183
+
140
184
  export const dropUserMdMigration: WorkspaceMigration = {
141
185
  id: "031-drop-user-md",
142
186
  description:
@@ -145,163 +189,203 @@ export const dropUserMdMigration: WorkspaceMigration = {
145
189
  run(workspaceDir: string): void {
146
190
  const userMdPath = join(workspaceDir, "USER.md");
147
191
 
148
- // Resolve the guardian contact. Prefer the vellum-channel binding
149
- // (the canonical local/native guardian); fall back to whichever
150
- // guardian has the most recently verified active channel.
151
- let guardian: { id: string; displayName: string; userFile: string | null };
192
+ const dbPath = join(workspaceDir, "data", "db", "assistant.db");
193
+ if (!existsSync(dbPath)) return; // DB not created yet — defer cleanup.
194
+
195
+ let db: Database;
152
196
  try {
153
- const vellumGuardian = findGuardianForChannel("vellum");
154
- if (vellumGuardian) {
155
- guardian = {
156
- id: vellumGuardian.contact.id,
157
- displayName: vellumGuardian.contact.displayName,
158
- userFile: vellumGuardian.contact.userFile ?? null,
159
- };
160
- } else {
161
- const anyGuardian = listGuardianChannels();
162
- if (!anyGuardian) {
163
- // Fresh install or pre-onboarding. If a stale USER.md somehow
164
- // remains on disk (e.g. leftover from an older build), best-
165
- // effort remove it so future first runs are clean.
166
- if (existsSync(userMdPath)) {
167
- try {
168
- unlinkSync(userMdPath);
169
- log.info(
170
- { path: userMdPath },
171
- "Deleted stale pre-onboarding USER.md with no guardian",
172
- );
173
- } catch (err) {
174
- log.warn(
175
- { err, path: userMdPath },
176
- "Failed to delete pre-onboarding USER.md; leaving in place",
177
- );
178
- }
179
- }
180
- return;
181
- }
182
- guardian = {
183
- id: anyGuardian.contact.id,
184
- displayName: anyGuardian.contact.displayName,
185
- userFile: anyGuardian.contact.userFile ?? null,
186
- };
187
- }
197
+ db = new Database(dbPath);
188
198
  } catch (err) {
189
- // DB not ready or query failed leave USER.md alone. The next
190
- // startup after DB init will try again.
191
- log.warn(
192
- { err },
193
- "Failed to resolve guardian contact; deferring USER.md cleanup",
194
- );
199
+ log.warn({ err }, "Cannot open assistant DB; deferring USER.md cleanup");
195
200
  return;
196
201
  }
197
202
 
198
- // Backfill a userFile slug on the guardian contact if one isn't set.
199
- if (!guardian.userFile) {
203
+ try {
204
+ // Resolve the guardian contact from the local DB. Prefer the
205
+ // vellum-channel binding (the canonical native guardian); fall back to
206
+ // whichever guardian has the most recently verified active channel.
207
+ let guardianRow: GuardianRow | null;
200
208
  try {
201
- const slug = generateUserFileSlug(guardian.displayName);
202
- const db = getDb();
203
- db.update(contacts)
204
- .set({ userFile: slug })
205
- .where(eq(contacts.id, guardian.id))
206
- .run();
207
- guardian.userFile = slug;
208
- log.info(
209
- { contactId: guardian.id, slug },
210
- "Backfilled missing guardian.userFile",
211
- );
209
+ guardianRow =
210
+ db
211
+ .query<GuardianRow, []>(
212
+ `SELECT c.id AS id, c.display_name AS display_name, c.user_file AS user_file
213
+ FROM contacts c JOIN contact_channels cc ON cc.contact_id = c.id
214
+ WHERE c.role = 'guardian' AND cc.status = 'active'
215
+ ORDER BY (cc.type = 'vellum') DESC, cc.verified_at DESC
216
+ LIMIT 1`,
217
+ )
218
+ .get() ?? null;
212
219
  } catch (err) {
220
+ // DB not ready or query failed — leave USER.md alone. The next
221
+ // startup after DB init tries again.
213
222
  log.warn(
214
- { err, contactId: guardian.id },
215
- "Failed to backfill guardian.userFile; deferring USER.md cleanup",
223
+ { err },
224
+ "Failed to resolve guardian contact; deferring USER.md cleanup",
216
225
  );
217
226
  return;
218
227
  }
219
- }
220
228
 
221
- const userFile = guardian.userFile;
222
- if (!userFile || !isValidSlug(userFile)) {
223
- log.warn(
224
- { userFile },
225
- "Guardian userFile is missing or not a safe basename; deferring USER.md cleanup",
226
- );
227
- return;
228
- }
229
+ if (!guardianRow) {
230
+ // No local guardian. The mirror can be stale (the gateway mirrors
231
+ // best-effort), so a customized USER.md may still belong to a real
232
+ // guardian — preserve it. Only delete the unmodified template/empty
233
+ // stale file.
234
+ if (existsSync(userMdPath)) {
235
+ let isStaleTemplate = false;
236
+ try {
237
+ isStaleTemplate =
238
+ !statSync(userMdPath).isFile() ||
239
+ isLegacyTemplateContent(readFileSync(userMdPath, "utf-8"));
240
+ } catch (err) {
241
+ log.warn(
242
+ { err, path: userMdPath },
243
+ "Cannot read USER.md with no guardian; leaving in place",
244
+ );
245
+ return;
246
+ }
247
+
248
+ if (!isStaleTemplate) {
249
+ log.info(
250
+ { path: userMdPath },
251
+ "Preserving customized USER.md with no local guardian",
252
+ );
253
+ return;
254
+ }
229
255
 
230
- const usersDir = join(workspaceDir, "users");
231
- mkdirSync(usersDir, { recursive: true });
256
+ try {
257
+ unlinkSync(userMdPath);
258
+ log.info(
259
+ { path: userMdPath },
260
+ "Deleting stale template USER.md with no guardian",
261
+ );
262
+ } catch (err) {
263
+ log.warn(
264
+ { err, path: userMdPath },
265
+ "Failed to delete stale USER.md; leaving in place",
266
+ );
267
+ }
268
+ }
269
+ return;
270
+ }
232
271
 
233
- const destPath = join(usersDir, userFile);
272
+ const guardian = {
273
+ id: guardianRow.id,
274
+ displayName: guardianRow.display_name,
275
+ userFile: guardianRow.user_file ?? null,
276
+ };
234
277
 
235
- // Read USER.md if it exists and classify its content.
236
- let userMdRaw: string | null = null;
237
- let userMdIsCustomized = false;
238
- if (existsSync(userMdPath)) {
239
- try {
240
- // Guard against USER.md being a directory (hostile state).
241
- if (statSync(userMdPath).isFile()) {
242
- userMdRaw = readFileSync(userMdPath, "utf-8");
243
- userMdIsCustomized = !isLegacyTemplateContent(userMdRaw);
278
+ // Backfill a userFile slug on the guardian contact if one isn't set.
279
+ if (!guardian.userFile) {
280
+ try {
281
+ const slug = generateUserFileSlug(db, guardian.displayName);
282
+ db.run("UPDATE contacts SET user_file = ? WHERE id = ?", [
283
+ slug,
284
+ guardian.id,
285
+ ]);
286
+ guardian.userFile = slug;
287
+ log.info(
288
+ { contactId: guardian.id, slug },
289
+ "Backfilled missing guardian.userFile",
290
+ );
291
+ } catch (err) {
292
+ log.warn(
293
+ { err, contactId: guardian.id },
294
+ "Failed to backfill guardian.userFile; deferring USER.md cleanup",
295
+ );
296
+ return;
244
297
  }
245
- } catch (err) {
246
- log.warn(
247
- { err, path: userMdPath },
248
- "Failed to read USER.md; treating as unreadable",
249
- );
250
298
  }
251
- }
252
299
 
253
- // Copy customized USER.md content into users/<slug>.md when the
254
- // destination is missing or effectively empty. Post-017 installs
255
- // that already populated users/<slug>.md are left untouched.
256
- if (
257
- userMdIsCustomized &&
258
- userMdRaw !== null &&
259
- destFileIsMissingOrEmpty(destPath)
260
- ) {
261
- try {
262
- copyFileSync(userMdPath, destPath);
263
- log.info(
264
- { src: userMdPath, dest: destPath },
265
- "Copied customized USER.md content into users/<slug>.md",
266
- );
267
- } catch (err) {
300
+ const userFile = guardian.userFile;
301
+ if (!userFile || !isValidSlug(userFile)) {
268
302
  log.warn(
269
- { err, src: userMdPath, dest: destPath },
270
- "Failed to copy USER.md; deferring USER.md cleanup",
303
+ { userFile },
304
+ "Guardian userFile is missing or not a safe basename; deferring USER.md cleanup",
271
305
  );
272
306
  return;
273
307
  }
274
- }
275
308
 
276
- // Seed the guardian persona scaffold when the destination file
277
- // still doesn't exist (e.g. no USER.md and no post-017 content).
278
- // This keeps parity with `ensureGuardianPersonaFile` for new
279
- // installs so downstream readers always find a file.
280
- if (!existsSync(destPath)) {
281
- try {
282
- writeFileSync(destPath, GUARDIAN_PERSONA_TEMPLATE, "utf-8");
283
- log.info(
284
- { dest: destPath },
285
- "Seeded guardian persona scaffold at users/<slug>.md",
286
- );
287
- } catch (err) {
288
- log.warn(
289
- { err, dest: destPath },
290
- "Failed to seed guardian persona scaffold; continuing with USER.md deletion",
291
- );
309
+ const usersDir = join(workspaceDir, "users");
310
+ mkdirSync(usersDir, { recursive: true });
311
+
312
+ const destPath = join(usersDir, userFile);
313
+
314
+ // Read USER.md if it exists and classify its content.
315
+ let userMdRaw: string | null = null;
316
+ let userMdIsCustomized = false;
317
+ if (existsSync(userMdPath)) {
318
+ try {
319
+ // Guard against USER.md being a directory (hostile state).
320
+ if (statSync(userMdPath).isFile()) {
321
+ userMdRaw = readFileSync(userMdPath, "utf-8");
322
+ userMdIsCustomized = !isLegacyTemplateContent(userMdRaw);
323
+ }
324
+ } catch (err) {
325
+ log.warn(
326
+ { err, path: userMdPath },
327
+ "Failed to read USER.md; treating as unreadable",
328
+ );
329
+ }
292
330
  }
293
- }
294
331
 
295
- // Finally, delete the legacy USER.md if it still exists. Template
296
- // or customized, it has no remaining consumer now that PR 11
297
- // dropped the seed/fallback read path.
298
- if (existsSync(userMdPath)) {
299
- try {
300
- unlinkSync(userMdPath);
301
- log.info({ path: userMdPath }, "Deleted legacy workspace USER.md");
302
- } catch (err) {
303
- log.warn({ err, path: userMdPath }, "Failed to delete legacy USER.md");
332
+ // Copy customized USER.md content into users/<slug>.md when the
333
+ // destination is missing or effectively empty. Post-017 installs
334
+ // that already populated users/<slug>.md are left untouched.
335
+ if (
336
+ userMdIsCustomized &&
337
+ userMdRaw !== null &&
338
+ destFileIsMissingOrEmpty(destPath)
339
+ ) {
340
+ try {
341
+ copyFileSync(userMdPath, destPath);
342
+ log.info(
343
+ { src: userMdPath, dest: destPath },
344
+ "Copied customized USER.md content into users/<slug>.md",
345
+ );
346
+ } catch (err) {
347
+ log.warn(
348
+ { err, src: userMdPath, dest: destPath },
349
+ "Failed to copy USER.md; deferring USER.md cleanup",
350
+ );
351
+ return;
352
+ }
353
+ }
354
+
355
+ // Seed the guardian persona scaffold when the destination file
356
+ // still doesn't exist (e.g. no USER.md and no post-017 content).
357
+ // This keeps parity with `ensureGuardianPersonaFile` for new
358
+ // installs so downstream readers always find a file.
359
+ if (!existsSync(destPath)) {
360
+ try {
361
+ writeFileSync(destPath, GUARDIAN_PERSONA_TEMPLATE, "utf-8");
362
+ log.info(
363
+ { dest: destPath },
364
+ "Seeded guardian persona scaffold at users/<slug>.md",
365
+ );
366
+ } catch (err) {
367
+ log.warn(
368
+ { err, dest: destPath },
369
+ "Failed to seed guardian persona scaffold; continuing with USER.md deletion",
370
+ );
371
+ }
372
+ }
373
+
374
+ // Finally, delete the legacy USER.md if it still exists — template or
375
+ // customized, it has no remaining consumer.
376
+ if (existsSync(userMdPath)) {
377
+ try {
378
+ unlinkSync(userMdPath);
379
+ log.info({ path: userMdPath }, "Deleted legacy workspace USER.md");
380
+ } catch (err) {
381
+ log.warn(
382
+ { err, path: userMdPath },
383
+ "Failed to delete legacy USER.md",
384
+ );
385
+ }
304
386
  }
387
+ } finally {
388
+ db.close();
305
389
  }
306
390
  },
307
391
 
@@ -1,81 +1,29 @@
1
1
  /**
2
- * Workspace migration 046: Remove legacy `workspace/hooks/` directory.
2
+ * Workspace migration 048: retained no-op.
3
3
  *
4
- * Migration 022 moved `~/.vellum/hooks/` into `$VELLUM_WORKSPACE_DIR/hooks/`.
5
- * With the hook system entirely removed, that directory is dead state — it is
6
- * no longer read or written by the assistant. This migration deletes the
7
- * directory (and everything under it) so stale hook manifests, config, and
8
- * executables do not linger in user workspaces.
4
+ * This migration previously deleted `<workspace>/hooks/`, treating it as dead
5
+ * state. `<workspace>/hooks/` is now a supported surface: standalone hook files
6
+ * placed there are loaded by the hook loader (see `src/hooks/hook-loader.ts`).
7
+ * Deleting it would destroy user-authored hooks including on workspaces that
8
+ * mount a preseeded `<workspace>/hooks/` before this migration first runs — so
9
+ * the deletion has been removed.
9
10
  *
10
- * Idempotent: safe to re-run after interruption. A no-op when the directory
11
- * is already absent.
11
+ * The migration is kept (rather than removed from the registry) so the applied
12
+ * checkpoint sequence stays stable across already-migrated workspaces. It is a
13
+ * no-op on every workspace, new or old.
12
14
  */
13
15
 
14
- import { existsSync, readdirSync, rmSync, statSync } from "node:fs";
15
- import { join } from "node:path";
16
-
17
- import { getLogger } from "../../util/logger.js";
18
16
  import type { WorkspaceMigration } from "./types.js";
19
17
 
20
- const log = getLogger("workspace-migration-048-remove-workspace-hooks");
21
-
22
- /**
23
- * Count files under `dir` recursively. Best-effort — returns the count we
24
- * could successfully stat, and silently skips entries that fail (e.g. a
25
- * symlink whose target is missing, a file removed concurrently). This is
26
- * only used for log output, so a slightly stale count is acceptable.
27
- */
28
- function countFilesRecursive(dir: string): number {
29
- let count = 0;
30
- let entries: string[];
31
- try {
32
- entries = readdirSync(dir);
33
- } catch {
34
- return 0;
35
- }
36
- for (const entry of entries) {
37
- const entryPath = join(dir, entry);
38
- try {
39
- const s = statSync(entryPath);
40
- if (s.isDirectory()) {
41
- count += countFilesRecursive(entryPath);
42
- } else {
43
- count += 1;
44
- }
45
- } catch {
46
- // best-effort
47
- }
48
- }
49
- return count;
50
- }
51
-
52
18
  export const removeWorkspaceHooksMigration: WorkspaceMigration = {
53
19
  id: "048-remove-workspace-hooks",
54
- description:
55
- "Remove legacy workspace/hooks/ directory now that the hook system is gone",
56
-
57
- run(workspaceDir: string): void {
58
- const hooksDir = join(workspaceDir, "hooks");
59
- if (!existsSync(hooksDir)) return;
20
+ description: "Retained no-op (workspace/hooks is now a supported surface)",
60
21
 
61
- const fileCount = countFilesRecursive(hooksDir);
62
- try {
63
- rmSync(hooksDir, { recursive: true, force: true });
64
- log.info(
65
- { path: hooksDir, fileCount },
66
- "Removed legacy workspace hooks directory",
67
- );
68
- } catch (err) {
69
- log.warn(
70
- { err, path: hooksDir },
71
- "Failed to remove legacy workspace hooks directory; leaving in place",
72
- );
73
- }
22
+ run(_workspaceDir: string): void {
23
+ // No-op: `<workspace>/hooks/` is a supported surface and must be preserved.
74
24
  },
75
25
 
76
26
  down(_workspaceDir: string): void {
77
- // Forward-only: the hook system is gone and the directory contained no
78
- // data the assistant still consumes. Restoring an empty directory would
79
- // just reintroduce dead state.
27
+ // No-op: forward-only; nothing to reverse.
80
28
  },
81
29
  };
@@ -0,0 +1,134 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ import type { WorkspaceMigration } from "./types.js";
5
+
6
+ // Exact system-owned default materializations. These are not user overrides, so
7
+ // matching entries are pruned and resolved through shipped call-site defaults.
8
+ const SEEDED_CALL_SITES: Record<string, Record<string, unknown>> = {
9
+ guardianQuestionCopy: latencySeed(),
10
+ interactionClassifier: latencySeed(),
11
+ skillCategoryInference: latencySeed(),
12
+ inviteInstructionGenerator: latencySeed(),
13
+ notificationDecision: latencySeed(),
14
+ preferenceExtraction: latencySeed(),
15
+ commitMessage: {
16
+ model: "claude-haiku-4-5-20251001",
17
+ maxTokens: 120,
18
+ temperature: 0.2,
19
+ effort: "low",
20
+ thinking: { enabled: false },
21
+ },
22
+ conversationStarters: latencySeed(),
23
+ conversationSummarization: {
24
+ model: "claude-opus-4-7",
25
+ effort: "low",
26
+ thinking: { enabled: false },
27
+ },
28
+ recall: {
29
+ profile: "cost-optimized",
30
+ maxTokens: 4096,
31
+ effort: "low",
32
+ thinking: { enabled: false, streamThinking: false },
33
+ temperature: 0,
34
+ disableCache: true,
35
+ },
36
+ heartbeatAgent: {
37
+ profile: "cost-optimized",
38
+ maxTokens: 2048,
39
+ effort: "low",
40
+ temperature: 0,
41
+ thinking: { enabled: false, streamThinking: false },
42
+ contextWindow: { maxInputTokens: 16000 },
43
+ },
44
+ replySuggestion: {
45
+ model: "claude-haiku-4-5-20251001",
46
+ effort: "low",
47
+ thinking: { enabled: false },
48
+ disableCache: true,
49
+ },
50
+ memoryRouter: {
51
+ profile: "cost-optimized",
52
+ contextWindow: { maxInputTokens: 1_000_000 },
53
+ },
54
+ };
55
+
56
+ export const pruneSeededCallsiteDefaultsMigration: WorkspaceMigration = {
57
+ id: "111-prune-seeded-callsite-defaults",
58
+ description:
59
+ "Remove seeded LLM call-site default materializations so fresh workspaces show no overrides",
60
+ run(workspaceDir: string): void {
61
+ const configPath = join(workspaceDir, "config.json");
62
+ if (!existsSync(configPath)) return;
63
+
64
+ let config: Record<string, unknown>;
65
+ try {
66
+ const raw = JSON.parse(readFileSync(configPath, "utf-8"));
67
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
68
+ config = raw as Record<string, unknown>;
69
+ } catch {
70
+ return;
71
+ }
72
+
73
+ const llm = readObject(config.llm);
74
+ if (llm === null) return;
75
+
76
+ const callSites = readObject(llm.callSites);
77
+ if (callSites === null) return;
78
+
79
+ let changed = false;
80
+ for (const [callSite, seed] of Object.entries(SEEDED_CALL_SITES)) {
81
+ if (deepEqual(callSites[callSite], seed)) {
82
+ delete callSites[callSite];
83
+ changed = true;
84
+ }
85
+ }
86
+ if (!changed) return;
87
+
88
+ if (Object.keys(callSites).length === 0) {
89
+ delete llm.callSites;
90
+ } else {
91
+ llm.callSites = callSites;
92
+ }
93
+ config.llm = llm;
94
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
95
+ },
96
+ down(_workspaceDir: string): void {
97
+ // Forward-only: restoring these entries would make defaults appear as
98
+ // user-visible overrides again.
99
+ },
100
+ };
101
+
102
+ function latencySeed(): Record<string, unknown> {
103
+ return {
104
+ model: "claude-haiku-4-5-20251001",
105
+ effort: "low",
106
+ thinking: { enabled: false },
107
+ };
108
+ }
109
+
110
+ function readObject(value: unknown): Record<string, unknown> | null {
111
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
112
+ return null;
113
+ }
114
+ return value as Record<string, unknown>;
115
+ }
116
+
117
+ function deepEqual(a: unknown, b: unknown): boolean {
118
+ if (a === b) return true;
119
+ if (a === null || b === null) return false;
120
+ if (typeof a !== "object" || typeof b !== "object") return false;
121
+ if (Array.isArray(a) || Array.isArray(b)) return false;
122
+
123
+ const aRecord = a as Record<string, unknown>;
124
+ const bRecord = b as Record<string, unknown>;
125
+ const aKeys = Object.keys(aRecord);
126
+ const bKeys = Object.keys(bRecord);
127
+ if (aKeys.length !== bKeys.length) return false;
128
+
129
+ for (const key of aKeys) {
130
+ if (!Object.prototype.hasOwnProperty.call(bRecord, key)) return false;
131
+ if (!deepEqual(aRecord[key], bRecord[key])) return false;
132
+ }
133
+ return true;
134
+ }