@vellumai/assistant 0.11.3 → 0.11.4-dev.202608190019.b94dbf2

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 (596) hide show
  1. package/AGENTS.md +9 -3
  2. package/ARCHITECTURE.md +32 -10
  3. package/docs/architecture/memory.md +43 -5
  4. package/docs/architecture/security.md +96 -152
  5. package/docs/architecture/turn-actor.md +70 -0
  6. package/docs/browser-use-architecture-phase2.md +128 -56
  7. package/docs/flux-turn-detection-spike.md +248 -0
  8. package/docs/guardian-request-flow.md +35 -0
  9. package/docs/stt-provider-onboarding.md +3 -1
  10. package/knip.json +3 -0
  11. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  12. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  13. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  14. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  15. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
  16. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  17. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  18. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  19. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  20. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  21. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
  22. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  23. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +34 -0
  24. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +166 -1
  25. package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
  26. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  27. package/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  28. package/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  29. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  30. package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
  31. package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  32. package/openapi.yaml +289 -113
  33. package/package.json +1 -1
  34. package/scripts/voice-ttft-spike.ts +3 -3
  35. package/scripts/write-plugin-api-shim.ts +10 -0
  36. package/src/__tests__/app-compiler.test.ts +38 -3
  37. package/src/__tests__/app-control-flow.test.ts +1 -0
  38. package/src/__tests__/approval-routes-http.test.ts +2 -2
  39. package/src/__tests__/assistant-feature-flag-guard.test.ts +25 -3
  40. package/src/__tests__/attachments-store.test.ts +21 -12
  41. package/src/__tests__/byok-default-profile-ensure.test.ts +17 -0
  42. package/src/__tests__/call-setup-flow-name-capture.test.ts +0 -1
  43. package/src/__tests__/call-site-routing-provider.test.ts +1 -1
  44. package/src/__tests__/channel-availability-routes.test.ts +14 -1
  45. package/src/__tests__/channel-capabilities-dedupe.test.ts +214 -0
  46. package/src/__tests__/channel-delivery-store.test.ts +14 -14
  47. package/src/__tests__/channel-inbound-disk-pressure.test.ts +4 -4
  48. package/src/__tests__/channel-setup-panel-ack.test.ts +1 -1
  49. package/src/__tests__/checker.test.ts +20 -314
  50. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +6 -2
  51. package/src/__tests__/compaction-events.test.ts +8 -10
  52. package/src/__tests__/config-loader-backfill.test.ts +3 -3
  53. package/src/__tests__/config-schema.test.ts +25 -10
  54. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  55. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +8 -11
  56. package/src/__tests__/conversation-agent-loop-overflow.test.ts +8 -11
  57. package/src/__tests__/conversation-agent-loop.test.ts +32 -21
  58. package/src/__tests__/conversation-attachments.test.ts +0 -1
  59. package/src/__tests__/conversation-attention-store.test.ts +63 -0
  60. package/src/__tests__/conversation-confirmation-signals.test.ts +112 -0
  61. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -4
  62. package/src/__tests__/conversation-fork-crud.test.ts +69 -0
  63. package/src/__tests__/conversation-fork-referential.test.ts +67 -0
  64. package/src/__tests__/conversation-fork-retrospective.test.ts +24 -0
  65. package/src/__tests__/conversation-load-history-repair.test.ts +209 -0
  66. package/src/__tests__/conversation-notifiers-provenance.test.ts +59 -0
  67. package/src/__tests__/conversation-queue.test.ts +154 -4
  68. package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
  69. package/src/__tests__/conversation-routes-enabled-plugins.test.ts +1 -1
  70. package/src/__tests__/conversation-routes-guardian-reply.test.ts +9 -9
  71. package/src/__tests__/conversation-routes-hidden-queue.test.ts +1 -1
  72. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -1
  73. package/src/__tests__/conversation-runtime-assembly.test.ts +187 -102
  74. package/src/__tests__/conversation-runtime-workspace.test.ts +14 -10
  75. package/src/__tests__/conversation-slash-queue.test.ts +3 -0
  76. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
  77. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +1 -0
  78. package/src/__tests__/conversation-surfaces-app-control.test.ts +1 -0
  79. package/src/__tests__/conversation-surfaces-app-open.test.ts +1 -1
  80. package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -1
  81. package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +21 -14
  82. package/src/__tests__/conversation-surfaces-queued-emit.test.ts +1 -0
  83. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1 -0
  84. package/src/__tests__/conversation-surfaces-standalone.test.ts +1 -0
  85. package/src/__tests__/conversation-surfaces-state-update.test.ts +1 -1
  86. package/src/__tests__/conversation-surfaces-table-action.test.ts +1 -1
  87. package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -1
  88. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +1 -1
  89. package/src/__tests__/conversation-tool-setup-attribution.test.ts +1 -1
  90. package/src/__tests__/credential-prompt-route.test.ts +7 -10
  91. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  92. package/src/__tests__/cu-unified-flow.test.ts +1 -0
  93. package/src/__tests__/custom-profile-ensure.test.ts +5 -1
  94. package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
  95. package/src/__tests__/discord-access-request-privacy.test.ts +5 -1
  96. package/src/__tests__/discord-requester-notice-privacy.test.ts +3 -3
  97. package/src/__tests__/document-append-idempotency.test.ts +233 -0
  98. package/src/__tests__/document-sync-tags.test.ts +0 -75
  99. package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
  100. package/src/__tests__/edit-propagation.test.ts +107 -11
  101. package/src/__tests__/file-ops-service.test.ts +163 -30
  102. package/src/__tests__/filesystem-tools.test.ts +23 -24
  103. package/src/__tests__/gateway-only-guard.test.ts +2 -5
  104. package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
  105. package/src/__tests__/helpers/mock-actor-context.ts +49 -0
  106. package/src/__tests__/helpers/mock-conversation.ts +13 -1
  107. package/src/__tests__/host-file-read-tool.test.ts +16 -19
  108. package/src/__tests__/http-user-message-parity.test.ts +1 -1
  109. package/src/__tests__/init-feature-flag-overrides.test.ts +49 -0
  110. package/src/__tests__/injector-chain.test.ts +63 -41
  111. package/src/__tests__/injector-disk-pressure.test.ts +11 -23
  112. package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
  113. package/src/__tests__/llm-context-resolution.test.ts +73 -1
  114. package/src/__tests__/llm-schema.test.ts +5 -2
  115. package/src/__tests__/managed-skill-lifecycle.test.ts +7 -0
  116. package/src/__tests__/mcp-list-plugin-servers.test.ts +250 -0
  117. package/src/__tests__/media-generate-image.test.ts +131 -21
  118. package/src/__tests__/memory-retrieval-hook.test.ts +100 -7
  119. package/src/__tests__/messages-read-boundary-guard.test.ts +134 -0
  120. package/src/__tests__/mtime-cache.test.ts +3 -1
  121. package/src/__tests__/non-member-access-request.test.ts +0 -20
  122. package/src/__tests__/outbound-slack-persistence.test.ts +40 -1
  123. package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
  124. package/src/__tests__/plugin-api-store-credential.test.ts +261 -0
  125. package/src/__tests__/plugin-api-webhook-url.test.ts +10 -7
  126. package/src/__tests__/plugin-disabled-state.test.ts +2 -0
  127. package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
  128. package/src/__tests__/plugin-execution-context.test.ts +73 -0
  129. package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -1
  130. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +7 -4
  131. package/src/__tests__/plugin-secret-pattern-contribution.test.ts +1 -1
  132. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +14 -7
  133. package/src/__tests__/provider-commit-message-generator.test.ts +20 -0
  134. package/src/__tests__/proxy-approval-callback.test.ts +1 -0
  135. package/src/__tests__/qdrant-manager.test.ts +14 -1
  136. package/src/__tests__/reaction-persistence.test.ts +200 -7
  137. package/src/__tests__/require-fresh-approval.test.ts +0 -4
  138. package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
  139. package/src/__tests__/run-conversation-turn-persistence.test.ts +460 -105
  140. package/src/__tests__/run-due-schedules.test.ts +21 -0
  141. package/src/__tests__/scaffold-managed-skill-tool.test.ts +187 -18
  142. package/src/__tests__/schedule-routes.test.ts +23 -0
  143. package/src/__tests__/schedule-store.test.ts +17 -0
  144. package/src/__tests__/scoped-approval-grants.test.ts +11 -6
  145. package/src/__tests__/secret-ingress-channel.test.ts +0 -1
  146. package/src/__tests__/secret-ingress-http.test.ts +1 -1
  147. package/src/__tests__/send-endpoint-busy.test.ts +3 -3
  148. package/src/__tests__/skills.test.ts +32 -0
  149. package/src/__tests__/slack-edit-ordering-characterization.test.ts +0 -1
  150. package/src/__tests__/starter-task-flow.test.ts +5 -4
  151. package/src/__tests__/subagent-call-site-routing.test.ts +31 -19
  152. package/src/__tests__/subagent-fork-prompt-role.test.ts +1 -1
  153. package/src/__tests__/subagent-spawn-and-await.test.ts +18 -17
  154. package/src/__tests__/subagent-tool-gate-mode.test.ts +1 -1
  155. package/src/__tests__/subagent-tools.test.ts +81 -101
  156. package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +1 -0
  157. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
  158. package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
  159. package/src/__tests__/tool-executor.test.ts +68 -39
  160. package/src/__tests__/turn-events-store.test.ts +43 -0
  161. package/src/__tests__/ui-choice-copy-surfaces.test.ts +1 -1
  162. package/src/__tests__/ui-shape-teaching.test.ts +33 -0
  163. package/src/__tests__/ui-visual-surface.test.ts +1 -1
  164. package/src/__tests__/ui-voice-picker-surface.test.ts +128 -0
  165. package/src/__tests__/ui-work-result-surface.test.ts +1 -1
  166. package/src/__tests__/user-plugin-loader.test.ts +3 -1
  167. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  168. package/src/__tests__/visible-app-context.test.ts +16 -9
  169. package/src/__tests__/voice-config-update.test.ts +40 -0
  170. package/src/__tests__/voice-scoped-grant-consumer.test.ts +5 -3
  171. package/src/__tests__/voice-session-bridge.test.ts +85 -29
  172. package/src/__tests__/worker-entrypoint-guards.test.ts +54 -0
  173. package/src/__tests__/worker-plugin-surface.test.ts +77 -0
  174. package/src/__tests__/workspace-migration-142-consolidate-voice-front-door.test.ts +158 -0
  175. package/src/__tests__/workspace-migration-143-repair-deprecated-codex-model-id.test.ts +133 -0
  176. package/src/__tests__/workspace-migration-144-convert-stranded-subscription-openai-profiles.test.ts +316 -0
  177. package/src/__tests__/workspace-migration-145-collapse-profile-bindings-to-entries.test.ts +325 -0
  178. package/src/__tests__/workspace-migration-146-repair-retired-fireworks-deepseek-flash-model-id.test.ts +235 -0
  179. package/src/acp/__tests__/acp-claude-oauth.test.ts +10 -2
  180. package/src/acp/__tests__/auth-required.test.ts +161 -0
  181. package/src/acp/acp-claude-oauth.ts +19 -2
  182. package/src/acp/agent-process.test.ts +100 -0
  183. package/src/acp/agent-process.ts +29 -26
  184. package/src/acp/auth-required.ts +102 -0
  185. package/src/acp/session-manager.test.ts +119 -0
  186. package/src/acp/session-manager.ts +76 -3
  187. package/src/api/events/acp-auth-required.ts +55 -0
  188. package/src/api/events/host-file.ts +2 -2
  189. package/src/api/index.ts +7 -0
  190. package/src/api/surfaces.ts +12 -3
  191. package/src/apps/app-store.ts +3 -0
  192. package/src/bundler/package-resolver.ts +2 -30
  193. package/src/calls/__tests__/voice-session-bridge.test.ts +194 -11
  194. package/src/calls/__tests__/voice-triage-escalate.test.ts +102 -0
  195. package/src/calls/call-controller.ts +19 -3
  196. package/src/calls/call-setup-flow.ts +0 -1
  197. package/src/calls/media-stream-stt-session.ts +15 -0
  198. package/src/calls/voice-session-bridge.ts +115 -41
  199. package/src/calls/voice-triage-escalate.ts +105 -2
  200. package/src/channels/__tests__/plugin-channel-declarations.test.ts +161 -0
  201. package/src/channels/config.ts +13 -0
  202. package/src/channels/plugin-channel-declarations.ts +108 -0
  203. package/src/channels/types.ts +30 -0
  204. package/src/cli/AGENTS.md +5 -2
  205. package/src/cli/bundled-modules.ts +29 -0
  206. package/src/cli/commands/credentials.help.ts +2 -2
  207. package/src/cli/commands/db/repair.ts +4 -8
  208. package/src/cli/commands/domain.ts +6 -3
  209. package/src/cli/commands/email.ts +6 -3
  210. package/src/cli/commands/inference-providers.ts +1 -1
  211. package/src/cli/commands/keys.ts +8 -3
  212. package/src/cli/commands/mcp.help.ts +13 -4
  213. package/src/cli/commands/mcp.ts +9 -0
  214. package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
  215. package/src/cli/commands/memory/__tests__/memory-v3.test.ts +128 -5
  216. package/src/cli/commands/memory/index.help.ts +84 -28
  217. package/src/cli/commands/memory/index.ts +2 -0
  218. package/src/cli/commands/memory/memory-v2.ts +58 -54
  219. package/src/cli/commands/memory/memory-v3.ts +64 -0
  220. package/src/cli/commands/memory/memory-validate.ts +18 -0
  221. package/src/cli/commands/plugins.ts +85 -36
  222. package/src/cli/commands/schedules.ts +35 -1
  223. package/src/cli/commands/stt.help.ts +27 -2
  224. package/src/cli/commands/trust.ts +3 -14
  225. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +39 -0
  226. package/src/cli/lib/bundled-marketplace.json +14 -1
  227. package/src/cli/lib/upgrade-plugin.ts +42 -0
  228. package/src/config/__tests__/balanced-model-experiment.test.ts +278 -0
  229. package/src/config/__tests__/default-profile-catalog.test.ts +34 -2
  230. package/src/config/__tests__/default-provider.test.ts +6 -1
  231. package/src/config/__tests__/profile-materialization.test.ts +75 -19
  232. package/src/config/assistant-feature-flags.ts +36 -15
  233. package/src/config/balanced-model-experiment.ts +35 -0
  234. package/src/config/bundled-skills/acp/SKILL.md +6 -7
  235. package/src/config/bundled-skills/document-editor/SKILL.md +2 -2
  236. package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
  237. package/src/config/bundled-skills/image-studio/SKILL.md +5 -4
  238. package/src/config/bundled-skills/image-studio/TOOLS.json +1 -1
  239. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +101 -0
  240. package/src/config/bundled-skills/media-processing/services/preprocess.ts +14 -4
  241. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  242. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.test.ts +65 -0
  243. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +7 -1
  244. package/src/config/bundled-skills/settings/tools/shared.ts +16 -0
  245. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -1
  246. package/src/config/bundled-skills/skill-management/TOOLS.json +9 -3
  247. package/src/config/bundled-skills/subagent/SKILL.md +17 -12
  248. package/src/config/bundled-skills/subagent/TOOLS.json +4 -4
  249. package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +22 -1
  250. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +9 -2
  251. package/src/config/call-site-defaults.ts +11 -5
  252. package/src/config/default-profile-catalog.ts +179 -16
  253. package/src/config/default-profile-names.ts +4 -1
  254. package/src/config/default-provider-resolution.ts +4 -0
  255. package/src/config/feature-flag-registry.json +12 -12
  256. package/src/config/llm-context-resolution.ts +11 -3
  257. package/src/config/llm-resolver.ts +28 -1
  258. package/src/config/profile-materialization.ts +70 -22
  259. package/src/config/schemas/__tests__/live-voice.test.ts +107 -4
  260. package/src/config/schemas/call-site-catalog.ts +4 -4
  261. package/src/config/schemas/live-voice.ts +57 -23
  262. package/src/config/schemas/llm.ts +59 -32
  263. package/src/config/schemas/mcp.ts +23 -0
  264. package/src/config/schemas/plugin-updates.ts +6 -2
  265. package/src/config/schemas/stt.ts +1 -0
  266. package/src/config/skills.ts +9 -2
  267. package/src/context/outbound-sanitize.ts +96 -1
  268. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +1 -1
  269. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +82 -0
  270. package/src/daemon/conversation-agent-loop-handlers.ts +15 -10
  271. package/src/daemon/conversation-agent-loop.ts +31 -19
  272. package/src/daemon/conversation-messaging.ts +5 -1
  273. package/src/daemon/conversation-notifiers.ts +20 -10
  274. package/src/daemon/conversation-process.ts +20 -6
  275. package/src/daemon/conversation-runtime-assembly.ts +12 -6
  276. package/src/daemon/conversation-store.ts +14 -4
  277. package/src/daemon/conversation-surfaces.ts +75 -26
  278. package/src/daemon/conversation-tool-setup.ts +3 -6
  279. package/src/daemon/conversation.ts +154 -51
  280. package/src/daemon/doordash-steps.ts +2 -2
  281. package/src/daemon/lifecycle.ts +14 -1
  282. package/src/daemon/mcp-reload-service.ts +36 -6
  283. package/src/daemon/process-message.ts +13 -26
  284. package/src/daemon/providers-setup.ts +6 -3
  285. package/src/daemon/trust-context-types.ts +29 -0
  286. package/src/daemon/trust-context.ts +12 -7
  287. package/src/daemon/wake-conversation-ops.ts +3 -2
  288. package/src/daemon/windows-compiled-entry.ts +4 -0
  289. package/src/documents/document-store.ts +143 -240
  290. package/src/hooks/hook-loader.ts +3 -3
  291. package/src/hooks/registry.ts +103 -45
  292. package/src/hooks/types.ts +5 -0
  293. package/src/inbound/__tests__/oauth-callback-url.test.ts +83 -0
  294. package/src/inbound/oauth-callback-url.ts +61 -0
  295. package/src/ipc/gateway-client.test.ts +1 -1
  296. package/src/ipc/gateway-client.ts +14 -21
  297. package/src/ipc/gateway-flag-listener.ts +17 -3
  298. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +1 -104
  299. package/src/live-voice/__tests__/live-voice-events.test.ts +7 -8
  300. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +1050 -0
  301. package/src/live-voice/__tests__/live-voice-metrics.test.ts +115 -8
  302. package/src/live-voice/__tests__/live-voice-photo.test.ts +100 -0
  303. package/src/live-voice/__tests__/live-voice-progress.test.ts +60 -194
  304. package/src/live-voice/__tests__/live-voice-stt.test.ts +14 -0
  305. package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +29 -0
  306. package/src/live-voice/__tests__/live-voice-tts-session.test.ts +0 -483
  307. package/src/live-voice/__tests__/live-voice-vad.test.ts +0 -16
  308. package/src/live-voice/__tests__/progress-narration.test.ts +214 -0
  309. package/src/live-voice/live-voice-archive.ts +2 -0
  310. package/src/live-voice/live-voice-manager.ts +16 -3
  311. package/src/live-voice/live-voice-metrics.ts +57 -32
  312. package/src/live-voice/live-voice-photo.ts +1 -2
  313. package/src/live-voice/live-voice-session.ts +598 -323
  314. package/src/live-voice/progress-narration.ts +277 -0
  315. package/src/live-voice/protocol.ts +21 -1
  316. package/src/live-voice/windows-compiled-live-voice.ts +4 -0
  317. package/src/mcp/__tests__/effective-config.test.ts +238 -0
  318. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
  319. package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +200 -0
  320. package/src/mcp/__tests__/mcp-oauth-provider.test.ts +9 -9
  321. package/src/mcp/__tests__/plugin-server-credential-isolation.test.ts +95 -0
  322. package/src/mcp/client.ts +16 -11
  323. package/src/mcp/effective-config.ts +113 -0
  324. package/src/mcp/manager.ts +11 -6
  325. package/src/mcp/mcp-auth-orchestrator.ts +13 -22
  326. package/src/mcp/mcp-oauth-provider.ts +205 -240
  327. package/src/monitoring/__tests__/plugin-auto-update.test.ts +166 -3
  328. package/src/monitoring/control.ts +1 -0
  329. package/src/monitoring/db-integrity-sample.ts +4 -5
  330. package/src/monitoring/plugin-auto-update.ts +128 -24
  331. package/src/notifications/AGENTS.md +2 -0
  332. package/src/notifications/approval-card-data.ts +33 -0
  333. package/src/notifications/signal.ts +1 -0
  334. package/src/permissions/AGENTS.md +16 -0
  335. package/src/permissions/checker.test.ts +75 -122
  336. package/src/permissions/checker.ts +44 -560
  337. package/src/permissions/confirmation-guardian-request.test.ts +15 -11
  338. package/src/permissions/confirmation-guardian-request.ts +2 -2
  339. package/src/permissions/prompter.ts +1 -5
  340. package/src/permissions/question-guardian-request.test.ts +14 -6
  341. package/src/permissions/question-guardian-request.ts +1 -2
  342. package/src/persistence/attachments-store.ts +8 -1
  343. package/src/persistence/bookmark-crud.ts +3 -7
  344. package/src/persistence/conversation-attention-store.ts +16 -45
  345. package/src/persistence/conversation-crud.ts +33 -4
  346. package/src/persistence/conversation-lineage.ts +9 -0
  347. package/src/persistence/conversation-queries.ts +182 -52
  348. package/src/persistence/delivery-crud.ts +150 -36
  349. package/src/persistence/embeddings/qdrant-manager.ts +84 -49
  350. package/src/persistence/external-conversation-store.ts +32 -4
  351. package/src/persistence/llm-request-log-store.ts +4 -10
  352. package/src/persistence/llm-usage-store.ts +8 -3
  353. package/src/persistence/message-reads.test.ts +197 -0
  354. package/src/persistence/message-reads.ts +211 -0
  355. package/src/persistence/migrations/360-add-document-workspace-path.ts +5 -14
  356. package/src/persistence/migrations/366-chatgpt-subscription-row-identity.test.ts +120 -0
  357. package/src/persistence/migrations/366-chatgpt-subscription-row-identity.ts +62 -0
  358. package/src/persistence/real-user-turn-filter.ts +27 -3
  359. package/src/persistence/schema/documents.ts +4 -4
  360. package/src/persistence/steps.ts +9 -0
  361. package/src/plugin-api/__tests__/oauth-callback-url-export.test.ts +29 -0
  362. package/src/plugin-api/constants.ts +8 -0
  363. package/src/plugin-api/conversation-turn.ts +195 -5
  364. package/src/plugin-api/index.ts +38 -6
  365. package/src/plugin-api/resolve-credential.ts +3 -2
  366. package/src/plugin-api/store-credential.ts +146 -0
  367. package/src/plugin-api/vision-support.test.ts +1 -1
  368. package/src/plugin-api/webhook-url.ts +13 -11
  369. package/src/plugins/__tests__/mcp-servers.test.ts +371 -0
  370. package/src/plugins/defaults/main.ts +21 -7
  371. package/src/plugins/defaults/memory/AGENTS.md +15 -3
  372. package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +204 -0
  373. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +72 -0
  374. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +4 -1
  375. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +4 -1
  376. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
  377. package/src/plugins/defaults/memory/buffer-format.ts +165 -0
  378. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +6 -0
  379. package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +77 -0
  380. package/src/plugins/defaults/memory/graph/conversation-graph-memory.ts +24 -6
  381. package/src/plugins/defaults/memory/graph/image-ref-utils.ts +3 -0
  382. package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -30
  383. package/src/plugins/defaults/memory/graph-topology/pending-buffer.test.ts +34 -0
  384. package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +17 -24
  385. package/src/plugins/defaults/memory/hooks/post-compact.ts +1 -4
  386. package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +53 -5
  387. package/src/plugins/defaults/memory/indexer.ts +3 -1
  388. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +19 -7
  389. package/src/plugins/defaults/memory/memory-retrospective-job.ts +4 -4
  390. package/src/plugins/defaults/memory/src/__tests__/memory-v3-gate-stats.test.ts +281 -0
  391. package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
  392. package/src/plugins/defaults/memory/src/memory-v3-routes.ts +207 -0
  393. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +147 -0
  394. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
  395. package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
  396. package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
  397. package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
  398. package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
  399. package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
  400. package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +195 -58
  401. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +93 -24
  402. package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
  403. package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
  404. package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
  405. package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
  406. package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
  407. package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
  408. package/src/plugins/defaults/memory/substrate/skill-content.ts +8 -1
  409. package/src/plugins/defaults/memory/substrate/static-context.ts +160 -33
  410. package/src/plugins/defaults/memory/substrate/sweep-job.ts +2 -4
  411. package/src/plugins/defaults/memory/v1/graph/extraction.ts +3 -1
  412. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +18 -0
  413. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +66 -1
  414. package/src/plugins/defaults/memory/v3/card.ts +9 -9
  415. package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
  416. package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
  417. package/src/plugins/defaults/memory/v3/edge.ts +13 -57
  418. package/src/plugins/defaults/memory/v3/injector.ts +8 -0
  419. package/src/plugins/defaults/memory/v3/prune.ts +2 -0
  420. package/src/plugins/defaults/memory/v3/selection-log-store.ts +2 -0
  421. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +23 -9
  422. package/src/plugins/defaults/memory/worker-control.ts +1 -0
  423. package/src/plugins/defaults/memory/worker.ts +6 -3
  424. package/src/plugins/defaults/worker-entrypoints.ts +3 -0
  425. package/src/plugins/external-plugin-loader.ts +47 -0
  426. package/src/plugins/mcp-servers.ts +361 -0
  427. package/src/plugins/mtime-cache.ts +37 -46
  428. package/src/plugins/pipeline.ts +14 -7
  429. package/src/plugins/plugin-execution-context.ts +35 -11
  430. package/src/plugins/worker-plugin-surface.ts +33 -0
  431. package/src/prompts/templates/system-sections.ts +0 -7
  432. package/src/providers/__tests__/connection-model-compat.test.ts +1 -1
  433. package/src/providers/__tests__/context-overflow-error.test.ts +24 -0
  434. package/src/providers/__tests__/dispatch-connection-routing.test.ts +214 -2
  435. package/src/providers/__tests__/preflight-resolved-config.test.ts +57 -0
  436. package/src/providers/__tests__/retry-callsite.test.ts +25 -2
  437. package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
  438. package/src/providers/anthropic/client.ts +332 -241
  439. package/src/providers/call-site-routing.ts +30 -3
  440. package/src/providers/connection-resolution.ts +194 -11
  441. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +4 -0
  442. package/src/providers/inference/adapter-factory.ts +3 -0
  443. package/src/providers/inference/auth.ts +6 -0
  444. package/src/providers/inference/connection-availability.ts +24 -2
  445. package/src/providers/inference/connections.ts +2 -0
  446. package/src/providers/model-catalog.ts +3 -3
  447. package/src/providers/model-intents.ts +28 -8
  448. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +163 -40
  449. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
  450. package/src/providers/openai/chat-completions-provider.ts +133 -36
  451. package/src/providers/openai/codex-models.ts +2 -1
  452. package/src/providers/provider-send-message.ts +32 -3
  453. package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +433 -0
  454. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +625 -0
  455. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +34 -0
  456. package/src/providers/speech-to-text/__tests__/resolve.test.ts +285 -6
  457. package/src/providers/speech-to-text/deepgram-flux-frames.ts +395 -0
  458. package/src/providers/speech-to-text/deepgram-flux-realtime.ts +679 -0
  459. package/src/providers/speech-to-text/provider-catalog.ts +99 -8
  460. package/src/providers/speech-to-text/resolve.ts +25 -2
  461. package/src/routes/control.ts +1 -0
  462. package/src/routes/route-host-client.ts +1 -0
  463. package/src/routes/route-host-protocol.ts +7 -0
  464. package/src/routes/worker.ts +48 -15
  465. package/src/runtime/AGENTS.md +16 -17
  466. package/src/runtime/access-request-helper.ts +9 -12
  467. package/src/runtime/agent-wake.ts +18 -15
  468. package/src/runtime/effective-capabilities.test.ts +43 -0
  469. package/src/runtime/effective-capabilities.ts +27 -0
  470. package/src/runtime/guardian-reply-router.ts +6 -2
  471. package/src/runtime/pre-first-message-gate.ts +4 -0
  472. package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +12 -4
  473. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +522 -2
  474. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +52 -0
  475. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +61 -0
  476. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +44 -0
  477. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +102 -1
  478. package/src/runtime/routes/__tests__/plugins-routes.test.ts +132 -35
  479. package/src/runtime/routes/__tests__/schedule-routes-disarm-reason.test.ts +215 -0
  480. package/src/runtime/routes/__tests__/stt-routes.test.ts +25 -0
  481. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
  482. package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
  483. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +133 -1
  484. package/src/runtime/routes/channel-availability-routes.ts +32 -14
  485. package/src/runtime/routes/channel-route-shared.ts +0 -6
  486. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +6 -6
  487. package/src/runtime/routes/conversation-list-routes.ts +181 -23
  488. package/src/runtime/routes/conversation-management-routes.ts +17 -12
  489. package/src/runtime/routes/conversation-query-routes.ts +40 -27
  490. package/src/runtime/routes/conversation-routes.ts +43 -29
  491. package/src/runtime/routes/credential-prompt-routes.ts +4 -7
  492. package/src/runtime/routes/credential-routes.ts +26 -82
  493. package/src/runtime/routes/default-provider-routes.ts +15 -0
  494. package/src/runtime/routes/documents-routes.ts +3 -222
  495. package/src/runtime/routes/inbound-message-handler.ts +21 -45
  496. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +0 -1
  497. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -9
  498. package/src/runtime/routes/inbound-stages/admission-policy.ts +1 -17
  499. package/src/runtime/routes/inbound-stages/bootstrap-intercept.test.ts +0 -1
  500. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -3
  501. package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -79
  502. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +0 -1
  503. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +12 -7
  504. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -8
  505. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -79
  506. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +2 -3
  507. package/src/runtime/routes/inference-profiles-routes.ts +20 -11
  508. package/src/runtime/routes/inference-provider-connection-routes.ts +77 -15
  509. package/src/runtime/routes/log-export-routes.ts +3 -0
  510. package/src/runtime/routes/mcp-auth-routes.ts +148 -57
  511. package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +2 -0
  512. package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +3 -0
  513. package/src/runtime/routes/playground/inject-failures.ts +2 -2
  514. package/src/runtime/routes/playground/reset-circuit.ts +1 -1
  515. package/src/runtime/routes/plugins-routes.ts +38 -8
  516. package/src/runtime/routes/schedule-routes.ts +94 -7
  517. package/src/runtime/routes/settings-routes.ts +6 -6
  518. package/src/runtime/routes/stt-routes.ts +31 -25
  519. package/src/runtime/routes/surface-action-routes.ts +20 -19
  520. package/src/runtime/routes/surface-conversation-resolver.ts +3 -0
  521. package/src/runtime/routes/user-route-dispatcher.ts +69 -15
  522. package/src/runtime/routes/user-route-import.ts +108 -0
  523. package/src/runtime/routes/user-route-resolution.ts +18 -1
  524. package/src/runtime/routes/workspace-routes.ts +0 -9
  525. package/src/runtime/routes/workspace-utils.ts +3 -13
  526. package/src/runtime/services/conversation-serializer.ts +7 -2
  527. package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +68 -5
  528. package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +81 -0
  529. package/src/schedule/plugin-schedule-availability.ts +58 -0
  530. package/src/schedule/plugin-schedule-declarations.ts +23 -27
  531. package/src/schedule/plugin-schedule-reconciler.ts +12 -3
  532. package/src/schedule/schedule-store.ts +5 -1
  533. package/src/schedule/scheduler.ts +9 -4
  534. package/src/schedule/worker-control.ts +1 -0
  535. package/src/schedule/worker.ts +6 -0
  536. package/src/security/oauth2.ts +6 -22
  537. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +22 -0
  538. package/src/stt/__tests__/types.test.ts +94 -0
  539. package/src/stt/daemon-batch-transcriber.ts +10 -0
  540. package/src/stt/stt-stream-session.ts +8 -4
  541. package/src/stt/types.ts +103 -0
  542. package/src/subagent/__tests__/consult-prompt.test.ts +26 -15
  543. package/src/subagent/consult-context.ts +11 -11
  544. package/src/subagent/consult-prompt.ts +26 -35
  545. package/src/subagent/manager.ts +21 -40
  546. package/src/subagent/notify.ts +7 -1
  547. package/src/subagent/types.ts +15 -12
  548. package/src/tools/__tests__/tool-input-schemas.test.ts +7 -7
  549. package/src/tools/acp/spawn.test.ts +97 -0
  550. package/src/tools/acp/spawn.ts +38 -4
  551. package/src/tools/credentials/ref-parse.ts +35 -0
  552. package/src/tools/credentials/resolve.ts +4 -10
  553. package/src/tools/credentials/store.ts +168 -0
  554. package/src/tools/document/document-tool.ts +12 -3
  555. package/src/tools/executor.ts +53 -15
  556. package/src/tools/filesystem/read.ts +27 -10
  557. package/src/tools/host-filesystem/read.ts +27 -15
  558. package/src/tools/network/url-safety.ts +9 -16
  559. package/src/tools/permission-checker.ts +43 -52
  560. package/src/tools/registry.ts +2 -1
  561. package/src/tools/shared/filesystem/file-ops-service.ts +63 -35
  562. package/src/tools/shared/filesystem/legacy-read-args.ts +22 -0
  563. package/src/tools/shared/filesystem/types.ts +5 -5
  564. package/src/tools/skills/scaffold-managed.ts +25 -7
  565. package/src/tools/subagent/spawn.ts +28 -88
  566. package/src/tools/tool-types.ts +26 -13
  567. package/src/tools/types.ts +6 -6
  568. package/src/tools/ui-surface/surface-shape-docs.ts +12 -1
  569. package/src/tools/workflows/run-workflow.ts +1 -2
  570. package/src/tts/__tests__/reasoning-tag-filter.test.ts +78 -0
  571. package/src/tts/reasoning-tag-filter.ts +89 -0
  572. package/src/util/__tests__/worker-process-command.test.ts +37 -0
  573. package/src/util/logger.ts +16 -0
  574. package/src/util/think-tag-stream.ts +95 -0
  575. package/src/util/worker-process.ts +37 -4
  576. package/src/windows-compiled-cli.ts +32 -0
  577. package/src/windows-compiled-entry.ts +4 -0
  578. package/src/windows-compiled-logger.ts +6 -0
  579. package/src/windows-compiled-worker-entry.ts +29 -0
  580. package/src/workspace/byok-default-profile-ensure.ts +76 -24
  581. package/src/workspace/custom-profile-ensure.ts +4 -24
  582. package/src/workspace/migrations/142-consolidate-voice-front-door.ts +70 -0
  583. package/src/workspace/migrations/143-repair-deprecated-codex-model-id.ts +134 -0
  584. package/src/workspace/migrations/144-convert-stranded-subscription-openai-profiles.ts +265 -0
  585. package/src/workspace/migrations/145-collapse-profile-bindings-to-entries.ts +328 -0
  586. package/src/workspace/migrations/146-repair-retired-fireworks-deepseek-flash-model-id.ts +195 -0
  587. package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +0 -10
  588. package/src/workspace/migrations/registry.ts +10 -0
  589. package/src/workspace/provider-commit-message-generator.ts +7 -5
  590. package/src/__tests__/document-workspace-file.test.ts +0 -467
  591. package/src/live-voice/__tests__/front-decision.test.ts +0 -645
  592. package/src/live-voice/front-decision.ts +0 -476
  593. package/src/permissions/ipc-risk-types.ts +0 -143
  594. package/src/permissions/risk-types.ts +0 -76
  595. package/src/subagent/__tests__/consult-transcript.test.ts +0 -184
  596. package/src/subagent/consult-transcript.ts +0 -90
