@vellumai/assistant 0.10.4 → 0.10.5-dev.202607022329.d89bf2f

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 (866) hide show
  1. package/ARCHITECTURE.md +94 -91
  2. package/Dockerfile +5 -16
  3. package/README.md +7 -10
  4. package/bun.lock +0 -4
  5. package/docs/architecture/security.md +20 -20
  6. package/docs/stt-provider-onboarding.md +4 -5
  7. package/docs/trusted-contact-access.md +7 -7
  8. package/eslint-rules/cli-no-daemon-internals.js +4 -0
  9. package/eslint.config.mjs +7 -1
  10. package/knip.json +0 -1
  11. package/node_modules/@vellumai/gateway-client/src/__tests__/contact-read-contracts.test.ts +36 -0
  12. package/node_modules/@vellumai/gateway-client/src/__tests__/invite-contract.test.ts +233 -0
  13. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +40 -0
  14. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +20 -8
  15. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
  16. package/node_modules/@vellumai/gateway-client/src/index.ts +33 -0
  17. package/node_modules/@vellumai/gateway-client/src/invite-contract.ts +216 -0
  18. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +91 -0
  19. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +39 -3
  20. package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +0 -8
  21. package/node_modules/@vellumai/service-contracts/src/__tests__/package-boundary.test.ts +1 -5
  22. package/node_modules/@vellumai/service-contracts/src/twilio-ingress.ts +3 -14
  23. package/openapi.yaml +1073 -225
  24. package/package.json +1 -4
  25. package/scripts/test.sh +35 -13
  26. package/src/__tests__/access-request-seed-content-blocks.test.ts +60 -4
  27. package/src/__tests__/acp-session.test.ts +4 -1
  28. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +0 -6
  29. package/src/__tests__/add-message-preassigned-id.test.ts +56 -0
  30. package/src/__tests__/agent-loop-set-system-prompt.test.ts +139 -0
  31. package/src/__tests__/app-conversation-ids-backfill.test.ts +3 -0
  32. package/src/__tests__/app-source-watcher.test.ts +11 -0
  33. package/src/__tests__/approval-interception-trust-gates.test.ts +148 -3
  34. package/src/__tests__/assistant-attachments.test.ts +62 -4
  35. package/src/__tests__/assistant-id-boundary-guard.test.ts +0 -1
  36. package/src/__tests__/attachments-store-heic-normalize.test.ts +230 -0
  37. package/src/__tests__/attachments-store.test.ts +3 -0
  38. package/src/__tests__/background-shell-bash.test.ts +34 -0
  39. package/src/__tests__/background-shell-host-bash.test.ts +12 -0
  40. package/src/__tests__/background-tool-registry.test.ts +97 -0
  41. package/src/__tests__/background-tool-routes.test.ts +72 -1
  42. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
  43. package/src/__tests__/btw-routes.test.ts +57 -3
  44. package/src/__tests__/call-controller.test.ts +474 -84
  45. package/src/__tests__/call-domain.test.ts +48 -1
  46. package/src/__tests__/call-pointer-messages.test.ts +57 -19
  47. package/src/__tests__/call-pointer-no-hardcoded-copy.guard.test.ts +2 -6
  48. package/src/__tests__/call-routes-http.test.ts +15 -14
  49. package/src/__tests__/call-setup-flow-invite.test.ts +568 -0
  50. package/src/__tests__/call-setup-flow-midcall-trust.test.ts +231 -0
  51. package/src/__tests__/call-setup-flow-name-capture.test.ts +754 -0
  52. package/src/__tests__/call-setup-flow-verification.test.ts +925 -0
  53. package/src/__tests__/call-setup-flow.test.ts +311 -0
  54. package/src/__tests__/cancel-resolves-conversation-key.test.ts +0 -1
  55. package/src/__tests__/channel-guardian.test.ts +4 -7
  56. package/src/__tests__/channel-readiness-service.test.ts +0 -6
  57. package/src/__tests__/channel-readiness-slack-remote.test.ts +0 -4
  58. package/src/__tests__/channel-retry-sweep.test.ts +76 -64
  59. package/src/__tests__/channel-setup-panel-ack.test.ts +184 -0
  60. package/src/__tests__/clear-all-lexical.test.ts +90 -0
  61. package/src/__tests__/compactor-media-strip.test.ts +177 -0
  62. package/src/__tests__/config-loader-backfill.test.ts +5 -5
  63. package/src/__tests__/config-schema.test.ts +17 -0
  64. package/src/__tests__/contact-store-interaction-info.test.ts +27 -70
  65. package/src/__tests__/contacts-relay-reads.test.ts +188 -23
  66. package/src/__tests__/contacts-tools.test.ts +42 -1
  67. package/src/__tests__/contacts-write.test.ts +181 -0
  68. package/src/__tests__/context-search-conversations-source.test.ts +220 -81
  69. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -0
  70. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  71. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  72. package/src/__tests__/conversation-agent-loop.test.ts +39 -0
  73. package/src/__tests__/conversation-analysis-routes.test.ts +1 -1
  74. package/src/__tests__/conversation-app-control-instantiation.test.ts +2 -0
  75. package/src/__tests__/conversation-clear-safety.test.ts +0 -1
  76. package/src/__tests__/conversation-crud-enabled-plugins.test.ts +60 -0
  77. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -1
  78. package/src/__tests__/conversation-disk-view.test.ts +1 -0
  79. package/src/__tests__/conversation-enabledplugins-route.test.ts +184 -0
  80. package/src/__tests__/conversation-error.test.ts +0 -18
  81. package/src/__tests__/conversation-fork-crud.test.ts +67 -0
  82. package/src/__tests__/conversation-history-web-search.test.ts +7 -137
  83. package/src/__tests__/conversation-initial-prompt.test.ts +110 -0
  84. package/src/__tests__/conversation-loop-db-lock-resilience.test.ts +191 -0
  85. package/src/__tests__/conversation-process-callsite.test.ts +37 -0
  86. package/src/__tests__/conversation-queue.test.ts +149 -0
  87. package/src/__tests__/conversation-routes-enabled-plugins.test.ts +392 -0
  88. package/src/__tests__/conversation-routes-guardian-reply.test.ts +84 -0
  89. package/src/__tests__/conversation-routes-hidden-queue.test.ts +407 -0
  90. package/src/__tests__/conversation-runtime-assembly.test.ts +36 -16
  91. package/src/__tests__/conversation-skill-tools.test.ts +73 -0
  92. package/src/__tests__/conversation-wipe.test.ts +20 -5
  93. package/src/__tests__/credential-prompt-route.test.ts +69 -0
  94. package/src/__tests__/credential-security-invariants.test.ts +1 -3
  95. package/src/__tests__/db-drop-messages-fts.test.ts +115 -0
  96. package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -62
  97. package/src/__tests__/edit-propagation.test.ts +56 -2
  98. package/src/__tests__/empty-state-greeting-cache.test.ts +9 -0
  99. package/src/__tests__/gateway-only-enforcement.test.ts +15 -110
  100. package/src/__tests__/guardian-expiry-notifier.test.ts +1 -0
  101. package/src/__tests__/guardian-question-mode.test.ts +57 -0
  102. package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -1
  103. package/src/__tests__/guardian-wait-controller.test.ts +619 -0
  104. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +33 -2
  105. package/src/__tests__/heic-fixture.ts +55 -0
  106. package/src/__tests__/helpers/seed-contact-channel.ts +0 -2
  107. package/src/__tests__/http-conversation-lineage.test.ts +6 -1
  108. package/src/__tests__/image-conversion.test.ts +170 -0
  109. package/src/__tests__/inbound-invite-redemption.test.ts +72 -341
  110. package/src/__tests__/introduction-card-resolver.test.ts +354 -0
  111. package/src/__tests__/invite-redeemed-ipc.test.ts +97 -0
  112. package/src/__tests__/invite-routes-http.test.ts +246 -420
  113. package/src/__tests__/job-handler-registration-guard.test.ts +104 -0
  114. package/src/__tests__/lexical-index-dual-write.test.ts +426 -0
  115. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +2 -2
  116. package/src/__tests__/list-messages-attachments.test.ts +159 -0
  117. package/src/__tests__/list-messages-background-tool-completion.test.ts +141 -0
  118. package/src/__tests__/list-messages-queued.test.ts +25 -0
  119. package/src/__tests__/managed-store.test.ts +129 -1
  120. package/src/__tests__/media-stream-audio-transcode.test.ts +113 -0
  121. package/src/__tests__/media-stream-output.test.ts +350 -19
  122. package/src/__tests__/media-stream-server-integration.test.ts +943 -169
  123. package/src/__tests__/media-stream-stt-session.test.ts +498 -1
  124. package/src/__tests__/memory-jobs-worker-lanes.test.ts +2 -2
  125. package/src/__tests__/model-intents.test.ts +1 -1
  126. package/src/__tests__/mtime-cache.test.ts +19 -0
  127. package/src/__tests__/non-member-access-request.test.ts +349 -3
  128. package/src/__tests__/notification-decision-fallback.test.ts +104 -8
  129. package/src/__tests__/notification-decision-strategy.test.ts +57 -8
  130. package/src/__tests__/onboarding-template-contract.test.ts +27 -43
  131. package/src/__tests__/openai-provider.test.ts +33 -0
  132. package/src/__tests__/openai-responses-provider.test.ts +40 -0
  133. package/src/__tests__/persistence-layering-guard.test.ts +29 -20
  134. package/src/__tests__/persona-resolver.test.ts +56 -2
  135. package/src/__tests__/personal-memory-auth-bypass.test.ts +62 -0
  136. package/src/__tests__/phone.test.ts +1 -15
  137. package/src/__tests__/plugin-bootstrap.test.ts +25 -196
  138. package/src/__tests__/plugin-disabled-state.test.ts +33 -0
  139. package/src/__tests__/plugin-effective-enabled-set.test.ts +227 -0
  140. package/src/__tests__/plugin-event-hub-facade.test.ts +323 -0
  141. package/src/__tests__/plugin-import-boundary-guard.test.ts +156 -42
  142. package/src/__tests__/plugin-injector-contribution.test.ts +4 -20
  143. package/src/__tests__/plugin-pipeline.test.ts +63 -1
  144. package/src/__tests__/plugin-route-contribution.test.ts +62 -115
  145. package/src/__tests__/plugin-tool-contribution.test.ts +0 -22
  146. package/src/__tests__/plugin-types.test.ts +0 -1
  147. package/src/__tests__/pointer-conversation-trust.test.ts +38 -6
  148. package/src/__tests__/pre-model-call-sanitize.test.ts +1 -1
  149. package/src/__tests__/prune-old-tool-invocations-job.test.ts +97 -0
  150. package/src/__tests__/public-ingress-urls.test.ts +0 -48
  151. package/src/__tests__/qdrant-collection-migration.test.ts +145 -2
  152. package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +0 -1
  153. package/src/__tests__/resolve-trust-class.test.ts +28 -10
  154. package/src/__tests__/scaffold-managed-skill-tool.test.ts +137 -0
  155. package/src/__tests__/schedule-retry.test.ts +38 -14
  156. package/src/__tests__/schedule-routes-workflow-validation.test.ts +3 -3
  157. package/src/__tests__/schedule-routes.test.ts +14 -6
  158. package/src/__tests__/schedule-store.test.ts +3 -1
  159. package/src/__tests__/scheduler-disk-pressure.test.ts +17 -6
  160. package/src/__tests__/scheduler-recurrence.test.ts +57 -107
  161. package/src/__tests__/scheduler-reuse-conversation.test.ts +51 -46
  162. package/src/__tests__/scheduler-wake.test.ts +13 -23
  163. package/src/__tests__/scheduler-worker-standdown.test.ts +220 -0
  164. package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
  165. package/src/__tests__/shell-observability.test.ts +1 -0
  166. package/src/__tests__/skill-boundary-guard.test.ts +3 -4
  167. package/src/__tests__/skill-load-plugin-scope.test.ts +238 -0
  168. package/src/__tests__/skill-projection-feature-flag.test.ts +2 -0
  169. package/src/__tests__/skill-script-runner-host.test.ts +53 -0
  170. package/src/__tests__/skill-script-runner-sandbox.test.ts +5 -2
  171. package/src/__tests__/skill-tool-factory.test.ts +73 -1
  172. package/src/__tests__/slack-app-setup-skill-regression.test.ts +37 -14
  173. package/src/__tests__/slack-inbound-verification.test.ts +36 -0
  174. package/src/__tests__/slack-notification-approval-card.test.ts +89 -2
  175. package/src/__tests__/slack-reaction-canonical-approval.test.ts +51 -1
  176. package/src/__tests__/stt-hints.test.ts +2 -256
  177. package/src/__tests__/subagent-allowlist-validation.test.ts +47 -0
  178. package/src/__tests__/subagent-call-site-routing.test.ts +72 -0
  179. package/src/__tests__/subagent-fork-notifications.test.ts +14 -2
  180. package/src/__tests__/subagent-persistence.test.ts +122 -0
  181. package/src/__tests__/subagent-spawn-and-await.test.ts +1 -0
  182. package/src/__tests__/subagent-terminal-message.test.ts +99 -0
  183. package/src/__tests__/subagent-tool-gate-mode.test.ts +78 -1
  184. package/src/__tests__/system-prompt.test.ts +17 -118
  185. package/src/__tests__/task-scheduler.test.ts +68 -89
  186. package/src/__tests__/telephony-credential-preflight.test.ts +312 -0
  187. package/src/__tests__/telephony-tts-guard.test.ts +538 -0
  188. package/src/__tests__/title-generate-hook.test.ts +16 -0
  189. package/src/__tests__/tool-approval-handler.test.ts +87 -1
  190. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +45 -20
  191. package/src/__tests__/tool-grant-request-escalation.test.ts +39 -0
  192. package/src/__tests__/trust-context-guards.test.ts +21 -10
  193. package/src/__tests__/trusted-contact-multichannel.test.ts +4 -0
  194. package/src/__tests__/twilio-provider.test.ts +17 -17
  195. package/src/__tests__/twilio-routes-twiml.test.ts +6 -543
  196. package/src/__tests__/twilio-routes.test.ts +257 -387
  197. package/src/__tests__/twilio-validation.test.ts +1 -1
  198. package/src/__tests__/unified-turn-context-client-os.test.ts +45 -0
  199. package/src/__tests__/usage-cache-backfill-migration.test.ts +5 -0
  200. package/src/__tests__/voice-approval-resolver-relay.test.ts +150 -0
  201. package/src/__tests__/voice-ingress-preflight.test.ts +61 -0
  202. package/src/__tests__/voice-quality.test.ts +2 -2
  203. package/src/__tests__/voice-scoped-grant-consumer.test.ts +1 -7
  204. package/src/__tests__/voice-session-bridge-processing-wait.test.ts +27 -0
  205. package/src/__tests__/voice-session-bridge.test.ts +1 -10
  206. package/src/__tests__/workspace-git-service.test.ts +1 -1
  207. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +1 -0
  208. package/src/__tests__/workspace-migration-118-seed-now-md.test.ts +120 -0
  209. package/src/__tests__/workspace-migration-120-revise-onboarding-threads.test.ts +175 -0
  210. package/src/__tests__/workspace-migration-121-seed-default-user-guardrails.test.ts +118 -0
  211. package/src/__tests__/workspace-migration-122-relocate-default-user-boundary.test.ts +119 -0
  212. package/src/__tests__/workspace-migration-123-swap-quality-profile-to-fable.test.ts +191 -0
  213. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +235 -241
  214. package/src/acp/__tests__/prepare-agent-env.test.ts +0 -97
  215. package/src/acp/__tests__/session-manager-persistence.test.ts +2 -0
  216. package/src/acp/__tests__/session-manager-resume.test.ts +8 -0
  217. package/src/acp/__tests__/session-manager-usage.test.ts +6 -0
  218. package/src/acp/__tests__/session-manager.test.ts +6 -0
  219. package/src/acp/agent-process.test.ts +116 -0
  220. package/src/acp/agent-process.ts +71 -0
  221. package/src/acp/auto-install.ts +12 -8
  222. package/src/acp/failure-error.test.ts +109 -0
  223. package/src/acp/failure-error.ts +111 -0
  224. package/src/acp/prepare-agent-env.ts +0 -12
  225. package/src/acp/resolve-agent.test.ts +6 -45
  226. package/src/acp/resolve-agent.ts +2 -4
  227. package/src/acp/session-manager.test.ts +312 -0
  228. package/src/acp/session-manager.ts +102 -46
  229. package/src/agent/ax-tree-compaction.test.ts +4 -1
  230. package/src/agent/image-optimize.ts +17 -127
  231. package/src/agent/loop.ts +45 -192
  232. package/src/api/events/background-tool-completed.test.ts +51 -0
  233. package/src/api/events/background-tool-completed.ts +29 -0
  234. package/src/api/events/background-tool-started.test.ts +36 -0
  235. package/src/api/events/background-tool-started.ts +28 -0
  236. package/src/api/events/conversation-error.ts +0 -1
  237. package/src/api/events/open-panel.ts +33 -0
  238. package/src/api/events/subagent-status-changed.ts +4 -1
  239. package/src/api/index.ts +19 -0
  240. package/src/api/responses/conversation-message.ts +34 -0
  241. package/src/approvals/guardian-decision-primitive.ts +74 -24
  242. package/src/approvals/guardian-request-resolvers.ts +450 -122
  243. package/src/apps/app-store.ts +1 -0
  244. package/src/apps/shared-app-links-store.ts +1 -0
  245. package/src/avatar/identity-avatar.ts +3 -7
  246. package/src/background-wake/wake-intent-hooks.test.ts +1 -1
  247. package/src/calls/__tests__/fish-audio-client.test.ts +73 -0
  248. package/src/calls/__tests__/gateway-invite-reader.test.ts +170 -0
  249. package/src/calls/__tests__/relay-setup-router.test.ts +126 -102
  250. package/src/calls/call-controller.ts +134 -48
  251. package/src/calls/call-domain.ts +18 -42
  252. package/src/calls/call-pointer-messages.ts +51 -67
  253. package/src/calls/call-setup-flow-types.ts +76 -0
  254. package/src/calls/call-setup-flow.ts +1743 -0
  255. package/src/calls/call-speech-output.ts +87 -23
  256. package/src/calls/call-store.ts +4 -0
  257. package/src/calls/call-transport.ts +22 -36
  258. package/src/calls/fish-audio-client.ts +3 -0
  259. package/src/calls/gateway-invite-reader.ts +98 -0
  260. package/src/calls/guardian-wait-controller.ts +451 -0
  261. package/src/calls/media-stream-audio-transcode.ts +82 -2
  262. package/src/calls/media-stream-output.ts +142 -36
  263. package/src/calls/media-stream-server.ts +297 -178
  264. package/src/calls/media-stream-stt-session.ts +393 -50
  265. package/src/calls/media-turn-detector.ts +4 -6
  266. package/src/calls/relay-access-wait.ts +3 -4
  267. package/src/calls/relay-setup-router.ts +22 -43
  268. package/src/calls/relay-verification.ts +17 -24
  269. package/src/calls/resolve-call-tts-provider.ts +96 -29
  270. package/src/calls/stt-hints.ts +24 -87
  271. package/src/calls/telephony-credential-preflight.ts +159 -0
  272. package/src/calls/telephony-tts-capability.ts +183 -0
  273. package/src/calls/tts-call-strategy.ts +18 -20
  274. package/src/calls/twilio-provider.ts +3 -2
  275. package/src/calls/twilio-routes.ts +47 -351
  276. package/src/calls/types.ts +5 -3
  277. package/src/calls/voice-ingress-preflight.ts +9 -0
  278. package/src/calls/voice-quality.ts +13 -18
  279. package/src/calls/voice-session-bridge.ts +25 -35
  280. package/src/channels/__tests__/types.test.ts +28 -0
  281. package/src/channels/channel-verification-sessions.ts +7 -2
  282. package/src/channels/config.ts +0 -42
  283. package/src/channels/slack-thread-store.ts +12 -0
  284. package/src/channels/types.ts +23 -0
  285. package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -1
  286. package/src/cli/commands/__tests__/schedules.test.ts +83 -34
  287. package/src/cli/commands/__tests__/status.test.ts +42 -2
  288. package/src/cli/commands/contacts.ts +2 -2
  289. package/src/cli/commands/credentials.ts +17 -0
  290. package/src/cli/commands/monitoring.ts +191 -0
  291. package/src/cli/commands/schedules-worker.ts +175 -0
  292. package/src/cli/commands/schedules.ts +169 -43
  293. package/src/cli/commands/status.ts +12 -1
  294. package/src/cli/commands/tools.ts +5 -1
  295. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +1 -0
  296. package/src/cli/lib/__tests__/search-plugins.test.ts +29 -1
  297. package/src/cli/lib/__tests__/toggle-plugin.test.ts +7 -0
  298. package/src/cli/lib/search-plugins.ts +6 -0
  299. package/src/cli/lib/toggle-plugin.ts +21 -2
  300. package/src/cli/program.ts +3 -1
  301. package/src/config/__tests__/deployment-context-defaults.test.ts +23 -0
  302. package/src/config/acp-defaults.test.ts +0 -10
  303. package/src/config/acp-defaults.ts +0 -6
  304. package/src/config/bundled-skills/acp/SKILL.md +13 -45
  305. package/src/config/bundled-skills/acp/TOOLS.json +3 -3
  306. package/src/config/bundled-skills/app-builder/SKILL.md +3 -3
  307. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +9 -9
  308. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +1 -1
  309. package/src/config/bundled-skills/contacts/tools/contact-search.ts +1 -1
  310. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  311. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +9 -9
  312. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +10 -7
  313. package/src/config/bundled-skills/skill-management/TOOLS.json +10 -0
  314. package/src/config/call-site-defaults.ts +0 -2
  315. package/src/config/feature-flag-registry.json +16 -8
  316. package/src/config/loader.ts +11 -0
  317. package/src/config/schema.ts +9 -1
  318. package/src/config/schemas/__tests__/memory-v2.test.ts +2 -2
  319. package/src/config/schemas/__tests__/memory-v3.test.ts +64 -0
  320. package/src/config/schemas/call-site-catalog.ts +0 -12
  321. package/src/config/schemas/calls.ts +8 -0
  322. package/src/config/schemas/elevenlabs.ts +1 -1
  323. package/src/config/schemas/llm.ts +0 -2
  324. package/src/config/schemas/memory-v2.ts +1 -1
  325. package/src/config/schemas/memory-v3.ts +82 -0
  326. package/src/config/schemas/monitoring.ts +61 -0
  327. package/src/config/schemas/schedules.ts +35 -0
  328. package/src/config/seed-inference-profiles.ts +2 -2
  329. package/src/config/skills-plugin-filter.test.ts +96 -0
  330. package/src/config/skills.ts +26 -0
  331. package/src/contacts/__tests__/member-write-relay.test.ts +156 -4
  332. package/src/contacts/canonical-guardian-store.ts +5 -0
  333. package/src/contacts/contact-store.ts +81 -35
  334. package/src/contacts/contacts-write.ts +50 -25
  335. package/src/contacts/member-write-relay.ts +115 -31
  336. package/src/contacts/types.ts +0 -8
  337. package/src/context/compactor.ts +29 -15
  338. package/src/context/outbound-sanitize.ts +202 -0
  339. package/src/daemon/__tests__/abort-null-controller.test.ts +167 -0
  340. package/src/daemon/__tests__/conversation-tool-setup-plugin-scope.test.ts +122 -0
  341. package/src/daemon/__tests__/embedding-reconcile.test.ts +99 -16
  342. package/src/daemon/__tests__/wake-conversation-ops-completion.test.ts +107 -0
  343. package/src/daemon/abort-watchdog.ts +7 -0
  344. package/src/daemon/app-source-watcher.ts +9 -0
  345. package/src/daemon/assistant-attachments.ts +42 -0
  346. package/src/daemon/conversation-agent-loop-handlers.ts +256 -147
  347. package/src/daemon/conversation-agent-loop.ts +61 -68
  348. package/src/daemon/conversation-error.ts +1 -15
  349. package/src/daemon/conversation-history.ts +32 -236
  350. package/src/daemon/conversation-initial-prompt.ts +77 -0
  351. package/src/daemon/conversation-lifecycle.ts +25 -1
  352. package/src/daemon/conversation-messaging.ts +2 -2
  353. package/src/daemon/conversation-plugin-scope.ts +38 -0
  354. package/src/daemon/conversation-process.ts +50 -14
  355. package/src/daemon/conversation-runtime-assembly.ts +36 -6
  356. package/src/daemon/conversation-skill-tools.ts +19 -3
  357. package/src/daemon/conversation-store.ts +3 -3
  358. package/src/daemon/conversation-surfaces.ts +171 -0
  359. package/src/daemon/conversation-tool-setup.test.ts +95 -0
  360. package/src/daemon/conversation-tool-setup.ts +116 -1
  361. package/src/daemon/conversation-turn-finalize.ts +211 -0
  362. package/src/daemon/conversation.ts +92 -1
  363. package/src/daemon/disk-pressure-policy.ts +2 -1
  364. package/src/daemon/event-loop-watchdog.ts +22 -6
  365. package/src/daemon/external-plugins-bootstrap.ts +47 -134
  366. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +1 -1
  367. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +26 -14
  368. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +27 -14
  369. package/src/daemon/handlers/__tests__/config-channels.test.ts +0 -5
  370. package/src/daemon/handlers/config-a2a.ts +7 -14
  371. package/src/daemon/handlers/config-channels.ts +2 -10
  372. package/src/daemon/handlers/conversation-history.ts +3 -1
  373. package/src/daemon/handlers/conversations.ts +0 -52
  374. package/src/daemon/handlers/shared.ts +4 -24
  375. package/src/daemon/handlers/skills.ts +4 -1
  376. package/src/daemon/host-cu-proxy.ts +1 -1
  377. package/src/daemon/lifecycle.ts +59 -472
  378. package/src/daemon/message-protocol.ts +3 -3
  379. package/src/daemon/message-provenance.ts +12 -14
  380. package/src/daemon/message-types/background-tools.ts +28 -0
  381. package/src/daemon/message-types/contacts.ts +2 -10
  382. package/src/daemon/message-types/conversations.ts +10 -0
  383. package/src/daemon/message-types/integrations.ts +2 -0
  384. package/src/daemon/message-types/surfaces.ts +1 -0
  385. package/src/daemon/message-types/sync.ts +1 -0
  386. package/src/daemon/pointer-conversation-trust.ts +23 -9
  387. package/src/daemon/pointer-turn-runner.ts +174 -0
  388. package/src/daemon/providers-setup.ts +16 -0
  389. package/src/daemon/shutdown-handlers.ts +139 -47
  390. package/src/daemon/skill-memory-refresh.ts +2 -2
  391. package/src/daemon/sqlite-corruption-watchdog.test.ts +217 -0
  392. package/src/daemon/sqlite-corruption-watchdog.ts +155 -0
  393. package/src/daemon/tool-setup-types.ts +8 -0
  394. package/src/daemon/trust-context.ts +21 -6
  395. package/src/daemon/turn-latency-tracker.test.ts +93 -0
  396. package/src/daemon/turn-latency-tracker.ts +38 -0
  397. package/src/daemon/wake-conversation-ops.ts +10 -8
  398. package/src/documents/document-comments-store.test.ts +7 -1
  399. package/src/documents/document-comments-store.ts +8 -0
  400. package/src/documents/document-store.ts +15 -0
  401. package/src/export/__tests__/transcript-formatter.test.ts +11 -0
  402. package/src/export/transcript-formatter.ts +49 -67
  403. package/src/home/__tests__/feed-writer.test.ts +247 -1
  404. package/src/home/conversation-starter-checkpoints.ts +1 -0
  405. package/src/home/feed-writer.ts +61 -0
  406. package/src/home/job-handlers/conversation-starters.ts +1 -0
  407. package/src/hooks/hook-loader.ts +12 -0
  408. package/src/hooks/registry.ts +32 -4
  409. package/src/inbound/public-ingress-urls.ts +0 -17
  410. package/src/ipc/__tests__/contacts-info-ipc.test.ts +285 -0
  411. package/src/ipc/assistant-server.ts +28 -7
  412. package/src/ipc/routes/__tests__/contacts-mirror-apply-atomicity.test.ts +62 -0
  413. package/src/ipc/routes/__tests__/contacts-mirror-apply.test.ts +272 -0
  414. package/src/ipc/routes/__tests__/contacts-mirror-ipc-routes.test.ts +322 -0
  415. package/src/ipc/routes/__tests__/invite-ipc-routes.test.ts +60 -4
  416. package/src/ipc/routes/contacts-info-ipc-routes.ts +239 -0
  417. package/src/ipc/routes/contacts-mirror-ipc-routes.ts +202 -0
  418. package/src/ipc/routes/db-proxy.ts +5 -2
  419. package/src/ipc/routes/invite-ipc-routes.ts +53 -39
  420. package/src/jobs/worker.ts +9 -2
  421. package/src/live-voice/__tests__/live-voice-archive.test.ts +10 -1
  422. package/src/live-voice/live-voice-archive.ts +5 -0
  423. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +22 -6
  424. package/src/messaging/providers/channel-transport.ts +6 -0
  425. package/src/messaging/providers/index.ts +3 -0
  426. package/src/messaging/providers/slack/api.test.ts +49 -1
  427. package/src/messaging/providers/slack/api.ts +110 -0
  428. package/src/messaging/providers/slack/send.test.ts +85 -0
  429. package/src/messaging/providers/slack/send.ts +100 -28
  430. package/src/messaging/providers/slack/transport.ts +7 -13
  431. package/src/messaging/providers/whatsapp/send.ts +29 -9
  432. package/src/monitoring/__tests__/control.test.ts +235 -0
  433. package/src/monitoring/__tests__/sample-ring-buffer.test.ts +96 -0
  434. package/src/monitoring/control.ts +105 -0
  435. package/src/monitoring/resource-sampler.ts +258 -0
  436. package/src/monitoring/sample-ring-buffer.ts +108 -0
  437. package/src/monitoring/worker.ts +94 -0
  438. package/src/notifications/README.md +1 -1
  439. package/src/notifications/access-request-copy.ts +94 -23
  440. package/src/notifications/adapters/slack.ts +89 -21
  441. package/src/notifications/approval-card-builder.ts +33 -18
  442. package/src/notifications/approval-card-data.ts +48 -15
  443. package/src/notifications/broadcaster.ts +25 -16
  444. package/src/notifications/decision-engine.ts +131 -32
  445. package/src/notifications/guardian-question-mode.ts +58 -10
  446. package/src/permissions/approval-policy.ts +8 -1
  447. package/src/permissions/checker.ts +2 -0
  448. package/src/permissions/gateway-threshold-reader.ts +7 -6
  449. package/src/permissions/ipc-risk-types.ts +1 -0
  450. package/src/permissions/secret-prompter.ts +11 -3
  451. package/src/permissions/types.ts +2 -2
  452. package/src/persistence/__tests__/conversation-queries-search.test.ts +397 -0
  453. package/src/persistence/__tests__/conversation-search-lexical.test.ts +103 -0
  454. package/src/persistence/__tests__/jobs-worker-memory-disabled.test.ts +95 -0
  455. package/src/persistence/__tests__/slow-query-log.test.ts +266 -0
  456. package/src/persistence/__tests__/slow-sync-log.test.ts +155 -0
  457. package/src/persistence/a2a-invite-store.ts +205 -0
  458. package/src/persistence/attachments-store.ts +94 -10
  459. package/src/persistence/checkpoints.ts +45 -0
  460. package/src/persistence/conversation-crud.ts +453 -260
  461. package/src/persistence/conversation-display-order-migration.ts +8 -2
  462. package/src/persistence/conversation-group-migration.test.ts +8 -0
  463. package/src/persistence/conversation-group-migration.ts +9 -0
  464. package/src/persistence/conversation-queries.ts +244 -125
  465. package/src/persistence/conversation-row-batch-delete.ts +9 -5
  466. package/src/persistence/conversation-search-lexical.ts +49 -0
  467. package/src/persistence/db-async-query.ts +42 -0
  468. package/src/persistence/db-connection.ts +13 -0
  469. package/src/persistence/delivery-channels.ts +0 -82
  470. package/src/persistence/delivery-crud.ts +27 -6
  471. package/src/persistence/embeddings/__tests__/embedding-identity.test.ts +18 -5
  472. package/src/persistence/embeddings/__tests__/messages-lexical-index.test.ts +334 -0
  473. package/src/persistence/embeddings/embedding-identity.ts +17 -5
  474. package/src/persistence/embeddings/embedding-runtime-manager.ts +31 -0
  475. package/src/persistence/embeddings/messages-lexical-index.ts +396 -0
  476. package/src/persistence/embeddings/qdrant-client.ts +81 -5
  477. package/src/persistence/group-crud.ts +13 -1
  478. package/src/persistence/job-handlers/__tests__/message-lexical-backfill.test.ts +506 -0
  479. package/src/persistence/job-handlers/__tests__/message-lexical-enqueue.test.ts +164 -0
  480. package/src/persistence/job-handlers/__tests__/message-lexical.test.ts +259 -0
  481. package/src/persistence/job-handlers/cleanup.ts +57 -9
  482. package/src/persistence/job-handlers/message-lexical-backfill.ts +187 -0
  483. package/src/persistence/job-handlers/message-lexical.ts +251 -0
  484. package/src/persistence/jobs-store.ts +102 -8
  485. package/src/persistence/jobs-worker.ts +68 -28
  486. package/src/persistence/llm-request-log-store.ts +35 -19
  487. package/src/persistence/llm-usage-store.ts +11 -0
  488. package/src/persistence/memory-lifecycle-hooks.test.ts +65 -0
  489. package/src/persistence/memory-lifecycle-hooks.ts +55 -1
  490. package/src/persistence/migrations/028-call-session-mode.ts +1 -1
  491. package/src/persistence/migrations/116-messages-fts.ts +8 -1
  492. package/src/persistence/migrations/311-create-subagents-table.ts +50 -0
  493. package/src/persistence/migrations/312-drop-inbox-conversation-state-table.ts +15 -0
  494. package/src/persistence/migrations/313-drop-messages-fts.ts +17 -0
  495. package/src/persistence/migrations/314-add-conversation-enabled-plugins.ts +25 -0
  496. package/src/persistence/migrations/315-create-a2a-invites.ts +58 -0
  497. package/src/persistence/migrations/316-drop-contact-channels-invite-id.ts +28 -0
  498. package/src/persistence/migrations/317-canonical-guardian-requester-signals.ts +24 -0
  499. package/src/persistence/migrations/318-drop-contact-channel-telemetry.ts +32 -0
  500. package/src/persistence/migrations/__tests__/315-create-a2a-invites.test.ts +161 -0
  501. package/src/persistence/migrations/__tests__/316-drop-contact-channels-invite-id.test.ts +156 -0
  502. package/src/persistence/migrations/__tests__/318-drop-contact-channel-telemetry.test.ts +134 -0
  503. package/src/persistence/migrations/__tests__/migration-prefix-guard.test.ts +99 -0
  504. package/src/persistence/raw-query.ts +50 -11
  505. package/src/persistence/schema/a2a.ts +14 -0
  506. package/src/persistence/schema/contacts.ts +0 -62
  507. package/src/persistence/schema/conversation-groups.ts +12 -0
  508. package/src/persistence/schema/conversations.ts +2 -0
  509. package/src/persistence/schema/documents.ts +65 -0
  510. package/src/persistence/schema/guardian.ts +4 -0
  511. package/src/persistence/schema/index.ts +4 -0
  512. package/src/persistence/schema/memory-injection.ts +62 -0
  513. package/src/persistence/schema/workflows.ts +55 -0
  514. package/src/persistence/slow-query-log.ts +437 -0
  515. package/src/persistence/slow-sync-log.ts +204 -0
  516. package/src/persistence/steps.ts +16 -0
  517. package/src/persistence/subagent-store.ts +125 -0
  518. package/src/persistence/worker-control.ts +45 -238
  519. package/src/plugin-api/event-hub-facade.ts +172 -0
  520. package/src/plugin-api/index.ts +9 -1
  521. package/src/plugin-api/types.ts +40 -9
  522. package/src/plugins/defaults/default-plugin-names.ts +60 -0
  523. package/src/plugins/defaults/index.ts +19 -29
  524. package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +46 -67
  525. package/src/plugins/defaults/memory/__tests__/db-async-query.test.ts +36 -1
  526. package/src/plugins/defaults/memory/__tests__/find-most-recent-retrospective-for.test.ts +2 -4
  527. package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +2 -2
  528. package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-schedule.test.ts +9 -0
  529. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +7 -4
  530. package/src/plugins/defaults/memory/config.ts +11 -0
  531. package/src/plugins/defaults/memory/context-search/agent-runner.ts +1 -1
  532. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +124 -113
  533. package/src/plugins/defaults/memory/embeddings.ts +77 -0
  534. package/src/plugins/defaults/memory/find-most-recent-retrospective-for.ts +66 -0
  535. package/src/plugins/defaults/memory/frontmatter.ts +50 -0
  536. package/src/plugins/defaults/memory/graph/bootstrap.test.ts +36 -10
  537. package/src/plugins/defaults/memory/graph/bootstrap.ts +6 -5
  538. package/src/plugins/defaults/memory/graph/consolidation.ts +3 -5
  539. package/src/plugins/defaults/memory/graph/extraction.ts +3 -6
  540. package/src/plugins/defaults/memory/graph/graph-search.ts +12 -23
  541. package/src/plugins/defaults/memory/graph/narrative.ts +3 -5
  542. package/src/plugins/defaults/memory/graph/pattern-scan.ts +3 -5
  543. package/src/plugins/defaults/memory/graph/retriever.test.ts +1 -6
  544. package/src/plugins/defaults/memory/graph/retriever.ts +5 -10
  545. package/src/plugins/defaults/memory/graph/tools.ts +1 -1
  546. package/src/plugins/defaults/memory/hooks/init.ts +31 -5
  547. package/src/plugins/defaults/memory/indexer.ts +2 -3
  548. package/src/plugins/defaults/memory/injectors.ts +2 -2
  549. package/src/plugins/defaults/memory/job-handler-registration.ts +96 -0
  550. package/src/plugins/defaults/memory/job-handlers/backfill.ts +4 -26
  551. package/src/plugins/defaults/memory/job-handlers/embedding.test.ts +6 -19
  552. package/src/plugins/defaults/memory/job-handlers/embedding.ts +9 -22
  553. package/src/plugins/defaults/memory/job-handlers/index-maintenance.ts +2 -3
  554. package/src/plugins/defaults/memory/job-handlers.ts +13 -12
  555. package/src/plugins/defaults/memory/jobs/embed-concept-page.ts +1 -1
  556. package/src/plugins/defaults/memory/llm-helpers.ts +76 -0
  557. package/src/plugins/defaults/memory/memory-retrospective-job.ts +8 -5
  558. package/src/plugins/defaults/memory/memory-retrospective-startup-cleanup.ts +2 -2
  559. package/src/plugins/defaults/memory/memory-v2-concept-frequency.ts +2 -0
  560. package/src/plugins/defaults/memory/persistence-hooks.ts +46 -1
  561. package/src/plugins/defaults/memory/pkb/pkb-index.ts +1 -5
  562. package/src/plugins/defaults/memory/pkb/pkb-search.ts +2 -2
  563. package/src/plugins/defaults/memory/routes/__tests__/memory-v2-simulate-route.test.ts +4 -6
  564. package/src/plugins/defaults/memory/routes/memory-item-routes.ts +1 -1
  565. package/src/plugins/defaults/memory/search/semantic.ts +2 -2
  566. package/src/plugins/defaults/memory/startup.ts +244 -0
  567. package/src/plugins/defaults/memory/task-memory-cleanup.ts +8 -0
  568. package/src/{daemon → plugins/defaults/memory/v2}/__tests__/memory-v2-startup.test.ts +6 -6
  569. package/src/plugins/defaults/memory/v2/__tests__/migration.test.ts +2 -13
  570. package/src/plugins/defaults/memory/v2/__tests__/router.test.ts +4 -5
  571. package/src/plugins/defaults/memory/v2/__tests__/sweep-job.test.ts +1 -8
  572. package/src/plugins/defaults/memory/v2/activation.ts +2 -4
  573. package/src/plugins/defaults/memory/v2/cli-command-store.ts +2 -4
  574. package/src/plugins/defaults/memory/v2/frontmatter-sweep.ts +1 -1
  575. package/src/{daemon → plugins/defaults/memory/v2}/memory-v2-startup.test.ts +8 -8
  576. package/src/{daemon → plugins/defaults/memory/v2}/memory-v2-startup.ts +20 -22
  577. package/src/plugins/defaults/memory/v2/migration.ts +2 -5
  578. package/src/plugins/defaults/memory/v2/page-store.ts +1 -1
  579. package/src/plugins/defaults/memory/v2/qdrant.ts +2 -3
  580. package/src/plugins/defaults/memory/v2/router.ts +5 -5
  581. package/src/plugins/defaults/memory/v2/sim.ts +2 -4
  582. package/src/plugins/defaults/memory/v2/skill-store.ts +2 -4
  583. package/src/plugins/defaults/memory/v2/sweep-job.ts +6 -6
  584. package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +11 -9
  585. package/src/plugins/defaults/memory/v3/__tests__/dense.test.ts +107 -2
  586. package/src/plugins/defaults/memory/v3/__tests__/gate-flag.test.ts +69 -0
  587. package/src/plugins/defaults/memory/v3/__tests__/gate.test.ts +181 -0
  588. package/src/plugins/defaults/memory/v3/__tests__/live-integration.test.ts +7 -3
  589. package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +345 -8
  590. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +1 -4
  591. package/src/plugins/defaults/memory/v3/__tests__/section-needle.test.ts +64 -0
  592. package/src/plugins/defaults/memory/v3/__tests__/shadow-integration.test.ts +18 -13
  593. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +62 -1
  594. package/src/plugins/defaults/memory/v3/card.ts +1 -4
  595. package/src/plugins/defaults/memory/v3/dense.ts +60 -25
  596. package/src/plugins/defaults/memory/v3/edge.ts +1 -1
  597. package/src/plugins/defaults/memory/v3/gate-flag.ts +20 -0
  598. package/src/plugins/defaults/memory/v3/gate.ts +158 -0
  599. package/src/plugins/defaults/memory/v3/maintain-job.ts +2 -4
  600. package/src/plugins/defaults/memory/v3/orchestrate.ts +186 -25
  601. package/src/plugins/defaults/memory/v3/pool-select.test.ts +7 -9
  602. package/src/plugins/defaults/memory/v3/pool-select.ts +6 -6
  603. package/src/plugins/defaults/memory/v3/prune.ts +2 -2
  604. package/src/plugins/defaults/memory/v3/section-dense-store.ts +8 -11
  605. package/src/plugins/defaults/memory/v3/section-needle.ts +27 -7
  606. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +10 -0
  607. package/src/plugins/defaults/memory/v3-eval/eval-packets.ts +1 -4
  608. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +5 -0
  609. package/src/plugins/defaults/turn-context/injectors.ts +1 -0
  610. package/src/plugins/defaults/turn-context/unified-turn-context.ts +12 -0
  611. package/src/plugins/injector-registry.ts +13 -2
  612. package/src/plugins/mtime-cache.ts +36 -47
  613. package/src/plugins/pipeline.ts +15 -1
  614. package/src/plugins/types.ts +14 -23
  615. package/src/prompts/__tests__/system-prompt.test.ts +170 -32
  616. package/src/prompts/persona-resolver.ts +13 -9
  617. package/src/prompts/system-prompt.ts +30 -9
  618. package/src/prompts/templates/BOOTSTRAP.md +16 -119
  619. package/src/prompts/templates/SOUL.md +43 -13
  620. package/src/prompts/templates/system-sections.ts +32 -27
  621. package/src/prompts/templates/users/default.md +19 -0
  622. package/src/providers/__tests__/placeholder-sentinels.test.ts +56 -0
  623. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +96 -1
  624. package/src/providers/model-intents.ts +2 -2
  625. package/src/providers/openai/chat-completions-provider.ts +14 -1
  626. package/src/providers/openai/responses-provider.ts +18 -0
  627. package/src/providers/placeholder-sentinels.ts +18 -8
  628. package/src/providers/retry.ts +1 -0
  629. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +0 -64
  630. package/src/providers/speech-to-text/__tests__/resolve.test.ts +92 -79
  631. package/src/providers/speech-to-text/deepgram-realtime.test.ts +98 -0
  632. package/src/providers/speech-to-text/deepgram-realtime.ts +62 -3
  633. package/src/providers/speech-to-text/provider-catalog.ts +0 -89
  634. package/src/providers/speech-to-text/resolve.ts +55 -0
  635. package/src/providers/tool-progress-events.test.ts +50 -0
  636. package/src/providers/tool-progress-events.ts +99 -0
  637. package/src/providers/types.ts +1 -1
  638. package/src/providers/vellum-model-routing.test.ts +51 -0
  639. package/src/providers/vellum-model-routing.ts +84 -0
  640. package/src/runtime/AGENTS.md +5 -4
  641. package/src/runtime/__tests__/agent-wake.test.ts +158 -1
  642. package/src/runtime/__tests__/channel-verification-service.test.ts +2 -2
  643. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +37 -8
  644. package/src/runtime/access-request-helper.ts +154 -3
  645. package/src/runtime/actor-trust-resolver.ts +7 -21
  646. package/src/runtime/agent-wake.ts +41 -1
  647. package/src/runtime/assistant-event.ts +86 -12
  648. package/src/runtime/auth/__tests__/route-policy.test.ts +31 -0
  649. package/src/runtime/channel-approval-types.ts +52 -2
  650. package/src/runtime/channel-approvals.ts +23 -8
  651. package/src/runtime/channel-invite-transport.ts +2 -2
  652. package/src/runtime/channel-invite-transports/voice.ts +1 -1
  653. package/src/runtime/channel-readiness-service.ts +3 -5
  654. package/src/runtime/channel-retry-sweep.ts +18 -24
  655. package/src/runtime/channel-verification-service.ts +9 -11
  656. package/src/runtime/finalize-event-delivery.ts +30 -16
  657. package/src/runtime/guardian-action-service.ts +10 -11
  658. package/src/runtime/guardian-reply-router.ts +122 -21
  659. package/src/runtime/http-server.ts +44 -101
  660. package/src/runtime/introduction-policy.test.ts +214 -0
  661. package/src/runtime/introduction-policy.ts +284 -0
  662. package/src/runtime/invite-service.ts +104 -361
  663. package/src/runtime/local-actor-identity.test.ts +38 -0
  664. package/src/runtime/local-actor-identity.ts +14 -0
  665. package/src/runtime/middleware/__tests__/rate-limiter.test.ts +22 -0
  666. package/src/runtime/middleware/rate-limiter.ts +30 -0
  667. package/src/runtime/middleware/twilio-validation.ts +5 -7
  668. package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +28 -0
  669. package/src/runtime/migrations/origin-mode.ts +6 -6
  670. package/src/runtime/pre-first-message-gate.ts +1 -0
  671. package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +6 -35
  672. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +24 -3
  673. package/src/runtime/routes/__tests__/contact-routes.test.ts +80 -15
  674. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +7 -1
  675. package/src/runtime/routes/__tests__/global-search-routes.test.ts +0 -3
  676. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +142 -1
  677. package/src/runtime/routes/__tests__/invite-relay-routes.test.ts +119 -30
  678. package/src/runtime/routes/__tests__/migration-import-allowed-hosts-env.test.ts +70 -0
  679. package/src/runtime/routes/__tests__/monitoring-routes.test.ts +194 -0
  680. package/src/runtime/routes/__tests__/plugins-routes.test.ts +755 -29
  681. package/src/runtime/routes/__tests__/retrospective-routes.test.ts +4 -0
  682. package/src/runtime/routes/__tests__/schedule-routes-create.test.ts +70 -0
  683. package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +161 -0
  684. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +1 -1
  685. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +1 -1
  686. package/src/runtime/routes/acp-routes.ts +15 -3
  687. package/src/runtime/routes/app-management-routes.ts +4 -7
  688. package/src/runtime/routes/approval-routes.ts +4 -0
  689. package/src/runtime/routes/attachment-routes.ts +54 -3
  690. package/src/runtime/routes/avatar-routes.ts +0 -4
  691. package/src/runtime/routes/background-tool-routes.ts +34 -5
  692. package/src/runtime/routes/btw-routes.ts +32 -4
  693. package/src/runtime/routes/channel-route-shared.ts +18 -14
  694. package/src/runtime/routes/channel-verification-routes.ts +0 -3
  695. package/src/runtime/routes/contact-routes.ts +298 -117
  696. package/src/runtime/routes/conversation-list-routes.ts +6 -0
  697. package/src/runtime/routes/conversation-management-routes.ts +74 -37
  698. package/src/runtime/routes/conversation-query-routes.ts +5 -2
  699. package/src/runtime/routes/conversation-routes.ts +222 -82
  700. package/src/runtime/routes/conversations-import-routes.ts +8 -1
  701. package/src/runtime/routes/credential-prompt-routes.ts +20 -5
  702. package/src/runtime/routes/debug-routes.ts +1 -0
  703. package/src/runtime/routes/documents-routes.ts +5 -2
  704. package/src/runtime/routes/empty-state-greeting-cache.ts +37 -10
  705. package/src/runtime/routes/global-search-routes.ts +2 -1
  706. package/src/runtime/routes/guardian-approval-interception.ts +51 -0
  707. package/src/runtime/routes/home-feed-routes.ts +60 -1
  708. package/src/runtime/routes/identity-routes.ts +4 -87
  709. package/src/runtime/routes/inbound-message-handler.ts +82 -41
  710. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +430 -57
  711. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +302 -572
  712. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +76 -270
  713. package/src/runtime/routes/inbound-stages/background-dispatch.ts +21 -116
  714. package/src/runtime/routes/inbound-stages/edit-intercept.ts +9 -1
  715. package/src/runtime/routes/inbound-stages/escalation-intercept.test.ts +158 -0
  716. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +22 -1
  717. package/src/runtime/routes/index.ts +6 -0
  718. package/src/runtime/routes/inference-provider-connection-routes.ts +22 -16
  719. package/src/runtime/routes/internal-twilio-routes.ts +0 -15
  720. package/src/runtime/routes/messages-lexical-routes.ts +75 -0
  721. package/src/runtime/routes/migration-routes.ts +69 -6
  722. package/src/runtime/routes/monitoring-routes.ts +237 -0
  723. package/src/runtime/routes/plugins-routes.ts +329 -8
  724. package/src/runtime/routes/schedule-routes.ts +49 -8
  725. package/src/runtime/routes/schedule-worker-routes.ts +210 -0
  726. package/src/runtime/routes/secret-routes.ts +1 -1
  727. package/src/runtime/routes/surface-conversation-resolver.ts +1 -0
  728. package/src/runtime/services/__tests__/analyze-conversation.test.ts +54 -9
  729. package/src/runtime/services/__tests__/auto-analysis-enqueue.test.ts +30 -6
  730. package/src/runtime/services/__tests__/conversation-analyze-job.test.ts +43 -8
  731. package/src/runtime/services/__tests__/conversation-serializer.test.ts +20 -0
  732. package/src/runtime/services/analyze-conversation.ts +14 -4
  733. package/src/runtime/services/conversation-serializer.ts +5 -0
  734. package/src/runtime/slack-no-response.ts +47 -0
  735. package/src/runtime/slack-reply-session.test.ts +633 -0
  736. package/src/runtime/slack-reply-session.ts +382 -0
  737. package/src/runtime/slack-task-progress.ts +95 -0
  738. package/src/runtime/sync/resource-sync-events.ts +25 -0
  739. package/src/runtime/tool-grant-request-helper.ts +17 -1
  740. package/src/runtime/trust-class.test.ts +141 -0
  741. package/src/runtime/trust-class.ts +121 -0
  742. package/src/runtime/trust-verdict-consumer.ts +7 -6
  743. package/src/schedule/__tests__/run-script.test.ts +21 -0
  744. package/src/schedule/__tests__/worker-control.test.ts +241 -0
  745. package/src/schedule/run-script.ts +13 -6
  746. package/src/schedule/schedule-usage-store.ts +1 -0
  747. package/src/schedule/scheduler.ts +178 -127
  748. package/src/schedule/worker-control.ts +118 -0
  749. package/src/schedule/worker.ts +131 -0
  750. package/src/skills/__tests__/categories-cache.test.ts +38 -0
  751. package/src/skills/categories-cache.ts +40 -5
  752. package/src/skills/managed-store.ts +41 -6
  753. package/src/skills/validate-input.ts +47 -0
  754. package/src/stt/types.ts +5 -9
  755. package/src/subagent/manager.ts +267 -39
  756. package/src/subagent/types.ts +6 -2
  757. package/src/subagent/validate-allowlists.ts +65 -0
  758. package/src/telemetry/watchdog-direct-emit.test.ts +109 -0
  759. package/src/telemetry/watchdog-direct-emit.ts +89 -0
  760. package/src/tools/acp/list-agents.test.ts +0 -6
  761. package/src/tools/background-tool-registry.ts +61 -0
  762. package/src/tools/document/document-comment-tool.test.ts +4 -0
  763. package/src/tools/host-terminal/host-shell.test.ts +397 -0
  764. package/src/tools/host-terminal/host-shell.ts +203 -14
  765. package/src/tools/skills/find-similar-skills.test.ts +152 -1
  766. package/src/tools/skills/find-similar-skills.ts +30 -4
  767. package/src/tools/skills/load.ts +36 -0
  768. package/src/tools/skills/scaffold-managed.ts +66 -34
  769. package/src/tools/skills/skill-script-runner.ts +17 -0
  770. package/src/tools/skills/skill-tool-factory.ts +5 -1
  771. package/src/tools/terminal/safe-env.ts +3 -0
  772. package/src/tools/terminal/shell.test.ts +177 -0
  773. package/src/tools/terminal/shell.ts +98 -4
  774. package/src/tools/tool-approval-handler.ts +106 -29
  775. package/{node_modules/@vellumai/skill-host-contracts/src → src/tools}/tool-types.ts +16 -17
  776. package/src/tools/types.ts +29 -17
  777. package/src/tools/ui-surface/definitions.ts +3 -1
  778. package/src/tts/__tests__/provider-adapters.test.ts +37 -0
  779. package/src/tts/__tests__/provider-catalog.test.ts +21 -0
  780. package/src/tts/provider-catalog.ts +40 -9
  781. package/src/tts/providers/fish-audio-provider.ts +19 -10
  782. package/src/tts/types.ts +3 -4
  783. package/src/util/__tests__/process-tree.test.ts +10 -2
  784. package/src/util/__tests__/text-spacing.test.ts +53 -0
  785. package/src/util/cgroup-memory.ts +142 -0
  786. package/src/util/image-conversion.ts +281 -0
  787. package/src/util/phone.ts +1 -8
  788. package/src/util/platform.ts +33 -0
  789. package/src/util/process-tree.ts +10 -2
  790. package/src/util/text-spacing.ts +53 -0
  791. package/src/util/worker-process.ts +271 -0
  792. package/src/workflows/journal-store.ts +16 -1
  793. package/src/workflows/library.test.ts +35 -20
  794. package/src/workflows/library.ts +105 -66
  795. package/src/workspace/git-service.ts +1 -0
  796. package/src/workspace/migrations/011-backfill-installation-id.ts +83 -31
  797. package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +41 -22
  798. package/src/workspace/migrations/070-memory-v2-summary-schema-rebuild.ts +1 -1
  799. package/src/workspace/migrations/118-seed-now-md.ts +64 -0
  800. package/src/workspace/migrations/119-strip-persisted-memory-v3-tuning-defaults.ts +169 -0
  801. package/src/workspace/migrations/120-revise-onboarding-threads.ts +47 -0
  802. package/src/workspace/migrations/121-seed-default-user-guardrails.ts +82 -0
  803. package/src/workspace/migrations/122-relocate-default-user-boundary.ts +111 -0
  804. package/src/workspace/migrations/123-swap-quality-profile-to-fable.ts +85 -0
  805. package/src/workspace/migrations/__tests__/119-strip-persisted-memory-v3-tuning-defaults.test.ts +181 -0
  806. package/src/workspace/migrations/registry.ts +12 -0
  807. package/drizzle/0000_dizzy_maggott.sql +0 -301
  808. package/drizzle/meta/0000_snapshot.json +0 -1933
  809. package/drizzle/meta/_journal.json +0 -13
  810. package/node_modules/@vellumai/skill-host-contracts/__tests__/client.test.ts +0 -887
  811. package/node_modules/@vellumai/skill-host-contracts/bun.lock +0 -24
  812. package/node_modules/@vellumai/skill-host-contracts/package.json +0 -18
  813. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -97
  814. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +0 -1349
  815. package/node_modules/@vellumai/skill-host-contracts/src/index.ts +0 -6
  816. package/node_modules/@vellumai/skill-host-contracts/src/runtime-mode.ts +0 -11
  817. package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +0 -32
  818. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +0 -333
  819. package/node_modules/@vellumai/skill-host-contracts/tsconfig.json +0 -20
  820. package/node_modules/@vellumai/skill-host-contracts/tsconfig.test.json +0 -12
  821. package/src/__tests__/invite-redemption-service.test.ts +0 -1180
  822. package/src/__tests__/invite-service-ipc.test.ts +0 -350
  823. package/src/__tests__/job-handler-registry-guard.test.ts +0 -125
  824. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +0 -203
  825. package/src/__tests__/relay-server.test.ts +0 -6153
  826. package/src/__tests__/telephony-stt-routing.test.ts +0 -329
  827. package/src/__tests__/voice-invite-redemption.test.ts +0 -702
  828. package/src/calls/relay-server.ts +0 -2463
  829. package/src/calls/telephony-stt-routing.ts +0 -145
  830. package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +0 -128
  831. package/src/daemon/__tests__/daemon-skill-host.test.ts +0 -273
  832. package/src/daemon/daemon-skill-host.ts +0 -262
  833. package/src/daemon/message-types/meet.ts +0 -143
  834. package/src/daemon/shutdown-registry.ts +0 -40
  835. package/src/ipc/__tests__/skill-server-bidirectional.test.ts +0 -253
  836. package/src/ipc/__tests__/skill-server.test.ts +0 -182
  837. package/src/ipc/skill-ipc-types.ts +0 -54
  838. package/src/ipc/skill-routes/__tests__/config.test.ts +0 -146
  839. package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +0 -448
  840. package/src/ipc/skill-routes/__tests__/identity.test.ts +0 -62
  841. package/src/ipc/skill-routes/__tests__/log.test.ts +0 -133
  842. package/src/ipc/skill-routes/__tests__/memory.test.ts +0 -203
  843. package/src/ipc/skill-routes/__tests__/platform.test.ts +0 -111
  844. package/src/ipc/skill-routes/__tests__/providers.test.ts +0 -265
  845. package/src/ipc/skill-routes/__tests__/registries.test.ts +0 -400
  846. package/src/ipc/skill-routes/config.ts +0 -47
  847. package/src/ipc/skill-routes/events.ts +0 -147
  848. package/src/ipc/skill-routes/identity.ts +0 -21
  849. package/src/ipc/skill-routes/index.ts +0 -37
  850. package/src/ipc/skill-routes/log.ts +0 -40
  851. package/src/ipc/skill-routes/memory.ts +0 -92
  852. package/src/ipc/skill-routes/platform.ts +0 -39
  853. package/src/ipc/skill-routes/providers.ts +0 -162
  854. package/src/ipc/skill-routes/registries.ts +0 -338
  855. package/src/ipc/skill-server.ts +0 -831
  856. package/src/ipc/skill-socket-path.ts +0 -20
  857. package/src/jobs/register-job-handlers.ts +0 -74
  858. package/src/persistence/invite-store.ts +0 -481
  859. package/src/plugins/job-handler-registry.ts +0 -94
  860. package/src/providers/cache-control.ts +0 -26
  861. package/src/runtime/__tests__/runtime-mode.test.ts +0 -62
  862. package/src/runtime/invite-redemption-service.ts +0 -828
  863. package/src/runtime/invite-redemption-templates.ts +0 -46
  864. package/src/runtime/runtime-mode.ts +0 -40
  865. package/src/runtime/slack-dm-text-delivery.ts +0 -177
  866. package/src/util/voice-code.ts +0 -31
