@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
@@ -569,13 +569,21 @@ export class McpOAuthProvider implements OAuthClientProvider {
569
569
 
570
570
  // --- Static helpers ---
571
571
 
572
+ /**
573
+ * Check whether OAuth tokens exist in the credential store for a server.
574
+ */
575
+ export async function hasMcpOAuthTokens(serverId: string): Promise<boolean> {
576
+ const raw = await getSecureKeyAsync(tokensKey(serverId));
577
+ return raw != null && raw.length > 0;
578
+ }
579
+
572
580
  /**
573
581
  * Delete all OAuth credentials for a given MCP server.
574
582
  * Used by `mcp remove` for cleanup.
575
583
  */
576
584
  export async function deleteMcpOAuthCredentials(
577
585
  serverId: string,
578
- ): Promise<void> {
586
+ ): Promise<{ ok: boolean; failedKeys: string[] }> {
579
587
  const [tokensResult, clientResult, discoveryResult] = await Promise.all([
580
588
  deleteSecureKeyAsync(tokensKey(serverId)),
581
589
  deleteSecureKeyAsync(clientInfoKey(serverId)),
@@ -586,20 +594,23 @@ export async function deleteMcpOAuthCredentials(
586
594
  { key: "client_info", result: clientResult },
587
595
  { key: "discovery", result: discoveryResult },
588
596
  ];
589
- const errors = results.filter((r) => r.result === "error").map((r) => r.key);
590
- if (errors.length > 0) {
597
+ const failedKeys = results
598
+ .filter((r) => r.result === "error")
599
+ .map((r) => r.key);
600
+ if (failedKeys.length > 0) {
591
601
  log.warn(
592
- { serverId, failedKeys: errors },
602
+ { serverId, failedKeys },
593
603
  "Some OAuth credentials could not be deleted from secure storage",
594
604
  );
595
605
  }
596
- const hasErrors = errors.length > 0;
606
+ const ok = failedKeys.length === 0;
597
607
  log.info(
598
608
  { serverId },
599
- hasErrors
600
- ? "OAuth credential deletion completed with errors"
601
- : "OAuth credentials deleted",
609
+ ok
610
+ ? "OAuth credentials deleted"
611
+ : "OAuth credential deletion completed with errors",
602
612
  );
613
+ return { ok, failedKeys };
603
614
  }
604
615
 
605
616
  // --- HTML rendering ---
@@ -0,0 +1,110 @@
1
+ import { mkdtempSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { Database } from "bun:sqlite";
5
+ import { describe, expect, mock, test } from "bun:test";
6
+
7
+ import { drizzle } from "drizzle-orm/bun-sqlite";
8
+
9
+ // The migration opens the telemetry connection via getTelemetrySqlite(), which
10
+ // routes through the singleton. Mock the path helper so the connection opens a
11
+ // temp file we control, then mock the db-connection getter so it returns that
12
+ // same file's Database.
13
+ const tmpDir = mkdtempSync(join(tmpdir(), "wd-migration-"));
14
+ const telemetryPath = join(tmpDir, "assistant-telemetry.db");
15
+ let openedRaw: Database | null = null;
16
+
17
+ mock.module("../../util/telemetry-db-path.js", () => ({
18
+ getTelemetryDbPath: () => telemetryPath,
19
+ }));
20
+
21
+ mock.module("../db-connection.js", () => ({
22
+ // The migration calls getTelemetrySqlite(); return our temp Database so the
23
+ // DDL runs against it. getSqliteFrom is used in other contexts but not by
24
+ // this migration when the mock is active.
25
+ getTelemetrySqlite: () => openedRaw,
26
+ getSqliteFrom: (db: unknown) =>
27
+ (db as unknown as { $client: Database }).$client,
28
+ }));
29
+
30
+ import { createWatchdogEventsTable } from "../migrations/301-create-watchdog-events.js";
31
+ import * as schema from "../schema.js";
32
+
33
+ function createTestDb() {
34
+ const sqlite = new Database(":memory:");
35
+ return { sqlite, db: drizzle(sqlite, { schema }) };
36
+ }
37
+
38
+ function openTelemetryRaw(): Database {
39
+ openedRaw = new Database(telemetryPath);
40
+ return openedRaw;
41
+ }
42
+
43
+ function columnNames(raw: Database): string[] {
44
+ return (
45
+ raw.query("PRAGMA table_info(watchdog_events)").all() as Array<{
46
+ name: string;
47
+ }>
48
+ ).map((c) => c.name);
49
+ }
50
+
51
+ function indexNames(raw: Database): string[] {
52
+ return (
53
+ raw.query("PRAGMA index_list(watchdog_events)").all() as Array<{
54
+ name: string;
55
+ }>
56
+ ).map((i) => i.name);
57
+ }
58
+
59
+ describe("migration 300: watchdog_events table on telemetry DB", () => {
60
+ test("creates the table with the expected columns", () => {
61
+ const raw = openTelemetryRaw();
62
+ const { db } = createTestDb();
63
+
64
+ createWatchdogEventsTable(db);
65
+
66
+ expect(columnNames(raw)).toEqual([
67
+ "id",
68
+ "created_at",
69
+ "check_name",
70
+ "value",
71
+ "detail",
72
+ ]);
73
+ });
74
+
75
+ test("creates the (created_at, id) cursor index", () => {
76
+ const raw = openTelemetryRaw();
77
+ const { db } = createTestDb();
78
+
79
+ createWatchdogEventsTable(db);
80
+
81
+ expect(indexNames(raw)).toContain("idx_watchdog_events_created_at_id");
82
+ const columns = (
83
+ raw
84
+ .query("PRAGMA index_info(idx_watchdog_events_created_at_id)")
85
+ .all() as Array<{ name: string }>
86
+ ).map((c) => c.name);
87
+ expect(columns).toEqual(["created_at", "id"]);
88
+ });
89
+
90
+ test("is idempotent — re-run is a no-op and preserves existing rows", () => {
91
+ const raw = openTelemetryRaw();
92
+ const { db } = createTestDb();
93
+
94
+ createWatchdogEventsTable(db);
95
+ raw.exec(/*sql*/ `
96
+ INSERT INTO watchdog_events (id, created_at, check_name, value)
97
+ VALUES ('wd-1', 1000, 'event_loop_blocked', 60000)
98
+ `);
99
+
100
+ expect(() => createWatchdogEventsTable(db)).not.toThrow();
101
+
102
+ const rows = raw.query("SELECT id FROM watchdog_events").all();
103
+ expect(rows).toEqual([{ id: "wd-1" }]);
104
+ expect(
105
+ indexNames(raw).filter(
106
+ (name) => name === "idx_watchdog_events_created_at_id",
107
+ ),
108
+ ).toHaveLength(1);
109
+ });
110
+ });
@@ -161,6 +161,14 @@ mock.module("../conversation-crud.js", () => ({
161
161
  title: "Source conversation",
162
162
  };
163
163
  },
164
+ // Mirrors the real `isConversationProcessing` which reads the persisted
165
+ // `processing_started_at` column. In tests, the mock registry
166
+ // (`loadedConversations`) is the source of truth — a conversation id
167
+ // present with `processing: true` simulates a mid-turn conversation.
168
+ isConversationProcessing: (id: string) => {
169
+ const entry = loadedConversations[id];
170
+ return entry?.processing ?? false;
171
+ },
164
172
  forkConversationForRetrospective: async (params: {
165
173
  conversationId: string;
166
174
  throughMessageId?: string;
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Tests for the shared prompt-override loader
3
+ * (`assistant/src/memory/prompt-override.ts`) and the memory-v3
4
+ * `resolveSelectorPrompt` built on it. Mirrors the v2 router/consolidation
5
+ * prompt-path suites: a configured file replaces the bundled prompt, and any
6
+ * missing / empty / oversized / non-regular / unreadable file degrades to the
7
+ * bundled prompt with a diagnostic warning.
8
+ */
9
+ import { execFileSync } from "node:child_process";
10
+ import { mkdtempSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
11
+ import { homedir, tmpdir } from "node:os";
12
+ import { join } from "node:path";
13
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
14
+
15
+ import { resolveSelectorPrompt } from "../../plugins/defaults/memory-v3-shadow/pool-select.js";
16
+ import {
17
+ loadPromptOverride,
18
+ MAX_PROMPT_OVERRIDE_BYTES,
19
+ resolveOverridePath,
20
+ } from "../prompt-override.js";
21
+
22
+ const warnCalls: Array<{ data: Record<string, unknown>; msg: string }> = [];
23
+ const recordingLogger = {
24
+ warn: (data: object, msg: string) => {
25
+ warnCalls.push({ data: data as Record<string, unknown>, msg });
26
+ },
27
+ };
28
+
29
+ let tmpDir: string;
30
+
31
+ beforeEach(() => {
32
+ warnCalls.length = 0;
33
+ tmpDir = mkdtempSync(join(tmpdir(), "prompt-override-"));
34
+ });
35
+
36
+ afterEach(() => {
37
+ rmSync(tmpDir, { recursive: true, force: true });
38
+ });
39
+
40
+ /** Load against the per-test temp dir with the recording logger. */
41
+ const load = (
42
+ overridePath: string | null | undefined,
43
+ label = "test prompt",
44
+ ): string | null =>
45
+ loadPromptOverride({
46
+ overridePath,
47
+ workspaceDir: tmpDir,
48
+ log: recordingLogger,
49
+ label,
50
+ });
51
+
52
+ describe("resolveOverridePath", () => {
53
+ test("expands a leading ~/ to the home directory", () => {
54
+ expect(resolveOverridePath("~/sub/x.md", tmpDir)).toBe(
55
+ join(homedir(), "sub/x.md"),
56
+ );
57
+ });
58
+
59
+ test("uses an absolute path as-is", () => {
60
+ expect(resolveOverridePath("/abs/x.md", tmpDir)).toBe("/abs/x.md");
61
+ });
62
+
63
+ test("resolves a relative path under the workspace dir", () => {
64
+ expect(resolveOverridePath("rel/x.md", tmpDir)).toBe(
65
+ join(tmpDir, "rel/x.md"),
66
+ );
67
+ });
68
+ });
69
+
70
+ describe("loadPromptOverride — usable override", () => {
71
+ test("null overridePath returns null without touching the filesystem", () => {
72
+ expect(load(null)).toBeNull();
73
+ expect(warnCalls).toHaveLength(0);
74
+ });
75
+
76
+ test("undefined overridePath (unset config field) returns null without throwing", () => {
77
+ expect(load(undefined)).toBeNull();
78
+ expect(warnCalls).toHaveLength(0);
79
+ });
80
+
81
+ test("returns an absolute-path file's contents verbatim", () => {
82
+ const p = join(tmpDir, "abs.md");
83
+ writeFileSync(p, "absolute body\n");
84
+ expect(load(p)).toBe("absolute body\n");
85
+ expect(warnCalls).toHaveLength(0);
86
+ });
87
+
88
+ test("resolves a relative path under the workspace dir", () => {
89
+ writeFileSync(join(tmpDir, "rel.md"), "relative body\n");
90
+ expect(load("rel.md")).toBe("relative body\n");
91
+ expect(warnCalls).toHaveLength(0);
92
+ });
93
+
94
+ test("expands a leading ~/ to the home directory", () => {
95
+ const filename = `.vellum-prompt-override-test-${process.pid}.md`;
96
+ const p = join(homedir(), filename);
97
+ writeFileSync(p, "home body\n");
98
+ try {
99
+ expect(load(`~/${filename}`)).toBe("home body\n");
100
+ expect(warnCalls).toHaveLength(0);
101
+ } finally {
102
+ rmSync(p, { force: true });
103
+ }
104
+ });
105
+ });
106
+
107
+ describe("loadPromptOverride — fallback to null with a diagnostic warning", () => {
108
+ test("missing file logs ENOENT and returns null", () => {
109
+ expect(load(join(tmpDir, "missing.md"))).toBeNull();
110
+ expect(warnCalls).toHaveLength(1);
111
+ expect(warnCalls[0].data.code).toBe("ENOENT");
112
+ expect(warnCalls[0].data.fallback).toBe("bundled");
113
+ });
114
+
115
+ test("empty file is rejected", () => {
116
+ const p = join(tmpDir, "empty.md");
117
+ writeFileSync(p, "");
118
+ expect(load(p)).toBeNull();
119
+ expect(warnCalls[0].data.reason).toBe("empty_override");
120
+ });
121
+
122
+ test("whitespace-only file is rejected", () => {
123
+ const p = join(tmpDir, "ws.md");
124
+ writeFileSync(p, " \n\t\n");
125
+ expect(load(p)).toBeNull();
126
+ expect(warnCalls[0].data.reason).toBe("empty_override");
127
+ });
128
+
129
+ test("oversized file is rejected with its size", () => {
130
+ const p = join(tmpDir, "huge.md");
131
+ // 1 MiB + 1 byte — just over the cap so we don't waste test memory.
132
+ writeFileSync(p, Buffer.alloc(MAX_PROMPT_OVERRIDE_BYTES + 1, 0x61));
133
+ expect(load(p)).toBeNull();
134
+ expect(warnCalls[0].data.reason).toBe("oversized_override");
135
+ expect(warnCalls[0].data.size).toBe(MAX_PROMPT_OVERRIDE_BYTES + 1);
136
+ });
137
+
138
+ test("a directory is not a regular file", () => {
139
+ expect(load(tmpDir)).toBeNull();
140
+ expect(warnCalls[0].data.reason).toBe("not_regular_file");
141
+ });
142
+
143
+ test("a symlink is not a regular file (lstat does not follow it)", () => {
144
+ const target = join(tmpDir, "target.md");
145
+ writeFileSync(target, "real body\n");
146
+ const link = join(tmpDir, "link.md");
147
+ symlinkSync(target, link);
148
+ expect(load(link)).toBeNull();
149
+ expect(warnCalls[0].data.reason).toBe("not_regular_file");
150
+ });
151
+
152
+ test("a FIFO is not a regular file", () => {
153
+ const fifoPath = join(tmpDir, "fifo");
154
+ try {
155
+ execFileSync("mkfifo", [fifoPath]);
156
+ } catch {
157
+ // mkfifo unavailable on this platform — skip without failing.
158
+ return;
159
+ }
160
+ expect(load(fifoPath)).toBeNull();
161
+ expect(warnCalls[0].data.reason).toBe("not_regular_file");
162
+ });
163
+
164
+ test("the label names the prompt in the warning message", () => {
165
+ load(join(tmpDir, "missing.md"), "router prompt");
166
+ expect(warnCalls[0].msg).toContain("router prompt override");
167
+ });
168
+ });
169
+
170
+ describe("resolveSelectorPrompt", () => {
171
+ // A stable phrase from the bundled selector prompt (`SYSTEM_PROMPT`).
172
+ const BUNDLED_PHRASE =
173
+ "Select EVERY candidate whose content the upcoming reply would draw on";
174
+
175
+ test("null path returns the bundled selector prompt", () => {
176
+ expect(resolveSelectorPrompt(null, tmpDir)).toContain(BUNDLED_PHRASE);
177
+ });
178
+
179
+ test("a configured file replaces the bundled prompt verbatim — no placeholder substitution", () => {
180
+ const body = "Custom selector instructions {{NOT_A_PLACEHOLDER}}\n";
181
+ writeFileSync(join(tmpDir, "selector.md"), body);
182
+ const out = resolveSelectorPrompt("selector.md", tmpDir);
183
+ expect(out).toBe(body);
184
+ expect(out).not.toContain(BUNDLED_PHRASE);
185
+ });
186
+
187
+ test("a missing override falls back to the bundled prompt", () => {
188
+ expect(resolveSelectorPrompt(join(tmpDir, "nope.md"), tmpDir)).toContain(
189
+ BUNDLED_PHRASE,
190
+ );
191
+ });
192
+ });
@@ -0,0 +1,161 @@
1
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ // Silence the logger.
4
+ mock.module("../../util/logger.js", () => ({
5
+ getLogger: () =>
6
+ new Proxy({} as Record<string, unknown>, {
7
+ get: () => () => {},
8
+ }),
9
+ }));
10
+
11
+ let shareAnalytics = true;
12
+
13
+ mock.module("../../platform/consent-cache.js", () => ({
14
+ getCachedShareAnalytics: () => shareAnalytics,
15
+ }));
16
+
17
+ import { getTelemetryDb } from "../db-connection.js";
18
+ import { initializeDb } from "../db-init.js";
19
+ import { watchdogEvents } from "../schema.js";
20
+ import {
21
+ queryUnreportedWatchdogEvents,
22
+ recordWatchdogEvent,
23
+ } from "../watchdog-events-store.js";
24
+
25
+ await initializeDb();
26
+
27
+ function insertEvent(
28
+ id: string,
29
+ createdAt: number,
30
+ checkName = "event_loop_blocked",
31
+ ): void {
32
+ const db = getTelemetryDb();
33
+ if (!db) throw new Error("telemetry DB unavailable in test");
34
+ db.insert(watchdogEvents).values({ id, createdAt, checkName }).run();
35
+ }
36
+
37
+ function clearEvents(): void {
38
+ const db = getTelemetryDb();
39
+ if (!db) throw new Error("telemetry DB unavailable in test");
40
+ db.delete(watchdogEvents).run();
41
+ }
42
+
43
+ describe("watchdog-events-store", () => {
44
+ beforeEach(() => {
45
+ shareAnalytics = true;
46
+ clearEvents();
47
+ });
48
+
49
+ test("honors the share_analytics opt-out (records nothing)", () => {
50
+ shareAnalytics = false;
51
+ recordWatchdogEvent({ checkName: "event_loop_blocked", value: 60000 });
52
+ expect(queryUnreportedWatchdogEvents(0, undefined, 10)).toHaveLength(0);
53
+ });
54
+
55
+ test("record + query round-trips all fields", () => {
56
+ recordWatchdogEvent({
57
+ checkName: "event_loop_blocked",
58
+ value: 12345,
59
+ detail: { reason: "no_bytes_60s", threshold_ms: 5000 },
60
+ });
61
+
62
+ const rows = queryUnreportedWatchdogEvents(0, undefined, 10);
63
+ expect(rows).toHaveLength(1);
64
+ const row = rows[0]!;
65
+ expect(row.id).toBeString();
66
+ expect(row.createdAt).toBeGreaterThan(0);
67
+ expect(row.checkName).toBe("event_loop_blocked");
68
+ expect(row.value).toBe(12345);
69
+ expect(row.detail).toBe(
70
+ JSON.stringify({ reason: "no_bytes_60s", threshold_ms: 5000 }),
71
+ );
72
+ });
73
+
74
+ test("optional fields persist as null", () => {
75
+ recordWatchdogEvent({ checkName: "stream_idle" });
76
+
77
+ const rows = queryUnreportedWatchdogEvents(0, undefined, 10);
78
+ expect(rows).toHaveLength(1);
79
+ expect(rows[0]).toMatchObject({
80
+ checkName: "stream_idle",
81
+ value: null,
82
+ detail: null,
83
+ });
84
+ });
85
+
86
+ test("explicit null value and detail persist as null", () => {
87
+ recordWatchdogEvent({
88
+ checkName: "restart",
89
+ value: null,
90
+ detail: null,
91
+ });
92
+
93
+ const rows = queryUnreportedWatchdogEvents(0, undefined, 10);
94
+ expect(rows).toHaveLength(1);
95
+ expect(rows[0]?.value).toBeNull();
96
+ expect(rows[0]?.detail).toBeNull();
97
+ });
98
+
99
+ test("returns rows in (createdAt, id) order", () => {
100
+ insertEvent("wd-b", 2000);
101
+ insertEvent("wd-a", 1000);
102
+
103
+ const rows = queryUnreportedWatchdogEvents(0, undefined, 100);
104
+ expect(rows.map((r) => r.id)).toEqual(["wd-a", "wd-b"]);
105
+ });
106
+
107
+ test("query advances past the compound (createdAt, id) cursor", () => {
108
+ // Two rows in the same millisecond: pagination must use the id
109
+ // tiebreaker to make forward progress, not loop.
110
+ insertEvent("wd-1", 5000);
111
+ insertEvent("wd-2", 5000);
112
+ insertEvent("wd-3", 6000);
113
+
114
+ const first = queryUnreportedWatchdogEvents(0, undefined, 1);
115
+ expect(first.map((r) => r.id)).toEqual(["wd-1"]);
116
+
117
+ const second = queryUnreportedWatchdogEvents(
118
+ first[0]!.createdAt,
119
+ first[0]!.id,
120
+ 100,
121
+ );
122
+ expect(second.map((r) => r.id)).toEqual(["wd-2", "wd-3"]);
123
+
124
+ // Without an id cursor the timestamp-only branch is used.
125
+ expect(
126
+ queryUnreportedWatchdogEvents(5000, undefined, 100).map((r) => r.id),
127
+ ).toEqual(["wd-3"]);
128
+
129
+ // Cursor past the last row returns nothing.
130
+ const last = second[second.length - 1]!;
131
+ expect(
132
+ queryUnreportedWatchdogEvents(last.createdAt, last.id, 100).length,
133
+ ).toBe(0);
134
+ });
135
+
136
+ test("resumes from a persisted watermark without re-reporting", () => {
137
+ insertEvent("wd-w1", 1000);
138
+ insertEvent("wd-w2", 2000);
139
+
140
+ const batch = queryUnreportedWatchdogEvents(0, undefined, 100);
141
+ const watermark = batch[batch.length - 1]!;
142
+
143
+ insertEvent("wd-w3", 3000);
144
+
145
+ const resumed = queryUnreportedWatchdogEvents(
146
+ watermark.createdAt,
147
+ watermark.id,
148
+ 100,
149
+ );
150
+ expect(resumed.map((r) => r.id)).toEqual(["wd-w3"]);
151
+ });
152
+
153
+ test("honors the limit", () => {
154
+ insertEvent("wd-l1", 1000);
155
+ insertEvent("wd-l2", 2000);
156
+ insertEvent("wd-l3", 3000);
157
+
158
+ const rows = queryUnreportedWatchdogEvents(0, undefined, 2);
159
+ expect(rows.map((r) => r.id)).toEqual(["wd-l1", "wd-l2"]);
160
+ });
161
+ });
@@ -0,0 +1,107 @@
1
+ import { and, desc, eq, lte } from "drizzle-orm";
2
+ import { v4 as uuid } from "uuid";
3
+
4
+ import { type DrizzleDb, getDb } from "./db-connection.js";
5
+ import { conversationCompactionEvents } from "./schema.js";
6
+
7
+ export interface CompactionEvent {
8
+ /** Wall-clock time the compaction ran (= `conversations.context_compacted_at`). */
9
+ compactedAt: number;
10
+ summary: string;
11
+ /** Count of leading persisted messages behind this compaction's summary. */
12
+ compactedMessageCount: number;
13
+ }
14
+
15
+ /**
16
+ * Append a compaction event to the ledger. Called alongside the
17
+ * `conversations` cache update on every compaction.
18
+ */
19
+ export function appendCompactionEvent(
20
+ conversationId: string,
21
+ event: CompactionEvent,
22
+ ): void {
23
+ const db = getDb();
24
+ db.insert(conversationCompactionEvents)
25
+ .values({
26
+ id: uuid(),
27
+ conversationId,
28
+ compactedAt: event.compactedAt,
29
+ summary: event.summary,
30
+ compactedMessageCount: event.compactedMessageCount,
31
+ createdAt: Date.now(),
32
+ })
33
+ .run();
34
+ }
35
+
36
+ /**
37
+ * The most recent compaction event whose `compactedAt` is at-or-before
38
+ * `atOrBefore`, or null if none (or `atOrBefore` is null). This is the fork
39
+ * inheritance rule: a compaction applies to a fork only if it happened before
40
+ * the message being forked from.
41
+ */
42
+ export function getLatestCompactionEventAtOrBefore(
43
+ conversationId: string,
44
+ atOrBefore: number | null,
45
+ ): CompactionEvent | null {
46
+ if (atOrBefore == null) return null;
47
+ const db = getDb();
48
+ const row = db
49
+ .select({
50
+ compactedAt: conversationCompactionEvents.compactedAt,
51
+ summary: conversationCompactionEvents.summary,
52
+ compactedMessageCount: conversationCompactionEvents.compactedMessageCount,
53
+ })
54
+ .from(conversationCompactionEvents)
55
+ .where(
56
+ and(
57
+ eq(conversationCompactionEvents.conversationId, conversationId),
58
+ lte(conversationCompactionEvents.compactedAt, atOrBefore),
59
+ ),
60
+ )
61
+ .orderBy(desc(conversationCompactionEvents.compactedAt))
62
+ .limit(1)
63
+ .get();
64
+ return row ?? null;
65
+ }
66
+
67
+ /**
68
+ * Copy the source conversation's ledger events with `compactedAt <=
69
+ * boundaryCreatedAt` into the fork, so the fork owns a correct ledger for its
70
+ * own future forks/compactions. Takes the active `db` so the copy joins the
71
+ * caller's transaction. No-op when the boundary is null.
72
+ */
73
+ export function forkCompactionLedger(
74
+ db: DrizzleDb,
75
+ sourceConversationId: string,
76
+ forkConversationId: string,
77
+ boundaryCreatedAt: number | null,
78
+ ): void {
79
+ if (boundaryCreatedAt == null) return;
80
+ const events = db
81
+ .select({
82
+ compactedAt: conversationCompactionEvents.compactedAt,
83
+ summary: conversationCompactionEvents.summary,
84
+ compactedMessageCount: conversationCompactionEvents.compactedMessageCount,
85
+ })
86
+ .from(conversationCompactionEvents)
87
+ .where(
88
+ and(
89
+ eq(conversationCompactionEvents.conversationId, sourceConversationId),
90
+ lte(conversationCompactionEvents.compactedAt, boundaryCreatedAt),
91
+ ),
92
+ )
93
+ .all();
94
+ const now = Date.now();
95
+ for (const event of events) {
96
+ db.insert(conversationCompactionEvents)
97
+ .values({
98
+ id: uuid(),
99
+ conversationId: forkConversationId,
100
+ compactedAt: event.compactedAt,
101
+ summary: event.summary,
102
+ compactedMessageCount: event.compactedMessageCount,
103
+ createdAt: now,
104
+ })
105
+ .run();
106
+ }
107
+ }