@vellumai/assistant 0.8.5 → 0.8.6

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 (544) hide show
  1. package/AGENTS.md +33 -1
  2. package/ARCHITECTURE.md +1 -1
  3. package/bunfig.toml +6 -1
  4. package/docs/credential-execution-service.md +6 -6
  5. package/docs/plugins.md +4 -3
  6. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +12 -13
  7. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +4 -1
  8. package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +16 -14
  9. package/openapi.yaml +1900 -166
  10. package/package.json +1 -1
  11. package/src/__tests__/actor-token-service.test.ts +3 -2
  12. package/src/__tests__/agent-loop-exit-reason.test.ts +102 -9
  13. package/src/__tests__/agent-loop-override-profile.test.ts +2 -1
  14. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +1 -0
  15. package/src/__tests__/agent-wake-override-profile.test.ts +1 -0
  16. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
  17. package/src/__tests__/annotate-risk-options.test.ts +1 -0
  18. package/src/__tests__/approval-cascade.test.ts +1 -0
  19. package/src/__tests__/approval-routes-http.test.ts +9 -13
  20. package/src/__tests__/assert-not-live-db.ts +79 -0
  21. package/src/__tests__/assistant-feature-flags-integration.test.ts +9 -25
  22. package/src/__tests__/audit-log-rotation.test.ts +2 -2
  23. package/src/__tests__/auto-analysis-end-to-end.test.ts +6 -6
  24. package/src/__tests__/background-workers-disk-pressure.test.ts +5 -8
  25. package/src/__tests__/browser-skill-endstate.test.ts +3 -3
  26. package/src/__tests__/btw-routes.test.ts +3 -2
  27. package/src/__tests__/call-controller.test.ts +3 -2
  28. package/src/__tests__/channel-approval-routes.test.ts +3 -2
  29. package/src/__tests__/channel-guardian.test.ts +3 -2
  30. package/src/__tests__/channel-readiness-slack-remote.test.ts +175 -0
  31. package/src/__tests__/channel-reply-delivery.test.ts +35 -0
  32. package/src/__tests__/channel-retry-sweep.test.ts +320 -3
  33. package/src/__tests__/checker.test.ts +12 -12
  34. package/src/__tests__/compaction-events.test.ts +1 -0
  35. package/src/__tests__/compaction-trail-store.test.ts +264 -0
  36. package/src/__tests__/compactor-call-site-logging.test.ts +1 -0
  37. package/src/__tests__/compactor-preserved-tail-count.test.ts +1 -0
  38. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +7 -5
  39. package/src/__tests__/computer-use-tools.test.ts +12 -14
  40. package/src/__tests__/config-loader-backfill.test.ts +13 -28
  41. package/src/__tests__/config-loader-corrupt.test.ts +5 -5
  42. package/src/__tests__/config-loader-platform-defaults.test.ts +93 -26
  43. package/src/__tests__/config-loader-quarantine-bulletin.test.ts +3 -3
  44. package/src/__tests__/config-managed-gemini-defaults.test.ts +3 -4
  45. package/src/__tests__/config-schema.test.ts +10 -10
  46. package/src/__tests__/connection-model-compat.test.ts +83 -0
  47. package/src/__tests__/contacts-tools.test.ts +3 -2
  48. package/src/__tests__/context-token-estimator.test.ts +22 -0
  49. package/src/__tests__/conversation-abort-tool-results.test.ts +5 -0
  50. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -0
  51. package/src/__tests__/conversation-agent-loop-handlers-max-tokens.test.ts +55 -0
  52. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  53. package/src/__tests__/conversation-agent-loop-overflow.test.ts +34 -0
  54. package/src/__tests__/conversation-agent-loop.test.ts +488 -2
  55. package/src/__tests__/conversation-analysis-routes.test.ts +1 -0
  56. package/src/__tests__/conversation-app-control-instantiation.test.ts +29 -19
  57. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -0
  58. package/src/__tests__/conversation-attention-store.test.ts +101 -0
  59. package/src/__tests__/conversation-attention-telegram.test.ts +3 -2
  60. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
  61. package/src/__tests__/conversation-error.test.ts +30 -0
  62. package/src/__tests__/conversation-fork-crud.test.ts +69 -8
  63. package/src/__tests__/conversation-fork-route.test.ts +3 -2
  64. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  65. package/src/__tests__/conversation-inference-profile-list.test.ts +3 -2
  66. package/src/__tests__/conversation-inference-profile-route.test.ts +3 -2
  67. package/src/__tests__/conversation-lifecycle.test.ts +1 -0
  68. package/src/__tests__/conversation-list-source.test.ts +3 -2
  69. package/src/__tests__/conversation-load-history-repair.test.ts +2 -1
  70. package/src/__tests__/conversation-load-history-stripped.test.ts +1 -0
  71. package/src/__tests__/conversation-pairing.test.ts +53 -0
  72. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +26 -7
  73. package/src/__tests__/conversation-process-callsite.test.ts +1 -0
  74. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -0
  75. package/src/__tests__/conversation-queue.test.ts +333 -291
  76. package/src/__tests__/conversation-routes-disk-view.test.ts +3 -18
  77. package/src/__tests__/conversation-routes-guardian-reply.test.ts +33 -8
  78. package/src/__tests__/conversation-routes-slash-commands.test.ts +33 -2
  79. package/src/__tests__/conversation-runtime-assembly.test.ts +78 -0
  80. package/src/__tests__/conversation-skill-tools.test.ts +38 -142
  81. package/src/__tests__/conversation-slash-queue.test.ts +84 -32
  82. package/src/__tests__/conversation-slash-unknown.test.ts +5 -0
  83. package/src/__tests__/conversation-speed-override.test.ts +1 -0
  84. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +46 -0
  85. package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -0
  86. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +6 -3
  87. package/src/__tests__/conversation-surfaces-standalone.test.ts +6 -3
  88. package/src/__tests__/conversation-surfaces-state-update.test.ts +3 -3
  89. package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -17
  90. package/src/__tests__/conversation-sync-tags.test.ts +128 -12
  91. package/src/__tests__/conversation-title-service.test.ts +1 -0
  92. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +30 -0
  93. package/src/__tests__/conversation-usage.test.ts +1 -0
  94. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
  95. package/src/__tests__/conversation-workspace-injection.test.ts +5 -0
  96. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -0
  97. package/src/__tests__/credential-broker-browser-fill.test.ts +3 -3
  98. package/src/__tests__/credential-broker-server-use.test.ts +5 -5
  99. package/src/__tests__/credential-execution-client.test.ts +72 -1
  100. package/src/__tests__/credential-execution-feature-gates.test.ts +10 -12
  101. package/src/__tests__/credential-health-service.test.ts +252 -3
  102. package/src/__tests__/credential-security-invariants.test.ts +5 -5
  103. package/src/__tests__/credential-vault-unit.test.ts +19 -19
  104. package/src/__tests__/credential-vault.test.ts +5 -5
  105. package/src/__tests__/cross-provider-web-search.test.ts +56 -2
  106. package/src/__tests__/db-connection-isolation.test.ts +7 -6
  107. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +8 -10
  108. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +7 -10
  109. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +9 -15
  110. package/src/__tests__/db-test-helpers.ts +58 -0
  111. package/src/__tests__/disk-pressure-guard.test.ts +58 -41
  112. package/src/__tests__/disk-pressure-lifecycle.test.ts +13 -10
  113. package/src/__tests__/disk-pressure-routes.test.ts +0 -33
  114. package/src/__tests__/disk-pressure-tools.test.ts +0 -4
  115. package/src/__tests__/dm-persistence.test.ts +26 -40
  116. package/src/__tests__/document-create-dedupe.test.ts +189 -0
  117. package/src/__tests__/document-find-replace.test.ts +3 -2
  118. package/src/__tests__/document-tool-security.test.ts +81 -2
  119. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +5 -4
  120. package/src/__tests__/encrypted-store-test-helpers.ts +56 -0
  121. package/src/__tests__/encrypted-store.test.ts +11 -9
  122. package/src/__tests__/feature-flag-test-helpers.ts +53 -0
  123. package/src/__tests__/filing-service.test.ts +1 -0
  124. package/src/__tests__/first-greeting.test.ts +62 -12
  125. package/src/__tests__/gateway-flag-listener.test.ts +0 -1
  126. package/src/__tests__/gemini-provider.test.ts +26 -0
  127. package/src/__tests__/guardian-action-sweep.test.ts +3 -2
  128. package/src/__tests__/guardian-outbound-http.test.ts +3 -2
  129. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +48 -3
  130. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -0
  131. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  132. package/src/__tests__/heartbeat-service.test.ts +1 -0
  133. package/src/__tests__/helpers/mock-logger.ts +26 -0
  134. package/src/__tests__/host-bash-routes.test.ts +1 -0
  135. package/src/__tests__/host-cu-routes-targeted.test.ts +1 -0
  136. package/src/__tests__/host-file-routes-targeted.test.ts +1 -0
  137. package/src/__tests__/host-shell-tool.test.ts +5 -4
  138. package/src/__tests__/host-transfer-routes-targeted.test.ts +1 -0
  139. package/src/__tests__/http-conversation-lineage.test.ts +3 -2
  140. package/src/__tests__/http-user-message-parity.test.ts +29 -7
  141. package/src/__tests__/identity-intro-cache.test.ts +133 -22
  142. package/src/__tests__/inbound-slack-persistence.test.ts +44 -72
  143. package/src/__tests__/inference-profile-reaper.test.ts +3 -2
  144. package/src/__tests__/inference-profile-session-ipc.test.ts +3 -2
  145. package/src/__tests__/injector-disk-pressure.test.ts +3 -17
  146. package/src/__tests__/inline-skill-load-permissions.test.ts +4 -4
  147. package/src/__tests__/list-messages-hidden-metadata.test.ts +80 -0
  148. package/src/__tests__/llm-context-normalization.test.ts +42 -0
  149. package/src/__tests__/llm-resolver.test.ts +331 -0
  150. package/src/__tests__/llm-schema.test.ts +1 -1
  151. package/src/__tests__/manual-token-reconciliation.test.ts +76 -1
  152. package/src/__tests__/mcp-abort-signal.test.ts +14 -0
  153. package/src/__tests__/mcp-client-auth.test.ts +14 -0
  154. package/src/__tests__/messaging-send-tool.test.ts +1 -0
  155. package/src/__tests__/migration-import-from-url.test.ts +3 -3
  156. package/src/__tests__/mock-gateway-ipc.ts +18 -2
  157. package/src/__tests__/model-intents.test.ts +3 -3
  158. package/src/__tests__/native-web-search.test.ts +30 -2
  159. package/src/__tests__/notification-deep-link.test.ts +62 -0
  160. package/src/__tests__/oauth-commands-routes.test.ts +37 -0
  161. package/src/__tests__/oauth-provider-visibility.test.ts +8 -8
  162. package/src/__tests__/oauth-store.test.ts +3 -2
  163. package/src/__tests__/onboarding-template-contract.test.ts +3 -2
  164. package/src/__tests__/openai-provider.test.ts +8 -9
  165. package/src/__tests__/openai-responses-provider.test.ts +70 -10
  166. package/src/__tests__/openrouter-provider-only.test.ts +27 -5
  167. package/src/__tests__/outbound-slack-persistence.test.ts +46 -1
  168. package/src/__tests__/persistence-pipeline.test.ts +139 -1
  169. package/src/__tests__/persistence-secret-redaction.test.ts +83 -12
  170. package/src/__tests__/plugin-bootstrap.test.ts +9 -11
  171. package/src/__tests__/plugin-tool-contribution.test.ts +41 -38
  172. package/src/__tests__/process-message-background-slack.test.ts +21 -16
  173. package/src/__tests__/process-message-display-content.test.ts +19 -22
  174. package/src/__tests__/provider-catalog-visibility.test.ts +9 -9
  175. package/src/__tests__/provider-platform-proxy-integration.test.ts +216 -4
  176. package/src/__tests__/provider-registry-ollama.test.ts +45 -22
  177. package/src/__tests__/recording-handler.test.ts +1 -0
  178. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
  179. package/src/__tests__/registry.test.ts +82 -76
  180. package/src/__tests__/relay-server.test.ts +10 -10
  181. package/src/__tests__/runtime-attachment-metadata.test.ts +3 -2
  182. package/src/__tests__/schedule-store.test.ts +16 -1
  183. package/src/__tests__/scheduler-reuse-conversation.test.ts +48 -3
  184. package/src/__tests__/secret-ingress-http.test.ts +5 -1
  185. package/src/__tests__/secure-keys.test.ts +3 -3
  186. package/src/__tests__/send-endpoint-busy.test.ts +81 -42
  187. package/src/__tests__/server-history-render.test.ts +4 -1
  188. package/src/__tests__/skill-feature-flags-integration.test.ts +8 -10
  189. package/src/__tests__/skill-feature-flags.test.ts +14 -16
  190. package/src/__tests__/skill-load-feature-flag.test.ts +5 -5
  191. package/src/__tests__/skill-projection-feature-flag.test.ts +44 -30
  192. package/src/__tests__/skill-projection.benchmark.test.ts +5 -7
  193. package/src/__tests__/skill-tool-factory.test.ts +96 -95
  194. package/src/__tests__/slack-channel-config.test.ts +3 -3
  195. package/src/__tests__/subagent-call-site-routing.test.ts +11 -3
  196. package/src/__tests__/subagent-disposal.test.ts +27 -8
  197. package/src/__tests__/subagent-fork-notifications.test.ts +24 -9
  198. package/src/__tests__/subagent-fork-spawn.test.ts +13 -4
  199. package/src/__tests__/subagent-manager-notify.test.ts +20 -8
  200. package/src/__tests__/subagent-notify-parent.test.ts +5 -4
  201. package/src/__tests__/subagent-spawn-tool-fork.test.ts +58 -0
  202. package/src/__tests__/subagent-tools.test.ts +2 -1
  203. package/src/__tests__/suggestion-routes.test.ts +1 -0
  204. package/src/__tests__/system-prompt.test.ts +38 -0
  205. package/src/__tests__/test-preload-verifier.ts +68 -0
  206. package/src/__tests__/test-preload.ts +32 -39
  207. package/src/__tests__/tool-executor-lifecycle-events.test.ts +20 -7
  208. package/src/__tests__/tool-executor.test.ts +55 -10
  209. package/src/__tests__/tool-preview-lifecycle.test.ts +1 -0
  210. package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
  211. package/src/__tests__/twilio-routes.test.ts +3 -2
  212. package/src/__tests__/validate-input.test.ts +381 -0
  213. package/src/__tests__/verification-control-plane-policy.test.ts +1 -0
  214. package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -1
  215. package/src/__tests__/voice-session-bridge.test.ts +37 -28
  216. package/src/__tests__/workspace-migration-090-memory-router-cost-optimized-profile.test.ts +326 -0
  217. package/src/__tests__/workspace-migration-091-retighten-migration-onboarding-thread.test.ts +166 -0
  218. package/src/acp/session-manager.ts +5 -6
  219. package/src/agent/loop.ts +80 -0
  220. package/src/api/README.md +124 -2
  221. package/src/api/constants/call-sites.ts +27 -0
  222. package/src/api/events/assistant-outbound-attachment.ts +51 -0
  223. package/src/api/events/assistant-text-delta.ts +32 -0
  224. package/src/api/events/assistant-turn-start.ts +33 -0
  225. package/src/api/events/document-comment-created.ts +48 -0
  226. package/src/api/events/document-comment-deleted.ts +24 -0
  227. package/src/api/events/document-comment-reopened.ts +25 -0
  228. package/src/api/events/document-comment-resolved.ts +27 -0
  229. package/src/api/events/generation-cancelled.ts +24 -0
  230. package/src/api/events/generation-handoff.ts +41 -0
  231. package/src/api/events/message-complete.ts +42 -0
  232. package/src/api/events/open-url.ts +30 -0
  233. package/src/{events → api/events}/relationship-state-updated.ts +3 -3
  234. package/src/api/events/tool-use-start.ts +32 -0
  235. package/src/api/index.ts +128 -3
  236. package/src/api/responses/llm-context-response.ts +39 -0
  237. package/src/api/responses/llm-request-log-entry.ts +93 -0
  238. package/src/api/responses/memory-recall-log.ts +65 -0
  239. package/src/api/responses/memory-v2-activation-log.ts +78 -0
  240. package/src/background-wake/background-wake-routes.test.ts +687 -52
  241. package/src/background-wake/platform-client.test.ts +308 -0
  242. package/src/background-wake/platform-client.ts +167 -0
  243. package/src/background-wake/publisher.ts +91 -0
  244. package/src/background-wake/runtime-registry.ts +2 -2
  245. package/src/background-wake/wake-intent-hooks.test.ts +282 -0
  246. package/src/calls/guardian-dispatch.ts +1 -0
  247. package/src/calls/voice-session-bridge.ts +4 -4
  248. package/src/cli/commands/__tests__/conversations-slack.test.ts +16 -0
  249. package/src/cli/commands/__tests__/notifications.test.ts +184 -40
  250. package/src/cli/commands/channels/__tests__/channels.test.ts +143 -0
  251. package/src/cli/commands/channels/index.ts +229 -0
  252. package/src/cli/commands/memory-v3-render.ts +147 -0
  253. package/src/cli/commands/memory-v3.ts +255 -4
  254. package/src/cli/commands/notifications.ts +365 -55
  255. package/src/cli/lib/open-browser.ts +7 -2
  256. package/src/cli/program.ts +2 -0
  257. package/src/config/assistant-feature-flags.ts +23 -42
  258. package/src/config/bundled-skills/document-editor/SKILL.md +5 -1
  259. package/src/config/bundled-skills/schedule/SKILL.md +1 -1
  260. package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
  261. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -0
  262. package/src/config/call-site-defaults.ts +1 -1
  263. package/src/config/feature-flag-cache.ts +86 -0
  264. package/src/config/feature-flag-registry.json +17 -17
  265. package/src/config/llm-context-resolution.ts +10 -1
  266. package/src/config/llm-resolver.ts +121 -15
  267. package/src/config/loader.ts +4 -5
  268. package/src/config/schemas/__tests__/memory-v2.test.ts +15 -0
  269. package/src/config/schemas/heartbeat.ts +1 -1
  270. package/src/config/schemas/llm.ts +90 -1
  271. package/src/config/schemas/memory-v2.ts +26 -0
  272. package/src/config/schemas/services.ts +6 -2
  273. package/src/config/seed-inference-profiles.ts +36 -16
  274. package/src/context/token-estimator.ts +10 -5
  275. package/src/credential-execution/executable-discovery.ts +40 -0
  276. package/src/credential-execution/process-manager.ts +6 -2
  277. package/src/credential-health/credential-health-service.ts +125 -40
  278. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -6
  279. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +13 -15
  280. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -2
  281. package/src/daemon/__tests__/daemon-skill-host.test.ts +2 -0
  282. package/src/daemon/__tests__/meet-manifest-loader.test.ts +25 -12
  283. package/src/daemon/__tests__/native-web-search-metadata.test.ts +1 -0
  284. package/src/daemon/__tests__/switch-inference-profile-tool.test.ts +107 -0
  285. package/src/daemon/__tests__/web-search-status-text.test.ts +1 -0
  286. package/src/daemon/conversation-agent-loop-handlers.ts +389 -68
  287. package/src/daemon/conversation-agent-loop.ts +132 -28
  288. package/src/daemon/conversation-error.ts +33 -5
  289. package/src/daemon/conversation-messaging.ts +84 -43
  290. package/src/daemon/conversation-process.ts +74 -37
  291. package/src/daemon/conversation-runtime-assembly.ts +29 -9
  292. package/src/daemon/conversation-skill-tools.ts +14 -30
  293. package/src/daemon/conversation-surfaces.ts +69 -34
  294. package/src/daemon/conversation-tool-setup.ts +33 -48
  295. package/src/daemon/conversation.ts +26 -46
  296. package/src/daemon/daemon-control.ts +1 -1
  297. package/src/daemon/daemon-skill-host.ts +9 -2
  298. package/src/daemon/disk-pressure-guard.ts +27 -29
  299. package/src/daemon/first-greeting.ts +31 -13
  300. package/src/daemon/handlers/shared.ts +6 -1
  301. package/src/daemon/lifecycle.ts +12 -12
  302. package/src/daemon/mcp-reload-service.ts +1 -1
  303. package/src/daemon/meet-manifest-loader.ts +10 -17
  304. package/src/daemon/message-types/conversations.ts +20 -22
  305. package/src/daemon/message-types/document-comments.ts +8 -44
  306. package/src/daemon/message-types/home.ts +2 -2
  307. package/src/daemon/message-types/integrations.ts +2 -7
  308. package/src/daemon/message-types/messages.ts +23 -38
  309. package/src/daemon/message-types/subagents.ts +6 -0
  310. package/src/daemon/process-message.ts +9 -9
  311. package/src/daemon/providers-setup.ts +1 -1
  312. package/src/daemon/server.ts +16 -0
  313. package/src/daemon/switch-inference-profile-tool.ts +13 -3
  314. package/src/daemon/tool-setup-types.ts +0 -6
  315. package/src/daemon/wake-target-adapter.ts +10 -0
  316. package/src/documents/document-store.ts +38 -0
  317. package/src/export/__tests__/transcript-formatter.test.ts +1 -0
  318. package/src/heartbeat/__tests__/heartbeat-service.test.ts +29 -0
  319. package/src/heartbeat/heartbeat-service.ts +63 -0
  320. package/src/home/__tests__/feed-writer.test.ts +161 -0
  321. package/src/home/__tests__/post-connect-feed.test.ts +1 -0
  322. package/src/home/__tests__/suggested-prompts.test.ts +55 -59
  323. package/src/home/feed-writer.ts +146 -7
  324. package/src/home/suggested-prompts.ts +27 -145
  325. package/src/ipc/__tests__/cli-ipc.test.ts +1 -0
  326. package/src/ipc/gateway-client.test.ts +4 -1
  327. package/src/ipc/skill-routes/__tests__/memory.test.ts +1 -0
  328. package/src/ipc/skill-routes/__tests__/registries.test.ts +36 -7
  329. package/src/ipc/skill-routes/memory.ts +4 -3
  330. package/src/ipc/skill-routes/registries.ts +28 -29
  331. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +1 -0
  332. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +26 -5
  333. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +1 -0
  334. package/src/memory/__tests__/memory-retrospective-job.test.ts +1 -0
  335. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +1 -0
  336. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +31 -0
  337. package/src/memory/conversation-attention-store.ts +17 -3
  338. package/src/memory/conversation-crud.ts +352 -112
  339. package/src/memory/db-connection.ts +29 -19
  340. package/src/memory/db-init.ts +4 -0
  341. package/src/memory/db-singleton.ts +77 -0
  342. package/src/memory/delivery-channels.ts +82 -0
  343. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +2 -4
  344. package/src/memory/graph/retriever.test.ts +3 -3
  345. package/src/memory/job-handlers/embedding.test.ts +3 -2
  346. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -2
  347. package/src/memory/jobs-worker.ts +12 -1
  348. package/src/memory/llm-request-log-source-clickhouse.ts +80 -0
  349. package/src/memory/llm-request-log-source-local.ts +24 -0
  350. package/src/memory/llm-request-log-source.ts +31 -0
  351. package/src/memory/llm-request-log-store.ts +188 -3
  352. package/src/memory/memory-v2-activation-log-store.ts +95 -1
  353. package/src/memory/migrations/265-drop-provider-connection-status.ts +26 -0
  354. package/src/memory/migrations/266-messages-client-message-id.ts +43 -0
  355. package/src/memory/migrations/index.ts +2 -0
  356. package/src/memory/schema/conversations.ts +9 -1
  357. package/src/memory/schema/inference.ts +0 -1
  358. package/src/memory/v2/__tests__/backfill-jobs.test.ts +5 -2
  359. package/src/memory/v2/__tests__/harness-metrics.test.ts +9 -0
  360. package/src/memory/v2/__tests__/harness-replay-input.test.ts +9 -4
  361. package/src/memory/v2/__tests__/harness-runner.test.ts +26 -0
  362. package/src/memory/v2/__tests__/sweep-job.test.ts +6 -3
  363. package/src/memory/v2/harness/metrics.ts +5 -1
  364. package/src/memory/v2/harness/replay-input.ts +19 -3
  365. package/src/memory/v2/harness/runner.ts +6 -0
  366. package/src/memory/v2/harness/trace.ts +6 -0
  367. package/src/memory/v3/__tests__/consolidation-job.test.ts +2 -4
  368. package/src/memory/v3/__tests__/coretrieval-seed.test.ts +270 -0
  369. package/src/memory/v3/__tests__/edges.test.ts +144 -1
  370. package/src/memory/v3/__tests__/filter.test.ts +48 -0
  371. package/src/memory/v3/__tests__/gate.test.ts +96 -33
  372. package/src/memory/v3/__tests__/index-composition.test.ts +58 -0
  373. package/src/memory/v3/__tests__/loop.test.ts +250 -5
  374. package/src/memory/v3/__tests__/scouts.test.ts +49 -0
  375. package/src/memory/v3/__tests__/shadow-diff.test.ts +225 -0
  376. package/src/memory/v3/__tests__/shadow-middleware.test.ts +88 -2
  377. package/src/memory/v3/__tests__/traversal.test.ts +39 -0
  378. package/src/memory/v3/__tests__/tree-walk.test.ts +77 -0
  379. package/src/memory/v3/__tests__/validate.test.ts +32 -0
  380. package/src/memory/v3/coretrieval-seed.ts +240 -0
  381. package/src/memory/v3/edges.ts +58 -21
  382. package/src/memory/v3/filter.ts +27 -22
  383. package/src/memory/v3/gate.ts +51 -36
  384. package/src/memory/v3/index-composition.ts +18 -5
  385. package/src/memory/v3/loop.ts +65 -17
  386. package/src/memory/v3/scouts.ts +15 -4
  387. package/src/memory/v3/shadow-diff.ts +287 -0
  388. package/src/memory/v3/shadow-middleware.ts +44 -2
  389. package/src/memory/v3/traversal.ts +6 -1
  390. package/src/memory/v3/tree-walk.ts +6 -1
  391. package/src/memory/v3/validate.ts +56 -33
  392. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
  393. package/src/notifications/__tests__/home-feed-side-effect.test.ts +1 -0
  394. package/src/notifications/adapters/slack.ts +45 -11
  395. package/src/notifications/broadcaster.ts +114 -63
  396. package/src/notifications/conversation-pairing.ts +23 -3
  397. package/src/notifications/decisions-store.ts +32 -1
  398. package/src/notifications/deliveries-store.ts +45 -0
  399. package/src/notifications/edit-notification.ts +201 -0
  400. package/src/notifications/emit-signal.ts +11 -1
  401. package/src/notifications/signal.ts +10 -0
  402. package/src/notifications/types.ts +37 -0
  403. package/src/oauth/byo-connection.test.ts +67 -3
  404. package/src/oauth/byo-connection.ts +32 -5
  405. package/src/oauth/connect-orchestrator.ts +9 -0
  406. package/src/oauth/connection-resolver.test.ts +76 -0
  407. package/src/oauth/connection-resolver.ts +49 -10
  408. package/src/oauth/manual-token-connection.ts +51 -3
  409. package/src/oauth/seed-providers.ts +3 -0
  410. package/src/permissions/approval-policy.test.ts +19 -5
  411. package/src/permissions/approval-policy.ts +14 -3
  412. package/src/permissions/checker.ts +21 -8
  413. package/src/platform/client.test.ts +24 -1
  414. package/src/platform/client.ts +8 -0
  415. package/src/platform/feature-gate.ts +15 -0
  416. package/src/plugins/defaults/injectors.ts +2 -8
  417. package/src/plugins/defaults/persistence.ts +25 -6
  418. package/src/plugins/types.ts +57 -13
  419. package/src/proactive-artifact/job.test.ts +1 -0
  420. package/src/prompts/__tests__/system-prompt.test.ts +4 -4
  421. package/src/prompts/system-prompt.ts +38 -40
  422. package/src/prompts/template-detection.ts +10 -4
  423. package/src/prompts/templates/BOOTSTRAP.md +7 -11
  424. package/src/prompts/templates/IDENTITY.md +0 -2
  425. package/src/providers/__tests__/connection-model-compat.test.ts +3 -4
  426. package/src/providers/__tests__/registry-native-web-search.test.ts +122 -0
  427. package/src/providers/call-site-routing.ts +33 -9
  428. package/src/providers/connection-model-compat.ts +23 -0
  429. package/src/providers/connection-resolution.ts +39 -20
  430. package/src/providers/fireworks/client.ts +1 -0
  431. package/src/providers/gemini/client.ts +24 -3
  432. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +0 -2
  433. package/src/providers/inference/__tests__/base-url-security.test.ts +2 -3
  434. package/src/providers/inference/__tests__/{connections-status-label.test.ts → connections-label.test.ts} +12 -111
  435. package/src/providers/inference/auth.ts +0 -8
  436. package/src/providers/inference/connections.ts +3 -66
  437. package/src/providers/inference/resolve-auth.ts +2 -3
  438. package/src/providers/model-catalog.ts +35 -1
  439. package/src/providers/model-intents.ts +3 -3
  440. package/src/providers/openai/__tests__/api-error-detail.test.ts +120 -0
  441. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +157 -5
  442. package/src/providers/openai/chat-completions-provider.ts +110 -12
  443. package/src/providers/openai/codex-models.ts +2 -0
  444. package/src/providers/openai/responses-provider.ts +53 -53
  445. package/src/providers/openrouter/client.ts +13 -8
  446. package/src/providers/provider-send-message.ts +18 -9
  447. package/src/providers/registry.ts +48 -8
  448. package/src/providers/retry.ts +16 -4
  449. package/src/providers/search-provider-catalog.ts +17 -9
  450. package/src/providers/types.ts +9 -0
  451. package/src/runtime/__tests__/agent-wake.test.ts +1 -0
  452. package/src/runtime/__tests__/background-job-runner.test.ts +1 -0
  453. package/src/runtime/access-request-helper.ts +1 -0
  454. package/src/runtime/auth/route-policy.ts +10 -0
  455. package/src/runtime/channel-readiness-service.ts +68 -0
  456. package/src/runtime/channel-reply-delivery.ts +23 -0
  457. package/src/runtime/channel-retry-sweep.ts +47 -14
  458. package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
  459. package/src/runtime/migrations/vbundle-builder.ts +3 -2
  460. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +1 -0
  461. package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +406 -0
  462. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -0
  463. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
  464. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +209 -1
  465. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +13 -50
  466. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +51 -3
  467. package/src/runtime/routes/__tests__/memory-v3-simulate-params.test.ts +35 -0
  468. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +3 -2
  469. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +294 -0
  470. package/src/runtime/routes/__tests__/task-routes.test.ts +48 -3
  471. package/src/runtime/routes/acp-routes-list.test.ts +3 -0
  472. package/src/runtime/routes/app-management-routes.ts +111 -4
  473. package/src/runtime/routes/background-wake-routes.ts +188 -20
  474. package/src/runtime/routes/btw-routes.ts +4 -4
  475. package/src/runtime/routes/conversation-analysis-routes.ts +6 -0
  476. package/src/runtime/routes/conversation-compaction-routes.ts +263 -0
  477. package/src/runtime/routes/conversation-list-routes.ts +147 -0
  478. package/src/runtime/routes/conversation-management-routes.ts +39 -14
  479. package/src/runtime/routes/conversation-query-routes.ts +60 -10
  480. package/src/runtime/routes/conversation-routes.ts +186 -140
  481. package/src/runtime/routes/conversations-import-routes.ts +19 -6
  482. package/src/runtime/routes/documents-routes.ts +10 -1
  483. package/src/runtime/routes/group-routes.ts +11 -0
  484. package/src/runtime/routes/home-feed-routes.ts +129 -0
  485. package/src/runtime/routes/identity-intro-cache.ts +61 -16
  486. package/src/runtime/routes/identity-routes.ts +30 -9
  487. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +530 -6
  488. package/src/runtime/routes/inbound-stages/background-dispatch.ts +57 -8
  489. package/src/runtime/routes/index.ts +2 -0
  490. package/src/runtime/routes/inference-provider-connection-routes.ts +5 -26
  491. package/src/runtime/routes/integrations/vercel.ts +15 -0
  492. package/src/runtime/routes/llm-context-normalization.ts +7 -2
  493. package/src/runtime/routes/memory-v3-routes.ts +160 -2
  494. package/src/runtime/routes/migration-routes.ts +20 -13
  495. package/src/runtime/routes/notification-routes.ts +63 -1
  496. package/src/runtime/routes/oauth-commands-routes.ts +6 -1
  497. package/src/runtime/routes/surface-action-routes.ts +1 -38
  498. package/src/runtime/routes/surface-content-routes.ts +12 -5
  499. package/src/runtime/routes/surface-conversation-resolver.ts +65 -0
  500. package/src/runtime/routes/wipe-conversation-routes.ts +3 -0
  501. package/src/runtime/services/__tests__/analyze-conversation.test.ts +2 -0
  502. package/src/runtime/slack-dm-text-delivery.ts +177 -0
  503. package/src/runtime/sync/resource-sync-events.ts +1 -1
  504. package/src/runtime/tool-grant-request-helper.ts +1 -0
  505. package/src/schedule/schedule-store.ts +8 -1
  506. package/src/schedule/scheduler.ts +111 -15
  507. package/src/security/__tests__/provider-key-env-fallback.test.ts +3 -3
  508. package/src/security/encrypted-store.ts +7 -16
  509. package/src/security/store-path-override.ts +61 -0
  510. package/src/signals/user-message.ts +5 -8
  511. package/src/skills/validate-input.ts +177 -0
  512. package/src/subagent/manager.ts +13 -13
  513. package/src/subagent/types.ts +6 -0
  514. package/src/tasks/tool-sanitizer.ts +2 -2
  515. package/src/tools/apps/definitions.ts +35 -21
  516. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +2 -8
  517. package/src/tools/computer-use/definitions.ts +268 -266
  518. package/src/tools/document/document-tool.ts +131 -8
  519. package/src/tools/execution-target.ts +2 -5
  520. package/src/tools/executor.ts +18 -55
  521. package/src/tools/host-filesystem/edit.test.ts +1 -0
  522. package/src/tools/host-filesystem/read.test.ts +1 -0
  523. package/src/tools/host-filesystem/transfer.test.ts +31 -6
  524. package/src/tools/host-filesystem/write.test.ts +1 -0
  525. package/src/tools/mcp/mcp-tool-factory.ts +0 -2
  526. package/src/tools/network/__tests__/managed-search-proxy.test.ts +282 -0
  527. package/src/tools/network/__tests__/web-search.test.ts +211 -3
  528. package/src/tools/network/managed-search-proxy.ts +183 -0
  529. package/src/tools/network/web-search.ts +199 -44
  530. package/src/tools/policy-context.ts +3 -1
  531. package/src/tools/registry.ts +146 -103
  532. package/src/tools/schedule/create.ts +1 -1
  533. package/src/tools/skills/skill-tool-factory.ts +17 -36
  534. package/src/tools/subagent/spawn.ts +3 -0
  535. package/src/tools/tool-approval-handler.ts +10 -4
  536. package/src/tools/tool-name-aliases.ts +72 -14
  537. package/src/tools/types.ts +17 -15
  538. package/src/tools/ui-surface/definitions.ts +98 -86
  539. package/src/types/onboarding-context.ts +6 -0
  540. package/src/usage/attribution.ts +32 -1
  541. package/src/util/browser.ts +7 -2
  542. package/src/workspace/migrations/090-memory-router-cost-optimized-profile.ts +109 -0
  543. package/src/workspace/migrations/091-retighten-migration-onboarding-thread.ts +41 -0
  544. package/src/workspace/migrations/registry.ts +4 -0
