@vellumai/assistant 0.8.1 → 0.8.3

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 (630) hide show
  1. package/ARCHITECTURE.md +13 -19
  2. package/Dockerfile +75 -1
  3. package/bun.lock +11 -1
  4. package/docker-entrypoint.sh +17 -0
  5. package/docker-init-apt-root.sh +167 -0
  6. package/docker-kata-apt-env.sh +39 -0
  7. package/docs/plugins.md +88 -47
  8. package/docs/skills.md +9 -7
  9. package/examples/plugins/echo/README.md +27 -27
  10. package/examples/plugins/echo/package.json +3 -0
  11. package/examples/plugins/echo/register.ts +31 -31
  12. package/node_modules/@vellumai/slack-text/src/index.test.ts +114 -14
  13. package/node_modules/@vellumai/slack-text/src/index.ts +82 -18
  14. package/openapi.yaml +642 -5
  15. package/package.json +3 -1
  16. package/scripts/generate-openapi.ts +83 -10
  17. package/scripts/sync-llm-catalog.ts +2 -2
  18. package/scripts/sync-web-search-catalog.ts +47 -25
  19. package/src/__tests__/agent-image-optimize.test.ts +11 -3
  20. package/src/__tests__/agent-loop-exit-reason.test.ts +272 -0
  21. package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
  22. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
  23. package/src/__tests__/anthropic-provider.test.ts +45 -0
  24. package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
  25. package/src/__tests__/app-executors.test.ts +220 -4
  26. package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
  27. package/src/__tests__/bundled-asset.test.ts +6 -6
  28. package/src/__tests__/channel-availability-routes.test.ts +206 -0
  29. package/src/__tests__/channel-delivery-store.test.ts +289 -1
  30. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
  31. package/src/__tests__/clawhub.test.ts +75 -16
  32. package/src/__tests__/compactor-tail-resolution.test.ts +147 -0
  33. package/src/__tests__/config-get-vision-flag.test.ts +136 -0
  34. package/src/__tests__/config-loader-backfill.test.ts +115 -18
  35. package/src/__tests__/config-schema.test.ts +21 -0
  36. package/src/__tests__/config-set-route.test.ts +80 -0
  37. package/src/__tests__/config-sounds-sync.test.ts +97 -0
  38. package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
  39. package/src/__tests__/context-search-conversations-source.test.ts +117 -2
  40. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  41. package/src/__tests__/context-search-workspace-source.test.ts +7 -0
  42. package/src/__tests__/context-token-estimator.test.ts +31 -65
  43. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  44. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  45. package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
  46. package/src/__tests__/conversation-agent-loop.test.ts +59 -1
  47. package/src/__tests__/conversation-error.test.ts +42 -3
  48. package/src/__tests__/conversation-fork-crud.test.ts +82 -0
  49. package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
  50. package/src/__tests__/conversation-lifecycle.test.ts +173 -0
  51. package/src/__tests__/conversation-media-retry.test.ts +19 -8
  52. package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
  53. package/src/__tests__/conversation-pairing.test.ts +54 -0
  54. package/src/__tests__/conversation-process-callsite.test.ts +4 -1
  55. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
  56. package/src/__tests__/conversation-queue.test.ts +4 -1
  57. package/src/__tests__/conversation-runtime-assembly.test.ts +102 -13
  58. package/src/__tests__/conversation-slash-queue.test.ts +59 -1
  59. package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
  60. package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
  61. package/src/__tests__/conversation-sync-tags.test.ts +235 -0
  62. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  63. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  64. package/src/__tests__/credential-security-invariants.test.ts +3 -2
  65. package/src/__tests__/date-context.test.ts +45 -0
  66. package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
  67. package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
  68. package/src/__tests__/disk-pressure-tools.test.ts +1 -0
  69. package/src/__tests__/dm-backfill.test.ts +121 -10
  70. package/src/__tests__/document-tool-security.test.ts +258 -0
  71. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  72. package/src/__tests__/edit-propagation.test.ts +33 -0
  73. package/src/__tests__/empty-response-pipeline.test.ts +0 -4
  74. package/src/__tests__/external-plugin-loader.test.ts +151 -55
  75. package/src/__tests__/filing-service.test.ts +140 -0
  76. package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
  77. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
  78. package/src/__tests__/guardian-dispatch.test.ts +1 -0
  79. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
  80. package/src/__tests__/heartbeat-service.test.ts +24 -164
  81. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  82. package/src/__tests__/helpers/tar-fixtures.ts +39 -0
  83. package/src/__tests__/helpers/wait-for.ts +21 -0
  84. package/src/__tests__/history-repair-pipeline.test.ts +0 -3
  85. package/src/__tests__/history-repair.test.ts +73 -0
  86. package/src/__tests__/host-app-control-proxy.test.ts +507 -10
  87. package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
  88. package/src/__tests__/image-credentials.test.ts +1 -1
  89. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  90. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
  91. package/src/__tests__/inference-profile-reaper.test.ts +4 -2
  92. package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
  93. package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
  94. package/src/__tests__/injector-background-turn.test.ts +153 -0
  95. package/src/__tests__/injector-chain.test.ts +15 -8
  96. package/src/__tests__/install-skill-routing.test.ts +155 -37
  97. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +99 -3
  98. package/src/__tests__/list-messages-page-latest.test.ts +55 -0
  99. package/src/__tests__/llm-call-pipeline.test.ts +0 -3
  100. package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
  101. package/src/__tests__/llm-catalog-parity.test.ts +58 -13
  102. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
  103. package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
  104. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +36 -0
  105. package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
  106. package/src/__tests__/llm-resolver.test.ts +255 -2
  107. package/src/__tests__/llm-usage-store.test.ts +114 -0
  108. package/src/__tests__/managed-profile-guard.test.ts +41 -29
  109. package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
  110. package/src/__tests__/managed-store.test.ts +84 -192
  111. package/src/__tests__/media-generate-image.test.ts +1 -1
  112. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
  113. package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
  114. package/src/__tests__/notification-decision-fallback.test.ts +0 -91
  115. package/src/__tests__/notification-decision-strategy.test.ts +14 -31
  116. package/src/__tests__/notification-deep-link.test.ts +15 -0
  117. package/src/__tests__/notification-guardian-path.test.ts +1 -2
  118. package/src/__tests__/notification-platform-adapter.test.ts +5 -4
  119. package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
  120. package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
  121. package/src/__tests__/oauth-commands-routes.test.ts +168 -16
  122. package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
  123. package/src/__tests__/openai-provider.test.ts +242 -3
  124. package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
  125. package/src/__tests__/openrouter-provider-only.test.ts +51 -3
  126. package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
  127. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
  128. package/src/__tests__/persistence-pipeline.test.ts +0 -2
  129. package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +7 -2
  130. package/src/__tests__/platform.test.ts +2 -0
  131. package/src/__tests__/plugin-api-shim.test.ts +125 -0
  132. package/src/__tests__/plugin-bootstrap.test.ts +10 -36
  133. package/src/__tests__/plugin-external-api.test.ts +68 -0
  134. package/src/__tests__/plugin-registry.test.ts +0 -77
  135. package/src/__tests__/plugin-route-contribution.test.ts +0 -1
  136. package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
  137. package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
  138. package/src/__tests__/plugin-types.test.ts +3 -13
  139. package/src/__tests__/process-message-background-slack.test.ts +8 -1
  140. package/src/__tests__/process-message-display-content.test.ts +421 -0
  141. package/src/__tests__/provider-catalog-visibility.test.ts +158 -0
  142. package/src/__tests__/provider-error-scenarios.test.ts +111 -0
  143. package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +33 -31
  144. package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
  145. package/src/__tests__/schedule-routes.test.ts +50 -3
  146. package/src/__tests__/schedule-store.test.ts +94 -0
  147. package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
  148. package/src/__tests__/schema-transforms.test.ts +20 -0
  149. package/src/__tests__/search-skills-unified.test.ts +0 -5
  150. package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +1 -1
  151. package/src/__tests__/server-history-render.test.ts +43 -0
  152. package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
  153. package/src/__tests__/skill-load-tool.test.ts +27 -89
  154. package/src/__tests__/skill-memory.test.ts +23 -3
  155. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
  156. package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
  157. package/src/__tests__/skills-install-extract.test.ts +49 -38
  158. package/src/__tests__/skills-install-staging.test.ts +159 -0
  159. package/src/__tests__/skills-uninstall.test.ts +9 -41
  160. package/src/__tests__/skills.test.ts +51 -58
  161. package/src/__tests__/slack-channel-config.test.ts +9 -0
  162. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  163. package/src/__tests__/system-prompt.test.ts +670 -63
  164. package/src/__tests__/terminal-tools.test.ts +28 -1
  165. package/src/__tests__/thread-backfill.test.ts +557 -27
  166. package/src/__tests__/title-generate-pipeline.test.ts +0 -13
  167. package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
  168. package/src/__tests__/tool-error-pipeline.test.ts +0 -3
  169. package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
  170. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  171. package/src/__tests__/tool-executor.test.ts +16 -4
  172. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
  173. package/src/__tests__/turn-events-store.test.ts +256 -0
  174. package/src/__tests__/twilio-routes.test.ts +4 -0
  175. package/src/__tests__/user-plugin-loader.test.ts +0 -7
  176. package/src/__tests__/voice-session-bridge.test.ts +198 -0
  177. package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
  178. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
  179. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
  180. package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
  181. package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
  182. package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
  183. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
  184. package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
  185. package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
  186. package/src/a2a/__tests__/agent-card.test.ts +98 -0
  187. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
  188. package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
  189. package/src/a2a/__tests__/task-store.test.ts +246 -0
  190. package/src/a2a/agent-card.ts +58 -0
  191. package/src/a2a/feature-gate.ts +8 -0
  192. package/src/a2a/protocol-constants.ts +21 -0
  193. package/src/a2a/protocol-errors.ts +50 -0
  194. package/src/a2a/protocol-types.ts +162 -0
  195. package/src/a2a/task-store.ts +168 -0
  196. package/src/acp/resolve-agent.ts +1 -1
  197. package/src/agent/image-optimize.ts +13 -5
  198. package/src/agent/loop.ts +167 -18
  199. package/src/calls/voice-session-bridge.ts +61 -42
  200. package/src/channels/config.ts +9 -0
  201. package/src/channels/types.ts +122 -0
  202. package/src/cli/__tests__/unknown-command.test.ts +24 -0
  203. package/src/cli/commands/__tests__/changelog.test.ts +304 -319
  204. package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
  205. package/src/cli/commands/__tests__/schedules.test.ts +960 -0
  206. package/src/cli/commands/changelog.ts +106 -42
  207. package/src/cli/commands/conversations.ts +102 -17
  208. package/src/cli/commands/default-action.ts +10 -53
  209. package/src/cli/commands/notifications.ts +388 -346
  210. package/src/cli/commands/plugins.ts +252 -0
  211. package/src/cli/commands/schedules.ts +683 -0
  212. package/src/cli/commands/telemetry.ts +40 -0
  213. package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
  214. package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
  215. package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
  216. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
  217. package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
  218. package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
  219. package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
  220. package/src/cli/lib/cli-colors.ts +12 -0
  221. package/src/cli/lib/confirm-prompt.ts +79 -0
  222. package/src/cli/lib/install-from-github.ts +303 -0
  223. package/src/cli/lib/list-installed-plugins.ts +137 -0
  224. package/src/cli/lib/search-plugins.ts +163 -0
  225. package/src/cli/lib/uninstall-plugin.ts +82 -0
  226. package/src/cli/lib/unknown-command.ts +111 -0
  227. package/src/cli/program.ts +52 -2
  228. package/src/config/assistant-feature-flags.ts +24 -54
  229. package/src/config/bundled-skills/app-builder/SKILL.md +140 -22
  230. package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
  231. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
  232. package/src/config/bundled-skills/document/SKILL.md +23 -3
  233. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  234. package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
  235. package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
  236. package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
  237. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  238. package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
  239. package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
  240. package/src/config/bundled-tool-registry.ts +6 -0
  241. package/src/config/call-site-defaults.ts +105 -0
  242. package/src/config/feature-flag-registry.json +41 -9
  243. package/src/config/llm-resolver.ts +52 -1
  244. package/src/config/loader.ts +64 -38
  245. package/src/config/schema.ts +9 -10
  246. package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
  247. package/src/config/schemas/__tests__/memory-v2.test.ts +3 -3
  248. package/src/config/schemas/channels.ts +17 -0
  249. package/src/config/schemas/compaction.ts +28 -0
  250. package/src/config/schemas/conversations.ts +10 -0
  251. package/src/config/schemas/heartbeat.ts +23 -0
  252. package/src/config/schemas/llm-request-logs.ts +31 -7
  253. package/src/config/schemas/llm.ts +1 -0
  254. package/src/config/schemas/memory-retrieval.ts +18 -0
  255. package/src/config/schemas/memory-retrospective.ts +1 -1
  256. package/src/config/schemas/memory-v2.ts +4 -4
  257. package/src/config/schemas/memory.ts +3 -1
  258. package/src/config/schemas/tools.ts +14 -0
  259. package/src/config/seed-inference-profiles.ts +99 -29
  260. package/src/config/skills.ts +3 -96
  261. package/src/context/compactor.ts +1107 -0
  262. package/src/context/token-estimator.ts +34 -36
  263. package/src/context/window-manager.ts +197 -1520
  264. package/src/credential-execution/managed-catalog.ts +37 -0
  265. package/src/credential-health/credential-health-service.ts +280 -19
  266. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +33 -18
  267. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
  268. package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
  269. package/src/daemon/approval-generators.ts +8 -6
  270. package/src/daemon/config-watcher.ts +94 -31
  271. package/src/daemon/conversation-agent-loop-handlers.ts +78 -0
  272. package/src/daemon/conversation-agent-loop.ts +198 -11
  273. package/src/daemon/conversation-error.ts +171 -37
  274. package/src/daemon/conversation-lifecycle.ts +53 -40
  275. package/src/daemon/conversation-messaging.ts +25 -6
  276. package/src/daemon/conversation-process.ts +49 -12
  277. package/src/daemon/conversation-runtime-assembly.ts +25 -1
  278. package/src/daemon/conversation-slash.ts +12 -5
  279. package/src/daemon/conversation-store.ts +11 -4
  280. package/src/daemon/conversation-tool-setup.ts +39 -7
  281. package/src/daemon/conversation.ts +33 -8
  282. package/src/daemon/date-context.ts +40 -0
  283. package/src/daemon/external-plugins-bootstrap.ts +217 -181
  284. package/src/daemon/first-greeting.ts +22 -2
  285. package/src/daemon/guardian-action-generators.ts +1 -125
  286. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
  287. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
  288. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
  289. package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
  290. package/src/daemon/handlers/config-a2a.ts +289 -0
  291. package/src/daemon/handlers/config-model.ts +6 -5
  292. package/src/daemon/handlers/config-slack-channel.ts +15 -3
  293. package/src/daemon/handlers/conversations.ts +1 -0
  294. package/src/daemon/handlers/shared.ts +14 -5
  295. package/src/daemon/handlers/skills.ts +111 -108
  296. package/src/daemon/history-repair.ts +28 -1
  297. package/src/daemon/host-app-control-proxy.ts +153 -27
  298. package/src/daemon/host-proxy-preactivation.ts +85 -18
  299. package/src/daemon/lifecycle.ts +89 -91
  300. package/src/daemon/meet-host-supervisor.ts +5 -4
  301. package/src/daemon/memory-v2-startup.ts +85 -0
  302. package/src/daemon/message-protocol.ts +1 -0
  303. package/src/daemon/message-types/conversations.ts +25 -0
  304. package/src/daemon/message-types/messages.ts +61 -0
  305. package/src/daemon/message-types/notifications.ts +21 -0
  306. package/src/daemon/message-types/subagents.ts +1 -0
  307. package/src/daemon/message-types/sync.ts +1 -0
  308. package/src/daemon/pkb-reminder-builder.test.ts +11 -54
  309. package/src/daemon/pkb-reminder-builder.ts +5 -20
  310. package/src/daemon/plugin-source-watcher.ts +146 -0
  311. package/src/daemon/process-message.ts +24 -3
  312. package/src/daemon/server.ts +11 -2
  313. package/src/daemon/skill-memory-refresh.ts +33 -0
  314. package/src/daemon/wake-target-adapter.ts +2 -0
  315. package/src/documents/document-store.ts +221 -3
  316. package/src/embedded/plugin-api.ts +40 -0
  317. package/src/export/__tests__/transcript-formatter.test.ts +121 -0
  318. package/src/export/transcript-formatter.ts +54 -20
  319. package/src/filing/filing-service.ts +39 -0
  320. package/src/heartbeat/__tests__/heartbeat-service.test.ts +135 -6
  321. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  322. package/src/heartbeat/heartbeat-service.ts +73 -189
  323. package/src/home/__tests__/feed-types.test.ts +80 -0
  324. package/src/home/feed-types.ts +36 -2
  325. package/src/home/post-connect-feed.ts +1 -0
  326. package/src/index.ts +18 -1
  327. package/src/ipc/cli-client.ts +147 -45
  328. package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
  329. package/src/mcp/client.ts +20 -4
  330. package/src/media/image-credentials.ts +3 -3
  331. package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
  332. package/src/memory/__tests__/conversation-queries.test.ts +483 -0
  333. package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
  334. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
  335. package/src/memory/__tests__/memory-retrospective-job.test.ts +87 -4
  336. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
  337. package/src/memory/__tests__/message-content.test.ts +35 -0
  338. package/src/memory/bookmark-crud.ts +42 -10
  339. package/src/memory/context-search/sources/conversations.ts +62 -2
  340. package/src/memory/context-search/sources/workspace.ts +4 -0
  341. package/src/memory/conversation-crud.ts +63 -19
  342. package/src/memory/conversation-queries.ts +197 -11
  343. package/src/memory/conversation-title-service.ts +26 -4
  344. package/src/memory/db-init.ts +12 -0
  345. package/src/memory/delivery-crud.ts +152 -5
  346. package/src/memory/embedding-backend.ts +4 -4
  347. package/src/memory/external-conversation-store.ts +66 -5
  348. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +150 -12
  349. package/src/memory/graph/conversation-graph-memory.ts +49 -21
  350. package/src/memory/graph/tools.ts +9 -40
  351. package/src/memory/indexer.ts +34 -29
  352. package/src/memory/invite-store.ts +53 -0
  353. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
  354. package/src/memory/jobs/embed-concept-page.ts +20 -11
  355. package/src/memory/jobs-worker.ts +6 -1
  356. package/src/memory/llm-request-log-source-clickhouse.ts +24 -12
  357. package/src/memory/llm-request-log-source.ts +19 -52
  358. package/src/memory/llm-request-log-store.ts +92 -1
  359. package/src/memory/llm-usage-store.ts +125 -5
  360. package/src/memory/memory-retrospective-enqueue.ts +1 -20
  361. package/src/memory/memory-retrospective-job.ts +33 -6
  362. package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
  363. package/src/memory/message-content.ts +1 -1
  364. package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
  365. package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
  366. package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
  367. package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
  368. package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
  369. package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
  370. package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
  371. package/src/memory/migrations/251-a2a-tasks.ts +49 -0
  372. package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
  373. package/src/memory/migrations/index.ts +9 -0
  374. package/src/memory/migrations/registry.ts +16 -0
  375. package/src/memory/onboarding-events-store.ts +106 -0
  376. package/src/memory/schema/a2a.ts +15 -0
  377. package/src/memory/schema/bookmarks.ts +0 -2
  378. package/src/memory/schema/calls.ts +1 -0
  379. package/src/memory/schema/index.ts +1 -0
  380. package/src/memory/schema/inference.ts +3 -3
  381. package/src/memory/schema/infrastructure.ts +13 -0
  382. package/src/memory/turn-events-store.ts +127 -2
  383. package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
  384. package/src/memory/v2/__tests__/activation.test.ts +0 -8
  385. package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
  386. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
  387. package/src/memory/v2/__tests__/injection.test.ts +288 -11
  388. package/src/memory/v2/__tests__/migration.test.ts +87 -0
  389. package/src/memory/v2/__tests__/page-index.test.ts +83 -0
  390. package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
  391. package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
  392. package/src/memory/v2/__tests__/router.test.ts +15 -0
  393. package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
  394. package/src/memory/v2/__tests__/static-context.test.ts +12 -1
  395. package/src/memory/v2/activation-store.ts +14 -16
  396. package/src/memory/v2/cli-command-content.ts +19 -0
  397. package/src/memory/v2/cli-command-store.ts +304 -0
  398. package/src/memory/v2/frontmatter-sweep.ts +7 -1
  399. package/src/memory/v2/injection.ts +81 -26
  400. package/src/memory/v2/migration.ts +49 -19
  401. package/src/memory/v2/page-index.ts +63 -8
  402. package/src/memory/v2/prompts/router.ts +11 -8
  403. package/src/memory/v2/prompts/sweep.ts +2 -2
  404. package/src/memory/v2/qdrant.ts +135 -7
  405. package/src/memory/v2/router.ts +9 -8
  406. package/src/memory/v2/skill-store.ts +120 -35
  407. package/src/memory/v2/static-context.ts +4 -4
  408. package/src/memory/v2/types.ts +23 -0
  409. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
  410. package/src/messaging/providers/a2a/deliver.ts +156 -0
  411. package/src/messaging/providers/gmail/client.ts +9 -2
  412. package/src/messaging/providers/index.ts +11 -2
  413. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
  414. package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
  415. package/src/messaging/providers/slack/adapter.ts +43 -5
  416. package/src/messaging/providers/slack/client.ts +27 -0
  417. package/src/messaging/providers/slack/deep-link.ts +65 -0
  418. package/src/messaging/providers/slack/download.ts +104 -0
  419. package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
  420. package/src/messaging/providers/slack/message-metadata.ts +27 -0
  421. package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
  422. package/src/messaging/providers/slack/render-transcript.ts +69 -5
  423. package/src/messaging/providers/slack/types.ts +20 -1
  424. package/src/notifications/__tests__/broadcaster.test.ts +203 -0
  425. package/src/notifications/__tests__/decision-engine.test.ts +283 -0
  426. package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
  427. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
  428. package/src/notifications/__tests__/home-feed-side-effect.test.ts +430 -7
  429. package/src/notifications/adapters/macos.ts +12 -2
  430. package/src/notifications/broadcaster.ts +29 -4
  431. package/src/notifications/conversation-pairing.ts +2 -1
  432. package/src/notifications/copy-composer.ts +17 -64
  433. package/src/notifications/decision-engine.ts +113 -45
  434. package/src/notifications/deterministic-checks.ts +96 -0
  435. package/src/notifications/emit-signal.ts +21 -1
  436. package/src/notifications/home-feed-side-effect.ts +138 -5
  437. package/src/notifications/signal.ts +3 -5
  438. package/src/notifications/types.ts +8 -0
  439. package/src/oauth/connection-resolver.ts +8 -4
  440. package/src/oauth/platform-connection.test.ts +43 -3
  441. package/src/oauth/platform-connection.ts +19 -6
  442. package/src/oauth/seed-providers.ts +10 -1
  443. package/src/permissions/checker.ts +2 -0
  444. package/src/permissions/ipc-risk-types.ts +1 -0
  445. package/src/permissions/question-prompter.test.ts +416 -0
  446. package/src/permissions/question-prompter.ts +294 -0
  447. package/src/platform/client.test.ts +1 -1
  448. package/src/platform/client.ts +1 -1
  449. package/src/plugin-api/constants.ts +26 -0
  450. package/src/plugin-api/index.ts +34 -1
  451. package/src/plugin-api/types.ts +104 -22
  452. package/src/plugins/defaults/circuit-breaker.ts +0 -5
  453. package/src/plugins/defaults/compaction.ts +0 -4
  454. package/src/plugins/defaults/empty-response.ts +0 -2
  455. package/src/plugins/defaults/history-repair.ts +0 -2
  456. package/src/plugins/defaults/injectors.ts +74 -22
  457. package/src/plugins/defaults/llm-call.ts +0 -2
  458. package/src/plugins/defaults/memory-retrieval.ts +0 -1
  459. package/src/plugins/defaults/overflow-reduce.ts +0 -1
  460. package/src/plugins/defaults/persistence.ts +0 -2
  461. package/src/plugins/defaults/title-generate.ts +0 -5
  462. package/src/plugins/defaults/token-estimate.ts +0 -2
  463. package/src/plugins/defaults/tool-error.ts +0 -7
  464. package/src/plugins/defaults/tool-execute.ts +0 -2
  465. package/src/plugins/defaults/tool-result-truncate.ts +0 -4
  466. package/src/plugins/ensure-plugin-api-shim.ts +96 -0
  467. package/src/plugins/external-api.ts +104 -0
  468. package/src/plugins/external-plugin-loader.ts +187 -42
  469. package/src/plugins/feature-gate.ts +22 -0
  470. package/src/plugins/pipeline.ts +37 -0
  471. package/src/plugins/registry.ts +48 -80
  472. package/src/plugins/types.ts +40 -26
  473. package/src/plugins/user-loader.ts +21 -2
  474. package/src/proactive-artifact/aux-message-injector.ts +11 -0
  475. package/src/proactive-artifact/job.test.ts +37 -5
  476. package/src/prompts/__tests__/system-prompt.test.ts +10 -43
  477. package/src/prompts/__tests__/task-progress-hint-section.test.ts +95 -0
  478. package/src/prompts/normalize-onboarding.ts +27 -0
  479. package/src/prompts/sections.ts +302 -0
  480. package/src/prompts/system-prompt.ts +63 -174
  481. package/src/prompts/templates/BOOTSTRAP.md +17 -1
  482. package/src/prompts/templates/system-sections.ts +164 -0
  483. package/src/providers/__tests__/inference.test.ts +24 -7
  484. package/src/providers/anthropic/client.ts +28 -28
  485. package/src/providers/call-site-routing.ts +24 -6
  486. package/src/providers/connection-resolution.ts +68 -11
  487. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
  488. package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
  489. package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
  490. package/src/providers/inference/adapter-factory.ts +32 -6
  491. package/src/providers/inference/auth.ts +12 -0
  492. package/src/providers/inference/backfill.ts +14 -1
  493. package/src/providers/inference/connections.ts +159 -34
  494. package/src/providers/inference/resolve-auth.ts +14 -4
  495. package/src/providers/model-catalog.ts +249 -12
  496. package/src/providers/model-intents.ts +3 -3
  497. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
  498. package/src/providers/openai/chat-completions-provider.ts +169 -8
  499. package/src/providers/openrouter/client.ts +49 -4
  500. package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -2
  501. package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
  502. package/src/providers/provider-availability.ts +17 -2
  503. package/src/providers/provider-catalog-visibility.ts +38 -0
  504. package/src/providers/provider-send-message.ts +27 -12
  505. package/src/providers/registry.ts +52 -15
  506. package/src/providers/retry.ts +47 -1
  507. package/src/runtime/__tests__/agent-wake.test.ts +152 -0
  508. package/src/runtime/agent-wake.ts +103 -15
  509. package/src/runtime/auth/route-policy.ts +21 -1
  510. package/src/runtime/btw-sidechain.ts +2 -0
  511. package/src/runtime/http-server.ts +7 -16
  512. package/src/runtime/http-types.ts +19 -47
  513. package/src/runtime/migrations/origin-mode.ts +1 -1
  514. package/src/runtime/pending-interactions.ts +1 -0
  515. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
  516. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
  517. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
  518. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +172 -23
  519. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
  520. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
  521. package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
  522. package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
  523. package/src/runtime/routes/acp-routes-list.test.ts +143 -0
  524. package/src/runtime/routes/acp-routes.ts +5 -3
  525. package/src/runtime/routes/auth-routes.ts +1 -1
  526. package/src/runtime/routes/bookmark-routes.ts +5 -3
  527. package/src/runtime/routes/btw-routes.ts +5 -1
  528. package/src/runtime/routes/channel-availability-routes.ts +126 -0
  529. package/src/runtime/routes/consolidation-routes.ts +100 -0
  530. package/src/runtime/routes/conversation-cli-routes.ts +44 -3
  531. package/src/runtime/routes/conversation-list-routes.ts +3 -20
  532. package/src/runtime/routes/conversation-management-routes.ts +17 -42
  533. package/src/runtime/routes/conversation-query-routes.ts +99 -35
  534. package/src/runtime/routes/conversation-routes.ts +97 -11
  535. package/src/runtime/routes/documents-routes.ts +25 -86
  536. package/src/runtime/routes/group-routes.ts +5 -0
  537. package/src/runtime/routes/inbound-conversation.ts +28 -8
  538. package/src/runtime/routes/inbound-message-handler.ts +236 -41
  539. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
  540. package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
  541. package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
  542. package/src/runtime/routes/index.ts +8 -0
  543. package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
  544. package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
  545. package/src/runtime/routes/inference-provider-connection-routes.ts +199 -22
  546. package/src/runtime/routes/integrations/a2a.ts +235 -0
  547. package/src/runtime/routes/integrations/slack/share.ts +4 -52
  548. package/src/runtime/routes/integrations/slack/token.ts +43 -0
  549. package/src/runtime/routes/integrations/twilio.ts +6 -13
  550. package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
  551. package/src/runtime/routes/notification-routes.ts +1 -1
  552. package/src/runtime/routes/oauth-commands-routes.ts +105 -15
  553. package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
  554. package/src/runtime/routes/question-routes.ts +259 -0
  555. package/src/runtime/routes/rename-conversation-routes.ts +2 -33
  556. package/src/runtime/routes/schedule-routes.ts +4 -7
  557. package/src/runtime/routes/subagents-routes.ts +98 -18
  558. package/src/runtime/routes/telemetry-routes.ts +27 -0
  559. package/src/runtime/routes/tts-routes.ts +27 -2
  560. package/src/runtime/routes/workspace-routes.test.ts +43 -0
  561. package/src/runtime/routes/workspace-routes.ts +28 -0
  562. package/src/runtime/services/conversation-serializer.ts +39 -7
  563. package/src/runtime/sync/resource-sync-events.ts +93 -1
  564. package/src/schedule/schedule-store.ts +27 -2
  565. package/src/schedule/scheduler.ts +9 -1
  566. package/src/security/__tests__/untrusted-content.test.ts +86 -0
  567. package/src/security/untrusted-content.ts +93 -8
  568. package/src/skills/catalog-files.ts +1 -1
  569. package/src/skills/catalog-install.ts +233 -116
  570. package/src/skills/clawhub.ts +70 -13
  571. package/src/skills/managed-store.ts +4 -119
  572. package/src/skills/skillssh-registry.ts +27 -48
  573. package/src/subagent/manager.ts +17 -7
  574. package/src/telemetry/types.ts +113 -1
  575. package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
  576. package/src/telemetry/usage-telemetry-reporter.ts +113 -7
  577. package/src/tools/apps/executors.ts +58 -7
  578. package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
  579. package/src/tools/ask-question/ask-question-tool.ts +304 -0
  580. package/src/tools/browser/browser-execution.ts +15 -11
  581. package/src/tools/computer-use/definitions.ts +3 -3
  582. package/src/tools/credentials/vault.ts +1 -1
  583. package/src/tools/document/document-tool.ts +124 -1
  584. package/src/tools/filesystem/edit.ts +1 -1
  585. package/src/tools/filesystem/list.ts +1 -1
  586. package/src/tools/filesystem/read.ts +1 -1
  587. package/src/tools/filesystem/write.ts +5 -2
  588. package/src/tools/host-filesystem/transfer.ts +1 -1
  589. package/src/tools/host-terminal/host-shell.ts +1 -1
  590. package/src/tools/memory/register.ts +1 -9
  591. package/src/tools/permission-checker.ts +1 -1
  592. package/src/tools/registry.ts +17 -7
  593. package/src/tools/schedule/create.ts +2 -2
  594. package/src/tools/schema-transforms.ts +7 -2
  595. package/src/tools/side-effects.ts +1 -0
  596. package/src/tools/skills/delete-managed.ts +4 -4
  597. package/src/tools/skills/execute.ts +1 -1
  598. package/src/tools/skills/scaffold-managed.ts +3 -2
  599. package/src/tools/subagent/notify-parent.ts +1 -1
  600. package/src/tools/system/request-permission.ts +2 -2
  601. package/src/tools/terminal/safe-env.ts +60 -1
  602. package/src/tools/tool-manifest.ts +2 -0
  603. package/src/tools/types.ts +107 -21
  604. package/src/tools/ui-surface/definitions.ts +6 -5
  605. package/src/tts/__tests__/provider-adapters.test.ts +76 -2
  606. package/src/tts/providers/elevenlabs-provider.ts +75 -1
  607. package/src/types/onboarding-context.ts +2 -0
  608. package/src/util/errors.ts +17 -0
  609. package/src/util/platform.ts +10 -0
  610. package/src/watcher/__tests__/engine.test.ts +22 -0
  611. package/src/watcher/engine.ts +6 -2
  612. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
  613. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
  614. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
  615. package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
  616. package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
  617. package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
  618. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
  619. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
  620. package/src/workspace/migrations/registry.ts +10 -0
  621. package/src/workspace/migrations/runner.ts +39 -9
  622. package/src/workspace/migrations/types.ts +4 -0
  623. package/examples/plugins/echo/bun.lock +0 -25
  624. package/src/__tests__/context-window-manager.test.ts +0 -2481
  625. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
  626. package/src/context/__tests__/compact-prompt.test.ts +0 -63
  627. package/src/context/prompts/compact.md +0 -26
  628. package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
  629. package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
  630. package/src/runtime/guardian-action-conversation-turn.ts +0 -99
