@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
@@ -6,7 +6,6 @@
6
6
  * without going through the HTTP layer.
7
7
  */
8
8
  import { rawAll, rawGet, rawRun } from "../persistence/raw-query.js";
9
- import { writeWorkspaceFile } from "../runtime/routes/workspace-utils.js";
10
9
  import { getLogger } from "../util/logger.js";
11
10
 
12
11
  const log = getLogger("document-store");
@@ -24,18 +23,13 @@ export interface DocumentRecord {
24
23
  wordCount: number;
25
24
  createdAt: number;
26
25
  updatedAt: number;
27
- /**
28
- * Workspace-relative path of the markdown file this document is bound to,
29
- * or `null` when the document has no file behind it.
30
- */
31
- workspacePath: string | null;
32
26
  }
33
27
 
34
28
  /**
35
- * The listing projection: neither the body nor the file binding, which the
36
- * list and search queries deliberately leave out of their SELECTs.
29
+ * The listing projection: everything but the body, which the list and search
30
+ * queries deliberately leave out of their SELECTs.
37
31
  */
38
- export type DocumentSummary = Omit<DocumentRecord, "content" | "workspacePath">;
32
+ export type DocumentSummary = Omit<DocumentRecord, "content">;
39
33
 
40
34
  /** Words in a markdown body, the count the `word_count` column stores. */