@@ -1,4 +1,14 @@
1
- import { and, count, desc, eq, inArray, isNull, lt, sql } from "drizzle-orm";
1
+ import {
2
+ and,
3
+ count,
4
+ desc,
5
+ eq,
6
+ getTableColumns,
7
+ isNull,
8
+ lt,
9
+ sql,
10
+ } from "drizzle-orm";
11
+ import type { SQLiteSelect } from "drizzle-orm/sqlite-core";
2
12
 
3
13
  import {
4
14
  parseExternalContentEnvelope,
@@ -26,11 +36,14 @@ import {
26
36
  parseContentRef,
27
37
  resolveMessageContentBlocks,
28
38
  } from "./message-content-file.js";
39
+ import {
40
+ countMessagesByRoleForConversations,
41
+ latestUserMessageRawContent,
42
+ } from "./message-reads.js";
29
43
  import { rawAll } from "./raw-query.js";
30
44
  import {
31
45
  conversationAssistantAttentionState,
32
46
  conversations,
33
- messages,
34
47
  } from "./schema/index.js";
35
48
 
36
49
  const log = getLogger("conversation-store");
@@ -186,7 +199,8 @@ function surfacedVisibilitySql(alias = "conversations"): string {
186
199
  * explicitly surfaced. {@link standardListingVisibilitySql} already admits
187
200
  * such rows through its surfaced and custom-group arms, so a caller that
188
201
  * must distinguish "visible in the listing" from "user-facing foreground"
189
- * (unread counting) applies this on top.
202
+ * (unread counting, the section index, a `foregroundOnly` list read) applies
203
+ * this on top.
190
204
  *
191
205
  * The SQL twin of `isBackgroundConversation` in the web client's
192
206
  * `utils/conversation-predicates.ts`.
@@ -260,6 +274,25 @@ export interface ConversationListFilter {
260
274
  * section, and a UUID one custom group. Omit to span every group.
261
275
  */
262
276
  groupId?: string;
277
+ /**
278
+ * Restrict to conversations whose latest assistant message the user has
279
+ * not seen: the same "unseen" predicate the unread count and the section
280
+ * index apply ({@link unseenAttentionStateConditions}), so every reader
281
+ * of "needs attention" agrees. Only `true` narrows; omit to span every
282
+ * conversation. A client that keeps no complete list can ask for exactly
283
+ * the rows its attention surfaces need instead of scanning for them.
284
+ */
285
+ needsAttention?: true;
286
+ /**
287
+ * Restrict to user-facing foreground conversations: drop the automated
288
+ * background/scheduled rows the standard listing admits through its
289
+ * custom-group arm ({@link notBackgroundVisibilitySql}, the same predicate
290
+ * the unread count and the section index apply on top of visibility).
291
+ * Only `true` narrows; omit to keep every visible row. A client that
292
+ * needs the newest conversation a user can open can ask for it in one
293
+ * row instead of paging past runs it would skip.
294
+ */
295
+ foregroundOnly?: true;
263
296
  }
264
297
 
265
298
  export interface ConversationListQuery extends ConversationListFilter {
@@ -324,21 +357,57 @@ function originChannelClause(originChannel: string) {
324
357
  return eq(conversations.originChannel, originChannel);
325
358
  }
326
359
 
360
+ /**
361
+ * The join between a conversation and its attention projection, one row per
362
+ * conversation in `conversation_assistant_attention_state`. Every reader of
363
+ * "unseen" joins on this: the list and count filters, the unread count, and
364
+ * the section index.
365
+ */
366
+ const attentionJoin = eq(
367
+ conversationAssistantAttentionState.conversationId,
368
+ conversations.id,
369
+ );
370
+
327
371
  function conversationListWhere(filter: ConversationListFilter) {
328
372
  const {
329
373
  conversationType = "standard",
330
374
  archiveStatus = "active",
331
375
  originChannel,
332
376
  groupId,
377
+ needsAttention,
378
+ foregroundOnly,
333
379
  } = filter;
334
380
  return and(
335
381
  conversationTypeClause(conversationType),
336
382
  archiveStatusClause(archiveStatus) ?? undefined,
337
383
  originChannel ? originChannelClause(originChannel) : undefined,
338
384
  groupId ? groupIdClause(groupId) : undefined,
385
+ ...(needsAttention ? unseenAttentionStateConditions() : []),
386
+ foregroundOnly ? sql.raw(notBackgroundVisibilitySql()) : undefined,
339
387
  );
340
388
  }
341
389
 
390
+ /**
391
+ * The joins {@link conversationListWhere}'s predicates need, applied to a
392
+ * list or count query over `conversations`. The attention predicate lives on
393
+ * `conversation_assistant_attention_state`, so `needsAttention` joins it.
394
+ * Inner, not left: a conversation with no
395
+ * attention row has no unseen message by definition. Joined only when the
396
+ * filter asks, so a query without the filter runs without the join and
397
+ * lists every conversation the predicates admit. One place for both
398
+ * {@link listConversations} and {@link countConversations}, so a page and
399
+ * its total always describe the same set.
400
+ */
401
+ function withListFilterJoins<T extends SQLiteSelect>(
402
+ query: T,
403
+ filter: ConversationListFilter,
404
+ ) {
405
+ if (!filter.needsAttention) {
406
+ return query;
407
+ }
408
+ return query.innerJoin(conversationAssistantAttentionState, attentionJoin);
409
+ }
410
+
342
411
  export function listConversations(
343
412
  query: ConversationListQuery = {},
344
413
  ): ConversationRow[] {
@@ -359,9 +428,13 @@ export function listConversations(
359
428
  // distinguish a total order from a lucky one. The guarantee becomes
360
429
  // observable, and gets its test, with the keyset pagination work.
361
430
  const tiebreak = desc(conversations.id);
362
- return db
363
- .select()
364
- .from(conversations)
431
+ // Selecting the conversation columns explicitly keeps the row shape
432
+ // `parseConversation` maps identical whether or not the attention join is
433
+ // present.
434
+ return withListFilterJoins(
435
+ db.select(getTableColumns(conversations)).from(conversations).$dynamic(),
436
+ filter,
437
+ )
365
438
  .where(conversationListWhere(filter))
366
439
  .orderBy(recency, tiebreak)
367
440
  .limit(limit ?? 100)
@@ -438,31 +511,7 @@ export function getMessageRoleStatsByConversation(
438
511
  conversationIds: string[],
439
512
  role: string = "assistant",
440
513
  ): Map<string, { count: number; lastAt: number }> {
441
- if (conversationIds.length === 0) {
442
- return new Map();
443
- }
444
- const db = getDb();
445
- const rows = db
446
- .select({
447
- conversationId: messages.conversationId,
448
- count: sql<number>`COUNT(*)`.as("count"),
449
- lastAt: sql<number>`MAX(${messages.createdAt})`.as("last_at"),
450
- })
451
- .from(messages)
452
- .where(
453
- and(
454
- inArray(messages.conversationId, conversationIds),
455
- eq(messages.role, role),
456
- ),
457
- )
458
- .groupBy(messages.conversationId)
459
- .all();
460
- return new Map(
461
- rows.map((r) => [
462
- r.conversationId,
463
- { count: Number(r.count), lastAt: Number(r.lastAt) },
464
- ]),
465
- );
514
+ return countMessagesByRoleForConversations(conversationIds, role);
466
515
  }
467
516
 
468
517
  export function listPinnedConversations(
@@ -535,6 +584,8 @@ export function listConversationsByTitlePrefix(
535
584
  const rows = rawAll<Row>(
536
585
  "conversation:listByTitlePrefix",
537
586
  `SELECT c.id, c.title,
587
+ -- Any-state count (streaming rows included): a list-surface size
588
+ -- hint where a transient +1 during a live turn is immaterial.
538
589
  (SELECT COUNT(*) FROM messages WHERE conversation_id = c.id) AS message_count,
539
590
  c.created_at
540
591
  FROM conversations c
@@ -561,9 +612,10 @@ export function countConversations(
561
612
  ): number {
562
613
  ensureGroupMigration();
563
614
  const db = getDb();
564
- const [{ total }] = db
565
- .select({ total: count() })
566
- .from(conversations)
615
+ const [{ total }] = withListFilterJoins(
616
+ db.select({ total: count() }).from(conversations).$dynamic(),
617
+ filter,
618
+ )
567
619
  .where(conversationListWhere(filter))
568
620
  .all();
569
621
  return total;
@@ -601,10 +653,7 @@ export function countUnreadConversations(): number {
601
653
  const [{ total }] = db
602
654
  .select({ total: count() })
603
655
  .from(conversations)
604
- .innerJoin(
605
- conversationAssistantAttentionState,
606
- eq(conversationAssistantAttentionState.conversationId, conversations.id),
607
- )
656
+ .innerJoin(conversationAssistantAttentionState, attentionJoin)
608
657
  .where(
609
658
  and(
610
659
  conversationTypeClause("standard"),
@@ -617,32 +666,109 @@ export function countUnreadConversations(): number {
617
666
  return total;
618
667
  }
619
668
 
669
+ /** Totals for one sidebar section: every member, and the unread among them. */
670
+ export interface ConversationSectionCount {
671
+ total: number;
672
+ unread: number;
673
+ }
674
+
620
675
  /**
621
- * Check whether the last user message in a conversation is a tool_result-only
622
- * message (i.e., not a real user-typed message). This is used by undo() to
623
- * determine if additional exchanges need to be deleted from the DB.
676
+ * Row counts for every non-empty sidebar section, with no rows fetched.
677
+ *
678
+ * Two axes, disjoint by construction because `group_id` is single-valued:
679
+ *
680
+ * - `groups`: rows filed somewhere, bucketed by `group_id`. Contains
681
+ * `'system:pinned'` and custom-group ids; the background/scheduled system
682
+ * buckets are excluded because no section renders them.
683
+ * - `channels`: rows filed nowhere, bucketed by effective origin channel,
684
+ * where NULL reads as {@link NATIVE_ORIGIN_CHANNEL} exactly as
685
+ * {@link originChannelClause} reads it. The native bucket is the Chats
686
+ * section; each other bucket is that channel's section.
687
+ *
688
+ * Only buckets with at least one row appear: GROUP BY cannot emit an empty
689
+ * bucket, and an empty section renders no card.
690
+ *
691
+ * `total` follows the standard-listing visibility
692
+ * ({@link conversationTypeClause}), active rows only, so it counts exactly
693
+ * the rows `GET /v1/conversations` would return for that section's filter.
694
+ * `unread` applies the same rules as {@link countUnreadConversations} on top
695
+ * (not an unsurfaced background/scheduled row, unseen per the attention
696
+ * projection), composed from the same predicates rather than restated, so
697
+ * the per-section numbers always sum against the global count.
624
698
  */
625
- export function isLastUserMessageToolResult(conversationId: string): boolean {
699
+ export interface ConversationSectionCounts {
700
+ groups: Array<{ groupId: string } & ConversationSectionCount>;
701
+ channels: Array<{ channel: string } & ConversationSectionCount>;
702
+ }
703
+
704
+ export function countConversationSections(): ConversationSectionCounts {
705
+ ensureGroupMigration();
626
706
  const db = getDb();
627
- const lastUserMsg = db
628
- .select({ content: messages.content })
629
- .from(messages)
707
+
708
+ /* `countUnreadConversations` gets the same effect with an INNER JOIN and
709
+ the unseen conditions in its WHERE; here every row must be counted and
710
+ only unread rows summed, so the join is LEFT and the conditions move
711
+ into the CASE. A row with no attention projection has NULL columns,
712
+ fails the conditions, and sums 0: identical membership. */
713
+ const unread = sql<number>`SUM(CASE WHEN ${and(
714
+ sql.raw(notBackgroundVisibilitySql()),
715
+ ...unseenAttentionStateConditions(),
716
+ )} THEN 1 ELSE 0 END)`;
717
+
718
+ const groups = db
719
+ .select({
720
+ groupId: sql<string>`group_id`,
721
+ total: count(),
722
+ unread,
723
+ })
724
+ .from(conversations)
725
+ .leftJoin(conversationAssistantAttentionState, attentionJoin)
630
726
  .where(
631
727
  and(
632
- eq(messages.conversationId, conversationId),
633
- eq(messages.role, "user"),
728
+ conversationTypeClause("standard"),
729
+ isNull(conversations.archivedAt),
730
+ sql`(group_id = ${PINNED_GROUP_ID} OR (group_id IS NOT NULL AND group_id NOT LIKE 'system:%'))`,
634
731
  ),
635
732
  )
636
- .orderBy(sql`rowid DESC`)
637
- .limit(1)
638
- .get();
733
+ .groupBy(sql`group_id`)
734
+ .all();
735
+
736
+ const effectiveChannel = sql<string>`COALESCE(${conversations.originChannel}, ${NATIVE_ORIGIN_CHANNEL})`;
737
+ const channels = db
738
+ .select({
739
+ channel: effectiveChannel,
740
+ total: count(),
741
+ unread,
742
+ })
743
+ .from(conversations)
744
+ .leftJoin(conversationAssistantAttentionState, attentionJoin)
745
+ .where(
746
+ and(
747
+ conversationTypeClause("standard"),
748
+ isNull(conversations.archivedAt),
749
+ groupIdClause(UNGROUPED_GROUP_ID),
750
+ ),
751
+ )
752
+ .groupBy(effectiveChannel)
753
+ .all();
754
+
755
+ return { groups, channels };
756
+ }
757
+
758
+ /**
759
+ * Check whether the last user message in a conversation is a tool_result-only
760
+ * message (i.e., not a real user-typed message). This is used by undo() to
761
+ * determine if additional exchanges need to be deleted from the DB.
762
+ */
763
+ export function isLastUserMessageToolResult(conversationId: string): boolean {
764
+ const lastUserContent = latestUserMessageRawContent(conversationId);
639
765
 
640
- if (!lastUserMsg) {
766
+ if (lastUserContent === null) {
641
767
  return false;
642
768
  }
643
769
 
644
770
  try {
645
- const parsed = JSON.parse(lastUserMsg.content);
771
+ const parsed = JSON.parse(lastUserContent);
646
772
  if (
647
773
  Array.isArray(parsed) &&
648
774
  parsed.length > 0 &&
@@ -816,6 +942,10 @@ export async function searchConversations(
816
942
  if (!opts?.includeArchived) {
817
943
  whereClauses.push(`c.archived_at IS NULL`);
818
944
  }
945
+ // No completeness predicate: candidate ids come from the lexical
946
+ // index, which filters finalized = 1 at index time, so an unfinalized
947
+ // row cannot be a candidate. Do not copy this shape for id sets with
948
+ // different provenance.
819
949
  const visibleRows = rawAll<CandidateRow>(
820
950
  "conversation:searchConversations:visibleCandidates",
821
951
  `
@@ -5,11 +5,11 @@
5
5
  * finding messages by source identifiers, and managing raw payload storage.
6
6
  */
7
7
 
8
- import { and, desc, eq, isNotNull, ne, or, sql } from "drizzle-orm";
8
+ import { and, desc, eq, isNotNull, like, ne, or, sql } from "drizzle-orm";
9
9
  import { v4 as uuid } from "uuid";
10
10
 
11
+ import type { ChannelId } from "../channels/types.js";
11
12
  import { readSlackMetadataFromMessageMetadata } from "../messaging/providers/slack/message-metadata.js";
12
- import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
13
13
  import type { SlackInboundMessageMetadata } from "../runtime/http-types.js";
14
14
  import { parseJsonSafe } from "../util/json.js";
15
15
  import { isPlainObject } from "../util/object.js";
@@ -19,8 +19,14 @@ import {
19
19
  getOrCreateConversation,
20
20
  setConversationKeyIfAbsent,
21
21
  } from "./conversation-key-store.js";
22
+ import type { NonScheduledConversationType } from "./conversation-types.js";
22
23
  import { getDb } from "./db-connection.js";
23
- import { channelInboundEvents, conversations } from "./schema.js";
24
+ import {
25
+ channelInboundEvents,
26
+ conversationKeys,
27
+ conversations,
28
+ messages,
29
+ } from "./schema.js";
24
30
 
25
31
  export interface InboundResult {
26
32
  accepted: boolean;
@@ -31,13 +37,27 @@ export interface InboundResult {
31
37
 
32
38
  export interface RecordInboundOptions {
33
39
  sourceMessageId?: string;
34
- assistantId?: string;
35
40
  sourceThreadId?: string;
41
+ /**
42
+ * Record the event against this conversation instead of resolving one from
43
+ * the address. For events that belong to a message rather than to a chat: a
44
+ * reaction lives in the conversation of the message it was attached to, and
45
+ * resolving from its own address would mint a second one.
46
+ */
47
+ conversationId?: string;
36
48
  }
37
49
 
38
50
  const SLACK_LEGACY_THREAD_EVIDENCE_BATCH_SIZE = 50;
39
51
  const SLACK_LEGACY_THREAD_EVIDENCE_MAX_SCAN = 500;
40
52
 
53
+ /**
54
+ * Rows examined when locating a Slack message the assistant itself posted.
55
+ * Bounded on purpose: the scan runs on the inbound path while the gateway
56
+ * waits for its ack, and reactions land on recent messages, so a cap costs
57
+ * almost no recall and keeps the cost flat as the database grows.
58
+ */
59
+ const SLACK_OUTBOUND_TS_MAX_SCAN = 400;
60
+
41
61
  /**
42
62
  * Channels where an inbound thread id scopes the conversation: a Slack thread
43
63
  * or a Telegram private-chat topic each maps to its own conversation. A
@@ -45,30 +65,25 @@ const SLACK_LEGACY_THREAD_EVIDENCE_MAX_SCAN = 500;
45
65
  */
46
66
  const THREAD_SCOPED_CHANNELS = new Set(["slack", "telegram"]);
47
67
 
48
- function buildScopedConversationKeyForAssistant(
49
- assistantId: string,
68
+ /**
69
+ * Scope prefix on every conversation key.
70
+ *
71
+ * Part of the stored key format, so it is fixed: rows are written under
72
+ * `asst:self:` and stop resolving if it changes. The daemon is single-tenant
73
+ * and scopes all its storage to `self` (`DAEMON_INTERNAL_ASSISTANT_ID`).
74
+ */
75
+ const CONVERSATION_KEY_SCOPE = "asst:self";
76
+
77
+ export function buildScopedConversationKey(
50
78
  sourceChannel: string,
51
79
  externalChatId: string,
52
80
  sourceThreadId?: string | null,
53
81
  ): string {
54
82
  const threadId = sourceThreadId?.trim();
55
83
  if (THREAD_SCOPED_CHANNELS.has(sourceChannel) && threadId) {
56
- return `asst:${assistantId}:${sourceChannel}:${externalChatId}:thread:${threadId}`;
84
+ return `${CONVERSATION_KEY_SCOPE}:${sourceChannel}:${externalChatId}:thread:${threadId}`;
57
85
  }
58
- return `asst:${assistantId}:${sourceChannel}:${externalChatId}`;
59
- }
60
-
61
- export function buildScopedConversationKey(
62
- sourceChannel: string,
63
- externalChatId: string,
64
- sourceThreadId?: string | null,
65
- ): string {
66
- return buildScopedConversationKeyForAssistant(
67
- DAEMON_INTERNAL_ASSISTANT_ID,
68
- sourceChannel,
69
- externalChatId,
70
- sourceThreadId,
71
- );
86
+ return `${CONVERSATION_KEY_SCOPE}:${sourceChannel}:${externalChatId}`;
72
87
  }
73
88
 
74
89
  function legacySlackConversationHasThreadEvidence(
@@ -135,14 +150,24 @@ function legacySlackConversationHasThreadEvidence(
135
150
  return false;
136
151
  }
137
152
 
138
- function resolveInboundConversation(
139
- assistantId: string,
153
+ /**
154
+ * Applied only where this call is what creates the conversation. An address
155
+ * that already resolves is an existing conversation, and neither its type nor
156
+ * where it came from is the current message's to restate.
157
+ */
158
+ export interface ResolveInboundConversationOptions {
159
+ conversationType?: NonScheduledConversationType;
160
+ /** Channel this conversation originates on, for first-message attribution. */
161
+ origin?: ChannelId;
162
+ }
163
+
164
+ export function resolveInboundConversation(
140
165
  sourceChannel: string,
141
166
  externalChatId: string,
142
167
  sourceThreadId?: string | null,
168
+ opts?: ResolveInboundConversationOptions,
143
169
  ): { conversationId: string } {
144
- const threadedKey = buildScopedConversationKeyForAssistant(
145
- assistantId,
170
+ const threadedKey = buildScopedConversationKey(
146
171
  sourceChannel,
147
172
  externalChatId,
148
173
  sourceThreadId,
@@ -155,7 +180,7 @@ function resolveInboundConversation(
155
180
  // other thread-scoped channel (Telegram topics) has no flat-key aliasing —
156
181
  // a thread id always resolves the threaded key directly.
157
182
  if (sourceChannel !== "slack" || !threadId) {
158
- return getOrCreateConversation(threadedKey);
183
+ return getOrCreateConversation(threadedKey, opts);
159
184
  }
160
185
 
161
186
  const threadedMapping = getConversationByKey(threadedKey);
@@ -163,8 +188,7 @@ function resolveInboundConversation(
163
188
  return { conversationId: threadedMapping.conversationId };
164
189
  }
165
190
 
166
- const legacyKey = buildScopedConversationKeyForAssistant(
167
- assistantId,
191
+ const legacyKey = buildScopedConversationKey(
168
192
  sourceChannel,
169
193
  externalChatId,
170
194
  null,
@@ -185,7 +209,7 @@ function resolveInboundConversation(
185
209
  }
186
210
  }
187
211
 
188
- return getOrCreateConversation(threadedKey);
212
+ return getOrCreateConversation(threadedKey, opts);
189
213
  }
190
214
 
191
215
  /**
@@ -236,7 +260,97 @@ export function findInboundConversationId(
236
260
  /**
237
261
  * Record an inbound channel event. Returns `duplicate: true` if this
238
262
  * exact (channel, chat, message) combination was already seen.
263
+ *
264
+ * The dedup half of this is also implemented gateway-side, on the same
265
+ * triple, in `gateway/src/db/inbound-dedup-store.ts`. Deliberately, not by
266
+ * accident: the gateway claims a delivery before handing it over, so a
267
+ * vendor's retry costs no crossing, and this stays as the record that binds
268
+ * the conversation and tracks the reply. Both must key on the same three
269
+ * fields for either to mean anything.
239
270
  */
271
+ /**
272
+ * The inbound event already recorded for this address, if any. Read-only twin
273
+ * of the dedup check inside {@link recordInbound}, for callers that must know
274
+ * an event is a redelivery before doing work that recording would otherwise
275
+ * gate (routing a guardian decision, for one).
276
+ */
277
+ export function findInboundEvent(
278
+ sourceChannel: string,
279
+ externalChatId: string,
280
+ externalMessageId: string,
281
+ ): { eventId: string; conversationId: string } | null {
282
+ const row = getDb()
283
+ .select({
284
+ id: channelInboundEvents.id,
285
+ conversationId: channelInboundEvents.conversationId,
286
+ })
287
+ .from(channelInboundEvents)
288
+ .where(
289
+ and(
290
+ eq(channelInboundEvents.sourceChannel, sourceChannel),
291
+ eq(channelInboundEvents.externalChatId, externalChatId),
292
+ eq(channelInboundEvents.externalMessageId, externalMessageId),
293
+ ),
294
+ )
295
+ .get();
296
+ return row ? { eventId: row.id, conversationId: row.conversationId } : null;
297
+ }
298
+
299
+ /**
300
+ * The conversation holding the Slack message with this `ts`, found by reading
301
+ * the `slackMeta` the assistant's own posts carry.
302
+ *
303
+ * `findMessageBySourceId` covers every message that arrived as an inbound
304
+ * event. It cannot see what the assistant posted, because an outbound reply
305
+ * opens no inbound event, so a reaction on the assistant's own message needs
306
+ * this. The search is confined to conversations already bound to the same
307
+ * Slack channel and to the most recent {@link SLACK_OUTBOUND_TS_MAX_SCAN}
308
+ * rows among them; beyond that it gives up and the caller drops the
309
+ * annotation, which is the same outcome as never finding it at all.
310
+ */
311
+ export function findSlackConversationByMessageTs(
312
+ externalChatId: string,
313
+ channelTs: string,
314
+ ): string | null {
315
+ const db = getDb();
316
+ const keyPrefix = `${CONVERSATION_KEY_SCOPE}:slack:${externalChatId}`;
317
+ const rows = db
318
+ .select({
319
+ conversationId: messages.conversationId,
320
+ metadata: messages.metadata,
321
+ })
322
+ .from(messages)
323
+ .innerJoin(
324
+ conversationKeys,
325
+ eq(conversationKeys.conversationId, messages.conversationId),
326
+ )
327
+ .where(
328
+ and(
329
+ or(
330
+ eq(conversationKeys.conversationKey, keyPrefix),
331
+ like(conversationKeys.conversationKey, `${keyPrefix}:thread:%`),
332
+ ),
333
+ like(messages.metadata, '%"slackMeta"%'),
334
+ ),
335
+ )
336
+ .orderBy(desc(messages.createdAt))
337
+ .limit(SLACK_OUTBOUND_TS_MAX_SCAN)
338
+ .all();
339
+
340
+ for (const row of rows) {
341
+ const slackMeta = readSlackMetadataFromMessageMetadata(row.metadata, {
342
+ allowFlatLegacy: true,
343
+ });
344
+ if (
345
+ slackMeta?.channelId === externalChatId &&
346
+ slackMeta.channelTs === channelTs
347
+ ) {
348
+ return row.conversationId;
349
+ }
350
+ }
351
+ return null;
352
+ }
353
+
240
354
  export function recordInbound(
241
355
  sourceChannel: string,
242
356
  externalChatId: string,
@@ -269,13 +383,13 @@ export function recordInbound(
269
383
  };
270
384
  }
271
385
 
272
- const assistantId = options?.assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID;
273
- const mapping = resolveInboundConversation(
274
- assistantId,
275
- sourceChannel,
276
- externalChatId,
277
- options?.sourceThreadId,
278
- );
386
+ const mapping = options?.conversationId
387
+ ? { conversationId: options.conversationId }
388
+ : resolveInboundConversation(
389
+ sourceChannel,
390
+ externalChatId,
391
+ options?.sourceThreadId,
392
+ );
279
393
  const now = Date.now();
280
394
  const eventId = uuid();
281
395