@@ -28,7 +28,6 @@ import {
28
28
  recordCompactionEndBestEffort,
29
29
  recordCompactionStartBestEffort,
30
30
  } from "../persistence/compaction-log-store-clickhouse.js";
31
- import { projectAssistantMessage } from "../persistence/conversation-attention-store.js";
32
31
  import {
33
32
  deleteMessageById,
34
33
  getConversation,
@@ -42,12 +41,14 @@ import {
42
41
  updateMessageContent,
43
42
  } from "../persistence/conversation-crud.js";
44
43
  import { syncMessageToDisk } from "../persistence/conversation-disk-view.js";
44
+ import { enqueueLexicalIndexForMessage } from "../persistence/job-handlers/message-lexical.js";
45
45
  import {
46
46
  backfillMessageIdOnLogs,
47
47
  buildProviderErrorResponsePayload,
48
48
  recordRequestLog,
49
49
  setAgentLoopExitReasonOnLatestLog,
50
50
  } from "../persistence/llm-request-log-store.js";
51
+ import { endSection, markSection } from "../persistence/slow-sync-log.js";
51
52
  import type { ContextWindowResult } from "../plugins/defaults/compaction/window-manager.js";
52
53
  import { indexMessageNow } from "../plugins/defaults/memory/indexer.js";
53
54
  import { backfillMemoryRecallLogMessageId } from "../plugins/defaults/memory/memory-recall-log-store.js";
@@ -59,7 +60,6 @@ import type {
59
60
  Message,
60
61
  } from "../providers/types.js";
61
62
  import { getCurrentSeq } from "../runtime/assistant-stream-state.js";
62
- import { publishSyncInvalidation } from "../runtime/sync/sync-publisher.js";
63
63
  import { redactSecrets } from "../security/secret-scanner.js";
64
64
  import { extractDomain } from "../tools/network/domain-normalize.js";
65
65
  import {
@@ -74,6 +74,7 @@ import {
74
74
  import { ProviderError } from "../util/errors.js";
75
75
  import { faviconUrlForDomain } from "../util/favicon.js";
76
76
  import { getLogger } from "../util/logger.js";
77
+ import { withSqliteRetry } from "../util/sqlite-retry.js";
77
78
  import type { DirectiveRequest } from "./assistant-attachments.js";
78
79
  import {
79
80
  cleanAssistantContent,
@@ -86,6 +87,7 @@ import {
86
87
  classifyConversationError,
87
88
  maxTokensReachedClassification,
88
89
  } from "./conversation-error.js";
90
+ import { buildDeferredFinalizeEffect } from "./conversation-turn-finalize.js";
89
91
  import { resolveTurnTimezoneContext } from "./date-context.js";
90
92
  import type {
91
93
  CardSurfaceData,
@@ -93,7 +95,6 @@ import type {
93
95
  SurfaceAction,
94
96
  UiSurfaceShow,
95
97
  } from "./message-protocol.js";
96
- import { conversationMetadataSyncTag } from "./message-types/sync.js";
97
98
  import type {
98
99
  ToolActivityMetadata,
99
100
  WebSearchMetadata,
@@ -317,6 +318,18 @@ export interface EventHandlerState {
317
318
  * latency segment. Advances on every `handleUsage`.
318
319
  */
319
320
  latencyCursor: number;
321
+ /**
322
+ * Non-critical finalize side-effects deferred off the turn's critical path —
323
+ * one closure per assistant message that completes (memory segment indexing,
324
+ * lexical indexing, attention projection). `handleMessageComplete` persists
325
+ * the message content synchronously (so a snapshot/refetch on the terminal
326
+ * `message_complete` SSE still sees the full reply) and pushes these
327
+ * follow-ups here; the orchestrator drains them after the terminal SSE has
328
+ * re-enabled the composer but before the next turn can start. Each closure is
329
+ * individually best-effort. Accumulates across retries/multi-call turns so
330
+ * every produced assistant row is indexed.
331
+ */
332
+ readonly deferredFinalizeEffects: Array<() => Promise<void>>;
320
333
  }
321
334
 
322
335
  /** Immutable context shared across event handlers within a single agent loop run. */
@@ -405,6 +418,7 @@ export function createEventHandlerState(): EventHandlerState {
405
418
  lastPersistedContentSeq: undefined,
406
419
  compactionStartMessages: new Map(),
407
420
  latencyCursor: 0,
421
+ deferredFinalizeEffects: [],
408
422
  };
409
423
  }
410
424
 
@@ -470,12 +484,18 @@ export function stampThinkingTiming(
470
484
  content: ContentBlock[],
471
485
  timings: ReadonlyArray<{ startedAt: number; completedAt: number }>,
472
486
  ): ContentBlock[] {
473
- if (timings.length === 0) return content;
487
+ if (timings.length === 0) {
488
+ return content;
489
+ }
474
490
  let thinkingIdx = 0;
475
491
  return content.map((block) => {
476
- if (block.type !== "thinking") return block;
492
+ if (block.type !== "thinking") {
493
+ return block;
494
+ }
477
495
  const timing = timings[thinkingIdx++];
478
- if (!timing) return block;
496
+ if (!timing) {
497
+ return block;
498
+ }
479
499
  return {
480
500
  ...block,
481
501
  _startedAt: timing.startedAt,
@@ -489,7 +509,9 @@ function appendTextToCurrentMessage(
489
509
  state: EventHandlerState,
490
510
  text: string,
491
511
  ): void {
492
- if (text.length === 0) return;
512
+ if (text.length === 0) {
513
+ return;
514
+ }
493
515
  const tail = state.currentMessageContent.at(-1);
494
516
  if (tail && tail.type === "text") {
495
517
  tail.text = tail.text + text;
@@ -509,13 +531,17 @@ function appendThinkingToCurrentMessage(
509
531
  state: EventHandlerState,
510
532
  thinking: string,
511
533
  ): void {
512
- if (thinking.length === 0) return;
534
+ if (thinking.length === 0) {
535
+ return;
536
+ }
513
537
  const now = Date.now();
514
538
  const tail = state.currentMessageContent.at(-1);
515
539
  if (tail && tail.type === "thinking") {
516
540
  tail.thinking = tail.thinking + thinking;
517
541
  const timing = state.currentThinkingTimestamps.at(-1);
518
- if (timing) timing.completedAt = now;
542
+ if (timing) {
543
+ timing.completedAt = now;
544
+ }
519
545
  } else {
520
546
  state.currentMessageContent.push({
521
547
  type: "thinking",
@@ -538,14 +564,51 @@ function resetPartialPersistAccumulator(state: EventHandlerState): void {
538
564
  state.pendingPartialFlushPromise = undefined;
539
565
  }
540
566
 
567
+ /**
568
+ * Persist an in-loop message-content write, retrying transient SQLite write
569
+ * contention (`SQLITE_BUSY`/`SQLITE_IOERR`) and swallowing a final failure so a
570
+ * lock held by another writer cannot abort the turn. Every in-loop write
571
+ * rewrites the full content snapshot of its assistant/tool-result row, so a
572
+ * dropped write is overwritten by a later write in the same turn (the
573
+ * end-of-turn finalize) or the next turn — missing one is a self-healing
574
+ * cosmetic gap, not corruption.
575
+ *
576
+ * Returns whether the write committed, so callers can gate dependent
577
+ * bookkeeping (e.g. advancing the persisted seq) on durable content.
578
+ */
579
+ async function persistLoopMessageContent(
580
+ messageId: string,
581
+ contentJson: string,
582
+ op: string,
583
+ rlog: pino.Logger,
584
+ ): Promise<boolean> {
585
+ try {
586
+ await withSqliteRetry(() => updateMessageContent(messageId, contentJson), {
587
+ op,
588
+ context: { messageId },
589
+ });
590
+ return true;
591
+ } catch (err) {
592
+ rlog.error(
593
+ { err, messageId, op },
594
+ "in-loop message-content write failed after retries; continuing without interrupting the turn",
595
+ );
596
+ return false;
597
+ }
598
+ }
599
+
541
600
  /** Flush `state.currentMessageContent` to the persisted assistant row. */
542
601
  async function flushAccumulatedContent(
543
602
  state: EventHandlerState,
544
603
  deps: EventHandlerDeps,
545
604
  ): Promise<void> {
546
605
  const messageId = state.lastAssistantMessageId;
547
- if (messageId === undefined) return;
548
- if (state.currentMessageContent.length === 0) return;
606
+ if (messageId === undefined) {
607
+ return;
608
+ }
609
+ if (state.currentMessageContent.length === 0) {
610
+ return;
611
+ }
549
612
 
550
613
  const built = buildPersistedAssistantContent(
551
614
  state.currentMessageContent,
@@ -558,18 +621,16 @@ async function flushAccumulatedContent(
558
621
  // again, but they are not part of this write.
559
622
  const flushedSeq = state.lastPersistedContentSeq;
560
623
 
561
- try {
562
- updateMessageContent(messageId, contentJson);
563
- // Record only after the write commits, so the snapshot seq never
564
- // claims content that is not yet durable.
565
- if (flushedSeq != null) {
566
- recordConversationPersistedSeq(deps.ctx.conversationId, flushedSeq);
567
- }
568
- } catch (err) {
569
- deps.rlog.warn(
570
- { err, messageId },
571
- "partial flush of accumulated assistant content failed; finalize at message_complete will recover",
572
- );
624
+ const persisted = await persistLoopMessageContent(
625
+ messageId,
626
+ contentJson,
627
+ "partial_flush_assistant_content",
628
+ deps.rlog,
629
+ );
630
+ // Record only after the write commits, so the snapshot seq never
631
+ // claims content that is not yet durable.
632
+ if (persisted && flushedSeq != null) {
633
+ recordConversationPersistedSeq(deps.ctx.conversationId, flushedSeq);
573
634
  }
574
635
  }
575
636
 
@@ -578,7 +639,9 @@ function schedulePartialFlush(
578
639
  state: EventHandlerState,
579
640
  deps: EventHandlerDeps,
580
641
  ): void {
581
- if (state.pendingPartialFlushTimer !== undefined) return;
642
+ if (state.pendingPartialFlushTimer !== undefined) {
643
+ return;
644
+ }
582
645
  state.pendingPartialFlushTimer = setTimeout(() => {
583
646
  state.pendingPartialFlushTimer = undefined;
584
647
  const flushPromise = flushAccumulatedContent(state, deps);
@@ -607,7 +670,9 @@ function emitLlmCallStartedIfNeeded(
607
670
  deps: EventHandlerDeps,
608
671
  providerNameOverride?: string,
609
672
  ): void {
610
- if (state.llmCallStartedEmitted) return;
673
+ if (state.llmCallStartedEmitted) {
674
+ return;
675
+ }
611
676
  state.llmCallStartedEmitted = true;
612
677
  const providerName = providerNameOverride ?? deps.ctx.provider.name;
613
678
  deps.ctx.traceEmitter.emit(
@@ -662,9 +727,13 @@ export function formatSearchStatusText(
662
727
  toolName: string,
663
728
  query: string,
664
729
  ): string {
665
- if (toolName !== "web_search") return `Running ${toolName}`;
730
+ if (toolName !== "web_search") {
731
+ return `Running ${toolName}`;
732
+ }
666
733
  const trimmed = query.trim();
667
- if (!trimmed) return "Searching the web";
734
+ if (!trimmed) {
735
+ return "Searching the web";
736
+ }
668
737
  const truncated =
669
738
  trimmed.length > 60 ? trimmed.slice(0, 57) + "..." : trimmed;
670
739
  return `Searching "${truncated}"`;
@@ -675,9 +744,13 @@ export function formatSearchStatusText(
675
744
  * Surfaces the domain so users can tell what page is being read.
676
745
  */
677
746
  export function formatFetchStatusText(url: unknown): string {
678
- if (typeof url !== "string") return "Reading a page";
747
+ if (typeof url !== "string") {
748
+ return "Reading a page";
749
+ }
679
750
  const domain = extractDomain(url);
680
- if (!domain) return "Reading a page";
751
+ if (!domain) {
752
+ return "Reading a page";
753
+ }
681
754
  return `Reading ${domain}`;
682
755
  }
683
756
 
@@ -858,7 +931,9 @@ function handleTextDelta(
858
931
  conversationId: deps.ctx.conversationId,
859
932
  messageId: state.lastAssistantMessageId,
860
933
  });
861
- if (deps.shouldGenerateTitle) state.firstAssistantText += drained.emitText;
934
+ if (deps.shouldGenerateTitle) {
935
+ state.firstAssistantText += drained.emitText;
936
+ }
862
937
  // Mirror the drained delta into state.currentMessageContent so partial
863
938
  // flushes mid-turn see the same content the user is watching live.
864
939
  appendTextToCurrentMessage(state, drained.emitText);
@@ -894,7 +969,9 @@ function handleThinkingDelta(
894
969
  });
895
970
  }
896
971
  }
897
- if (!deps.ctx.streamThinking) return;
972
+ if (!deps.ctx.streamThinking) {
973
+ return;
974
+ }
898
975
  emitLlmCallStartedIfNeeded(state, deps);
899
976
  deps.onEvent({
900
977
  type: "assistant_thinking_delta",
@@ -1081,7 +1158,9 @@ export function handleInputJsonDelta(
1081
1158
  // Only forward input deltas for app tools — the client only uses this
1082
1159
  // stream for app_create code previews. Non-app tools would send large
1083
1160
  // cumulative JSON on every delta with no benefit.
1084
- if (!APP_TOOL_NAMES.has(event.toolName)) return;
1161
+ if (!APP_TOOL_NAMES.has(event.toolName)) {
1162
+ return;
1163
+ }
1085
1164
  deps.onEvent({
1086
1165
  type: "tool_input_delta",
1087
1166
  toolName: event.toolName,
@@ -1180,7 +1259,9 @@ async function persistPendingToolResultRow(
1180
1259
  deps: EventHandlerDeps,
1181
1260
  seq: number,
1182
1261
  ): Promise<void> {
1183
- if (state.pendingToolResults.size === 0) return;
1262
+ if (state.pendingToolResults.size === 0) {
1263
+ return;
1264
+ }
1184
1265
  const rowId = await ensureToolResultRowReserved(
1185
1266
  state,
1186
1267
  deps.ctx.conversationId,
@@ -1188,11 +1269,15 @@ async function persistPendingToolResultRow(
1188
1269
  );
1189
1270
  // Serialize the content after the reservation resolves so the last of the
1190
1271
  // concurrent writers reflects the fullest batch.
1191
- updateMessageContent(
1272
+ const persisted = await persistLoopMessageContent(
1192
1273
  rowId,
1193
1274
  JSON.stringify(buildToolResultBlocks(state.pendingToolResults)),
1275
+ "persist_tool_result_row",
1276
+ deps.rlog,
1194
1277
  );
1195
- recordConversationPersistedSeq(deps.ctx.conversationId, seq);
1278
+ if (persisted) {
1279
+ recordConversationPersistedSeq(deps.ctx.conversationId, seq);
1280
+ }
1196
1281
  const conv = getConversation(deps.ctx.conversationId);
1197
1282
  if (conv != null) {
1198
1283
  syncMessageToDisk(deps.ctx.conversationId, rowId, conv.createdAt);
@@ -1213,7 +1298,9 @@ export async function finalizePendingToolResultRow(
1213
1298
  metadata: Record<string, unknown>,
1214
1299
  rlog: pino.Logger,
1215
1300
  ): Promise<void> {
1216
- if (state.pendingToolResults.size === 0) return;
1301
+ if (state.pendingToolResults.size === 0) {
1302
+ return;
1303
+ }
1217
1304
  const rowId = await ensureToolResultRowReserved(
1218
1305
  state,
1219
1306
  conversationId,
@@ -1222,7 +1309,12 @@ export async function finalizePendingToolResultRow(
1222
1309
  const contentJson = JSON.stringify(
1223
1310
  buildToolResultBlocks(state.pendingToolResults),
1224
1311
  );
1225
- updateMessageContent(rowId, contentJson);
1312
+ await persistLoopMessageContent(
1313
+ rowId,
1314
+ contentJson,
1315
+ "finalize_tool_result_row",
1316
+ rlog,
1317
+ );
1226
1318
  // Sync the row to the JSONL disk view so it stays in lockstep with the DB.
1227
1319
  // `getConversation` returns `ConversationRow | null`, so `!= null` gates on a
1228
1320
  // real row (skipping the sync when the conversation was not found rather than
@@ -1277,6 +1369,10 @@ export async function finalizePendingToolResultRow(
1277
1369
  "Failed to index tool-result message for memory (non-fatal)",
1278
1370
  );
1279
1371
  }
1372
+ // Dual-write the finalized tool-result content into the lexical index. The
1373
+ // reserve+finalize path bypasses `onMessagePersisted`, so enqueue here to
1374
+ // keep the lexical index in lockstep with the segment index.
1375
+ enqueueLexicalIndexForMessage(rowId);
1280
1376
  }
1281
1377
  for (const id of state.pendingToolResults.keys()) {
1282
1378
  state.persistedToolUseIds.add(id);
@@ -1350,7 +1446,9 @@ export async function handleToolResult(
1350
1446
  // Record tool completion timestamp
1351
1447
  const completedAt = Date.now();
1352
1448
  const ts = state.toolCallTimestamps.get(event.toolUseId);
1353
- if (ts) ts.completedAt = completedAt;
1449
+ if (ts) {
1450
+ ts.completedAt = completedAt;
1451
+ }
1354
1452
  state.currentToolUseId = undefined;
1355
1453
 
1356
1454
  // Capture risk metadata when present. autoApproved is true when the tool
@@ -1502,10 +1600,14 @@ function recordToolStartOnPersistedMessage(
1502
1600
  startedAt: number,
1503
1601
  ): void {
1504
1602
  const messageId = state.lastAssistantMessageId;
1505
- if (!messageId) return;
1603
+ if (!messageId) {
1604
+ return;
1605
+ }
1506
1606
 
1507
1607
  const row = getMessageById(messageId);
1508
- if (!row) return;
1608
+ if (!row) {
1609
+ return;
1610
+ }
1509
1611
 
1510
1612
  let content: ContentBlock[];
1511
1613
  try {
@@ -1515,12 +1617,28 @@ function recordToolStartOnPersistedMessage(
1515
1617
  }
1516
1618
 
1517
1619
  for (const block of content) {
1518
- if (block.type !== "tool_use") continue;
1620
+ if (block.type !== "tool_use") {
1621
+ continue;
1622
+ }
1519
1623
  const rec = block as unknown as Record<string, unknown>;
1520
- if (rec.id !== toolUseId) continue;
1521
- if (rec._startedAt === startedAt) return;
1624
+ if (rec.id !== toolUseId) {
1625
+ continue;
1626
+ }
1627
+ if (rec._startedAt === startedAt) {
1628
+ return;
1629
+ }
1522
1630
  rec._startedAt = startedAt;
1523
- updateMessageContent(messageId, JSON.stringify(content));
1631
+ // Best-effort early stamp: `annotatePersistedAssistantMessage` re-stamps
1632
+ // once every tool in the turn completes, so a transient `SQLITE_BUSY` here
1633
+ // must not abort the turn — the end-of-turn write recovers it.
1634
+ try {
1635
+ updateMessageContent(messageId, JSON.stringify(content));
1636
+ } catch (err) {
1637
+ log.error(
1638
+ { err, messageId },
1639
+ "stamping tool start time failed; end-of-turn annotation will recover",
1640
+ );
1641
+ }
1524
1642
  return;
1525
1643
  }
1526
1644
  }
@@ -1541,10 +1659,14 @@ function recordToolPreviewStartOnPersistedMessage(
1541
1659
  previewStartedAt: number,
1542
1660
  ): void {
1543
1661
  const messageId = state.lastAssistantMessageId;
1544
- if (!messageId) return;
1662
+ if (!messageId) {
1663
+ return;
1664
+ }
1545
1665
 
1546
1666
  const row = getMessageById(messageId);
1547
- if (!row) return;
1667
+ if (!row) {
1668
+ return;
1669
+ }
1548
1670
 
1549
1671
  let content: ContentBlock[];
1550
1672
  try {
@@ -1554,12 +1676,28 @@ function recordToolPreviewStartOnPersistedMessage(
1554
1676
  }
1555
1677
 
1556
1678
  for (const block of content) {
1557
- if (block.type !== "tool_use") continue;
1679
+ if (block.type !== "tool_use") {
1680
+ continue;
1681
+ }
1558
1682
  const rec = block as unknown as Record<string, unknown>;
1559
- if (rec.id !== toolUseId) continue;
1560
- if (rec._previewStartedAt === previewStartedAt) return;
1683
+ if (rec.id !== toolUseId) {
1684
+ continue;
1685
+ }
1686
+ if (rec._previewStartedAt === previewStartedAt) {
1687
+ return;
1688
+ }
1561
1689
  rec._previewStartedAt = previewStartedAt;
1562
- updateMessageContent(messageId, JSON.stringify(content));
1690
+ // Best-effort early stamp, mirroring `recordToolStartOnPersistedMessage`:
1691
+ // `annotatePersistedAssistantMessage` re-stamps at end of turn, so a
1692
+ // transient `SQLITE_BUSY` here must not abort the turn.
1693
+ try {
1694
+ updateMessageContent(messageId, JSON.stringify(content));
1695
+ } catch (err) {
1696
+ log.error(
1697
+ { err, messageId },
1698
+ "stamping tool preview-start time failed; end-of-turn annotation will recover",
1699
+ );
1700
+ }
1563
1701
  return;
1564
1702
  }
1565
1703
  }
@@ -1575,10 +1713,14 @@ function annotatePersistedAssistantMessage(
1575
1713
  deps: EventHandlerDeps,
1576
1714
  ): void {
1577
1715
  const messageId = state.lastAssistantMessageId;
1578
- if (!messageId) return;
1716
+ if (!messageId) {
1717
+ return;
1718
+ }
1579
1719
 
1580
1720
  const row = getMessageById(messageId);
1581
- if (!row) return;
1721
+ if (!row) {
1722
+ return;
1723
+ }
1582
1724
 
1583
1725
  let content: ContentBlock[];
1584
1726
  try {
@@ -1592,7 +1734,9 @@ function annotatePersistedAssistantMessage(
1592
1734
  if (block.type === "tool_use") {
1593
1735
  const rec = block as unknown as Record<string, unknown>;
1594
1736
  const id = rec.id as string | undefined;
1595
- if (!id) continue;
1737
+ if (!id) {
1738
+ continue;
1739
+ }
1596
1740
 
1597
1741
  const ts = state.toolCallTimestamps.get(id);
1598
1742
  if (ts) {
@@ -1616,26 +1760,38 @@ function annotatePersistedAssistantMessage(
1616
1760
  const risk = state.toolRiskOutcomes.get(id);
1617
1761
  if (risk) {
1618
1762
  rec._riskLevel = risk.riskLevel;
1619
- if (risk.riskReason) rec._riskReason = risk.riskReason;
1763
+ if (risk.riskReason) {
1764
+ rec._riskReason = risk.riskReason;
1765
+ }
1620
1766
  rec._autoApproved = risk.autoApproved;
1621
- if (risk.matchedTrustRuleId)
1767
+ if (risk.matchedTrustRuleId) {
1622
1768
  rec._matchedTrustRuleId = risk.matchedTrustRuleId;
1623
- if (risk.approvalMode) rec._approvalMode = risk.approvalMode;
1624
- if (risk.approvalReason) rec._approvalReason = risk.approvalReason;
1625
- if (risk.riskThreshold) rec._riskThreshold = risk.riskThreshold;
1769
+ }
1770
+ if (risk.approvalMode) {
1771
+ rec._approvalMode = risk.approvalMode;
1772
+ }
1773
+ if (risk.approvalReason) {
1774
+ rec._approvalReason = risk.approvalReason;
1775
+ }
1776
+ if (risk.riskThreshold) {
1777
+ rec._riskThreshold = risk.riskThreshold;
1778
+ }
1626
1779
  // Persist the 3 risk-option arrays so the rule editor's chip ladder
1627
1780
  // survives chat-history reload. These mirror the same-named fields
1628
1781
  // on the live `tool_result` event; clients should read them back via
1629
1782
  // `shared.ts` and treat them identically to the live values.
1630
- if (risk.riskScopeOptions && risk.riskScopeOptions.length > 0)
1783
+ if (risk.riskScopeOptions && risk.riskScopeOptions.length > 0) {
1631
1784
  rec._riskScopeOptions = risk.riskScopeOptions;
1632
- if (risk.riskAllowlistOptions && risk.riskAllowlistOptions.length > 0)
1785
+ }
1786
+ if (risk.riskAllowlistOptions && risk.riskAllowlistOptions.length > 0) {
1633
1787
  rec._riskAllowlistOptions = risk.riskAllowlistOptions;
1788
+ }
1634
1789
  if (
1635
1790
  risk.riskDirectoryScopeOptions &&
1636
1791
  risk.riskDirectoryScopeOptions.length > 0
1637
- )
1792
+ ) {
1638
1793
  rec._riskDirectoryScopeOptions = risk.riskDirectoryScopeOptions;
1794
+ }
1639
1795
  modified = true;
1640
1796
  }
1641
1797
  // External provider tools (brave/perplexity/tavily) + web_fetch produce
@@ -1680,6 +1836,10 @@ function annotatePersistedAssistantMessage(
1680
1836
  }
1681
1837
 
1682
1838
  if (modified) {
1839
+ // This end-of-turn write is best-effort: the caller wraps it in a
1840
+ // try/catch (and `dispatchAgentEvent` swallows `tool_result` handler
1841
+ // errors), so a transient `SQLITE_BUSY` here is logged and the turn
1842
+ // continues — it never reaches the turn-level catch.
1683
1843
  updateMessageContent(messageId, JSON.stringify(content));
1684
1844
  }
1685
1845
 
@@ -1845,8 +2005,9 @@ export async function handleMessageComplete(
1845
2005
  conversationId: deps.ctx.conversationId,
1846
2006
  messageId: state.lastAssistantMessageId,
1847
2007
  });
1848
- if (deps.shouldGenerateTitle)
2008
+ if (deps.shouldGenerateTitle) {
1849
2009
  state.firstAssistantText += state.pendingDirectiveDisplayBuffer;
2010
+ }
1850
2011
  state.pendingDirectiveDisplayBuffer = "";
1851
2012
  }
1852
2013
 
@@ -1917,7 +2078,12 @@ export async function handleMessageComplete(
1917
2078
  );
1918
2079
  }
1919
2080
  const contentJson = JSON.stringify(contentForPersistence);
1920
- updateMessageContent(assistantMessageId, contentJson);
2081
+ const persisted = await persistLoopMessageContent(
2082
+ assistantMessageId,
2083
+ contentJson,
2084
+ "finalize_assistant_message",
2085
+ deps.rlog,
2086
+ );
1921
2087
  state.assistantRowAwaitingFinalization = false;
1922
2088
  // The assistant row now holds the authoritative content (text + thinking +
1923
2089
  // tool_use blocks from `event.message`), and any drained tool-result rows
@@ -1927,8 +2093,9 @@ export async function handleMessageComplete(
1927
2093
  // turn, so this seq already covers it; a call that streams no content (a
1928
2094
  // pure tool call) advances instead via `tool_use_start`.
1929
2095
  // `recordConversationPersistedSeq` clamps monotonically, so a lower value
1930
- // here never regresses the seq.
1931
- if (state.lastPersistedContentSeq != null) {
2096
+ // here never regresses the seq. Gate on `persisted` so a swallowed finalize
2097
+ // write never advances the seq past content that is not durable.
2098
+ if (persisted && state.lastPersistedContentSeq != null) {
1932
2099
  recordConversationPersistedSeq(
1933
2100
  deps.ctx.conversationId,
1934
2101
  state.lastPersistedContentSeq,
@@ -1940,89 +2107,24 @@ export async function handleMessageComplete(
1940
2107
  state.currentThinkingTimestamps = [];
1941
2108
  state.lastPersistedContentSeq = undefined;
1942
2109
 
1943
- // ── Indexing + attention projection ──
1944
- // `reserveMessage` + `updateMessageContent` are CRUD-only: they don't run
1945
- // the memory indexer or the attention-cursor projector (unlike `addMessage`,
1946
- // which runs both as side-effects of the insert). Because the assistant row
1947
- // is reserved empty and finalized via `updateMessageContent`, both must be
1948
- // invoked explicitly here to keep the assistant row's external state
1949
- // (Qdrant segments, conversation attention cursor) in lockstep with the
1950
- // finalized content. Both are non-fatal a memory hiccup must not
1951
- // escalate a successful generation into a turn-level throw. Indexing
1952
- // intentionally fires AFTER `updateContent` succeeds so we never index
1953
- // the empty reserved placeholder.
1954
- const finalizedRow = getMessageById(
1955
- assistantMessageId,
1956
- deps.ctx.conversationId,
2110
+ // ── Indexing + attention projection (deferred off the critical path) ──
2111
+ // `reserveMessage` + `updateMessageContent` are CRUD-only unlike
2112
+ // `addMessage`, they don't run the memory indexer or the attention-cursor
2113
+ // projector as insert side-effects so the assistant row's external state
2114
+ // must be brought into lockstep explicitly. Neither gates delivery of the
2115
+ // reply or the composer re-enabling, so the work is queued here and drained
2116
+ // by the orchestrator after the terminal `message_complete` SSE fires (but
2117
+ // before the next turn). See `conversation-turn-finalize.ts`. The content
2118
+ // persisted synchronously above, so a snapshot/refetch on `message_complete`
2119
+ // still sees the full reply.
2120
+ state.deferredFinalizeEffects.push(
2121
+ buildDeferredFinalizeEffect({
2122
+ conversationId: deps.ctx.conversationId,
2123
+ assistantMessageId,
2124
+ contentJson,
2125
+ rlog: deps.rlog,
2126
+ }),
1957
2127
  );
1958
- if (finalizedRow) {
1959
- let provenanceTrustClass:
1960
- | "guardian"
1961
- | "trusted_contact"
1962
- | "unverified_contact"
1963
- | "unknown"
1964
- | undefined;
1965
- let automated: boolean | undefined;
1966
- if (finalizedRow.metadata) {
1967
- try {
1968
- const parsedMeta = messageMetadataSchema.safeParse(
1969
- JSON.parse(finalizedRow.metadata),
1970
- );
1971
- if (parsedMeta.success) {
1972
- provenanceTrustClass = parsedMeta.data.provenanceTrustClass;
1973
- automated = parsedMeta.data.automated;
1974
- }
1975
- } catch {
1976
- // Malformed metadata JSON — fall through with undefined fields,
1977
- // matching the legacy behavior in `addMessage`.
1978
- }
1979
- }
1980
- try {
1981
- await indexMessageNow(
1982
- {
1983
- messageId: assistantMessageId,
1984
- conversationId: deps.ctx.conversationId,
1985
- role: "assistant",
1986
- content: contentJson,
1987
- createdAt: finalizedRow.createdAt,
1988
- scopeId: "default",
1989
- provenanceTrustClass,
1990
- automated,
1991
- },
1992
- getConfig().memory,
1993
- );
1994
- } catch (err) {
1995
- deps.rlog.warn(
1996
- {
1997
- err,
1998
- conversationId: deps.ctx.conversationId,
1999
- messageId: assistantMessageId,
2000
- },
2001
- "Failed to index assistant message for memory (non-fatal)",
2002
- );
2003
- }
2004
- try {
2005
- const attentionStateChanged = projectAssistantMessage({
2006
- conversationId: deps.ctx.conversationId,
2007
- messageId: assistantMessageId,
2008
- messageAt: finalizedRow.createdAt,
2009
- });
2010
- if (attentionStateChanged) {
2011
- void publishSyncInvalidation([
2012
- conversationMetadataSyncTag(deps.ctx.conversationId),
2013
- ]);
2014
- }
2015
- } catch (err) {
2016
- deps.rlog.warn(
2017
- {
2018
- err,
2019
- conversationId: deps.ctx.conversationId,
2020
- messageId: assistantMessageId,
2021
- },
2022
- "Failed to project assistant message for attention tracking (non-fatal)",
2023
- );
2024
- }
2025
- }
2026
2128
 
2027
2129
  // Backfill message_id on all LLM request logs from this turn.
2028
2130
  // The agent loop is single-threaded per conversation, so all rows with
@@ -2298,6 +2400,11 @@ export async function dispatchAgentEvent(
2298
2400
  deps: EventHandlerDeps,
2299
2401
  event: AgentEvent,
2300
2402
  ): Promise<void> {
2403
+ // Section-trail breadcrumb for event-loop freeze attribution: the dispatch
2404
+ // is the single choke point for per-turn persistence work (message writes,
2405
+ // usage/request-log rows, SSE fan-out), so a watchdog report during a
2406
+ // handler names the event type that was being processed.
2407
+ const sectionMark = markSection(`agent-event:${event.type}`);
2301
2408
  try {
2302
2409
  switch (event.type) {
2303
2410
  case "llm_call_started":
@@ -2605,5 +2712,7 @@ export async function dispatchAgentEvent(
2605
2712
  ) {
2606
2713
  throw err;
2607
2714
  }
2715
+ } finally {
2716
+ endSection(sectionMark);
2608
2717
  }
2609
2718
  }