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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (546) hide show
  1. package/bun.lock +20 -0
  2. package/docs/workspace-tools.md +42 -33
  3. package/eslint-rules/cli-no-daemon-internals.js +6 -0
  4. package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +91 -0
  5. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +31 -0
  6. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +44 -0
  7. package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +48 -0
  8. package/node_modules/@vellumai/gateway-client/src/index.ts +14 -0
  9. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +17 -0
  10. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  11. package/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +28 -0
  12. package/node_modules/@vellumai/service-contracts/src/channels.ts +41 -0
  13. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  14. package/openapi.yaml +196 -56
  15. package/package.json +4 -1
  16. package/scripts/test.sh +36 -15
  17. package/src/__tests__/actor-token-service.test.ts +36 -14
  18. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +83 -31
  19. package/src/__tests__/agent-loop-override-profile.test.ts +1 -0
  20. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  21. package/src/__tests__/agent-wake-override-profile.test.ts +2 -0
  22. package/src/__tests__/annotate-activity-metadata.test.ts +2 -0
  23. package/src/__tests__/annotate-risk-options.test.ts +2 -0
  24. package/src/__tests__/approval-cascade.test.ts +2 -0
  25. package/src/__tests__/assistant-attachments.test.ts +42 -0
  26. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -0
  27. package/src/__tests__/btw-routes.test.ts +2 -0
  28. package/src/__tests__/build-persisted-content.test.ts +2 -0
  29. package/src/__tests__/call-controller.test.ts +19 -0
  30. package/src/__tests__/channel-approval-routes.test.ts +21 -26
  31. package/src/__tests__/channel-delivery-store.test.ts +28 -0
  32. package/src/__tests__/channel-guardian.test.ts +164 -78
  33. package/src/__tests__/channel-inbound-disk-pressure.test.ts +11 -19
  34. package/src/__tests__/channel-reply-delivery.test.ts +2 -0
  35. package/src/__tests__/code-search-tool.test.ts +585 -0
  36. package/src/__tests__/compaction-events.test.ts +2 -0
  37. package/src/__tests__/compaction-ledger-store.test.ts +128 -0
  38. package/src/__tests__/compaction.benchmark.test.ts +2 -0
  39. package/src/__tests__/compactor-call-site-logging.test.ts +2 -0
  40. package/src/__tests__/compactor-low-watermark-cut.test.ts +2 -0
  41. package/src/__tests__/compactor-preserved-tail-count.test.ts +2 -0
  42. package/src/__tests__/compactor-summary-call-truncation.test.ts +2 -0
  43. package/src/__tests__/compactor-web-search-strip.test.ts +2 -0
  44. package/src/__tests__/config-loader-backfill.test.ts +123 -10
  45. package/src/__tests__/config-schema.test.ts +1 -0
  46. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +31 -29
  47. package/src/__tests__/consult-deadline.test.ts +60 -0
  48. package/src/__tests__/contact-store-interaction-info.test.ts +156 -0
  49. package/src/__tests__/contact-store-user-file.test.ts +7 -10
  50. package/src/__tests__/contacts-relay-reads.test.ts +19 -24
  51. package/src/__tests__/contacts-write.test.ts +0 -2
  52. package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
  53. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +2 -0
  54. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
  55. package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -0
  56. package/src/__tests__/conversation-agent-loop.test.ts +134 -0
  57. package/src/__tests__/conversation-analysis-routes.test.ts +2 -0
  58. package/src/__tests__/conversation-app-control-lifecycle.test.ts +2 -0
  59. package/src/__tests__/conversation-attention-telegram.test.ts +9 -11
  60. package/src/__tests__/conversation-confirmation-signals.test.ts +2 -0
  61. package/src/__tests__/conversation-fork-crud.test.ts +354 -24
  62. package/src/__tests__/conversation-history-web-search.test.ts +2 -0
  63. package/src/__tests__/conversation-load-history-repair.test.ts +2 -0
  64. package/src/__tests__/conversation-load-history-stripped.test.ts +2 -0
  65. package/src/__tests__/conversation-pairing.test.ts +2 -0
  66. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
  67. package/src/__tests__/conversation-process-callsite.test.ts +2 -0
  68. package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
  69. package/src/__tests__/conversation-queue.test.ts +91 -0
  70. package/src/__tests__/conversation-routes-guardian-reply.test.ts +14 -0
  71. package/src/__tests__/conversation-routes-slash-commands.test.ts +14 -0
  72. package/src/__tests__/conversation-slash-queue.test.ts +2 -0
  73. package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
  74. package/src/__tests__/conversation-speed-override.test.ts +2 -0
  75. package/src/__tests__/conversation-surfaces-task-progress.test.ts +29 -0
  76. package/src/__tests__/conversation-title-service.test.ts +2 -0
  77. package/src/__tests__/conversation-tool-setup-attribution.test.ts +47 -0
  78. package/src/__tests__/conversation-usage.test.ts +2 -0
  79. package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -0
  80. package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
  81. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
  82. package/src/__tests__/credential-security-invariants.test.ts +1 -1
  83. package/src/__tests__/db-compaction-events-migration.test.ts +129 -0
  84. package/src/__tests__/db-migration-rollback.test.ts +205 -171
  85. package/src/__tests__/db-test-helpers.ts +5 -4
  86. package/src/__tests__/delete-propagation.test.ts +5 -3
  87. package/src/__tests__/deterministic-verification-control-plane.test.ts +4 -2
  88. package/src/__tests__/disk-pressure-guard.test.ts +41 -0
  89. package/src/__tests__/dm-backfill.test.ts +6 -4
  90. package/src/__tests__/dm-persistence.test.ts +2 -0
  91. package/src/__tests__/emit-signal-routing-intent.test.ts +7 -6
  92. package/src/__tests__/events-dev-bypass-actor.test.ts +7 -1
  93. package/src/__tests__/exploration-drift-hook.test.ts +3 -2
  94. package/src/__tests__/filing-service.test.ts +2 -0
  95. package/src/__tests__/guardian-binding-drift-heal.test.ts +104 -19
  96. package/src/__tests__/guardian-dispatch.test.ts +140 -1
  97. package/src/__tests__/guardian-expiry-notifier.test.ts +282 -0
  98. package/src/__tests__/guardian-outbound-http.test.ts +13 -0
  99. package/src/__tests__/guardian-routing-state.test.ts +6 -10
  100. package/src/__tests__/heartbeat-disk-pressure.test.ts +2 -0
  101. package/src/__tests__/heartbeat-service.test.ts +2 -0
  102. package/src/__tests__/helpers/channel-test-adapter.ts +46 -19
  103. package/src/__tests__/helpers/create-guardian-binding.ts +15 -23
  104. package/src/__tests__/helpers/mock-logger.ts +1 -0
  105. package/src/__tests__/helpers/seed-contact-channel.ts +96 -0
  106. package/src/__tests__/host-app-control-routes.test.ts +24 -30
  107. package/src/__tests__/host-bash-routes.test.ts +31 -41
  108. package/src/__tests__/host-browser-routes.test.ts +26 -32
  109. package/src/__tests__/host-cu-routes-targeted.test.ts +25 -33
  110. package/src/__tests__/host-file-routes-targeted.test.ts +40 -52
  111. package/src/__tests__/host-transfer-routes-targeted.test.ts +31 -43
  112. package/src/__tests__/http-user-message-parity.test.ts +290 -8
  113. package/src/__tests__/inbound-invite-redemption.test.ts +115 -10
  114. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  115. package/src/__tests__/invite-redemption-service.test.ts +471 -53
  116. package/src/__tests__/invite-routes-http.test.ts +34 -0
  117. package/src/__tests__/invite-service-ipc.test.ts +65 -2
  118. package/src/__tests__/llm-context-normalization.test.ts +105 -0
  119. package/src/__tests__/llm-request-log-error-payload.test.ts +71 -9
  120. package/src/__tests__/llm-usage-store.test.ts +25 -0
  121. package/src/__tests__/mcp-config-secret-boundary.test.ts +392 -0
  122. package/src/__tests__/mcp-health-check.test.ts +2 -1
  123. package/src/__tests__/media-stream-server-integration.test.ts +127 -0
  124. package/src/__tests__/memory-retrieval-hook.test.ts +2 -0
  125. package/src/__tests__/messaging-send-tool.test.ts +2 -0
  126. package/src/__tests__/migration-import-from-url.test.ts +2 -2
  127. package/src/__tests__/mtime-cache.test.ts +275 -5
  128. package/src/__tests__/native-web-search.test.ts +2 -0
  129. package/src/__tests__/non-member-access-request.test.ts +191 -17
  130. package/src/__tests__/notification-broadcaster.test.ts +4 -0
  131. package/src/__tests__/notification-decision-recipient-context.test.ts +33 -32
  132. package/src/__tests__/notification-deep-link.test.ts +6 -0
  133. package/src/__tests__/notification-guardian-path.test.ts +19 -0
  134. package/src/__tests__/onboarding-persona-write.test.ts +52 -22
  135. package/src/__tests__/openai-provider.test.ts +22 -12
  136. package/src/__tests__/openai-responses-provider.test.ts +12 -2
  137. package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
  138. package/src/__tests__/pending-interactions-resolved-event.test.ts +7 -4
  139. package/src/__tests__/persist-onboarding-artifacts.test.ts +1 -0
  140. package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
  141. package/src/__tests__/persona-resolver.test.ts +75 -45
  142. package/src/__tests__/plugin-bootstrap.test.ts +21 -83
  143. package/src/__tests__/plugin-disabled-state.test.ts +190 -0
  144. package/src/__tests__/plugin-pipeline.test.ts +96 -0
  145. package/src/__tests__/plugin-route-contribution.test.ts +6 -19
  146. package/src/__tests__/plugin-tool-contribution.test.ts +5 -20
  147. package/src/__tests__/plugin-types.test.ts +2 -4
  148. package/src/__tests__/process-message-background-slack.test.ts +2 -0
  149. package/src/__tests__/process-message-display-content.test.ts +2 -0
  150. package/src/__tests__/provider-error-scenarios.test.ts +5 -4
  151. package/src/__tests__/provider-usage-tracking.test.ts +39 -0
  152. package/src/__tests__/reaction-intercept-cold-cache-warm.test.ts +135 -0
  153. package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +158 -0
  154. package/src/__tests__/reaction-persistence.test.ts +51 -4
  155. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +2 -0
  156. package/src/__tests__/registry.test.ts +4 -1
  157. package/src/__tests__/relay-server.test.ts +758 -32
  158. package/src/__tests__/runtime-attachment-metadata.test.ts +9 -12
  159. package/src/__tests__/secret-ingress-http.test.ts +14 -0
  160. package/src/__tests__/send-endpoint-busy.test.ts +30 -8
  161. package/src/__tests__/settings-routes.test.ts +32 -0
  162. package/src/__tests__/skills.test.ts +44 -0
  163. package/src/__tests__/slack-inbound-verification.test.ts +47 -2
  164. package/src/__tests__/sse-actor-principal-guardian-source.test.ts +79 -0
  165. package/src/__tests__/steer-on-enqueue-question.test.ts +181 -0
  166. package/src/__tests__/stt-hints.test.ts +49 -15
  167. package/src/__tests__/subagent-detail.test.ts +27 -0
  168. package/src/__tests__/subagent-disposal.test.ts +65 -0
  169. package/src/__tests__/subagent-fork-prompt-role.test.ts +195 -0
  170. package/src/__tests__/subagent-fork-spawn.test.ts +6 -7
  171. package/src/__tests__/subagent-notify-parent.test.ts +2 -0
  172. package/src/__tests__/subagent-role-registry.test.ts +24 -6
  173. package/src/__tests__/subagent-spawn-and-await.test.ts +546 -0
  174. package/src/__tests__/subagent-spawn-tool-fork.test.ts +2 -0
  175. package/src/__tests__/subagent-tools.test.ts +398 -1
  176. package/src/__tests__/suggestion-routes.test.ts +2 -0
  177. package/src/__tests__/thread-backfill.test.ts +3 -3
  178. package/src/__tests__/title-generate-hook.test.ts +2 -0
  179. package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -0
  180. package/src/__tests__/tool-executor.test.ts +16 -11
  181. package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
  182. package/src/__tests__/tool-result-metadata-plumbing.test.ts +2 -0
  183. package/src/__tests__/tool-start-timestamp.test.ts +2 -0
  184. package/src/__tests__/trusted-contact-approval-notifier.test.ts +37 -51
  185. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +12 -12
  186. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +9 -7
  187. package/src/__tests__/trusted-contact-multichannel.test.ts +16 -7
  188. package/src/__tests__/trusted-contact-verification.test.ts +79 -54
  189. package/src/__tests__/twilio-routes.test.ts +96 -0
  190. package/src/__tests__/ui-file-upload-surface.test.ts +86 -0
  191. package/src/__tests__/verification-control-plane-policy.test.ts +2 -0
  192. package/src/__tests__/voice-guardian-cold-cache-warm.test.ts +137 -0
  193. package/src/__tests__/voice-invite-redemption.test.ts +216 -20
  194. package/src/__tests__/web-search-backend-failure.test.ts +2 -0
  195. package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +3 -3
  196. package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +208 -0
  197. package/src/__tests__/workspace-migration-112-remove-advisor-callsite-override.test.ts +170 -0
  198. package/src/__tests__/workspace-migration-drop-user-md.test.ts +196 -238
  199. package/src/__tests__/workspace-migration-remove-hooks.test.ts +14 -35
  200. package/src/__tests__/workspace-tool-loader.test.ts +195 -2
  201. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +35 -47
  202. package/src/agent/loop-exclusive-tool.test.ts +154 -0
  203. package/src/agent/loop-native-web-search.test.ts +200 -0
  204. package/src/agent/loop.ts +164 -1
  205. package/src/api/constants/sse-replay.ts +41 -0
  206. package/src/api/events/conversation-notice.ts +26 -0
  207. package/src/api/index.ts +19 -1
  208. package/src/api/responses/llm-request-log-entry.ts +29 -0
  209. package/src/api/responses/subagent-detail.ts +17 -0
  210. package/src/api/surfaces.ts +39 -3
  211. package/src/approvals/guardian-channel-delivery.ts +30 -0
  212. package/src/approvals/guardian-expiry-notifier.ts +148 -0
  213. package/src/approvals/guardian-request-resolvers.ts +17 -15
  214. package/src/calls/__tests__/relay-setup-router.test.ts +267 -17
  215. package/src/calls/call-domain.ts +3 -3
  216. package/src/calls/guardian-dispatch.ts +14 -9
  217. package/src/calls/inbound-trust-reader.ts +24 -2
  218. package/src/calls/media-stream-server.ts +21 -0
  219. package/src/calls/relay-access-wait.ts +6 -6
  220. package/src/calls/relay-server.ts +188 -51
  221. package/src/calls/relay-setup-router.ts +47 -17
  222. package/src/calls/relay-verification.ts +4 -4
  223. package/src/calls/stt-hints.ts +9 -12
  224. package/src/calls/twilio-routes.ts +14 -4
  225. package/src/channels/types.ts +10 -20
  226. package/src/cli/commands/__tests__/cache.test.ts +8 -1
  227. package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
  228. package/src/cli/commands/cache.ts +194 -181
  229. package/src/cli/commands/contacts.ts +10 -7
  230. package/src/cli/commands/db/__tests__/repair.test.ts +6 -5
  231. package/src/cli/commands/db/status.ts +37 -1
  232. package/src/cli/commands/mcp.ts +252 -218
  233. package/src/cli/commands/memory/__tests__/worker.test.ts +432 -0
  234. package/src/cli/commands/memory/index.ts +2 -0
  235. package/src/cli/commands/memory/worker.ts +242 -0
  236. package/src/cli/commands/plugins.ts +125 -13
  237. package/src/cli/lib/__tests__/install-from-github.test.ts +102 -0
  238. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +160 -1
  239. package/src/cli/lib/__tests__/publish-plugin.test.ts +404 -0
  240. package/src/cli/lib/list-installed-plugins.ts +179 -1
  241. package/src/cli/lib/publish-plugin.ts +633 -0
  242. package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +143 -0
  243. package/src/config/__tests__/sync-gated-profiles.test.ts +16 -10
  244. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +27 -17
  245. package/src/config/bundled-skills/contacts/tools/contact-search.ts +13 -3
  246. package/src/config/bundled-skills/subagent/SKILL.md +17 -2
  247. package/src/config/bundled-skills/subagent/TOOLS.json +5 -4
  248. package/src/config/call-site-defaults.ts +0 -6
  249. package/src/config/feature-flag-registry.json +5 -13
  250. package/src/config/llm-resolver.ts +0 -3
  251. package/src/config/loader.ts +38 -5
  252. package/src/config/prune-seeded-callsite-defaults.ts +110 -0
  253. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  254. package/src/config/schemas/call-site-catalog.ts +0 -7
  255. package/src/config/schemas/heartbeat.ts +2 -5
  256. package/src/config/schemas/llm.ts +3 -12
  257. package/src/config/schemas/memory-lifecycle.ts +12 -0
  258. package/src/config/schemas/memory-v3.ts +7 -0
  259. package/src/config/schemas/memory.ts +4 -0
  260. package/src/config/schemas/timeouts.ts +8 -0
  261. package/src/config/seed-inference-profiles.ts +30 -34
  262. package/src/config/skills.ts +27 -5
  263. package/src/config/sync-gated-profiles.ts +12 -16
  264. package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +128 -0
  265. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +312 -0
  266. package/src/contacts/__tests__/member-write-relay.test.ts +226 -0
  267. package/src/contacts/contact-store.ts +27 -237
  268. package/src/contacts/contacts-write.ts +18 -55
  269. package/src/contacts/gateway-channel-read.ts +51 -0
  270. package/src/contacts/guardian-delivery-reader.ts +223 -0
  271. package/src/contacts/member-write-relay.ts +183 -0
  272. package/src/contacts/types.ts +3 -15
  273. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +35 -0
  274. package/src/daemon/__tests__/conversation-tool-setup.test.ts +0 -44
  275. package/src/daemon/assistant-attachments.ts +27 -4
  276. package/src/daemon/conversation-agent-loop.ts +28 -0
  277. package/src/daemon/conversation-notices.ts +60 -0
  278. package/src/daemon/conversation-process.ts +35 -16
  279. package/src/daemon/conversation-surfaces.ts +111 -38
  280. package/src/daemon/conversation-tool-setup.ts +71 -30
  281. package/src/daemon/conversation.ts +23 -1
  282. package/src/daemon/disk-pressure-guard.ts +12 -2
  283. package/src/daemon/event-loop-watchdog.ts +28 -1
  284. package/src/daemon/external-plugins-bootstrap.ts +17 -41
  285. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +0 -1
  286. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -2
  287. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +25 -2
  288. package/src/daemon/handlers/__tests__/config-channels.test.ts +220 -0
  289. package/src/daemon/handlers/config-a2a.ts +6 -14
  290. package/src/daemon/handlers/config-channels.ts +67 -26
  291. package/src/daemon/handlers/conversations.ts +77 -0
  292. package/src/daemon/lifecycle.ts +16 -0
  293. package/src/daemon/mcp-reload-service.ts +10 -0
  294. package/src/daemon/memory-v2-startup.test.ts +72 -0
  295. package/src/daemon/memory-v2-startup.ts +87 -19
  296. package/src/daemon/message-types/conversations.ts +2 -0
  297. package/src/daemon/message-types/surfaces.ts +12 -12
  298. package/src/daemon/server.ts +0 -4
  299. package/src/daemon/shutdown-handlers.ts +20 -0
  300. package/src/daemon/tool-setup-types.ts +9 -0
  301. package/src/heartbeat/heartbeat-service.ts +5 -0
  302. package/src/home/relationship-state-writer.ts +5 -0
  303. package/src/hooks/hook-loader.ts +341 -0
  304. package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
  305. package/src/ipc/assistant-server.ts +2 -2
  306. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +1 -0
  307. package/src/mcp/client.ts +15 -1
  308. package/src/mcp/mcp-auth-orchestrator.ts +6 -1
  309. package/src/mcp/mcp-header-store.ts +134 -0
  310. package/src/mcp/mcp-oauth-provider.ts +19 -8
  311. package/src/memory/__tests__/301-create-watchdog-events.test.ts +110 -0
  312. package/src/memory/__tests__/memory-retrospective-job.test.ts +8 -0
  313. package/src/memory/__tests__/prompt-override.test.ts +192 -0
  314. package/src/memory/__tests__/watchdog-events-store.test.ts +161 -0
  315. package/src/memory/compaction-ledger-store.ts +107 -0
  316. package/src/memory/conversation-crud.ts +129 -61
  317. package/src/memory/db-connection.ts +22 -3
  318. package/src/memory/db-init.ts +37 -503
  319. package/src/memory/db-singleton.ts +6 -4
  320. package/src/memory/jobs-worker.ts +104 -0
  321. package/src/memory/llm-request-log-store.ts +26 -1
  322. package/src/memory/llm-usage-store.ts +48 -20
  323. package/src/memory/memory-retrospective-job.ts +14 -8
  324. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +152 -56
  325. package/src/memory/migrations/300-add-processing-started-at.ts +30 -0
  326. package/src/memory/migrations/301-create-watchdog-events.ts +45 -0
  327. package/src/memory/migrations/302-create-compaction-events.ts +107 -0
  328. package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +297 -0
  329. package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
  330. package/src/memory/migrations/run-migrations.ts +106 -10
  331. package/src/memory/migrations/validate-migration-state.ts +105 -67
  332. package/src/memory/prompt-override.ts +129 -0
  333. package/src/memory/schema/contacts.ts +6 -2
  334. package/src/memory/schema/conversations.ts +37 -0
  335. package/src/memory/schema/infrastructure.ts +20 -0
  336. package/src/memory/steps.ts +1294 -0
  337. package/src/memory/v2/__tests__/cli-command-store.test.ts +25 -0
  338. package/src/memory/v2/__tests__/skill-store.test.ts +80 -0
  339. package/src/memory/v2/cli-command-store.ts +75 -38
  340. package/src/memory/v2/prompts/consolidation.ts +13 -82
  341. package/src/memory/v2/prompts/router.ts +21 -93
  342. package/src/memory/v2/skill-store.ts +68 -31
  343. package/src/memory/watchdog-events-store.ts +87 -0
  344. package/src/memory/worker-control.ts +204 -0
  345. package/src/memory/worker-process.ts +89 -0
  346. package/src/messaging/channel-binding-metadata.ts +31 -0
  347. package/src/messaging/provider-types.ts +8 -0
  348. package/src/messaging/providers/slack/binding-metadata.ts +60 -0
  349. package/src/notifications/__tests__/broadcaster.test.ts +8 -8
  350. package/src/notifications/__tests__/connected-channels.test.ts +86 -0
  351. package/src/notifications/__tests__/decision-engine.test.ts +78 -9
  352. package/src/notifications/__tests__/destination-resolver.test.ts +151 -0
  353. package/src/notifications/broadcaster.ts +8 -1
  354. package/src/notifications/decision-engine.ts +15 -7
  355. package/src/notifications/destination-resolver.ts +53 -25
  356. package/src/notifications/emit-signal.ts +34 -15
  357. package/src/onboarding/checkin-event.test.ts +222 -0
  358. package/src/onboarding/checkin-event.ts +321 -0
  359. package/src/onboarding/schedule-checkin.ts +190 -0
  360. package/src/permissions/question-prompter.test.ts +1 -1
  361. package/src/permissions/question-prompter.ts +7 -4
  362. package/src/plugin-api/index.ts +12 -12
  363. package/src/plugin-api/types.ts +12 -14
  364. package/src/plugin-api/vision-support.test.ts +28 -4
  365. package/src/plugin-api/vision-support.ts +66 -31
  366. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +2 -2
  367. package/src/plugins/defaults/empty-response/hooks/stop.ts +2 -2
  368. package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +4 -3
  369. package/src/plugins/defaults/history-repair/hooks/post-model-call.ts +2 -2
  370. package/src/plugins/defaults/history-repair/hooks/stop.ts +2 -2
  371. package/src/plugins/defaults/history-repair/hooks/user-prompt-submit.ts +2 -2
  372. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +47 -7
  373. package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +12 -13
  374. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +14 -22
  375. package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +42 -11
  376. package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +2 -2
  377. package/src/plugins/defaults/image-recovery/hooks/stop.ts +2 -2
  378. package/src/plugins/defaults/index.ts +0 -35
  379. package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +2 -2
  380. package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +2 -2
  381. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +2 -2
  382. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  383. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +33 -3
  384. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +48 -4
  385. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +4 -8
  386. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +2 -2
  387. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +2 -2
  388. package/src/plugins/defaults/memory-v3-shadow/injector.ts +43 -15
  389. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +11 -2
  390. package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +146 -0
  391. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +77 -13
  392. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +12 -11
  393. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +2 -2
  394. package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +2 -2
  395. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +2 -2
  396. package/src/plugins/defaults/title-generate/hooks/stop.ts +2 -2
  397. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +2 -2
  398. package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +2 -2
  399. package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +2 -2
  400. package/src/plugins/disabled-state.ts +31 -0
  401. package/src/plugins/external-plugin-loader.ts +2 -2
  402. package/src/plugins/mtime-cache.ts +186 -330
  403. package/src/plugins/pipeline.ts +111 -13
  404. package/src/plugins/registry.ts +59 -16
  405. package/src/plugins/surface-import.ts +121 -0
  406. package/src/plugins/types.ts +7 -9
  407. package/src/prompts/persona-resolver.ts +43 -11
  408. package/src/providers/anthropic/client.ts +5 -0
  409. package/src/providers/call-site-routing.ts +45 -0
  410. package/src/providers/model-catalog.ts +16 -0
  411. package/src/providers/openai/__tests__/api-error-normalization.test.ts +321 -0
  412. package/src/providers/openai/api-error-normalization.ts +270 -0
  413. package/src/providers/openai/chat-completions-provider.ts +37 -83
  414. package/src/providers/openai/responses-provider.ts +50 -46
  415. package/src/providers/openrouter/client.ts +5 -0
  416. package/src/providers/provider-send-message.ts +10 -0
  417. package/src/providers/ratelimit.ts +10 -0
  418. package/src/providers/registry.ts +1 -1
  419. package/src/providers/retry.ts +10 -0
  420. package/src/providers/types.ts +22 -0
  421. package/src/providers/usage-tracking.ts +10 -0
  422. package/src/runtime/__tests__/channel-verification-service.test.ts +133 -0
  423. package/src/runtime/__tests__/guardian-vellum-migration.test.ts +184 -0
  424. package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +66 -0
  425. package/src/runtime/__tests__/local-principal-trust.test.ts +162 -0
  426. package/src/runtime/__tests__/member-verdict-cache.test.ts +119 -0
  427. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +402 -123
  428. package/src/runtime/access-request-helper.ts +18 -39
  429. package/src/runtime/actor-trust-resolver.ts +46 -19
  430. package/src/runtime/anchored-guardian.test.ts +109 -0
  431. package/src/runtime/anchored-guardian.ts +86 -0
  432. package/src/runtime/assistant-event-hub.ts +1 -1
  433. package/src/runtime/assistant-stream-state.ts +9 -2
  434. package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +99 -0
  435. package/src/runtime/auth/require-bound-guardian.ts +21 -11
  436. package/src/runtime/channel-verification-service.ts +56 -31
  437. package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
  438. package/src/runtime/guardian-vellum-migration.ts +69 -8
  439. package/src/runtime/invite-redemption-service.ts +213 -187
  440. package/src/runtime/local-actor-identity.test.ts +108 -0
  441. package/src/runtime/local-actor-identity.ts +85 -13
  442. package/src/runtime/local-principal-trust.ts +52 -0
  443. package/src/runtime/member-verdict-cache.ts +0 -0
  444. package/src/runtime/pending-interactions.ts +11 -1
  445. package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +56 -5
  446. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
  447. package/src/runtime/routes/__tests__/contact-routes.test.ts +305 -0
  448. package/src/runtime/routes/__tests__/global-search-routes.test.ts +92 -0
  449. package/src/runtime/routes/__tests__/http-adapter-actor-header.test.ts +129 -0
  450. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +188 -0
  451. package/src/runtime/routes/browser-routes.ts +1 -1
  452. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +13 -5
  453. package/src/runtime/routes/channel-verification-routes.ts +3 -3
  454. package/src/runtime/routes/contact-routes.ts +48 -57
  455. package/src/runtime/routes/conversation-cli-routes.ts +4 -5
  456. package/src/runtime/routes/conversation-list-routes.ts +4 -7
  457. package/src/runtime/routes/conversation-query-routes.ts +72 -0
  458. package/src/runtime/routes/conversation-routes.ts +84 -85
  459. package/src/runtime/routes/events-routes.ts +2 -2
  460. package/src/runtime/routes/global-search-routes.ts +3 -1
  461. package/src/runtime/routes/guardian-action-routes.ts +4 -5
  462. package/src/runtime/routes/host-app-control-routes.ts +5 -4
  463. package/src/runtime/routes/host-bash-routes.ts +5 -4
  464. package/src/runtime/routes/host-browser-routes.ts +9 -11
  465. package/src/runtime/routes/host-cu-routes.ts +5 -4
  466. package/src/runtime/routes/host-file-routes.ts +5 -4
  467. package/src/runtime/routes/host-transfer-routes.ts +6 -6
  468. package/src/runtime/routes/http-adapter.ts +16 -1
  469. package/src/runtime/routes/identity-routes.ts +3 -2
  470. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  471. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +97 -5
  472. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +61 -59
  473. package/src/runtime/routes/inbound-stages/background-dispatch.ts +13 -5
  474. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
  475. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +21 -8
  476. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -3
  477. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +19 -0
  478. package/src/runtime/routes/index.ts +2 -0
  479. package/src/runtime/routes/llm-context-normalization.ts +83 -0
  480. package/src/runtime/routes/mcp-auth-routes.ts +171 -19
  481. package/src/runtime/routes/migration-rollback-routes.ts +4 -3
  482. package/src/runtime/routes/migration-routes.ts +4 -1
  483. package/src/runtime/routes/onboarding-checkin-routes.ts +86 -0
  484. package/src/runtime/routes/settings-routes.ts +8 -3
  485. package/src/runtime/routes/subagents-routes.ts +5 -0
  486. package/src/runtime/routes/surface-action-routes.ts +42 -56
  487. package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
  488. package/src/runtime/services/conversation-serializer.ts +13 -58
  489. package/src/runtime/tool-grant-request-helper.ts +3 -3
  490. package/src/runtime/trust-verdict-consumer.ts +111 -40
  491. package/src/runtime/verification-outbound-actions.ts +18 -18
  492. package/src/signals/user-message.ts +16 -0
  493. package/src/subagent/__tests__/consult-prompt.test.ts +35 -0
  494. package/src/{plugins/defaults/advisor/__tests__/transcript.test.ts → subagent/__tests__/consult-transcript.test.ts} +47 -10
  495. package/src/{plugins/defaults/advisor/steering.ts → subagent/consult-prompt.ts} +22 -32
  496. package/src/{plugins/defaults/advisor/transcript.ts → subagent/consult-transcript.ts} +18 -8
  497. package/src/subagent/index.ts +1 -1
  498. package/src/subagent/manager.ts +254 -33
  499. package/src/subagent/types.ts +11 -4
  500. package/src/telemetry/types.ts +34 -1
  501. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
  502. package/src/telemetry/usage-telemetry-reporter.ts +87 -3
  503. package/src/tools/ask-question/ask-question-tool.test.ts +29 -0
  504. package/src/tools/ask-question/ask-question-tool.ts +13 -0
  505. package/src/tools/executor.ts +4 -4
  506. package/src/tools/filesystem/search.ts +543 -0
  507. package/src/tools/registry.ts +38 -0
  508. package/src/tools/shared/filesystem/path-policy.ts +12 -5
  509. package/src/tools/subagent/consult-deadline.ts +49 -0
  510. package/src/tools/subagent/spawn.ts +234 -5
  511. package/src/tools/tool-approval-handler.ts +1 -1
  512. package/src/tools/tool-defaults.ts +9 -2
  513. package/src/tools/tool-manifest.ts +3 -0
  514. package/src/tools/types.ts +131 -25
  515. package/src/tools/workspace-tools/loader.ts +348 -244
  516. package/src/util/errors.ts +26 -1
  517. package/src/util/logger.ts +9 -0
  518. package/src/util/platform.ts +19 -0
  519. package/src/util/telemetry-db-path.ts +24 -0
  520. package/src/workflows/library.test.ts +140 -0
  521. package/src/workflows/library.ts +82 -28
  522. package/src/workspace/migrations/017-seed-persona-dirs.ts +3 -34
  523. package/src/workspace/migrations/019-scope-journal-to-guardian.ts +3 -24
  524. package/src/workspace/migrations/031-drop-user-md.ts +232 -148
  525. package/src/workspace/migrations/048-remove-workspace-hooks.ts +14 -66
  526. package/src/workspace/migrations/111-prune-seeded-callsite-defaults.ts +134 -0
  527. package/src/workspace/migrations/112-remove-advisor-callsite-override.ts +64 -0
  528. package/src/workspace/migrations/registry.ts +4 -0
  529. package/src/__tests__/workspace-tools-watcher-flag.test.ts +0 -70
  530. package/src/daemon/workspace-tools-watcher.ts +0 -328
  531. package/src/memory/migrations/registry.ts +0 -573
  532. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +0 -56
  533. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +0 -43
  534. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +0 -137
  535. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -153
  536. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +0 -138
  537. package/src/plugins/defaults/advisor/advisor-gate.ts +0 -29
  538. package/src/plugins/defaults/advisor/advisor-state-store.ts +0 -94
  539. package/src/plugins/defaults/advisor/config.ts +0 -21
  540. package/src/plugins/defaults/advisor/consult.ts +0 -93
  541. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +0 -34
  542. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +0 -30
  543. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +0 -19
  544. package/src/plugins/defaults/advisor/package.json +0 -14
  545. package/src/plugins/defaults/advisor/tools/advisor.ts +0 -65
  546. package/src/providers/openai/__tests__/api-error-detail.test.ts +0 -120