@@ -27,10 +27,8 @@ interface RegisteredInteraction {
27
27
  const registeredInteractions: RegisteredInteraction[] = [];
28
28
 
29
29
  mock.module("../runtime/pending-interactions.js", () => ({
30
- register: (
31
- _requestId: string,
32
- entry: RegisteredInteraction,
33
- ) => registeredInteractions.push(entry),
30
+ register: (_requestId: string, entry: RegisteredInteraction) =>
31
+ registeredInteractions.push(entry),
34
32
  resolve: (requestId: string) => {
35
33
  resolvedInteractionIds.push(requestId);
36
34
  return undefined;
@@ -44,6 +42,7 @@ mock.module("../runtime/pending-interactions.js", () => ({
44
42
  const {
45
43
  HostAppControlProxy,
46
44
  _getActiveAppControlSession,
45
+ _getConfirmedAppControlSession,
47
46
  _resetActiveAppControlSession,
48
47
  _setActiveAppControlSession,
49
48
  } = await import("../daemon/host-app-control-proxy.js");
@@ -594,6 +593,504 @@ describe("HostAppControlProxy", () => {
594
593
  });
595
594
  });
596
595
 
596
+ // -------------------------------------------------------------------------
597
+ // (c.1) Failed re-start restores the prior session
598
+ // -------------------------------------------------------------------------
599
+
600
+ describe("failed re-start restores prior session", () => {
601
+ test("non-running re-start in the same conversation restores the prior session", async () => {
602
+ const proxy = new HostAppControlProxy("conv-1");
603
+ const ctrl = new AbortController();
604
+
605
+ // Establish an active session targeting the editor.
606
+ const p1 = proxy.request(
607
+ "app_control_start",
608
+ { tool: "start", app: "com.example.editor" },
609
+ "conv-1",
610
+ ctrl.signal,
611
+ );
612
+ proxy.resolve(
613
+ (sentMessages[0] as Record<string, unknown>).requestId as string,
614
+ payload({ pngBase64: PNG_A }),
615
+ );
616
+ await p1;
617
+ expect(_getActiveAppControlSession()?.app).toBe("com.example.editor");
618
+
619
+ // Re-start against a different app — host returns "missing".
620
+ sentMessages.length = 0;
621
+ const p2 = proxy.request(
622
+ "app_control_start",
623
+ { tool: "start", app: "com.example.other" },
624
+ "conv-1",
625
+ ctrl.signal,
626
+ );
627
+ proxy.resolve(
628
+ (sentMessages[0] as Record<string, unknown>).requestId as string,
629
+ payload({ state: "missing" }),
630
+ );
631
+ await p2;
632
+
633
+ // Prior session restored (editor) — not stranded as undefined and not
634
+ // overwritten with the failed re-start target.
635
+ const session = _getActiveAppControlSession();
636
+ expect(session?.conversationId).toBe("conv-1");
637
+ expect(session?.app).toBe("com.example.editor");
638
+
639
+ proxy.dispose();
640
+ });
641
+
642
+ test("dispatch failure on re-start in the same conversation restores the prior session", async () => {
643
+ const proxy = new HostAppControlProxy("conv-1");
644
+ const ctrl = new AbortController();
645
+
646
+ const p1 = proxy.request(
647
+ "app_control_start",
648
+ { tool: "start", app: "com.example.editor" },
649
+ "conv-1",
650
+ ctrl.signal,
651
+ );
652
+ proxy.resolve(
653
+ (sentMessages[0] as Record<string, unknown>).requestId as string,
654
+ payload({ pngBase64: PNG_A }),
655
+ );
656
+ await p1;
657
+ expect(_getActiveAppControlSession()?.app).toBe("com.example.editor");
658
+
659
+ // Re-start against a different app, then abort before the host
660
+ // responds. The catch path in `request()` should restore the prior
661
+ // session rather than stranding the lock.
662
+ sentMessages.length = 0;
663
+ const reCtrl = new AbortController();
664
+ const p2 = proxy.request(
665
+ "app_control_start",
666
+ { tool: "start", app: "com.example.other" },
667
+ "conv-1",
668
+ reCtrl.signal,
669
+ );
670
+ reCtrl.abort();
671
+ const r = await p2;
672
+ expect(r.isError).toBe(true);
673
+ expect(r.content).toContain("Aborted");
674
+
675
+ const session = _getActiveAppControlSession();
676
+ expect(session?.conversationId).toBe("conv-1");
677
+ expect(session?.app).toBe("com.example.editor");
678
+
679
+ proxy.dispose();
680
+ });
681
+
682
+ test("late-failing start does not clobber a newer successful start (out-of-order rollback)", async () => {
683
+ // Overlapping starts from the same conversation where the older one
684
+ // fails AFTER the newer succeeds. Identity-keyed rollback must make
685
+ // the stale failure a no-op rather than restoring the pre-A session.
686
+ const proxy = new HostAppControlProxy("conv-1");
687
+ const ctrl = new AbortController();
688
+
689
+ // Establish prior session A.
690
+ const pA = proxy.request(
691
+ "app_control_start",
692
+ { tool: "start", app: "com.example.a" },
693
+ "conv-1",
694
+ ctrl.signal,
695
+ );
696
+ const reqIdA = (sentMessages[0] as Record<string, unknown>)
697
+ .requestId as string;
698
+ proxy.resolve(reqIdA, payload({ pngBase64: PNG_A }));
699
+ await pA;
700
+ expect(_getActiveAppControlSession()?.app).toBe("com.example.a");
701
+
702
+ // Start B is dispatched but its host response is delayed.
703
+ sentMessages.length = 0;
704
+ const pB = proxy.request(
705
+ "app_control_start",
706
+ { tool: "start", app: "com.example.b" },
707
+ "conv-1",
708
+ ctrl.signal,
709
+ );
710
+ const reqIdB = (sentMessages[0] as Record<string, unknown>)
711
+ .requestId as string;
712
+
713
+ // Start C overtakes B and succeeds first.
714
+ sentMessages.length = 0;
715
+ const pC = proxy.request(
716
+ "app_control_start",
717
+ { tool: "start", app: "com.example.c" },
718
+ "conv-1",
719
+ ctrl.signal,
720
+ );
721
+ const reqIdC = (sentMessages[0] as Record<string, unknown>)
722
+ .requestId as string;
723
+ proxy.resolve(reqIdC, payload({ pngBase64: PNG_A }));
724
+ await pC;
725
+ expect(_getActiveAppControlSession()?.app).toBe("com.example.c");
726
+
727
+ // Now B finally fails — rollback must NOT restore A or clobber C.
728
+ proxy.resolve(reqIdB, payload({ state: "missing" }));
729
+ await pB;
730
+
731
+ const session = _getActiveAppControlSession();
732
+ expect(session?.conversationId).toBe("conv-1");
733
+ expect(session?.app).toBe("com.example.c");
734
+
735
+ proxy.dispose();
736
+ });
737
+
738
+ test("both-fail overlapping starts release the lock (no phantom session)", async () => {
739
+ // Two same-conversation starts overlap, both fail. The later
740
+ // rollback must release the lock instead of resurrecting the
741
+ // earlier (never-confirmed) optimistic write.
742
+ const proxy = new HostAppControlProxy("conv-1");
743
+ const ctrl = new AbortController();
744
+
745
+ const pA = proxy.request(
746
+ "app_control_start",
747
+ { tool: "start", app: "com.example.a" },
748
+ "conv-1",
749
+ ctrl.signal,
750
+ );
751
+ const reqIdA = (sentMessages[0] as Record<string, unknown>)
752
+ .requestId as string;
753
+
754
+ sentMessages.length = 0;
755
+ const pC = proxy.request(
756
+ "app_control_start",
757
+ { tool: "start", app: "com.example.c" },
758
+ "conv-1",
759
+ ctrl.signal,
760
+ );
761
+ const reqIdC = (sentMessages[0] as Record<string, unknown>)
762
+ .requestId as string;
763
+ expect(_getActiveAppControlSession()?.app).toBe("com.example.c");
764
+
765
+ // A fails first — identity check makes it a no-op (C is live).
766
+ proxy.resolve(reqIdA, payload({ state: "missing" }));
767
+ await pA;
768
+ expect(_getActiveAppControlSession()?.app).toBe("com.example.c");
769
+
770
+ // C fails second — must roll back to confirmed (undefined), not
771
+ // to A's never-confirmed optimistic write.
772
+ proxy.resolve(reqIdC, payload({ state: "missing" }));
773
+ await pC;
774
+
775
+ expect(_getActiveAppControlSession()).toBeUndefined();
776
+
777
+ proxy.dispose();
778
+ });
779
+
780
+ test("late running confirmation from older overlapping start is preserved", async () => {
781
+ // Same-conversation overlapping starts where the older one's `running`
782
+ // response arrives AFTER a newer optimistic start has superseded it,
783
+ // and the newer one then fails. The host has actually confirmed A as
784
+ // running, so the lock must remain held for A rather than going
785
+ // undefined and desyncing from the host.
786
+ const proxy = new HostAppControlProxy("conv-1");
787
+ const ctrl = new AbortController();
788
+
789
+ // A is dispatched; do not resolve yet.
790
+ const pA = proxy.request(
791
+ "app_control_start",
792
+ { tool: "start", app: "com.example.a" },
793
+ "conv-1",
794
+ ctrl.signal,
795
+ );
796
+ const reqIdA = (sentMessages[0] as Record<string, unknown>)
797
+ .requestId as string;
798
+
799
+ // C is dispatched, overwriting the optimistic active pointer to C.
800
+ sentMessages.length = 0;
801
+ const pC = proxy.request(
802
+ "app_control_start",
803
+ { tool: "start", app: "com.example.c" },
804
+ "conv-1",
805
+ ctrl.signal,
806
+ );
807
+ const reqIdC = (sentMessages[0] as Record<string, unknown>)
808
+ .requestId as string;
809
+ expect(_getActiveAppControlSession()?.app).toBe("com.example.c");
810
+
811
+ // A finally returns running — by object identity active is no longer A,
812
+ // but the host has confirmed A so we must still record it as confirmed.
813
+ proxy.resolve(reqIdA, payload({ pngBase64: PNG_A }));
814
+ await pA;
815
+ // Active is still the newer optimistic C; nothing has rolled it back.
816
+ expect(_getActiveAppControlSession()?.app).toBe("com.example.c");
817
+
818
+ // C fails — rollback must restore active to A (the current confirmed),
819
+ // not to undefined (the snapshot prior at C's dispatch time).
820
+ proxy.resolve(reqIdC, payload({ state: "missing" }));
821
+ await pC;
822
+
823
+ const session = _getActiveAppControlSession();
824
+ expect(session?.conversationId).toBe("conv-1");
825
+ expect(session?.app).toBe("com.example.a");
826
+
827
+ proxy.dispose();
828
+ });
829
+
830
+ test("both-succeed older-arrives-last does not overwrite newer confirmed session", async () => {
831
+ // Two same-conversation starts both succeed, but the older one's
832
+ // `running` response arrives AFTER the newer one's. With a
833
+ // conversation-only ownership guard, the older response would
834
+ // overwrite the newer confirmed session — a latent bug that surfaces
835
+ // on a subsequent rollback. Verify the confirmed pointer stays on the
836
+ // newer session (C), and that a later rollback restores to C, not A.
837
+ const proxy = new HostAppControlProxy("conv-1");
838
+ const ctrl = new AbortController();
839
+
840
+ const pA = proxy.request(
841
+ "app_control_start",
842
+ { tool: "start", app: "com.example.a" },
843
+ "conv-1",
844
+ ctrl.signal,
845
+ );
846
+ const reqIdA = (sentMessages[0] as Record<string, unknown>)
847
+ .requestId as string;
848
+
849
+ sentMessages.length = 0;
850
+ const pC = proxy.request(
851
+ "app_control_start",
852
+ { tool: "start", app: "com.example.c" },
853
+ "conv-1",
854
+ ctrl.signal,
855
+ );
856
+ const reqIdC = (sentMessages[0] as Record<string, unknown>)
857
+ .requestId as string;
858
+ expect(_getActiveAppControlSession()?.app).toBe("com.example.c");
859
+
860
+ // C succeeds first → active = C, confirmed = C.
861
+ proxy.resolve(reqIdC, payload({ pngBase64: PNG_A }));
862
+ await pC;
863
+ expect(_getActiveAppControlSession()?.app).toBe("com.example.c");
864
+ expect(_getConfirmedAppControlSession()?.app).toBe("com.example.c");
865
+
866
+ // A succeeds second (older arrives last). The conversation-ownership
867
+ // check passes for A, but A is no longer the live optimistic write
868
+ // and a session is already confirmed — must NOT overwrite confirmed.
869
+ proxy.resolve(reqIdA, payload({ pngBase64: PNG_B }));
870
+ await pA;
871
+ expect(_getConfirmedAppControlSession()?.app).toBe("com.example.c");
872
+ expect(_getActiveAppControlSession()?.app).toBe("com.example.c");
873
+
874
+ // A later restart D that fails must roll back to C (the true
875
+ // confirmed), not to A — guards against the latent-bug case.
876
+ sentMessages.length = 0;
877
+ const pD = proxy.request(
878
+ "app_control_start",
879
+ { tool: "start", app: "com.example.d" },
880
+ "conv-1",
881
+ ctrl.signal,
882
+ );
883
+ const reqIdD = (sentMessages[0] as Record<string, unknown>)
884
+ .requestId as string;
885
+ proxy.resolve(reqIdD, payload({ state: "missing" }));
886
+ await pD;
887
+
888
+ const session = _getActiveAppControlSession();
889
+ expect(session?.conversationId).toBe("conv-1");
890
+ expect(session?.app).toBe("com.example.c");
891
+
892
+ proxy.dispose();
893
+ });
894
+
895
+ test("rollback after confirmed-A,dispatch-B,dispatch-C,B-confirms,C-fails restores B (Codex P2)", async () => {
896
+ // A is confirmed, then B and C are dispatched. B's `running` arrives
897
+ // while C is the live write; under the dispatch-counter rule that
898
+ // confirmation must promote because B is newer than A. A subsequent
899
+ // failure of C must then roll back to B, not back to A.
900
+ const proxy = new HostAppControlProxy("conv-1");
901
+ const ctrl = new AbortController();
902
+
903
+ const pA = proxy.request(
904
+ "app_control_start",
905
+ { tool: "start", app: "com.example.a" },
906
+ "conv-1",
907
+ ctrl.signal,
908
+ );
909
+ const reqIdA = (sentMessages[0] as Record<string, unknown>)
910
+ .requestId as string;
911
+ proxy.resolve(reqIdA, payload({ pngBase64: PNG_A }));
912
+ await pA;
913
+ expect(_getConfirmedAppControlSession()?.app).toBe("com.example.a");
914
+
915
+ sentMessages.length = 0;
916
+ const pB = proxy.request(
917
+ "app_control_start",
918
+ { tool: "start", app: "com.example.b" },
919
+ "conv-1",
920
+ ctrl.signal,
921
+ );
922
+ const reqIdB = (sentMessages[0] as Record<string, unknown>)
923
+ .requestId as string;
924
+
925
+ sentMessages.length = 0;
926
+ const pC = proxy.request(
927
+ "app_control_start",
928
+ { tool: "start", app: "com.example.c" },
929
+ "conv-1",
930
+ ctrl.signal,
931
+ );
932
+ const reqIdC = (sentMessages[0] as Record<string, unknown>)
933
+ .requestId as string;
934
+ expect(_getActiveAppControlSession()?.app).toBe("com.example.c");
935
+
936
+ // B confirms (newer than A): confirmed should move to B even though
937
+ // C is the live optimistic write.
938
+ proxy.resolve(reqIdB, payload({ pngBase64: PNG_B }));
939
+ await pB;
940
+ expect(_getConfirmedAppControlSession()?.app).toBe("com.example.b");
941
+ expect(_getActiveAppControlSession()?.app).toBe("com.example.c");
942
+
943
+ // C fails → rollback restores to confirmed (B), not A.
944
+ proxy.resolve(reqIdC, payload({ state: "missing" }));
945
+ await pC;
946
+ const session = _getActiveAppControlSession();
947
+ expect(session?.conversationId).toBe("conv-1");
948
+ expect(session?.app).toBe("com.example.b");
949
+
950
+ proxy.dispose();
951
+ });
952
+
953
+ test("three-overlapping-starts A-confirms,B-confirms,C-fails restores B (Devin P2)", async () => {
954
+ // A, B, C dispatched in order with no prior confirmed session.
955
+ // A confirms first (confirmed = A), B confirms second (confirmed
956
+ // must move forward to B because B is newer), C fails → rollback
957
+ // restores to B, not A.
958
+ const proxy = new HostAppControlProxy("conv-1");
959
+ const ctrl = new AbortController();
960
+
961
+ const pA = proxy.request(
962
+ "app_control_start",
963
+ { tool: "start", app: "com.example.a" },
964
+ "conv-1",
965
+ ctrl.signal,
966
+ );
967
+ const reqIdA = (sentMessages[0] as Record<string, unknown>)
968
+ .requestId as string;
969
+
970
+ sentMessages.length = 0;
971
+ const pB = proxy.request(
972
+ "app_control_start",
973
+ { tool: "start", app: "com.example.b" },
974
+ "conv-1",
975
+ ctrl.signal,
976
+ );
977
+ const reqIdB = (sentMessages[0] as Record<string, unknown>)
978
+ .requestId as string;
979
+
980
+ sentMessages.length = 0;
981
+ const pC = proxy.request(
982
+ "app_control_start",
983
+ { tool: "start", app: "com.example.c" },
984
+ "conv-1",
985
+ ctrl.signal,
986
+ );
987
+ const reqIdC = (sentMessages[0] as Record<string, unknown>)
988
+ .requestId as string;
989
+ expect(_getActiveAppControlSession()?.app).toBe("com.example.c");
990
+
991
+ proxy.resolve(reqIdA, payload({ pngBase64: PNG_A }));
992
+ await pA;
993
+ expect(_getConfirmedAppControlSession()?.app).toBe("com.example.a");
994
+
995
+ proxy.resolve(reqIdB, payload({ pngBase64: PNG_B }));
996
+ await pB;
997
+ expect(_getConfirmedAppControlSession()?.app).toBe("com.example.b");
998
+ expect(_getActiveAppControlSession()?.app).toBe("com.example.c");
999
+
1000
+ proxy.resolve(reqIdC, payload({ state: "missing" }));
1001
+ await pC;
1002
+ const session = _getActiveAppControlSession();
1003
+ expect(session?.conversationId).toBe("conv-1");
1004
+ expect(session?.app).toBe("com.example.b");
1005
+
1006
+ proxy.dispose();
1007
+ });
1008
+
1009
+ test("confirmed-A,dispatch-B,dispatch-C,C-fails-first,B-confirms syncs active to B", async () => {
1010
+ // A confirmed, B and C dispatched, C returns `missing` first →
1011
+ // rollback restores active to A. Then B returns `running` →
1012
+ // confirmed advances to B; active must advance alongside it,
1013
+ // otherwise the tool reports B started while
1014
+ // app_control_observe/actions for B target A.
1015
+ const proxy = new HostAppControlProxy("conv-1");
1016
+ const ctrl = new AbortController();
1017
+
1018
+ const pA = proxy.request(
1019
+ "app_control_start",
1020
+ { tool: "start", app: "com.example.a" },
1021
+ "conv-1",
1022
+ ctrl.signal,
1023
+ );
1024
+ const reqIdA = (sentMessages[0] as Record<string, unknown>)
1025
+ .requestId as string;
1026
+ proxy.resolve(reqIdA, payload({ pngBase64: PNG_A }));
1027
+ await pA;
1028
+ expect(_getConfirmedAppControlSession()?.app).toBe("com.example.a");
1029
+
1030
+ sentMessages.length = 0;
1031
+ const pB = proxy.request(
1032
+ "app_control_start",
1033
+ { tool: "start", app: "com.example.b" },
1034
+ "conv-1",
1035
+ ctrl.signal,
1036
+ );
1037
+ const reqIdB = (sentMessages[0] as Record<string, unknown>)
1038
+ .requestId as string;
1039
+
1040
+ sentMessages.length = 0;
1041
+ const pC = proxy.request(
1042
+ "app_control_start",
1043
+ { tool: "start", app: "com.example.c" },
1044
+ "conv-1",
1045
+ ctrl.signal,
1046
+ );
1047
+ const reqIdC = (sentMessages[0] as Record<string, unknown>)
1048
+ .requestId as string;
1049
+ expect(_getActiveAppControlSession()?.app).toBe("com.example.c");
1050
+
1051
+ // C fails first → rollback restores active to confirmed (A).
1052
+ proxy.resolve(reqIdC, payload({ state: "missing" }));
1053
+ await pC;
1054
+ expect(_getActiveAppControlSession()?.app).toBe("com.example.a");
1055
+ expect(_getConfirmedAppControlSession()?.app).toBe("com.example.a");
1056
+
1057
+ // B confirms after C's rollback. Confirmed advances to B AND active
1058
+ // must advance to B too — otherwise non-start tool calls against B
1059
+ // would be rejected because active still targets A.
1060
+ proxy.resolve(reqIdB, payload({ pngBase64: PNG_B }));
1061
+ await pB;
1062
+ expect(_getConfirmedAppControlSession()?.app).toBe("com.example.b");
1063
+ const session = _getActiveAppControlSession();
1064
+ expect(session?.conversationId).toBe("conv-1");
1065
+ expect(session?.app).toBe("com.example.b");
1066
+
1067
+ proxy.dispose();
1068
+ });
1069
+
1070
+ test("first-start failure releases the lock (no prior session to restore)", async () => {
1071
+ const proxy = new HostAppControlProxy("conv-1");
1072
+ const ctrl = new AbortController();
1073
+
1074
+ // No prior session; re-start the first time and get a non-running.
1075
+ const p1 = proxy.request(
1076
+ "app_control_start",
1077
+ { tool: "start", app: "com.example.editor" },
1078
+ "conv-1",
1079
+ ctrl.signal,
1080
+ );
1081
+ proxy.resolve(
1082
+ (sentMessages[0] as Record<string, unknown>).requestId as string,
1083
+ payload({ state: "missing" }),
1084
+ );
1085
+ await p1;
1086
+
1087
+ // Lock released so another conversation can acquire.
1088
+ expect(_getActiveAppControlSession()).toBeUndefined();
1089
+
1090
+ proxy.dispose();
1091
+ });
1092
+ });
1093
+
597
1094
  // -------------------------------------------------------------------------
598
1095
  // (d) dispose releases the lock
599
1096
  // -------------------------------------------------------------------------
@@ -805,8 +1302,8 @@ describe("HostAppControlProxy", () => {
805
1302
  { tool: "observe", app: "com.example.app" },
806
1303
  "conv-1",
807
1304
  ctrl.signal,
808
- "actor-principal-1", // sourceActorPrincipalId
809
- "client-A", // targetClientId
1305
+ "actor-principal-1", // sourceActorPrincipalId
1306
+ "client-A", // targetClientId
810
1307
  );
811
1308
 
812
1309
  expect(sentMessages).toHaveLength(1);
@@ -840,8 +1337,8 @@ describe("HostAppControlProxy", () => {
840
1337
  { tool: "observe", app: "com.example.app" },
841
1338
  "conv-1",
842
1339
  ctrl.signal,
843
- "user-1", // sourceActorPrincipalId
844
- "client-A", // targetClientId → hub resolves actorPrincipalId = "user-1"
1340
+ "user-1", // sourceActorPrincipalId
1341
+ "client-A", // targetClientId → hub resolves actorPrincipalId = "user-1"
845
1342
  );
846
1343
 
847
1344
  const sent = sentMessages[0] as Record<string, unknown>;
@@ -863,8 +1360,8 @@ describe("HostAppControlProxy", () => {
863
1360
  { tool: "start", app: "com.example.app" },
864
1361
  "conv-1",
865
1362
  ctrl.signal,
866
- "user-1", // sourceActorPrincipalId
867
- undefined, // no targetClientId
1363
+ "user-1", // sourceActorPrincipalId
1364
+ undefined, // no targetClientId
868
1365
  );
869
1366
 
870
1367
  const sent = sentMessages[0] as Record<string, unknown>;