@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
@@ -1,4 +1,4 @@
1
- import { and, asc, desc, eq, isNotNull, like, sql } from "drizzle-orm";
1
+ import { and, asc, desc, eq, like, sql } from "drizzle-orm";
2
2
  import { v4 as uuid } from "uuid";
3
3
 
4
4
  import type { ChannelId } from "../channels/types.js";
@@ -96,13 +96,12 @@ function parseContact(row: typeof contacts.$inferSelect): Contact {
96
96
  id: row.id,
97
97
  displayName: row.displayName,
98
98
  notes: row.notes,
99
+ role: row.role,
99
100
  lastInteraction: null,
100
101
  interactionCount: 0,
101
102
  createdAt: row.createdAt,
102
103
  updatedAt: row.updatedAt,
103
- role: row.role as Contact["role"],
104
- contactType: (row.contactType as Contact["contactType"]) ?? "human",
105
- principalId: row.principalId,
104
+ contactType: row.contactType,
106
105
  userFile: row.userFile ?? null,
107
106
  };
108
107
  }
@@ -117,13 +116,7 @@ function parseChannel(
117
116
  address: row.address,
118
117
  isPrimary: row.isPrimary,
119
118
  externalChatId: row.externalChatId,
120
- status: row.status as ContactChannel["status"],
121
- policy: row.policy as ContactChannel["policy"],
122
- verifiedAt: row.verifiedAt,
123
- verifiedVia: row.verifiedVia,
124
119
  inviteId: row.inviteId,
125
- revokedReason: row.revokedReason,
126
- blockedReason: row.blockedReason,
127
120
  lastSeenAt: row.lastSeenAt,
128
121
  interactionCount: row.interactionCount,
129
122
  lastInteraction: row.lastInteraction,
@@ -145,6 +138,8 @@ function getChannelsForContact(contactId: string): ContactChannel[] {
145
138
 
146
139
  function withChannels(contact: Contact): ContactWithChannels {
147
140
  const channels = getChannelsForContact(contact.id);
141
+ // INFO telemetry aggregated from channel rows (not ACL): sum interaction
142
+ // counts, take the most recent interaction across channels.
148
143
  const interactionCount = channels.reduce(
149
144
  (sum, ch) => sum + ch.interactionCount,
150
145
  0,
@@ -225,7 +220,6 @@ export function upsertContact(params: {
225
220
  notes?: string | null;
226
221
  role?: ContactRole;
227
222
  contactType?: ContactType;
228
- principalId?: string | null;
229
223
  userFile?: string | null;
230
224
  channels?: SyncChannelData[];
231
225
  /** When true, conflicting channels on other contacts are reassigned to this
@@ -260,11 +254,8 @@ export function upsertContact(params: {
260
254
  updatedAt: now,
261
255
  };
262
256
  if (params.notes !== undefined) updateSet.notes = params.notes;
263
- if (params.role !== undefined) updateSet.role = params.role;
264
257
  if (params.contactType !== undefined)
265
258
  updateSet.contactType = params.contactType;
266
- if (params.principalId !== undefined)
267
- updateSet.principalId = params.principalId;
268
259
  if (params.userFile !== undefined) updateSet.userFile = params.userFile;
269
260
 
270
261
  db.update(contacts)
@@ -298,11 +289,8 @@ export function upsertContact(params: {
298
289
  updatedAt: now,
299
290
  };
300
291
  if (params.notes !== undefined) updateSet.notes = params.notes;
301
- if (params.role !== undefined) updateSet.role = params.role;
302
292
  if (params.contactType !== undefined)
303
293
  updateSet.contactType = params.contactType;
304
- if (params.principalId !== undefined)
305
- updateSet.principalId = params.principalId;
306
294
  if (params.userFile !== undefined) updateSet.userFile = params.userFile;
307
295
 
308
296
  db.update(contacts)
@@ -319,35 +307,16 @@ export function upsertContact(params: {
319
307
 
320
308
  // Create new contact
321
309
  contactId = contactId ?? uuid();
322
- // Sibling contacts sharing a principal_id must share a user_file so every
323
- // channel for one principal resolves to the same persona + journal slug.
324
- let resolvedUserFile: string | null;
325
- if (params.userFile !== undefined) {
326
- resolvedUserFile = params.userFile;
327
- } else if (params.principalId) {
328
- const sibling = db
329
- .select({ userFile: contacts.userFile })
330
- .from(contacts)
331
- .where(
332
- and(
333
- eq(contacts.principalId, params.principalId),
334
- isNotNull(contacts.userFile),
335
- ),
336
- )
337
- .get();
338
- resolvedUserFile =
339
- sibling?.userFile ?? generateUserFileSlug(params.displayName);
340
- } else {
341
- resolvedUserFile = generateUserFileSlug(params.displayName);
342
- }
310
+ const resolvedUserFile =
311
+ params.userFile !== undefined
312
+ ? params.userFile
313
+ : generateUserFileSlug(params.displayName);
343
314
  db.insert(contacts)
344
315
  .values({
345
316
  id: contactId,
346
317
  displayName: params.displayName,
347
318
  notes: params.notes ?? null,
348
- role: params.role ?? "contact",
349
319
  contactType: params.contactType ?? "human",
350
- principalId: params.principalId ?? null,
351
320
  userFile: resolvedUserFile,
352
321
  createdAt: now,
353
322
  updatedAt: now,
@@ -396,27 +365,12 @@ function syncChannels(
396
365
  .get();
397
366
 
398
367
  if (existing) {
399
- // Preserve guardian blocks: if the channel is blocked, do not overwrite
400
- // its status/policy — mirrors the guard in the cross-contact reassignment
401
- // path so a blocked channel cannot be unblocked via a same-contact sync.
402
- const isBlocked = existing.status === "blocked";
403
-
404
368
  const updateSet: Record<string, unknown> = {};
405
369
  // Self-heal legacy lowercased addresses to canonical form.
406
370
  if (existing.address !== ch.address) updateSet.address = ch.address;
407
371
  if (ch.isPrimary !== undefined) updateSet.isPrimary = ch.isPrimary;
408
372
  if (ch.externalChatId !== undefined)
409
373
  updateSet.externalChatId = ch.externalChatId;
410
- if (!isBlocked) {
411
- if (ch.status !== undefined) updateSet.status = ch.status;
412
- if (ch.policy !== undefined) updateSet.policy = ch.policy;
413
- if (ch.revokedReason !== undefined)
414
- updateSet.revokedReason = ch.revokedReason;
415
- if (ch.blockedReason !== undefined)
416
- updateSet.blockedReason = ch.blockedReason;
417
- }
418
- if (ch.verifiedAt !== undefined) updateSet.verifiedAt = ch.verifiedAt;
419
- if (ch.verifiedVia !== undefined) updateSet.verifiedVia = ch.verifiedVia;
420
374
  if (ch.inviteId !== undefined) updateSet.inviteId = ch.inviteId;
421
375
 
422
376
  if (Object.keys(updateSet).length > 0) {
@@ -434,11 +388,6 @@ function syncChannels(
434
388
 
435
389
  if (conflicting) {
436
390
  if (reassignConflicting) {
437
- // Preserve guardian blocks: if the existing channel is blocked, do not
438
- // overwrite its status/policy — a valid invite must not bypass an
439
- // explicit guardian block on a different contact.
440
- const isBlocked = conflicting.status === "blocked";
441
-
442
391
  // Reassign the channel to the target contact. Used by invite redemption
443
392
  // to bind a redeemer's existing channel identity to the invite's target.
444
393
  const reassignSet: Record<string, unknown> = {
@@ -447,17 +396,6 @@ function syncChannels(
447
396
  };
448
397
  if (ch.externalChatId !== undefined)
449
398
  reassignSet.externalChatId = ch.externalChatId;
450
- if (!isBlocked) {
451
- if (ch.status !== undefined) reassignSet.status = ch.status;
452
- if (ch.policy !== undefined) reassignSet.policy = ch.policy;
453
- if (ch.revokedReason !== undefined)
454
- reassignSet.revokedReason = ch.revokedReason;
455
- if (ch.blockedReason !== undefined)
456
- reassignSet.blockedReason = ch.blockedReason;
457
- }
458
- if (ch.verifiedAt !== undefined) reassignSet.verifiedAt = ch.verifiedAt;
459
- if (ch.verifiedVia !== undefined)
460
- reassignSet.verifiedVia = ch.verifiedVia;
461
399
  if (ch.inviteId !== undefined) reassignSet.inviteId = ch.inviteId;
462
400
 
463
401
  db.update(contactChannels)
@@ -478,10 +416,6 @@ function syncChannels(
478
416
  address: ch.address,
479
417
  isPrimary: ch.isPrimary ?? false,
480
418
  externalChatId: ch.externalChatId ?? null,
481
- status: ch.status ?? "unverified",
482
- policy: ch.policy ?? "allow",
483
- verifiedAt: ch.verifiedAt ?? null,
484
- verifiedVia: ch.verifiedVia ?? null,
485
419
  inviteId: ch.inviteId ?? null,
486
420
  createdAt: now,
487
421
  updatedAt: now,
@@ -494,7 +428,6 @@ export function searchContacts(params: {
494
428
  query?: string;
495
429
  channelAddress?: string;
496
430
  channelType?: string;
497
- role?: ContactRole;
498
431
  contactType?: ContactType;
499
432
  limit?: number;
500
433
  }): ContactWithChannels[] {
@@ -534,7 +467,6 @@ export function searchContacts(params: {
534
467
  const contact = getContactInternal(id);
535
468
  if (
536
469
  contact &&
537
- (!params.role || contact.role === params.role) &&
538
470
  (!params.contactType || contact.contactType === params.contactType) &&
539
471
  (!sanitizedQuery ||
540
472
  (contact.displayName &&
@@ -564,7 +496,6 @@ export function searchContacts(params: {
564
496
  const contact = getContactInternal(id);
565
497
  if (
566
498
  contact &&
567
- (!params.role || contact.role === params.role) &&
568
499
  (!params.contactType || contact.contactType === params.contactType)
569
500
  ) {
570
501
  results.push(contact);
@@ -577,14 +508,11 @@ export function searchContacts(params: {
577
508
  const conditions = [];
578
509
  if (params.query) {
579
510
  const sanitized = escapeLike(params.query);
580
- if (!sanitized && !params.role && !params.contactType) return [];
511
+ if (!sanitized && !params.contactType) return [];
581
512
  if (sanitized) {
582
513
  conditions.push(like(contacts.displayName, `%${sanitized}%`));
583
514
  }
584
515
  }
585
- if (params.role) {
586
- conditions.push(eq(contacts.role, params.role));
587
- }
588
516
  if (params.contactType) {
589
517
  conditions.push(eq(contacts.contactType, params.contactType));
590
518
  }
@@ -633,20 +561,16 @@ export function searchContacts(params: {
633
561
 
634
562
  export function listContacts(
635
563
  limit = 50,
636
- role?: ContactRole,
637
564
  contactType?: ContactType,
638
565
  opts?: { uncapped?: boolean },
639
566
  ): ContactWithChannels[] {
640
567
  const db = getDb();
641
568
  const effectiveLimit = opts?.uncapped ? limit : Math.min(limit, 200);
642
- const conditions = [];
643
- if (role) conditions.push(eq(contacts.role, role));
644
- if (contactType) conditions.push(eq(contacts.contactType, contactType));
645
569
  const rows = db
646
570
  .select()
647
571
  .from(contacts)
648
- .where(conditions.length === 1 ? conditions[0] : and(...conditions))
649
- .orderBy(sql`${contacts.role} = 'guardian' DESC`, desc(contacts.updatedAt))
572
+ .where(contactType ? eq(contacts.contactType, contactType) : undefined)
573
+ .orderBy(desc(contacts.updatedAt))
650
574
  .limit(effectiveLimit)
651
575
  .all();
652
576
  return rows.map((r) => withChannels(parseContact(r)));
@@ -758,7 +682,8 @@ export function findContactByAddress(
758
682
  /**
759
683
  * Find a contact by channel external chat ID. Fallback for callers that only
760
684
  * have a chat ID (no user-level address) — matches by (type, externalChatId).
761
- * No unique constraint exists on externalChatId, so ORDER BY is needed.
685
+ * No unique constraint exists on externalChatId, so ORDER BY is needed for a
686
+ * deterministic pick; channel ranking (status) is owned by the gateway now.
762
687
  */
763
688
  function findContactByChannelExternalChatId(
764
689
  channelType: string,
@@ -774,14 +699,7 @@ function findContactByChannelExternalChatId(
774
699
  eq(contactChannels.externalChatId, externalChatId),
775
700
  ),
776
701
  )
777
- .orderBy(
778
- sql`CASE ${contactChannels.status}
779
- WHEN 'active' THEN 0
780
- WHEN 'unverified' THEN 1
781
- ELSE 2
782
- END`,
783
- desc(contactChannels.updatedAt),
784
- )
702
+ .orderBy(desc(contactChannels.updatedAt), desc(contactChannels.createdAt))
785
703
  .get();
786
704
  if (!channel) return null;
787
705
  return getContactInternal(channel.contactId);
@@ -830,131 +748,10 @@ export function findContactChannel(params: {
830
748
  }
831
749
 
832
750
  /**
833
- * Find the guardian contact and their specific channel entry for a given channel type.
834
- * This is the contacts-based equivalent of getGuardianBinding(assistantId, channel).
835
- * Returns null if no guardian contact has a channel of the specified type.
836
- */
837
- export function findGuardianForChannel(
838
- channelType: string,
839
- ): { contact: Contact; channel: ContactChannel } | null {
840
- const db = getDb();
841
- const conditions = [
842
- eq(contacts.role, "guardian"),
843
- eq(contactChannels.type, channelType),
844
- eq(contactChannels.status, "active"),
845
- ];
846
- const rows = db
847
- .select({
848
- contact: contacts,
849
- channel: contactChannels,
850
- })
851
- .from(contacts)
852
- .innerJoin(contactChannels, eq(contacts.id, contactChannels.contactId))
853
- .where(and(...conditions))
854
- .orderBy(desc(contactChannels.verifiedAt))
855
- .limit(1)
856
- .all();
857
-
858
- if (rows.length === 0) return null;
859
- const row = rows[0];
860
- return {
861
- contact: parseContact(row.contact),
862
- channel: parseChannel(row.channel),
863
- };
864
- }
865
-
866
- /**
867
- * List all active channels for guardian contacts.
868
- * This is the contacts-based equivalent of listActiveBindingsByAssistant(assistantId).
869
- * Joins contacts+channels with status='active' in a single query so we never
870
- * pick a guardian that has no active channels.
871
- * Returns channels ordered by most-recently-verified first.
872
- */
873
- export function listGuardianChannels(): {
874
- contact: Contact;
875
- channels: ContactChannel[];
876
- } | null {
877
- const db = getDb();
878
- const rows = db
879
- .select({
880
- contact: contacts,
881
- channel: contactChannels,
882
- })
883
- .from(contacts)
884
- .innerJoin(contactChannels, eq(contacts.id, contactChannels.contactId))
885
- .where(
886
- and(eq(contacts.role, "guardian"), eq(contactChannels.status, "active")),
887
- )
888
- .orderBy(desc(contactChannels.verifiedAt))
889
- .all();
890
-
891
- if (rows.length === 0) return null;
892
-
893
- // Use the first row's contact (the guardian with the most-recently-verified
894
- // active channel) and collect all active channels for that contact.
895
- const guardian = parseContact(rows[0].contact);
896
- const channels = rows
897
- .filter((r) => r.contact.id === guardian.id)
898
- .map((r) => parseChannel(r.channel));
899
-
900
- return { contact: guardian, channels };
901
- }
902
-
903
- /**
904
- * Update a channel's access-control fields (status, policy, reasons).
905
- * Returns the updated channel, or null if the channel does not exist.
906
- */
907
- export function updateChannelStatus(
908
- channelId: string,
909
- params: {
910
- status?: ChannelStatus;
911
- policy?: ChannelPolicy;
912
- revokedReason?: string | null;
913
- blockedReason?: string | null;
914
- },
915
- ): ContactChannel | null {
916
- const db = getDb();
917
- const existing = db
918
- .select()
919
- .from(contactChannels)
920
- .where(eq(contactChannels.id, channelId))
921
- .get();
922
-
923
- if (!existing) return null;
924
-
925
- const updateSet: Record<string, unknown> = {};
926
- if (params.status !== undefined) updateSet.status = params.status;
927
- if (params.policy !== undefined) updateSet.policy = params.policy;
928
- if (params.revokedReason !== undefined)
929
- updateSet.revokedReason = params.revokedReason;
930
- if (params.blockedReason !== undefined)
931
- updateSet.blockedReason = params.blockedReason;
932
-
933
- if (Object.keys(updateSet).length > 0) {
934
- updateSet.updatedAt = Date.now();
935
- db.update(contactChannels)
936
- .set(updateSet)
937
- .where(eq(contactChannels.id, channelId))
938
- .run();
939
-
940
- const updated = db
941
- .select()
942
- .from(contactChannels)
943
- .where(eq(contactChannels.id, channelId))
944
- .get();
945
-
946
- const result = updated ? parseChannel(updated) : null;
947
- emitContactChange();
948
- return result;
949
- }
950
-
951
- return parseChannel(existing);
952
- }
953
-
954
- /**
955
- * Update a guardian contact's principalId and its channel's identity fields.
956
- * Used for healing guardian binding drift when the JWT principal no longer
957
- * matches the stored guardian binding after a DB reset.
751
+ * Heal a guardian channel's identity address when the JWT principal no longer
752
+ * matches the stored guardian binding after a DB reset. The principalId ACL
753
+ * column is gateway-owned and no longer written here; only the channel identity
754
+ * address is repaired.
958
755
  *
959
756
  * Returns false if the update would violate the unique (type, address)
960
757
  * constraint on contact_channels — e.g. when the incoming principal already
@@ -962,7 +759,7 @@ export function updateChannelStatus(
962
759
  * In that case the heal is skipped and trust stays `unknown`.
963
760
  */
964
761
  export function updateContactPrincipalAndChannel(
965
- contactId: string,
762
+ _contactId: string,
966
763
  channelId: string,
967
764
  newPrincipalId: string,
968
765
  ): boolean {
@@ -983,20 +780,13 @@ export function updateContactPrincipalAndChannel(
983
780
  return false;
984
781
  }
985
782
 
986
- db.transaction(() => {
987
- db.update(contacts)
988
- .set({ principalId: newPrincipalId, updatedAt: now })
989
- .where(eq(contacts.id, contactId))
990
- .run();
991
-
992
- db.update(contactChannels)
993
- .set({
994
- address: newPrincipalId,
995
- updatedAt: now,
996
- })
997
- .where(eq(contactChannels.id, channelId))
998
- .run();
999
- });
783
+ db.update(contactChannels)
784
+ .set({
785
+ address: newPrincipalId,
786
+ updatedAt: now,
787
+ })
788
+ .where(eq(contactChannels.id, channelId))
789
+ .run();
1000
790
 
1001
791
  emitContactChange();
1002
792
  return true;
@@ -6,47 +6,33 @@
6
6
  * identity and access-control state.
7
7
  */
8
8
 
9
- import { emitContactChange } from "./contact-events.js";
10
9
  import {
11
10
  findContactChannel,
12
- findGuardianForChannel,
13
11
  getChannelById,
14
12
  getContact,
15
13
  getContactInternal,
16
- updateChannelStatus,
17
14
  upsertContact,
18
15
  } from "./contact-store.js";
19
- import type {
20
- ChannelPolicy,
21
- ChannelStatus,
22
- ContactRole,
23
- ContactWriteResult,
24
- } from "./types.js";
16
+ import type { ContactWriteResult } from "./types.js";
25
17
 
26
18
  // ── Guardian operations ──────────────────────────────────────────────
27
19
 
28
20
  /**
29
- * Revoke a guardian binding by updating the contacts table.
30
- * Returns true when a guardian channel was found and revoked, false otherwise.
21
+ * No-op shim: the guardian channel ACL revoke is gateway-owned (relayed via
22
+ * mark_channel_revoked). Retained while callers still invoke it; the return is
23
+ * discarded.
31
24
  */
32
- export function revokeGuardianBinding(channel: string): boolean {
33
- const guardian = findGuardianForChannel(channel);
34
- if (!guardian) return false;
35
-
36
- updateChannelStatus(guardian.channel.id, {
37
- status: "revoked",
38
- revokedReason: "binding_revoked",
39
- });
40
- emitContactChange();
41
- return true;
25
+ export function revokeGuardianBinding(_channel: string): boolean {
26
+ return false;
42
27
  }
43
28
 
44
29
  // ── Member operations ────────────────────────────────────────────────
45
30
 
46
31
  /**
47
- * Upsert a contact and channel by writing to the contacts table.
48
- * Returns the native Contact + ContactChannel, or null if no usable
49
- * identity was provided or the lookup failed after upsert.
32
+ * Upsert a contact and channel identity by writing to the contacts table.
33
+ * Persists only identity/INFO the gateway owns the ACL verdict. Returns the
34
+ * native Contact + ContactChannel, or null if no usable identity was provided
35
+ * or the lookup failed after upsert.
50
36
  */
51
37
  export function upsertContactChannel(params: {
52
38
  sourceChannel: string;
@@ -54,12 +40,7 @@ export function upsertContactChannel(params: {
54
40
  externalChatId?: string;
55
41
  displayName?: string;
56
42
  username?: string;
57
- policy?: string;
58
- status?: string;
59
43
  inviteId?: string;
60
- verifiedAt?: number;
61
- verifiedVia?: string;
62
- role?: ContactRole;
63
44
  contactId?: string;
64
45
  }): ContactWriteResult | null {
65
46
  let address: string;
@@ -88,19 +69,12 @@ export function upsertContactChannel(params: {
88
69
  upsertContact({
89
70
  id: params.contactId,
90
71
  displayName,
91
- role: params.role,
92
72
  channels: [
93
73
  {
94
74
  type: params.sourceChannel,
95
75
  address,
96
76
  externalChatId: params.externalChatId ?? null,
97
- status: (params.status as ChannelStatus) ?? undefined,
98
- policy: (params.policy as ChannelPolicy) ?? undefined,
99
77
  inviteId: params.inviteId ?? null,
100
- revokedReason: params.status === "active" ? null : undefined,
101
- blockedReason: params.status === "active" ? null : undefined,
102
- verifiedAt: params.verifiedAt ?? undefined,
103
- verifiedVia: params.verifiedVia ?? undefined,
104
78
  },
105
79
  ],
106
80
  // When a specific contactId is provided, reassign conflicting channels from
@@ -129,32 +103,21 @@ export function upsertContactChannel(params: {
129
103
  }
130
104
 
131
105
  /**
132
- * Revoke a contact channel by updating its status.
133
- * The memberId may be a plain channel ID (internal callers) or a composite
134
- * contactId:channelId (from the API response format).
106
+ * Resolve the native contact/channel for a member id. The ACL downgrade is
107
+ * gateway-owned (relayed via mark_channel_revoked); this no longer mutates
108
+ * local status. The memberId may be a plain channel ID (internal callers) or a
109
+ * composite contactId:channelId (from the API response format).
135
110
  */
136
- export function revokeMember(
137
- memberId: string,
138
- reason?: string,
139
- ): ContactWriteResult | null {
111
+ export function revokeMember(memberId: string): ContactWriteResult | null {
140
112
  const channelId = memberId.includes(":") ? memberId.split(":")[1] : memberId;
141
113
 
142
114
  const channelRow = getChannelById(channelId);
143
115
  if (!channelRow) return null;
144
- if (channelRow.status !== "active" && channelRow.status !== "pending")
145
- return null;
146
-
147
- updateChannelStatus(channelId, {
148
- status: "revoked",
149
- revokedReason: reason ?? null,
150
- });
151
116
 
152
- // Use unscoped lookup — the contact was already resolved via channel ID
153
117
  const contact = getContactInternal(channelRow.contactId);
154
118
  if (!contact) return null;
155
- const updatedChannel = contact.channels.find((ch) => ch.id === channelId);
156
- if (!updatedChannel) return null;
119
+ const channel = contact.channels.find((ch) => ch.id === channelId);
120
+ if (!channel) return null;
157
121
 
158
- emitContactChange();
159
- return { contact, channel: updatedChannel };
122
+ return { contact, channel };
160
123
  }
@@ -0,0 +1,51 @@
1
+ import { GetContactIpcResponseSchema } from "@vellumai/gateway-client/gateway-ipc-contracts";
2
+
3
+ import { ipcCallPersistent } from "../ipc/gateway-client.js";
4
+ import { getLogger } from "../util/logger.js";
5
+ import type { ContactChannel } from "./types.js";
6
+
7
+ const log = getLogger("gateway-channel-read");
8
+
9
+ /**
10
+ * Read a contact channel's verified state from the gateway contact-channel read
11
+ * (ACL source of truth). Covers all contacts, not just guardian deliveries.
12
+ *
13
+ * Matches the gateway row by logical identity — `(type, address)`,
14
+ * case-insensitive — not by id, so a reconcile-divergent row that the gateway
15
+ * write helpers re-keyed under a different UUID is still found.
16
+ *
17
+ * Returns `undefined` for unreachable reads (gateway down, IPC timeout, schema
18
+ * mismatch) or when no such channel exists, so callers fail open.
19
+ */
20
+ export async function gatewayContactChannelState(
21
+ channel: Pick<ContactChannel, "contactId" | "type" | "address">,
22
+ ): Promise<{ status: string; verifiedAt: number | null } | undefined> {
23
+ let result: unknown;
24
+ try {
25
+ result = await ipcCallPersistent("contacts_get_rich", {
26
+ contactId: channel.contactId,
27
+ });
28
+ } catch (err) {
29
+ log.warn(
30
+ { err, contactId: channel.contactId },
31
+ "contacts_get_rich unreachable — failing open",
32
+ );
33
+ return undefined;
34
+ }
35
+ if (!result || (result as { contact?: unknown }).contact == null) {
36
+ return undefined;
37
+ }
38
+ const parsed = GetContactIpcResponseSchema.safeParse(result);
39
+ if (!parsed.success) {
40
+ log.warn(
41
+ { err: parsed.error, contactId: channel.contactId },
42
+ "contacts_get_rich response failed schema parse — failing open",
43
+ );
44
+ return undefined;
45
+ }
46
+ const address = channel.address.toLowerCase();
47
+ const ch = parsed.data.contact.channels.find(
48
+ (c) => c.type === channel.type && c.address.toLowerCase() === address,
49
+ );
50
+ return ch ? { status: ch.status, verifiedAt: ch.verifiedAt } : undefined;
51
+ }