@@ -0,0 +1,156 @@
1
+ /**
2
+ * INFO telemetry (interactionCount/lastInteraction/lastSeenAt) is local, not
3
+ * ACL: the gateway's handle-inbound mirror writes it to the assistant DB, and
4
+ * model-facing turn context reads it back. These tests assert the daemon-native
5
+ * read paths (getContact / searchContacts) re-hydrate and aggregate it.
6
+ */
7
+
8
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
9
+
10
+ mock.module("../util/logger.js", () => ({
11
+ getLogger: () =>
12
+ new Proxy({} as Record<string, unknown>, {
13
+ get: () => () => {},
14
+ }),
15
+ }));
16
+
17
+ import {
18
+ findContactInfoById,
19
+ getContact,
20
+ searchContacts,
21
+ } from "../contacts/contact-store.js";
22
+ import { getSqlite } from "../memory/db-connection.js";
23
+ import { initializeDb } from "../memory/db-init.js";
24
+
25
+ await initializeDb();
26
+
27
+ function resetContactTables(): void {
28
+ const sqlite = getSqlite();
29
+ sqlite.run("DELETE FROM contact_channels");
30
+ sqlite.run("DELETE FROM contacts");
31
+ }
32
+
33
+ function insertContact(id: string, displayName: string): void {
34
+ const now = Date.now();
35
+ getSqlite().run(
36
+ "INSERT INTO contacts (id, display_name, role, contact_type, user_file, created_at, updated_at) VALUES (?, ?, 'contact', 'human', ?, ?, ?)",
37
+ [id, displayName, `${id}.md`, now, now],
38
+ );
39
+ }
40
+
41
+ function insertChannel(params: {
42
+ id: string;
43
+ contactId: string;
44
+ type: string;
45
+ address: string;
46
+ interactionCount: number;
47
+ lastInteraction: number | null;
48
+ lastSeenAt?: number | null;
49
+ }): void {
50
+ const now = Date.now();
51
+ getSqlite().run(
52
+ "INSERT INTO contact_channels (id, contact_id, type, address, is_primary, interaction_count, last_interaction, last_seen_at, created_at, updated_at) VALUES (?, ?, ?, ?, 0, ?, ?, ?, ?, ?)",
53
+ [
54
+ params.id,
55
+ params.contactId,
56
+ params.type,
57
+ params.address,
58
+ params.interactionCount,
59
+ params.lastInteraction,
60
+ params.lastSeenAt ?? null,
61
+ now,
62
+ now,
63
+ ],
64
+ );
65
+ }
66
+
67
+ describe("contact interaction INFO aggregation", () => {
68
+ beforeEach(() => {
69
+ resetContactTables();
70
+ });
71
+
72
+ test("getContact sums interaction_count and takes the latest last_interaction across channels", () => {
73
+ insertContact("ct_1", "Alice");
74
+ insertChannel({
75
+ id: "ch_a",
76
+ contactId: "ct_1",
77
+ type: "phone",
78
+ address: "+15550100",
79
+ interactionCount: 3,
80
+ lastInteraction: 1900,
81
+ lastSeenAt: 1850,
82
+ });
83
+ insertChannel({
84
+ id: "ch_b",
85
+ contactId: "ct_1",
86
+ type: "email",
87
+ address: "alice@example.com",
88
+ interactionCount: 4,
89
+ lastInteraction: 2100,
90
+ lastSeenAt: 2050,
91
+ });
92
+
93
+ const contact = getContact("ct_1");
94
+ expect(contact).not.toBeNull();
95
+ expect(contact!.interactionCount).toBe(7);
96
+ expect(contact!.lastInteraction).toBe(2100);
97
+
98
+ // Per-channel INFO is hydrated too.
99
+ const phone = contact!.channels.find((c) => c.type === "phone");
100
+ expect(phone?.interactionCount).toBe(3);
101
+ expect(phone?.lastInteraction).toBe(1900);
102
+ expect(phone?.lastSeenAt).toBe(1850);
103
+ });
104
+
105
+ test("findContactInfoById surfaces the real interaction count", () => {
106
+ insertContact("ct_2", "Bob");
107
+ insertChannel({
108
+ id: "ch_c",
109
+ contactId: "ct_2",
110
+ type: "phone",
111
+ address: "+15550200",
112
+ interactionCount: 5,
113
+ lastInteraction: 1000,
114
+ });
115
+
116
+ const info = findContactInfoById("ct_2");
117
+ expect(info?.interactionCount).toBe(5);
118
+ });
119
+
120
+ test("lastInteraction is null when no channel has interacted", () => {
121
+ insertContact("ct_3", "Carol");
122
+ insertChannel({
123
+ id: "ch_d",
124
+ contactId: "ct_3",
125
+ type: "phone",
126
+ address: "+15550300",
127
+ interactionCount: 0,
128
+ lastInteraction: null,
129
+ });
130
+
131
+ const contact = getContact("ct_3");
132
+ expect(contact!.interactionCount).toBe(0);
133
+ expect(contact!.lastInteraction).toBeNull();
134
+ });
135
+
136
+ test("filtered searchContacts carries channel INFO fields", () => {
137
+ insertContact("ct_4", "Dana");
138
+ insertChannel({
139
+ id: "ch_e",
140
+ contactId: "ct_4",
141
+ type: "phone",
142
+ address: "+15550400",
143
+ interactionCount: 9,
144
+ lastInteraction: 3000,
145
+ lastSeenAt: 2900,
146
+ });
147
+
148
+ const results = searchContacts({ query: "Dana", limit: 10 });
149
+ expect(results).toHaveLength(1);
150
+ expect(results[0].interactionCount).toBe(9);
151
+ expect(results[0].lastInteraction).toBe(3000);
152
+ const ch = results[0].channels[0];
153
+ expect(ch.interactionCount).toBe(9);
154
+ expect(ch.lastSeenAt).toBe(2900);
155
+ });
156
+ });
@@ -66,11 +66,10 @@ describe("upsertContact user_file selection", () => {
66
66
  resetContactTables();
67
67
  });