41
35
  function countWords(content: string): number {
@@ -72,10 +66,9 @@ interface DocumentRow {
72
66
  word_count: number;
73
67
  created_at: number;
74
68
  updated_at: number;
75
- workspace_path: string | null;
76
69
  }
77
70
 
78
- type DocumentListRow = Omit<DocumentRow, "content" | "workspace_path">;
71
+ type DocumentListRow = Omit<DocumentRow, "content">;
79
72
 
80
73
  function escapeSqlLikePattern(value: string): string {
81
74
  return value.replace(/[\\%_]/g, "\\$&");
@@ -90,7 +83,6 @@ function mapRowToRecord(row: DocumentRow): DocumentRecord {
90
83
  wordCount: row.word_count,
91
84
  createdAt: row.created_at,
92
85
  updatedAt: row.updated_at,
93
- workspacePath: row.workspace_path,
94
86
  };
95
87
  }
96
88
 
@@ -99,7 +91,7 @@ export function getDocumentById(surfaceId: string): DocumentRecord | null {
99
91
  try {
100
92
  const row = rawGet<DocumentRow>(
101
93
  "documents:getDocumentById",
102
- /*sql*/ `SELECT surface_id, conversation_id, title, content, word_count, created_at, updated_at, workspace_path
94
+ /*sql*/ `SELECT surface_id, conversation_id, title, content, word_count, created_at, updated_at
103
95
  FROM documents
104
96
  WHERE surface_id = ?`,
105
97
  surfaceId,
@@ -118,29 +110,6 @@ export function getDocumentById(surfaceId: string): DocumentRecord | null {
118
110
  }
119
111
  }
120
112
 
121
- /**
122
- * Look up the document bound to a workspace file. `workspacePath` must be the
123
- * normalized workspace-relative path stored on the row. Returns `null` when no
124
- * document is bound to that file.
125
- */
126
- export function getDocumentByWorkspacePath(
127
- workspacePath: string,
128
- ): DocumentRecord | null {
129
- try {
130
- const row = rawGet<DocumentRow>(
131
- "documents:getDocumentByWorkspacePath",
132
- /*sql*/ `SELECT surface_id, conversation_id, title, content, word_count, created_at, updated_at, workspace_path
133
- FROM documents
134
- WHERE workspace_path = ?`,
135
- workspacePath,
136
- );
137
- return row ? mapRowToRecord(row) : null;
138
- } catch (error) {
139
- log.error({ err: error, workspacePath }, "Load-by-workspace-path error");
140
- return null;
141
- }
142
- }
143
-
144
113
  /** Return true when a document is associated with a conversation. */
145
114
  export function isDocumentAssociatedWithConversation(
146
115
  surfaceId: string,
@@ -429,202 +398,6 @@ export function findInDocument(
429
398
  }
430
399
  }
431
400
 
432
- // ---------------------------------------------------------------------------
433
- // Workspace file binding
434
- // ---------------------------------------------------------------------------
435
-
436
- /**
437
- * Mirror `content` onto the workspace file a document is bound to.
438
- *
439
- * This is the single write-through choke point: every path that persists
440
- * document content — {@link saveDocument} (client save and `document_create`),
441
- * {@link updateDocumentContent} (`document_update`), and
442
- * {@link replaceInDocument} (`document_replace_text`) — calls it immediately
443
- * before its own row write, so a file-backed document's markdown on disk always
444
- * matches the stored row.
445
- *
446
- * The file is written first and a failure throws: a rejected write aborts the
447
- * update before the row changes rather than leaving the two silently diverged.
448
- * Documents with no file behind them are a no-op.
449
- */
450
- function writeThroughToWorkspaceFile(surfaceId: string, content: string): void {
451
- const row = rawGet<{ workspace_path: string | null }>(
452
- "documents:writeThroughToWorkspaceFile:getPath",
453
- /*sql*/ `SELECT workspace_path FROM documents WHERE surface_id = ?`,
454
- surfaceId,
455
- );
456
- const workspacePath = row?.workspace_path;
457
- if (!workspacePath) {
458
- return;
459
- }
460
-
461
- try {
462
- writeWorkspaceFile(workspacePath, Buffer.from(content, "utf-8"));
463
- log.info({ surfaceId, workspacePath }, "Wrote document through to file");
464
- } catch (error) {
465
- const message = error instanceof Error ? error.message : String(error);
466
- throw new Error(
467
- `Failed to write file-backed document to ${workspacePath}: ${message}`,
468
- );
469
- }
470
- }
471
-
472
- /**
473
- * Create a document seeded from a workspace markdown file and bind it to that
474
- * file. The returned surface ID is the file's document identity — comments,
475
- * assistant iteration, and PDF export all key off it.
476
- *
477
- * The partial unique index on `workspace_path` makes a concurrent second
478
- * create fail rather than produce a duplicate binding; callers re-read by path
479
- * on failure.
480
- */
481
- export function createFileBackedDocument(params: {
482
- surfaceId: string;
483
- conversationId: string;
484
- title: string;
485
- content: string;
486
- workspacePath: string;
487
- }): { success: true } | { success: false; error: string } {
488
- try {
489
- const now = Date.now();
490
- rawRun(
491
- "documents:createFileBackedDocument",
492
- /*sql*/ `INSERT INTO documents (surface_id, conversation_id, title, content, word_count, created_at, updated_at, workspace_path)
493
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
494
- params.surfaceId,
495
- params.conversationId,
496
- params.title,
497
- params.content,
498
- countWords(params.content),
499
- now,
500
- now,
501
- params.workspacePath,
502
- );
503
- addDocumentConversation(params.surfaceId, params.conversationId);
504
- log.info(
505
- { surfaceId: params.surfaceId, workspacePath: params.workspacePath },
506
- "Created file-backed document",
507
- );
508
- return { success: true };
509
- } catch (error) {
510
- log.error(
511
- { err: error, workspacePath: params.workspacePath },
512
- "File-backed document create error",
513
- );
514
- return {
515
- success: false,
516
- error: error instanceof Error ? error.message : "Unknown error",
517
- };
518
- }
519
- }
520
-
521
- /**
522
- * Move file-backed documents onto the path a workspace rename moved their file
523
- * to. `oldPath` and `newPath` are canonical workspace-relative paths, the same
524
- * form {@link createFileBackedDocument} stores.
525
- *
526
- * A document bound to `oldPath` itself moves, and so does every document nested
527
- * under it (`oldPath + "/"` prefix), which is what a directory rename produces.
528
- *
529
- * The rename on disk has already happened by the time this runs, so it is best
530
- * effort and never throws: the caller has no way to undo the rename, and a
531
- * document left on a stale path is recoverable while a failed rename response
532
- * is not. Rows move one statement at a time so a single row that cannot move
533
- * does not strand its siblings.
534
- *
535
- * A row whose destination is already taken by another binding is one such case:
536
- * the partial unique index on `workspace_path` rejects the update, and the
537
- * losing row keeps pointing at the old path. That is deliberate. Overwriting
538
- * the winner would drop a binding that a client may have open, whereas the
539
- * stale binding self-heals: opening the file refreshes the row's content from
540
- * disk, and a document whose file is gone is reported as gone rather than
541
- * rewritten from the row.
542
- */
543
- export function rebindDocumentsToRenamedPath(params: {
544
- oldPath: string;
545
- newPath: string;
546
- }): void {
547
- const { oldPath, newPath } = params;
548
- if (!oldPath || !newPath || oldPath === newPath) {
549
- return;
550
- }
551
-
552
- try {
553
- const rows = rawAll<{ surface_id: string; workspace_path: string }>(
554
- "documents:rebindRenamedPath:select",
555
- /*sql*/ `SELECT surface_id, workspace_path
556
- FROM documents
557
- WHERE workspace_path = ? OR workspace_path LIKE ? ESCAPE '\\'`,
558
- oldPath,
559
- `${escapeSqlLikePattern(oldPath)}/%`,
560
- );
561
-
562
- for (const row of rows) {
563
- const rebound =
564
- row.workspace_path === oldPath
565
- ? newPath
566
- : newPath + row.workspace_path.slice(oldPath.length);
567
- try {
568
- rawRun(
569
- "documents:rebindRenamedPath:update",
570
- /*sql*/ `UPDATE documents SET workspace_path = ? WHERE surface_id = ?`,
571
- rebound,
572
- row.surface_id,
573
- );
574
- log.info(
575
- {
576
- surfaceId: row.surface_id,
577
- from: row.workspace_path,
578
- to: rebound,
579
- },
580
- "Rebound file-backed document to renamed path",
581
- );
582
- } catch (error) {
583
- log.warn(
584
- {
585
- err: error,
586
- surfaceId: row.surface_id,
587
- from: row.workspace_path,
588
- to: rebound,
589
- },
590
- "Left file-backed document on its old path after a rename",
591
- );
592
- }
593
- }
594
- } catch (error) {
595
- log.error({ err: error, oldPath, newPath }, "Path rebind error");
596
- }
597
- }
598
-
599
- /**
600
- * Replace a document's stored content with the current text of the file it is
601
- * bound to. The file is the source of truth when a document is opened, so this
602
- * deliberately skips the write-through step — disk and row already agree.
603
- */
604
- export function refreshDocumentContentFromFile(
605
- surfaceId: string,
606
- content: string,
607
- ): { success: true } | { success: false; error: string } {
608
- try {
609
- rawRun(
610
- "documents:refreshDocumentContentFromFile",
611
- /*sql*/ `UPDATE documents SET content = ?, word_count = ?, updated_at = ? WHERE surface_id = ?`,
612
- content,
613
- countWords(content),
614
- Date.now(),
615
- surfaceId,
616
- );
617
- log.info({ surfaceId }, "Refreshed document content from file");
618
- return { success: true };
619
- } catch (error) {
620
- log.error({ err: error, surfaceId }, "Document refresh error");
621
- return {
622
- success: false,
623
- error: error instanceof Error ? error.message : "Unknown error",
624
- };
625
- }
626
- }
627
-
628
401
  // ---------------------------------------------------------------------------
629
402
  // Document persistence
630
403
  // ---------------------------------------------------------------------------
@@ -637,7 +410,6 @@ export function saveDocument(params: {
637
410
  wordCount: number;
638
411
  }): { success: true; surfaceId: string } | { success: false; error: string } {
639
412
  try {
640
- writeThroughToWorkspaceFile(params.surfaceId, params.content);
641
413
  const now = Date.now();
642
414
  rawRun(
643
415
  "documents:saveDocument",
@@ -773,7 +545,6 @@ export function replaceInDocument(
773
545
  replacementsMade = totalMatches;
774
546
  }
775
547
 
776
- writeThroughToWorkspaceFile(surfaceId, newContent);
777
548
  const wordCount = countWords(newContent);
778
549
  rawRun(
779
550
  "documents:replaceInDocument:update",
@@ -798,12 +569,125 @@ export function replaceInDocument(
798
569
  }
799
570
  }
800
571
 
572
+ // ---------------------------------------------------------------------------
573
+ // Append idempotency
574
+ // ---------------------------------------------------------------------------
575
+
576
+ /** A blank-line-delimited block of markdown, located in its source string. */
577
+ interface MarkdownBlock {
578
+ /** Whitespace-trimmed block text, the unit duplicate detection compares. */
579
+ text: string;
580
+ /** Offset of the block's first character in the source string. */
581
+ start: number;
582
+ }
583
+
584
+ /**
585
+ * Split markdown into blank-line-delimited blocks, dropping empty ones but
586
+ * keeping each surviving block's offset so a suffix of the source can be
587
+ * recovered verbatim.
588
+ */
589
+ function splitIntoBlocks(content: string): MarkdownBlock[] {
590
+ const blocks: MarkdownBlock[] = [];
591
+ const separator = /\n[^\S\n]*\n/g;
592
+ let cursor = 0;
593
+ let match: RegExpExecArray | null;
594
+ const push = (start: number, end: number): void => {
595
+ const text = content.slice(start, end).trim();
596
+ if (text.length > 0) {
597
+ blocks.push({ text, start });
598
+ }
599
+ };
600
+ while ((match = separator.exec(content)) !== null) {
601
+ push(cursor, match.index);
602
+ cursor = match.index + match[0].length;
603
+ }
604
+ push(cursor, content.length);
605
+ return blocks;
606
+ }
607
+
608
+ /**
609
+ * An append has to repeat at least this many characters of the document's tail
610
+ * before the repetition is treated as an accident.
611
+ *
612
+ * The floor is what keeps legitimate repetition intact. A document can honestly
613
+ * repeat a heading, a refrain, a table row, or a bare "TODO" back to back, and
614
+ * those are all short. Only a substantial restatement (an opening paragraph
615
+ * passed to both `document_create` and the first append) clears the bar.
616
+ */
617
+ const MIN_DUPLICATE_APPEND_CHARS = 40;
618
+
619
+ /**
620
+ * Drop a leading run of blocks from `markdown` that exactly repeats the tail of
621
+ * `existing`, so an append that restates content already committed does not
622
+ * write it a second time.
623
+ *
624
+ * The guard is deliberately narrow: the repeated run must be an exact,
625
+ * block-aligned match, it must sit at the very start of the append and the very
626
+ * end of the document, and it must be at least
627
+ * {@link MIN_DUPLICATE_APPEND_CHARS} long. Anything else, including the same
628
+ * text reappearing further down the append, is passed through untouched.
629
+ */
630
+ function stripDuplicateLeadingBlocks(
631
+ existing: string,
632
+ markdown: string,
633
+ ): string {
634
+ const existingBlocks = splitIntoBlocks(existing);
635
+ const incomingBlocks = splitIntoBlocks(markdown);
636
+ const maxOverlap = Math.min(existingBlocks.length, incomingBlocks.length);
637
+
638
+ let overlap = 0;
639
+ for (let candidate = maxOverlap; candidate >= 1; candidate--) {
640
+ const offset = existingBlocks.length - candidate;
641
+ let matches = true;
642
+ for (let i = 0; i < candidate; i++) {
643
+ if (existingBlocks[offset + i].text !== incomingBlocks[i].text) {
644
+ matches = false;
645
+ break;
646
+ }
647
+ }
648
+ if (matches) {
649
+ overlap = candidate;
650
+ break;
651
+ }
652
+ }
653
+ if (overlap === 0) {
654
+ return markdown;
655
+ }
656
+
657
+ const duplicatedChars = incomingBlocks
658
+ .slice(0, overlap)
659
+ .reduce((total, block) => total + block.text.length, 0);
660
+ if (duplicatedChars < MIN_DUPLICATE_APPEND_CHARS) {
661
+ return markdown;
662
+ }
663
+
664
+ if (overlap === incomingBlocks.length) {
665
+ return "";
666
+ }
667
+ return markdown
668
+ .slice(incomingBlocks[overlap].start)
669
+ .replace(/^(?:[^\S\n]*\n)+/, "");
670
+ }
671
+
672
+ /** Outcome of a successful {@link updateDocumentContent} call. */
673
+ export interface DocumentContentUpdated {
674
+ success: true;
675
+ /**
676
+ * The markdown that actually landed. Equals the submitted markdown except
677
+ * when an append's leading blocks duplicated the document's tail, in which
678
+ * case it is the remainder. Clients render this so their view matches the row.
679
+ */
680
+ appliedMarkdown: string;
681
+ /** True when a duplicated leading run was dropped from an append. */
682
+ duplicateLeadingContentSkipped: boolean;
683
+ }
684
+
801
685
  /** Update persisted document content (append or replace). */
802
686
  export function updateDocumentContent(
803
687
  surfaceId: string,
804
688
  markdown: string,
805
689
  mode: string,
806
- ): { success: true } | { success: false; error: string } {
690
+ ): DocumentContentUpdated | { success: false; error: string } {
807
691
  try {
808
692
  const existing = rawGet<{ content: string }>(
809
693
  "documents:updateDocumentContent:get",
@@ -814,10 +698,19 @@ export function updateDocumentContent(
814
698
  log.info({ surfaceId }, "No persisted document to update");
815
699
  return { success: false, error: "Document not found" };
816
700
  }
817
- const sep = mode === "append" && existing.content.length > 0 ? "\n\n" : "";
818
- const newContent =
819
- mode === "append" ? existing.content + sep + markdown : markdown;
820
- writeThroughToWorkspaceFile(surfaceId, newContent);
701
+ const appending = mode === "append";
702
+ const appliedMarkdown = appending
703
+ ? stripDuplicateLeadingBlocks(existing.content, markdown)
704
+ : markdown;
705
+ const duplicateLeadingContentSkipped =
706
+ appending && appliedMarkdown !== markdown;
707
+ const sep =
708
+ appending && existing.content.length > 0 && appliedMarkdown.length > 0
709
+ ? "\n\n"
710
+ : "";
711
+ const newContent = appending
712
+ ? existing.content + sep + appliedMarkdown
713
+ : appliedMarkdown;
821
714
  const wordCount = countWords(newContent);
822
715
  rawRun(
823
716
  "documents:updateDocumentContent:update",
@@ -827,8 +720,18 @@ export function updateDocumentContent(
827
720
  Date.now(),
828
721
  surfaceId,
829
722
  );
723
+ if (duplicateLeadingContentSkipped) {
724
+ log.info(
725
+ { surfaceId, skippedChars: markdown.length - appliedMarkdown.length },
726
+ "Skipped append content duplicating the document tail",
727
+ );
728
+ }
830
729
  log.info({ surfaceId, mode }, "Updated document content");
831
- return { success: true };
730
+ return {
731
+ success: true,
732
+ appliedMarkdown,
733
+ duplicateLeadingContentSkipped,
734
+ };
832
735
  } catch (error) {
833
736
  log.error({ err: error, surfaceId }, "Document content update error");
834
737
  return {
@@ -39,9 +39,9 @@
39
39
  * registry, so the next read re-resolves fresh.
40
40
  *
41
41
  * Plugin *discovery* (which plugin directories exist, in what order) lives in
42
- * `../plugins/mtime-cache.ts`; the orchestrator there passes the discovered
43
- * directories into {@link collectUserHookEntries}. Keeping discovery out of
44
- * this module lets it sit below the plugin cache with no import cycle.
42
+ * `../plugins/mtime-cache.ts`; hook lookup in `./registry.ts` passes the
43
+ * discovered names into {@link collectUserHookEntries}. Keeping discovery out
44
+ * of this module lets it sit below the plugin cache with no import cycle.
45
45
  */
46
46
 
47
47
  import {
@@ -6,20 +6,25 @@
6
6
  * hook-chain ordering.
7
7
  *
8
8
  * User-land hooks (from the filesystem) are owned by
9
- * {@link ./hook-loader.ts} and surfaced through `getUserHooksFor` in
10
- * `plugins/mtime-cache.ts`. This module owns only the in-process hooks that
11
- * default plugins register at boot.
9
+ * {@link ./hook-loader.ts} and surfaced through {@link getUserHooksFor}.
10
+ * This module owns the in-process hooks that default plugins register at
11
+ * boot, plus the user-land lookup that walks discovered plugin names from
12
+ * the plugin cache.
12
13
  *
13
- * {@link getHooksFor} combines both sources: in-process hooks from this
14
- * registry (filtered by `isPluginDisabled` at read time) and user-land hooks
15
- * from the plugin cache. The read-time filtering is what makes `assistant
16
- * plugins disable default-*` take effect immediately in a running assistant
17
- * — the hooks stay registered but are filtered out on the next turn.
14
+ * {@link getHooksFor} and {@link getUserHooksFor} both resolve through
15
+ * {@link getUserHookEntriesFor}, which prepends in-process default-plugin
16
+ * hooks from this registry (filtered by `isPluginDisabled` at read time)
17
+ * ahead of user-land hooks from the plugin cache. The read-time filtering
18
+ * is what makes `assistant plugins disable default-*` take effect immediately
19
+ * in a running assistant: the hooks stay registered but are filtered out on
20
+ * the next turn.
18
21
  */
19
22
 
23
+ import { isFirstPartyDefaultPlugin } from "../plugins/defaults/main.js";
20
24
  import { isPluginDisabled } from "../plugins/disabled-state.js";
21
- import { getUserHookEntriesFor } from "../plugins/mtime-cache.js";
25
+ import { getDiscoveredUserPluginNames } from "../plugins/mtime-cache.js";
22
26
  import type { HookEntry, HookFunction } from "../plugins/types.js";
27
+ import { collectUserHookEntries } from "./hook-loader.js";
23
28
 
24
29
  // ─── Internal state ──────────────────────────────────────────────────────────
25
30
 
@@ -75,6 +80,87 @@ export function unregisterPluginHooks(pluginName: string): void {
75
80
 
76
81
  // ─── Queries ─────────────────────────────────────────────────────────────────
77
82
 
83
+ /**
84
+ * In-process (default plugin) hook entries for `name`, filtered by the
85
+ * `.disabled` sentinel at read time. First-party default plugins always pass
86
+ * the per-chat allowlist; other in-process registrations (test fixtures) honor
87
+ * it. Used by {@link getUserHookEntriesFor} so `getUserHooksFor` returns the
88
+ * same default-plugin hooks {@link getHooksFor} does.
89
+ */
90
+ function collectInProcessHookEntries<TCtx = unknown>(
91
+ name: string,
92
+ effectiveEnabledPlugins?: Set<string> | null,
93
+ ): HookEntry<TCtx>[] {
94
+ const defaultEntries: HookEntry<TCtx>[] = [];
95
+ for (const entry of hookRegistry.get(name) ?? []) {
96
+ if (isPluginDisabled(entry.pluginName)) {
97
+ continue;
98
+ }
99
+ if (
100
+ effectiveEnabledPlugins != null &&
101
+ !effectiveEnabledPlugins.has(entry.pluginName) &&
102
+ !isFirstPartyDefaultPlugin(entry.pluginName)
103
+ ) {
104
+ continue;
105
+ }
106
+ defaultEntries.push({
107
+ fn: entry.fn as HookFunction<TCtx>,
108
+ owner: { kind: "plugin", id: entry.pluginName },
109
+ });
110
+ }
111
+ return defaultEntries;
112
+ }
113
+
114
+ /**
115
+ * Get all hooks for a given event name: in-process default-plugin hooks
116
+ * first, then user plugins and standalone workspace hooks. Plugin hooks run
117
+ * in install-date order, the workspace hook runs last.
118
+ *
119
+ * Default-plugin hooks come from the in-process registry (registration at
120
+ * boot). User-land hooks are a pure cache read: this never scans disk,
121
+ * activates a plugin, or runs `init`. Activation happens only at boot
122
+ * (`populateCacheAtBoot`) and through the imperative install/uninstall
123
+ * poke (`reconcilePluginSourcesNow`), both main-daemon paths, so a
124
+ * sidecar process that dispatches hooks (a worker running conversation
125
+ * turns) can never bring a plugin up in its own process.
126
+ *
127
+ * `effectiveEnabledPlugins` carries the per-chat plugin scope: when non-null,
128
+ * user plugins outside the set are skipped. First-party default plugins
129
+ * always pass that allowlist (a workspace `.disabled` sentinel still
130
+ * excludes them). Standalone workspace hooks always run. `null`/omitted
131
+ * means no per-chat restriction.
132
+ */
133
+ export async function getUserHookEntriesFor<TCtx = unknown>(
134
+ hookName: string,
135
+ effectiveEnabledPlugins?: Set<string> | null,
136
+ ): Promise<HookEntry<TCtx>[]> {
137
+ const defaultEntries = collectInProcessHookEntries<TCtx>(
138
+ hookName,
139
+ effectiveEnabledPlugins,
140
+ );
141
+ const userEntries = await collectUserHookEntries<TCtx>(
142
+ hookName,
143
+ getDiscoveredUserPluginNames(),
144
+ effectiveEnabledPlugins,
145
+ );
146
+ return [...defaultEntries, ...userEntries];
147
+ }
148
+
149
+ /**
150
+ * {@link getUserHookEntriesFor} without owner attribution. Returns just the
151
+ * hook functions in the same order, including in-process default-plugin hooks.
152
+ */
153
+ export async function getUserHooksFor<TCtx = unknown>(
154
+ hookName: string,
155
+ effectiveEnabledPlugins?: Set<string> | null,
156
+ ): Promise<HookFunction<TCtx>[]> {
157
+ const entries = await getUserHookEntriesFor<TCtx>(
158
+ hookName,
159
+ effectiveEnabledPlugins,
160
+ );
161
+ return entries.map((e) => e.fn);
162
+ }
163
+
78
164
  /**
79
165
  * Collect every registered hook for the given name, in registration order.
80
166
  * Plugins that don't declare a hook for `name` are skipped. Used by the
@@ -87,17 +173,19 @@ export function unregisterPluginHooks(pluginName: string): void {
87
173
  * (async, pure cache read). Default hooks are prepended so they compose
88
174
  * innermost, ahead of any user plugins.
89
175
  *
90
- * The `TCtx` generic mirrors {@link HookFunction}'s — callers parameterize
176
+ * The `TCtx` generic mirrors {@link HookFunction}'s: callers parameterize
91
177
  * over the concrete context type their hook receives. Hooks that mutate the
92
178
  * context in place return `void`; hooks that return a new context replace
93
179
  * the threaded value for the next hook in the chain.
94
180
  *
95
181
  * When `conversationId` is given, the conversation's effective plugin scope is
96
182
  * resolved from it (memory, then DB) and layered on top of the global disabled
97
- * check: a hook's contributing plugin must also be a member of that set or the
98
- * hook is excluded for this turn (applies to both in-process default plugins
99
- * and user-land plugins). Omit it (or pass a conversation with no per-chat
100
- * restriction) and every globally-enabled plugin's hooks run, unchanged.
183
+ * check: a user plugin must also be a member of that set or its hook is
184
+ * excluded for this turn. First-party default plugins are not subject to the
185
+ * per-chat allowlist (they are core runtime infrastructure; the chat pills
186
+ * only list user-installed plugins), but a workspace `.disabled` sentinel
187
+ * still excludes them. Omit `conversationId` (or pass a conversation with no
188
+ * per-chat restriction) and every globally-enabled plugin's hooks run.
101
189
  */
102
190
  export async function getHookEntriesFor<TCtx = unknown>(
103
191
  name: string,
@@ -116,37 +204,7 @@ export async function getHookEntriesFor<TCtx = unknown>(
116
204
  options.conversationId,
117
205
  );
118
206
  }
119
- // First-party defaults from the hook registry, filtered by the `.disabled`
120
- // sentinel at read time. This is what makes `assistant plugins disable
121
- // default-*` take effect immediately in a running assistant: the hooks stay
122
- // registered but are filtered out on the next turn.
123
- const defaultEntries: HookEntry<TCtx>[] = [];
124
- for (const entry of hookRegistry.get(name) ?? []) {
125
- if (isPluginDisabled(entry.pluginName)) {
126
- continue;
127
- }
128
- if (
129
- effectiveEnabledPlugins != null &&
130
- !effectiveEnabledPlugins.has(entry.pluginName)
131
- ) {
132
- continue;
133
- }
134
- defaultEntries.push({
135
- fn: entry.fn as HookFunction<TCtx>,
136
- owner: { kind: "plugin", id: entry.pluginName },
137
- });
138
- }
139
-
140
- // User-land hooks from the plugin cache (async, pure cache read; dispatch
141
- // never activates plugins). The per-chat
142
- // scope is threaded through so a deselected user plugin's hooks are excluded
143
- // too — standalone workspace hooks (not owned by a plugin) always run.
144
- const userEntries = await getUserHookEntriesFor<TCtx>(
145
- name,
146
- effectiveEnabledPlugins,
147
- );
148
-
149
- return [...defaultEntries, ...userEntries];
207
+ return getUserHookEntriesFor<TCtx>(name, effectiveEnabledPlugins);
150
208
  }
151
209
 
152
210
  /**
@@ -173,6 +173,11 @@ export interface UserPromptSubmitInputContext {
173
173
  * user speech (e.g. title generation) should skip these turns.
174
174
  */
175
175
  readonly isHiddenPrompt?: boolean;
176
+ /**
177
+ * Daemon-authored kind from the triggering row's `metadata.messageKind`,
178
+ * when the turn needs a more specific identity than role or visibility.
179
+ */
180
+ readonly messageKind?: string;
176
181
  /**
177
182
  * The user's original message list, immutable for the hook. Plugins
178
183
  * may snapshot or compare against this but MUST NOT mutate it.