@@ -34,6 +34,7 @@ import { routeGuardianReply } from "../runtime/guardian-reply-router.js";
34
34
  import { publishConversationMessagesChanged } from "../runtime/sync/resource-sync-events.js";
35
35
  import { getLogger } from "../util/logger.js";
36
36
  import type { CleanResult } from "./conversation.js";
37
+ import type { PersistMessageOptions } from "./conversation-messaging.js";
37
38
  import {
38
39
  persistQueuedMessageBody,
39
40
  serializePersistedUserMessageContent,
@@ -158,12 +159,8 @@ export interface ProcessConversationContext {
158
159
  currentTurnChannelCapabilities?: ChannelCapabilities;
159
160
  ensureActorScopedHistory(): Promise<void>;
160
161
  persistUserMessage(
161
- content: string,
162
- attachments: UserMessageAttachment[],
163
- requestId?: string,
164
- metadata?: Record<string, unknown>,
165
- displayContent?: string,
166
- ): Promise<string>;
162
+ options: PersistMessageOptions,
163
+ ): Promise<{ id: string; deduplicated: boolean }>;
167
164
  runAgentLoop(
168
165
  content: string,
169
166
  userMessageId: string,
@@ -921,15 +918,16 @@ async function drainSingleMessage(
921
918
  // succeeds, runAgentLoop is called and its finally block will drain
922
919
  // the next message. If persistUserMessage fails, processMessage
923
920
  // resolves early (no runAgentLoop call), so we must continue draining.
924
- let userMessageId: string;
921
+ let persistResult: { id: string; deduplicated: boolean };
925
922
  try {
926
- userMessageId = await conversation.persistUserMessage(
927
- resolvedContent,
928
- next.attachments,
929
- next.requestId,
930
- { ...next.metadata, sentAt: next.sentAt },
931
- next.displayContent,
932
- );
923
+ persistResult = await conversation.persistUserMessage({
924
+ content: resolvedContent,
925
+ attachments: next.attachments,
926
+ requestId: next.requestId,
927
+ metadata: { ...next.metadata, sentAt: next.sentAt },
928
+ displayContent: next.displayContent,
929
+ clientMessageId: next.clientMessageId,
930
+ });
933
931
  } catch (err) {
934
932
  const message = err instanceof Error ? err.message : String(err);
935
933
  log.error(
@@ -961,6 +959,18 @@ async function drainSingleMessage(
961
959
  return;
962
960
  }
963
961
 
962
+ const userMessageId = persistResult.id;
963
+
964
+ if (persistResult.deduplicated) {
965
+ log.info(
966
+ { conversationId: conversation.conversationId, userMessageId },
967
+ "Skipping agent loop for deduplicated queued message",
968
+ );
969
+ conversation.preactivatedSkillIds = undefined;
970
+ await drainQueue(conversation);
971
+ return;
972
+ }
973
+
964
974
  // Broadcast the user message to all hub subscribers so passive devices
965
975
  // see the user turn before the assistant reply starts streaming.
966
976
  next.onEvent({
@@ -1217,24 +1227,43 @@ async function drainBatch(
1217
1227
  const qmContent = qmSlash.content;
1218
1228
 
1219
1229
  try {
1230
+ let batchPersistResult: { id: string; deduplicated: boolean };
1231
+ const persistOptions = {
1232
+ content: qmContent,
1233
+ attachments: qm.attachments,
1234
+ requestId: qm.requestId,
1235
+ metadata: { ...qm.metadata, sentAt: qm.sentAt },
1236
+ displayContent: qm.displayContent,
1237
+ clientMessageId: qm.clientMessageId,
1238
+ };
1220
1239
  if (i === 0) {
1221
- lastUserMessageId = await conversation.persistUserMessage(
1222
- qmContent,
1223
- qm.attachments,
1224
- qm.requestId,
1225
- { ...qm.metadata, sentAt: qm.sentAt },
1226
- qm.displayContent,
1227
- );
1240
+ batchPersistResult =
1241
+ await conversation.persistUserMessage(persistOptions);
1228
1242
  } else {
1229
- lastUserMessageId = await persistQueuedMessageBody(
1243
+ batchPersistResult = await persistQueuedMessageBody(
1230
1244
  conversation,
1231
- qmContent,
1232
- qm.attachments,
1233
- qm.requestId,
1234
- { ...qm.metadata, sentAt: qm.sentAt },
1235
- qm.displayContent,
1245
+ persistOptions,
1236
1246
  );
1237
1247
  }
1248
+ if (batchPersistResult.deduplicated) {
1249
+ if (i === 0) {
1250
+ // Head was deduplicated — persistUserMessage cleared the
1251
+ // processing flag. Recursively drain remaining items so the
1252
+ // first non-duplicate becomes the new batch head and sets
1253
+ // processing via persistUserMessage.
1254
+ const remaining = batch.slice(1);
1255
+ if (remaining.length >= 2) {
1256
+ await drainBatch(conversation, remaining, reason);
1257
+ } else if (remaining.length === 1) {
1258
+ await drainSingleMessage(conversation, remaining[0], reason);
1259
+ } else {
1260
+ await drainQueue(conversation);
1261
+ }
1262
+ return;
1263
+ }
1264
+ continue;
1265
+ }
1266
+ lastUserMessageId = batchPersistResult.id;
1238
1267
  } catch (err) {
1239
1268
  const message = err instanceof Error ? err.message : String(err);
1240
1269
  log.error(
@@ -1377,12 +1406,19 @@ async function drainBatch(
1377
1406
  conversation.currentActiveSurfaceId = lastSuccessfulActiveSurfaceId;
1378
1407
  conversation.currentPage = lastSuccessfulCurrentPage;
1379
1408
 
1380
- // Broadcast agent-loop events only to members whose persist succeeded.
1381
- // Members whose persist failed already received an error event in the
1382
- // catch block above; sending them the assistant's streaming response
1383
- // would surface a reply for a user message that isn't in their DB.
1409
+ // Broadcast agent-loop events only to unique sinks whose persist succeeded.
1410
+ // Multiple web-queued messages share the same broadcastMessage callback; if
1411
+ // we call it once per queued message, every text delta is published N times
1412
+ // to the same SSE stream and the client renders duplicated text.
1413
+ //
1414
+ // Members whose persist failed already received an error event in the catch
1415
+ // block above; sending them the assistant's streaming response would surface
1416
+ // a reply for a user message that isn't in their DB.
1417
+ const successfulEventSinks = Array.from(
1418
+ new Set(successfulBatch.map((qm) => qm.onEvent)),
1419
+ );
1384
1420
  const fanOutOnEvent = (msg: ServerMessage) => {
1385
- for (const qm of successfulBatch) qm.onEvent(msg);
1421
+ for (const onEvent of successfulEventSinks) onEvent(msg);
1386
1422
  };
1387
1423
 
1388
1424
  const drainLoopOptions: {
@@ -1857,15 +1893,14 @@ export async function processMessage(
1857
1893
  }
1858
1894
  }
1859
1895
 
1860
- let userMessageId: string;
1896
+ let pmResult: { id: string; deduplicated: boolean };
1861
1897
  try {
1862
- userMessageId = await conversation.persistUserMessage(
1863
- resolvedContent,
1898
+ pmResult = await conversation.persistUserMessage({
1899
+ content: resolvedContent,
1864
1900
  attachments,
1865
1901
  requestId,
1866
- undefined,
1867
1902
  displayContent,
1868
- );
1903
+ });
1869
1904
  publishConversationMessagesChanged(conversation.conversationId);
1870
1905
  } catch (err) {
1871
1906
  const message = err instanceof Error ? err.message : String(err);
@@ -1879,6 +1914,8 @@ export async function processMessage(
1879
1914
  return "";
1880
1915
  }
1881
1916
 
1917
+ const userMessageId = pmResult.id;
1918
+
1882
1919
  // Fire-and-forget: detect notification preferences in the user message
1883
1920
  // and persist any that are found. Runs in the background so it doesn't
1884
1921
  // block the main conversation flow.
@@ -997,23 +997,38 @@ export function buildUnifiedTurnContextBlock(
997
997
  // ---------------------------------------------------------------------------
998
998
 
999
999
  /**
1000
- * Remove text blocks from user messages whose text starts with any of the
1001
- * given prefixes. If stripping removes all content blocks from a message,
1002
- * the message itself is dropped.
1000
+ * A matcher for an injected text block. A plain string matches by prefix
1001
+ * (`startsWith`). A `{ prefix, suffix }` wrapper requires BOTH the opening
1002
+ * prefix and the closing suffix, so user-authored content that merely begins
1003
+ * with an injection-like opening tag (e.g. a message discussing `<info>`
1004
+ * markup) is not mistaken for an injected block and dropped. This mirrors
1005
+ * `countMemoryPrefixBlocks`, which only treats `<memory>…</memory>` /
1006
+ * `<info>…</info>` blocks as injected when the full wrapper is present.
1007
+ */
1008
+ type InjectionMatcher = string | { prefix: string; suffix: string };
1009
+
1010
+ /**
1011
+ * Remove text blocks from user messages that match any of the given matchers.
1012
+ * If stripping removes all content blocks from a message, the message itself
1013
+ * is dropped.
1003
1014
  *
1004
1015
  * This is the shared primitive behind the individual strip* functions and
1005
1016
  * the `stripInjectionsForCompaction` pipeline.
1006
1017
  */
1007
1018
  function stripUserTextBlocksByPrefix(
1008
1019
  messages: Message[],
1009
- prefixes: string[],
1020
+ matchers: InjectionMatcher[],
1010
1021
  ): Message[] {
1011
1022
  return messages
1012
1023
  .map((message) => {
1013
1024
  if (message.role !== "user") return message;
1014
1025
  const nextContent = message.content.filter((block) => {
1015
1026
  if (block.type !== "text") return true;
1016
- return !prefixes.some((p) => block.text.startsWith(p));
1027
+ return !matchers.some((m) =>
1028
+ typeof m === "string"
1029
+ ? block.text.startsWith(m)
1030
+ : block.text.startsWith(m.prefix) && block.text.endsWith(m.suffix),
1031
+ );
1017
1032
  });
1018
1033
  if (nextContent.length === message.content.length) return message;
1019
1034
  if (nextContent.length === 0) return null;
@@ -1720,8 +1735,8 @@ export function loadSlackActiveThreadFocusBlock(
1720
1735
  return assembleSlackActiveThreadFocusBlock(rows, capabilities);
1721
1736
  }
1722
1737
 
1723
- /** Prefixes stripped by the pipeline (order doesn't matter — single pass). */
1724
- const RUNTIME_INJECTION_PREFIXES = [
1738
+ /** Matchers stripped by the pipeline (order doesn't matter — single pass). */
1739
+ const RUNTIME_INJECTION_PREFIXES: InjectionMatcher[] = [
1725
1740
  "<channel_capabilities>",
1726
1741
  "<channel_command_context>",
1727
1742
  "<disk_pressure_warning>",
@@ -1742,8 +1757,13 @@ const RUNTIME_INJECTION_PREFIXES = [
1742
1757
  // cadence. The activation pipeline dedupes via `everInjected`, and
1743
1758
  // compaction handles aggregate growth, so accumulation does not cause
1744
1759
  // unbounded context growth. Both wrappers may appear in persisted rows.
1745
- "<memory>\n",
1746
- "<info>\n",
1760
+ //
1761
+ // These two use the full `{ prefix, suffix }` wrapper shape (not a bare
1762
+ // prefix) so that user-authored text merely starting with `<memory>\n` or
1763
+ // `<info>\n` is never silently dropped during compaction/`/clean`. This
1764
+ // matches the full-wrapper requirement in `countMemoryPrefixBlocks`.
1765
+ { prefix: "<memory>\n", suffix: "\n</memory>" },
1766
+ { prefix: "<info>\n", suffix: "\n</info>" },
1747
1767
  "<voice_call_control>",
1748
1768
  "<workspace_top_level>", // backward-compat: strip legacy workspace blocks
1749
1769
  // NOTE: <workspace> is intentionally NOT stripped — workspace context
@@ -23,6 +23,7 @@ import { parseToolManifestFile } from "../skills/tool-manifest.js";
23
23
  import { computeSkillVersionHash } from "../skills/version-hash.js";
24
24
  import {
25
25
  getTool,
26
+ getToolOwner,
26
27
  registerSkillTools,
27
28
  unregisterSkillTools,
28
29
  } from "../tools/registry.js";
@@ -313,7 +314,6 @@ export function projectSkillTools(
313
314
  // Create runtime Tool objects
314
315
  const tools = createSkillToolsFromManifest(
315
316
  manifest.tools,
316
- skillId,
317
317
  skill.directoryPath,
318
318
  currentHash,
319
319
  skill.bundled,
@@ -324,7 +324,7 @@ export function projectSkillTools(
324
324
  const prevHash = prevActive.get(skillId);
325
325
  if (prevHash === undefined) {
326
326
  // Newly active skill — register for the first time
327
- accepted = registerSkillTools(tools);
327
+ accepted = registerSkillTools(skillId, tools);
328
328
  } else if (prevHash !== currentHash) {
329
329
  // Hash changed — unregister stale tools, then re-register with new definitions
330
330
  log.info(
@@ -334,7 +334,7 @@ export function projectSkillTools(
334
334
  unregisterSkillTools(skillId);
335
335
  alreadyUnregistered.add(skillId);
336
336
  try {
337
- accepted = registerSkillTools(tools);
337
+ accepted = registerSkillTools(skillId, tools);
338
338
  } catch (err) {
339
339
  log.error(
340
340
  { err, skillId },
@@ -344,33 +344,17 @@ export function projectSkillTools(
344
344
  continue;
345
345
  }
346
346
  } else {
347
- // Hash unchanged — check if the bundled status drifted (e.g. a
348
- // managed skill override was added/removed with identical content).
349
- // Re-register so the ownerSkillBundled flag stays accurate.
350
- const existing = getTool(tools[0].name);
351
- if (
352
- existing &&
353
- existing.ownerSkillBundled !== (skill.bundled ?? undefined)
354
- ) {
355
- log.info(
356
- { skillId, bundled: skill.bundled },
357
- "Skill bundled status changed, re-registering tools",
358
- );
359
- unregisterSkillTools(skillId);
360
- accepted = registerSkillTools(tools);
361
- } else {
362
- // Filter to only tools that are actually registered for this skill.
363
- // Some tools may have been skipped during initial registration due
364
- // to core-name collisions — don't let them leak back in.
365
- accepted = tools.filter((t) => {
366
- const reg = getTool(t.name);
367
- return (
368
- reg !== undefined &&
369
- reg.origin === "skill" &&
370
- reg.ownerSkillId === skillId
371
- );
372
- });
373
- }
347
+ // Hash unchanged — filter to only tools that are actually registered
348
+ // for this skill. Some tools may have been skipped during initial
349
+ // registration due to core-name collisions don't let them leak
350
+ // back in. Bundled-status drift no longer requires re-registration
351
+ // because the permission checker derives bundled state from the
352
+ // live catalog instead of a stamped tool field.
353
+ accepted = tools.filter((t) => {
354
+ if (getTool(t.name) === undefined) return false;
355
+ const owner = getToolOwner(t.name);
356
+ return owner?.kind === "skill" && owner.id === skillId;
357
+ });
374
358
  }
375
359
 
376
360
  successfulEntries.set(skillId, currentHash);
@@ -28,6 +28,7 @@ import { getLogger } from "../util/logger.js";
28
28
  import { isPlainObject } from "../util/object.js";
29
29
  import { buildConversationErrorMessage } from "./conversation-error.js";
30
30
  import { launchConversation } from "./conversation-launch.js";
31
+ import type { EnqueueMessageOptions } from "./conversation-messaging.js";
31
32
  import type { HostAppControlProxy } from "./host-app-control-proxy.js";
32
33
  import type { HostCuProxy } from "./host-cu-proxy.js";
33
34
  import type {
@@ -45,7 +46,6 @@ import type {
45
46
  UiSurfaceShow,
46
47
  } from "./message-protocol.js";
47
48
  import { INTERACTIVE_SURFACE_TYPES } from "./message-protocol.js";
48
- import type { ConversationTransportMetadata } from "./message-types/conversations.js";
49
49
  import type { HostAppControlInput } from "./message-types/host-app-control.js";
50
50
  import type { UserMessageAttachment } from "./message-types/shared.js";
51
51
  import type { TrustContext } from "./trust-context.js";
@@ -518,18 +518,11 @@ export interface SurfaceConversationContext {
518
518
  /** True when no interactive client is connected (headless / channel-only). */
519
519
  readonly hasNoClient?: boolean;
520
520
  isProcessing(): boolean;
521
- enqueueMessage(
522
- content: string,
523
- attachments: UserMessageAttachment[],
524
- onEvent?: (msg: ServerMessage) => void,
525
- requestId?: string,
526
- activeSurfaceId?: string,
527
- currentPage?: string,
528
- metadata?: Record<string, unknown>,
529
- options?: { isInteractive?: boolean },
530
- displayContent?: string,
531
- transport?: ConversationTransportMetadata,
532
- ): { queued: boolean; requestId: string; rejected?: boolean };
521
+ enqueueMessage(options: EnqueueMessageOptions): {
522
+ queued: boolean;
523
+ requestId: string;
524
+ rejected?: boolean;
525
+ };
533
526
  getQueueDepth(): number;
534
527
  processMessage(
535
528
  content: string,
@@ -1142,6 +1135,34 @@ export type SurfaceActionResult =
1142
1135
  | { accepted: false; error: string }
1143
1136
  | void;
1144
1137
 
1138
+ const SURFACE_COMPLETE_FLAG = "_completeSurface";
1139
+ const SURFACE_COMPLETION_SUMMARY_FIELD = "_completionSummary";
1140
+
1141
+ function getRequestedSurfaceCompletionSummary(
1142
+ data?: Record<string, unknown>,
1143
+ ): string | null {
1144
+ if (data?.[SURFACE_COMPLETE_FLAG] !== true) return null;
1145
+ const summary =
1146
+ typeof data[SURFACE_COMPLETION_SUMMARY_FIELD] === "string"
1147
+ ? data[SURFACE_COMPLETION_SUMMARY_FIELD].trim()
1148
+ : "";
1149
+ return summary || "Completed";
1150
+ }
1151
+
1152
+ function completeSurfaceFromAction(
1153
+ ctx: SurfaceConversationContext,
1154
+ surfaceId: string,
1155
+ summary: string,
1156
+ ): void {
1157
+ broadcastMessage({
1158
+ type: "ui_surface_complete",
1159
+ conversationId: ctx.conversationId,
1160
+ surfaceId,
1161
+ summary,
1162
+ });
1163
+ markSurfaceCompleted(ctx, surfaceId, summary);
1164
+ }
1165
+
1145
1166
  export async function handleSurfaceAction(
1146
1167
  ctx: SurfaceConversationContext,
1147
1168
  surfaceId: string,
@@ -1318,9 +1339,16 @@ export async function handleSurfaceAction(
1318
1339
  }
1319
1340
 
1320
1341
  // Determine message content from the action.
1342
+ const stored = ctx.surfaceState.get(surfaceId);
1343
+ const actionDef = stored?.actions?.find((a) => a.id === actionId);
1344
+ const mergedData: Record<string, unknown> | undefined =
1345
+ actionDef?.data || data ? { ...actionDef?.data, ...data } : undefined;
1346
+
1321
1347
  const isRelay = actionId === "relay_prompt" || actionId === "agent_prompt";
1322
1348
  const prompt =
1323
- isRelay && typeof data?.prompt === "string" ? data.prompt.trim() : "";
1349
+ isRelay && typeof mergedData?.prompt === "string"
1350
+ ? mergedData.prompt.trim()
1351
+ : "";
1324
1352
 
1325
1353
  // Read accumulated state once — used by both relay and custom action paths.
1326
1354
  const accState = ctx.accumulatedSurfaceState.get(surfaceId);
@@ -1329,9 +1357,9 @@ export async function handleSurfaceAction(
1329
1357
  // Extract file attachments from action data so they are sent as proper
1330
1358
  // image/file content blocks instead of dumping base64 into the text.
1331
1359
  let attachments: UserMessageAttachment[] = [];
1332
- let actionDataForText = data;
1333
- if (data && Array.isArray(data.files)) {
1334
- const files = data.files as Array<Record<string, unknown>>;
1360
+ let actionDataForText = mergedData;
1361
+ if (mergedData && Array.isArray(mergedData.files)) {
1362
+ const files = mergedData.files as Array<Record<string, unknown>>;
1335
1363
  attachments = files
1336
1364
  .filter(
1337
1365
  (f) =>
@@ -1351,7 +1379,7 @@ export async function handleSurfaceAction(
1351
1379
  // attachments — otherwise preserve the original data so the model still
1352
1380
  // sees the files field (e.g. IDs/paths from dynamic app actions).
1353
1381
  if (attachments.length > 0) {
1354
- const { files: _files, ...rest } = data;
1382
+ const { files: _files, ...rest } = mergedData;
1355
1383
  actionDataForText = Object.keys(rest).length > 0 ? rest : undefined;
1356
1384
  }
1357
1385
  }
@@ -1414,23 +1442,26 @@ export async function handleSurfaceAction(
1414
1442
  attributes: { source: "surface_action", surfaceId, actionId },
1415
1443
  });
1416
1444
 
1417
- const result = ctx.enqueueMessage(
1445
+ const result = ctx.enqueueMessage({
1418
1446
  content,
1419
1447
  attachments,
1420
1448
  onEvent,
1421
1449
  requestId,
1422
- surfaceId,
1423
- undefined,
1424
- undefined,
1425
- undefined,
1450
+ activeSurfaceId: surfaceId,
1426
1451
  displayContent,
1427
- );
1452
+ });
1428
1453
 
1429
1454
  if (result.rejected) {
1430
1455
  ctx.surfaceActionRequestIds.delete(requestId);
1431
1456
  return;
1432
1457
  }
1433
1458
 
1459
+ const requestedCompletionSummary =
1460
+ getRequestedSurfaceCompletionSummary(mergedData);
1461
+ if (requestedCompletionSummary) {
1462
+ completeSurfaceFromAction(ctx, surfaceId, requestedCompletionSummary);
1463
+ }
1464
+
1434
1465
  // One-shot: clear accumulated state now that the message has been accepted.
1435
1466
  // Deferred until after rejection check so state is preserved for retry on rejection.
1436
1467
  if (hasAccState) {
@@ -1653,22 +1684,22 @@ export async function handleSurfaceAction(
1653
1684
  "Surface action follow-up: preparing to send message to model",
1654
1685
  );
1655
1686
 
1656
- const result = ctx.enqueueMessage(
1687
+ const result = ctx.enqueueMessage({
1657
1688
  content,
1658
- pendingAttachments,
1689
+ attachments: pendingAttachments,
1659
1690
  onEvent,
1660
1691
  requestId,
1661
- surfaceId,
1662
- undefined,
1663
- undefined,
1664
- undefined,
1692
+ activeSurfaceId: surfaceId,
1665
1693
  displayContent,
1666
- );
1694
+ });
1667
1695
  if (result.rejected) {
1668
1696
  ctx.surfaceActionRequestIds.delete(requestId);
1669
1697
  return;
1670
1698
  }
1671
1699
 
1700
+ const requestedCompletionSummary =
1701
+ getRequestedSurfaceCompletionSummary(mergedData);
1702
+
1672
1703
  // One-shot interactive surfaces — auto-complete now that the message has
1673
1704
  // been accepted. Deferred until after rejection check so the surface stays
1674
1705
  // active and retryable if the queue was full.
@@ -1678,15 +1709,19 @@ export async function handleSurfaceAction(
1678
1709
  "file_upload",
1679
1710
  "task_preferences",
1680
1711
  ];
1681
- if (ONE_SHOT_SURFACE_TYPES.includes(pending.surfaceType)) {
1712
+ if (
1713
+ requestedCompletionSummary ||
1714
+ ONE_SHOT_SURFACE_TYPES.includes(pending.surfaceType)
1715
+ ) {
1716
+ const completionSummary = requestedCompletionSummary ?? summary;
1682
1717
  broadcastMessage({
1683
1718
  type: "ui_surface_complete",
1684
1719
  conversationId: ctx.conversationId,
1685
1720
  surfaceId,
1686
- summary,
1721
+ summary: completionSummary,
1687
1722
  submittedData: mergedDataForText,
1688
1723
  });
1689
- markSurfaceCompleted(ctx, surfaceId, summary);
1724
+ markSurfaceCompleted(ctx, surfaceId, completionSummary);
1690
1725
  }
1691
1726
 
1692
1727
  // One-shot: clear accumulated state now that the message has been accepted.