68
68
 
69
- test("reuses an existing sibling's userFile when principalId matches", () => {
69
+ test("assigns a fresh slug per contact; principalId is gateway-owned and no longer groups siblings locally", () => {
70
70
  const primary = upsertContact({
71
71
  displayName: "Chris",
72
72
  role: "guardian",
73
- principalId: "principal-abc",
74
73
  channels: [
75
74
  {
76
75
  type: "vellum",
@@ -80,12 +79,12 @@ describe("upsertContact user_file selection", () => {
80
79
  });
81
80
  expect(primary.userFile).toBe("chris.md");
82
81
 
83
- // Second contact for the same principal on Slack must inherit the
84
- // first contact's userFile, NOT auto-increment to chris-2.md.
82
+ // A second contact with the same display name no longer inherits the
83
+ // first's userFile via a local principal lookup — it gets a fresh
84
+ // (collision-incremented) slug. Sibling grouping is owned by the gateway.
85
85
  const slack = upsertContact({
86
86
  displayName: "chris",
87
87
  role: "guardian",
88
- principalId: "principal-abc",
89
88
  channels: [
90
89
  {
91
90
  type: "slack",
@@ -94,15 +93,14 @@ describe("upsertContact user_file selection", () => {
94
93
  },
95
94
  ],
96
95
  });
97
- expect(slack.userFile).toBe("chris.md");
96
+ expect(slack.userFile).toBe("chris-2.md");
98
97
  expect(slack.id).not.toBe(primary.id);
99
98
  });
100
99
 
101
- test("falls back to generateUserFileSlug when principalId has no existing sibling", () => {
100
+ test("generates a slug from the display name for a brand-new contact", () => {
102
101
  const contact = upsertContact({
103
102
  displayName: "Alice",
104
103
  role: "contact",
105
- principalId: "principal-alone",
106
104
  channels: [
107
105
  {
108
106
  type: "slack",
@@ -141,7 +139,7 @@ describe("upsertContact user_file selection", () => {
141
139
  expect(second.userFile).toBe("bob-2.md");
142
140
  });
143
141
 
144
- test("ignores a sibling whose userFile is null and generates a new slug", () => {
142
+ test("a null-userFile contact does not block a fresh slug for a new contact", () => {
145
143
  insertContact({
146
144
  id: "seed-null",
147
145
  displayName: "legacy",
@@ -154,7 +152,6 @@ describe("upsertContact user_file selection", () => {
154
152
  const contact = upsertContact({
155
153
  displayName: "Legacy",
156
154
  role: "guardian",
157
- principalId: "principal-null",
158
155
  channels: [
159
156
  {
160
157
  type: "phone",
@@ -3,8 +3,9 @@
3
3
  *
4
4
  * handleListContacts (non-search) and handleGetContact relay to the gateway
5
5
  * via `ipcCallPersistent`. On the happy path they serve gateway-sourced data
6
- * and do NOT read the assistant DB. On IPC failure they fall back to the
7
- * assistant-DB read and log a warning. getContact still 404s for unknown ids.
6
+ * and do NOT read the assistant DB. On IPC failure they FAIL CLOSED the relay
7
+ * error propagates rather than falling back to the assistant DB. getContact
8
+ * surfaces a clean gateway not-found as a 404.
8
9
  */
9
10
 
10
11
  import { beforeEach, describe, expect, mock, test } from "bun:test";
@@ -62,19 +63,18 @@ const realContactStore = await import("../contacts/contact-store.js");
62
63
  // contactType is filtered in SQL (before the limit) rather than relayed.
63
64
  const listContactsArgs: Array<{
64
65
  limit?: number;
65
- role?: string;
66
66
  contactType?: string;
67
67
  }> = [];
68
68
  mock.module("../contacts/contact-store.js", () => ({
69
69
  ...realContactStore,
70
- listContacts: (limit?: number, role?: string, contactType?: string) => {
70
+ listContacts: (limit?: number, contactType?: string) => {
71
71
  localCalls.push("listContacts");
72
- listContactsArgs.push({ limit, role, contactType });
72
+ listContactsArgs.push({ limit, contactType });
73
73
  return [
74
74
  {
75
75
  id: "local-1",
76
76
  displayName: "Local Contact",
77
- role: role ?? "contact",
77
+ role: "contact",
78
78
  contactType: contactType ?? "human",
79
79
  interactionCount: 0,
80
80
  channels: [],
@@ -213,16 +213,15 @@ describe("handleListContacts relay", () => {
213
213
  expect(result.contacts[0].displayName).toBe("Your Guardian");
214
214
  });
215
215
 
216
- test("falls back to the assistant DB on IPC failure", async () => {
216
+ test("fails closed on IPC failure (no assistant-DB fallback)", async () => {
217
217
  ipcStub = () => {
218
218
  throw new Error("gateway down");
219
219
  };
220
220
 
221
- const result = await handleListContacts({});
221
+ await expect(handleListContacts({})).rejects.toThrow("gateway down");
222
222
 
223
223
  expect(ipcCalls.map((c) => c.method)).toEqual(["contacts_list_rich"]);
224
- expect(localCalls).toContain("listContacts");
225
- expect(result.contacts[0].id).toBe("local-1");
224
+ expect(localCalls).toEqual([]);
226
225
  });
227
226
 
228
227
  test("search params stay daemon-native and log the boundary note", async () => {
@@ -248,20 +247,18 @@ describe("handleListContacts relay", () => {
248
247
  // contactType + limit are pushed into the SQL-filtered daemon read (the
249
248
  // daemon-native listContacts filters contactType BEFORE applying limit).
250
249
  expect(listContactsArgs).toEqual([
251
- { limit: 50, role: undefined, contactType: "assistant" },
250
+ { limit: 50, contactType: "assistant" },
252
251
  ]);
253
252
  expect(result.contacts[0].id).toBe("local-1");
254
253
  expect(result.contacts[0].contactType).toBe("assistant");
255
254
  expect(debugLogs.some((m) => m.includes("daemon-native"))).toBe(true);
256
255
  });
257
256
 
258
- test("contactType + role both flow into the daemon-native read", async () => {
257
+ test("contactType filters daemon-native; role is gateway-owned and no longer a local predicate", async () => {
259
258
  await handleListContacts({ contactType: "human", role: "guardian" });
260
259
 
261
260
  expect(ipcCalls).toEqual([]);
262
- expect(listContactsArgs).toEqual([
263
- { limit: 50, role: "guardian", contactType: "human" },
264
- ]);
261
+ expect(listContactsArgs).toEqual([{ limit: 50, contactType: "human" }]);
265
262
  });
266
263
  });
267
264
 
@@ -384,26 +381,24 @@ describe("handleGetContact relay", () => {
384
381
  expect(localCalls).toEqual([]);
385
382
  });
386
383
 
387
- test("falls back to the assistant DB on IPC transport failure", async () => {
384
+ test("fails closed on IPC transport failure (no assistant-DB fallback)", async () => {
388
385
  ipcStub = () => {
389
386
  throw new Error("gateway down");
390
387
  };
391
388
 
392
- const result = await handleGetContact("gw-1");
389
+ await expect(handleGetContact("gw-1")).rejects.toThrow("gateway down");
393
390
 
394
391
  expect(ipcCalls.map((c) => c.method)).toEqual(["contacts_get_rich"]);
395
- expect(localCalls).toContain("getContact");
396
- expect(result.contact.id).toBe("gw-1");
392
+ expect(localCalls).toEqual([]);
397
393
  });
398
394
 
399
- test("fallback path still 404s for unknown ids", async () => {
400
- ipcStub = () => {
401
- throw new Error("gateway down");
402
- };
395
+ test("clean gateway not-found surfaces as a 404 for unknown ids", async () => {
396
+ ipcStub = () => null;
403
397
 
404
398
  await expect(handleGetContact("missing")).rejects.toThrow(
405
399
  'Contact "missing" not found',
406
400
  );
407
- expect(localCalls).toContain("getContact");
401
+ expect(ipcCalls.map((c) => c.method)).toEqual(["contacts_get_rich"]);
402
+ expect(localCalls).toEqual([]);
408
403
  });
409
404
  });
@@ -63,8 +63,6 @@ describe("guardian persona seeding and trust-cache invariants", () => {
63
63
  externalUserId: "Bob",
64
64
  externalChatId: "chat-bob",
65
65
  displayName: "Bob",
66
- role: "contact",
67
- status: "active",
68
66
  });
69
67
 
70
68
  expect(result).not.toBeNull();
@@ -112,6 +112,8 @@ let reservedRowContent: Map<string, string> = new Map();
112
112
  let reserveCounter = 0;
113
113
 
114
114
  mock.module("../memory/conversation-crud.js", () => ({
115
+ setConversationProcessingStartedAt: () => {},
116
+ isConversationProcessing: () => false,
115
117
  setConversationOriginChannelIfUnset: () => {},
116
118
  updateConversationContextWindow: () => {},
117
119
  deleteMessageById: () => {},
@@ -87,6 +87,8 @@ mock.module("../daemon/disk-pressure-guard.js", () => ({
87
87
  }));
88
88
 
89
89
  mock.module("../memory/conversation-crud.js", () => ({
90
+ setConversationProcessingStartedAt: () => {},
91
+ isConversationProcessing: () => false,
90
92
  getConversation: () => ({
91
93
  id: "conv-123",
92
94
  conversationType: "background",
@@ -147,6 +147,8 @@ let mockConversationRow: {
147
147
  };
148
148
 
149
149
  mock.module("../memory/conversation-crud.js", () => ({
150
+ setConversationProcessingStartedAt: () => {},
151
+ isConversationProcessing: () => false,
150
152
  setConversationOriginChannelIfUnset: () => {},
151
153
  setConversationHistoryStrippedAt: () => {},
152
154
  updateConversationUsage: () => {},
@@ -257,6 +257,8 @@ mock.module("../plugins/defaults/compaction/overflow-policy.js", () => ({
257
257
  }));
258
258
 
259
259
  mock.module("../memory/conversation-crud.js", () => ({
260
+ setConversationProcessingStartedAt: () => {},
261
+ isConversationProcessing: () => false,
260
262
  setConversationOriginChannelIfUnset: () => {},
261
263
  setConversationHistoryStrippedAt: () => {},
262
264
  updateConversationUsage: () => {},
@@ -10,8 +10,14 @@ import {
10
10
  } from "bun:test";
11
11
 
12
12
  import type { LoopToolExecutor } from "../agent/loop.js";
13
+ import {
14
+ queueConversationNotice,
15
+ resetConversationNoticesForTests,
16
+ } from "../daemon/conversation-notices.js";
13
17
  import type { ServerMessage } from "../daemon/message-protocol.js";
18
+ import type { UserPromptSubmitContext } from "../plugin-api/types.js";
14
19
  import { resetPluginRegistryAndRegisterDefaults } from "../plugins/defaults/index.js";
20
+ import { registerPlugin } from "../plugins/registry.js";
15
21
  import type { Message, Provider, ToolDefinition } from "../providers/types.js";
16
22
  import { ContextOverflowError } from "../providers/types.js";
17
23
 
@@ -273,6 +279,8 @@ const deleteMessageByIdMock = mock(() => ({
273
279
  const reserveMessageMock = mock(async () => ({ id: "msg-reserve" }));
274
280
  const updateMessageContentMock = mock(() => {});
275
281
  mock.module("../memory/conversation-crud.js", () => ({
282
+ setConversationProcessingStartedAt: () => {},
283
+ isConversationProcessing: () => false,
276
284
  setConversationOriginChannelIfUnset: () => {},
277
285
  updateConversationUsage: () => {},
278
286
  updateMessageMetadata: updateMessageMetadataMock,
@@ -700,6 +708,7 @@ function makeCtx(
700
708
  mockConversationRow?.slackContextCompactionWatermarkTs ?? null,
701
709
  lastNotifiedInferenceProfile:
702
710
  mockConversationRow?.lastNotifiedInferenceProfile ?? null,
711
+ processingStartedAt: mockConversationRow?.processingStartedAt ?? null,
703
712
 
704
713
  memoryPolicy: { scopeId: "default", includeDefaultFallback: true },
705
714
 
@@ -864,7 +873,14 @@ beforeEach(() => {
864
873
  indexMessageNowMock.mockClear();
865
874
  projectAssistantMessageMock.mockClear();
866
875
  publishSyncInvalidationMock.mockClear();
876
+ resolveAssistantAttachmentsMock.mockClear();
877
+ resolveAssistantAttachmentsMock.mockImplementation(async () => ({
878
+ assistantAttachments: [],
879
+ emittedAttachments: [],
880
+ directiveWarnings: [],
881
+ }));
867
882
  mockMessageById = null;
883
+ resetConversationNoticesForTests();
868
884
  // The compaction pipeline runs through the plugin registry; reset and
869
885
  // re-register every default so it dispatches to middleware backed by the
870
886
  // mocked collaborators these tests install (`syncMessageToDisk`, etc.)
@@ -873,6 +889,120 @@ beforeEach(() => {
873
889
  });
874
890
 
875
891
  describe("session-agent-loop", () => {
892
+ describe("user-prompt-submit hook failures", () => {
893
+ test("logs and continues with prior hook mutations", async () => {
894
+ registerPlugin({
895
+ manifest: {
896
+ name: "test-user-prompt-rewrite",
897
+ version: "1.0.0",
898
+ },
899
+ hooks: {
900
+ "user-prompt-submit": async (_ctx: UserPromptSubmitContext) => ({
901
+ latestMessages: [
902
+ {
903
+ role: "user" as const,
904
+ content: [{ type: "text" as const, text: "rewritten prompt" }],
905
+ },
906
+ ],
907
+ }),
908
+ },
909
+ });
910
+ registerPlugin({
911
+ manifest: {
912
+ name: "test-user-prompt-throw",
913
+ version: "1.0.0",
914
+ },
915
+ hooks: {
916
+ "user-prompt-submit": async () => {
917
+ throw new Error("simulated hook failure");
918
+ },
919
+ },
920
+ });
921
+
922
+ const events: ServerMessage[] = [];
923
+ const ctx = makeCtx({ providerResponses: [textResponse("ok")] });
924
+ const runSpy = spyOn(ctx.agentLoop, "run");
925
+
926
+ await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
927
+
928
+ expect(runSpy).toHaveBeenCalledTimes(1);
929
+ const call = runSpy.mock.calls[0]?.[0] as
930
+ | { messages: Message[] }
931
+ | undefined;
932
+ expect(call?.messages[0]?.content).toEqual([
933
+ { type: "text", text: "rewritten prompt" },
934
+ ]);
935
+ expect(
936
+ events.find((event) => event.type === "conversation_error"),
937
+ ).toBeUndefined();
938
+ expect(
939
+ events.find((event) => event.type === "message_complete"),
940
+ ).toBeDefined();
941
+ });
942
+ });
943
+
944
+ describe("conversation notices", () => {
945
+ test("emits queued billing notices after a successful turn", async () => {
946
+ const events: ServerMessage[] = [];
947
+ const ctx = makeCtx({ providerResponses: [textResponse("ok")] });
948
+ queueConversationNotice(ctx.conversationId, "memory-v3-test", {
949
+ source: "memory_v3",
950
+ code: "PROVIDER_BILLING",
951
+ userMessage: "You've run out of credits.",
952
+ errorCategory: "credits_exhausted",
953
+ });
954
+
955
+ await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
956
+
957
+ expect(
958
+ events.find((event) => event.type === "conversation_error"),
959
+ ).toBeUndefined();
960
+ const messageCompleteIndex = events.findIndex(
961
+ (event) => event.type === "message_complete",
962
+ );
963
+ const conversationNoticeIndex = events.findIndex(
964
+ (event) => event.type === "conversation_notice",
965
+ );
966
+
967
+ expect(messageCompleteIndex).toBeGreaterThanOrEqual(0);
968
+ expect(conversationNoticeIndex).toBeGreaterThan(messageCompleteIndex);
969
+ expect(events[conversationNoticeIndex]).toEqual({
970
+ type: "conversation_notice",
971
+ conversationId: "test-conv",
972
+ source: "memory_v3",
973
+ code: "PROVIDER_BILLING",
974
+ userMessage: "You've run out of credits.",
975
+ errorCategory: "credits_exhausted",
976
+ });
977
+ });
978
+
979
+ test("clears queued notices when post-loop success work fails", async () => {
980
+ resolveAssistantAttachmentsMock.mockImplementation(async () => {
981
+ throw new Error("attachment resolution failed");
982
+ });
983
+ const events: ServerMessage[] = [];
984
+ const ctx = makeCtx({ providerResponses: [textResponse("ok")] });
985
+ queueConversationNotice(ctx.conversationId, "memory-v3-test", {
986
+ source: "memory_v3",
987
+ code: "PROVIDER_BILLING",
988
+ userMessage: "You've run out of credits.",
989
+ errorCategory: "credits_exhausted",
990
+ });
991
+
992
+ await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
993
+
994
+ expect(
995
+ events.find((event) => event.type === "conversation_notice"),
996
+ ).toBeUndefined();
997
+ expect(
998
+ events.find((event) => event.type === "message_complete"),
999
+ ).toBeUndefined();
1000
+ expect(
1001
+ events.find((event) => event.type === "conversation_error"),
1002
+ ).toBeDefined();
1003
+ });
1004
+ });
1005
+
876
1006
  describe("timezone turn context", () => {
877
1007
  test("passes ctx.clientTimezone and ui.detectedTimezone into timezone resolution", async () => {
878
1008
  mockUiConfig = {
@@ -1705,6 +1835,10 @@ describe("session-agent-loop", () => {
1705
1835
  expect(ctx.abortController).toBeNull();
1706
1836
  expect(ctx.currentRequestId).toBeUndefined();
1707
1837
  expect(ctx.commandIntent).toBeUndefined();
1838
+ // Turn-scoped interactivity is stamped during the run and must be cleared
1839
+ // so paths that bypass this loop (e.g. opportunity wakes) don't inherit a
1840
+ // stale value instead of falling back to live client state.
1841
+ expect(ctx.currentTurnIsNonInteractive).toBeUndefined();
1708
1842
  });
1709
1843
 
1710
1844
  test("clears state and surfaces a processing error when the provider call fails", async () => {
@@ -22,6 +22,8 @@ mock.module("../memory/conversation-key-store.js", () => ({
22
22
  }));
23
23
 
24
24
  mock.module("../memory/conversation-crud.js", () => ({
25
+ setConversationProcessingStartedAt: () => {},
26
+ isConversationProcessing: () => false,
25
27
  getConversation: mockGetConversation,
26
28
  getMessages: mockGetMessages,
27
29
  createConversation: mockCreateConversation,
@@ -76,6 +76,8 @@ mock.module("../security/secret-allowlist.js", () => ({
76
76
  }));
77
77
 
78
78
  mock.module("../memory/conversation-crud.js", () => ({
79
+ setConversationProcessingStartedAt: () => {},
80
+ isConversationProcessing: () => false,
79
81
  updateConversationContextWindow: () => {},
80
82
  deleteMessageById: () => {},
81
83
  updateConversationTitle: () => {},
@@ -31,7 +31,6 @@ mock.module("../daemon/handlers/shared.js", () => ({
31
31
 
32
32
  import { eq } from "drizzle-orm";
33
33
 
34
- import { upsertContact } from "../contacts/contact-store.js";
35
34
  import { getDb } from "../memory/db-connection.js";
36
35
  import { initializeDb } from "../memory/db-init.js";
37
36
  import * as deliveryChannels from "../memory/delivery-channels.js";
@@ -39,7 +38,10 @@ import { resetTestTables } from "../memory/raw-query.js";
39
38
  import { attachments, conversationAttentionEvents } from "../memory/schema.js";
40
39
  import * as pendingInteractions from "../runtime/pending-interactions.js";
41
40
  import { resetDbForTesting } from "./db-test-helpers.js";
42
- import { handleChannelInbound } from "./helpers/channel-test-adapter.js";
41
+ import {
42
+ handleChannelInbound,
43
+ seedContactChannel,
44
+ } from "./helpers/channel-test-adapter.js";
43
45
 
44
46
  await initializeDb();
45
47
 
@@ -69,16 +71,12 @@ function resetTables(): void {
69
71
  }
70
72
 
71
73
  function ensureTestContact(): void {
72
- upsertContact({
74
+ seedContactChannel({
75
+ sourceChannel: "telegram",
76
+ externalUserId: "telegram-user-default",
73
77
  displayName: "Test User",
74
- channels: [
75
- {
76
- type: "telegram",
77
- address: "telegram-user-default",
78
- status: "active",
79
- policy: "allow",
80
- },
81
- ],
78
+ status: "active",
79
+ policy: "allow",
82
80
  });
83
81
  }
84
82
 
@@ -112,6 +112,8 @@ mock.module("../security/secret-allowlist.js", () => ({
112
112
  }));
113
113
 
114
114
  mock.module("../memory/conversation-crud.js", () => ({
115
+ setConversationProcessingStartedAt: () => {},
116
+ isConversationProcessing: () => false,
115
117
  setConversationOriginChannelIfUnset: () => {},
116
118
  updateConversationContextWindow: () => {},
117
119
  deleteMessageById: () => {},