@vellumai/assistant 0.12.1-staging.1 → 0.12.1

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 (404) hide show
  1. package/AGENTS.md +0 -4
  2. package/ARCHITECTURE.md +12 -11
  3. package/docs/architecture/turn-actor.md +8 -15
  4. package/openapi.yaml +1 -291
  5. package/package.json +1 -1
  6. package/src/__tests__/always-loaded-tools-guard.test.ts +10 -18
  7. package/src/__tests__/app-compiler.test.ts +0 -6
  8. package/src/__tests__/app-control-tool-schemas.test.ts +2 -5
  9. package/src/__tests__/app-dir-path-guard.test.ts +3 -1
  10. package/src/__tests__/app-source-watcher.test.ts +218 -0
  11. package/src/{calls/__tests__ → __tests__}/call-controller.test.ts +82 -503
  12. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +2 -5
  13. package/src/__tests__/computer-use-tools.test.ts +5 -17
  14. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  15. package/src/__tests__/config-schema.test.ts +29 -28
  16. package/src/__tests__/config-watcher-skill-reseed.test.ts +359 -0
  17. package/src/__tests__/config-watcher.test.ts +8 -23
  18. package/src/__tests__/connection-model-compat.test.ts +2 -2
  19. package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
  20. package/src/__tests__/conversation-pairing.test.ts +0 -26
  21. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +10 -4
  22. package/src/__tests__/conversation-queue.test.ts +207 -48
  23. package/src/__tests__/conversation-retry-discard.test.ts +1 -54
  24. package/src/__tests__/conversation-skill-tools.test.ts +0 -27
  25. package/src/__tests__/conversation-surfaces-app-control.test.ts +3 -8
  26. package/src/__tests__/conversation-surfaces-task-progress.test.ts +0 -67
  27. package/src/__tests__/conversation-title-service.test.ts +10 -14
  28. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +5 -3
  29. package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -51
  30. package/src/__tests__/credential-record-retire.test.ts +1 -171
  31. package/src/__tests__/credential-routes.test.ts +3 -212
  32. package/src/__tests__/credential-security-invariants.test.ts +1 -2
  33. package/src/__tests__/cu-unified-flow.test.ts +2 -8
  34. package/src/__tests__/drain-kick-guard.test.ts +2 -0
  35. package/src/__tests__/drain-requeue-on-contention.test.ts +6 -0
  36. package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
  37. package/src/__tests__/host-bash-proxy.test.ts +45 -26
  38. package/src/__tests__/host-browser-proxy.test.ts +17 -6
  39. package/src/__tests__/host-file-proxy-targeted.test.ts +0 -23
  40. package/src/__tests__/host-file-proxy.test.ts +23 -11
  41. package/src/__tests__/host-transfer-proxy-targeted.test.ts +9 -30
  42. package/src/__tests__/llm-catalog-parity.test.ts +0 -25
  43. package/src/__tests__/mcp-auth-routes.test.ts +9 -6
  44. package/src/__tests__/mcp-config-secret-boundary.test.ts +136 -78
  45. package/src/__tests__/mcp-health-check.test.ts +11 -8
  46. package/src/__tests__/mcp-list-plugin-servers.test.ts +10 -8
  47. package/src/{calls/__tests__ → __tests__}/media-stream-audio-transcode.test.ts +1 -1
  48. package/src/{calls/__tests__ → __tests__}/media-stream-output.test.ts +8 -37
  49. package/src/{calls/__tests__ → __tests__}/media-stream-parser.test.ts +1 -1
  50. package/src/{calls/__tests__ → __tests__}/media-stream-server-integration.test.ts +57 -150
  51. package/src/{calls/__tests__ → __tests__}/media-stream-stt-session.test.ts +5 -5
  52. package/src/{calls/__tests__ → __tests__}/media-turn-detector.test.ts +1 -1
  53. package/src/__tests__/messages-after-tiebreaker.test.ts +0 -91
  54. package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
  55. package/src/__tests__/messaging-send-tool.test.ts +548 -355
  56. package/src/__tests__/model-intents.test.ts +0 -3
  57. package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
  58. package/src/__tests__/platform-client-verify-credential.test.ts +0 -2
  59. package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -13
  60. package/src/__tests__/script-proxy-certs.test.ts +0 -35
  61. package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
  62. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  63. package/src/__tests__/speaker-identification.test.ts +56 -0
  64. package/src/__tests__/subagent-tool-gate-mode.test.ts +3 -19
  65. package/src/__tests__/subagent-tools.test.ts +2 -3
  66. package/src/__tests__/telephony-tts-guard.test.ts +3 -38
  67. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +0 -4
  68. package/src/{calls/__tests__/voice-session-bridge-turn-flow.test.ts → __tests__/voice-session-bridge.test.ts} +12 -20
  69. package/src/__tests__/watch-retro-tool-availability.test.ts +17 -8
  70. package/src/__tests__/workspace-git-service.test.ts +0 -26
  71. package/src/acp/__tests__/agent-process.test.ts +4 -258
  72. package/src/acp/__tests__/auth-required.test.ts +0 -96
  73. package/src/acp/__tests__/client-handler.test.ts +2 -90
  74. package/src/acp/__tests__/session-manager-persistence.test.ts +1 -5
  75. package/src/acp/__tests__/session-manager-resume.test.ts +6 -270
  76. package/src/acp/__tests__/session-manager-usage.test.ts +2 -4
  77. package/src/acp/__tests__/session-manager.test.ts +4 -895
  78. package/src/acp/agent-process.test.ts +1 -4
  79. package/src/acp/agent-process.ts +9 -94
  80. package/src/acp/auth-required.ts +1 -50
  81. package/src/acp/auto-install.test.ts +7 -9
  82. package/src/acp/client-handler.ts +3 -16
  83. package/src/acp/resolve-agent.test.ts +6 -220
  84. package/src/acp/resolve-agent.ts +23 -88
  85. package/src/acp/session-manager.test.ts +3 -196
  86. package/src/acp/session-manager.ts +18 -441
  87. package/src/acp/types.ts +5 -50
  88. package/src/api/events/app-files-changed.ts +3 -4
  89. package/src/api/index.ts +0 -6
  90. package/src/apps/app-store.ts +1 -1
  91. package/src/bundler/app-compiler.ts +0 -15
  92. package/src/calls/__tests__/voice-control-protocol.test.ts +25 -58
  93. package/src/calls/__tests__/voice-session-bridge.test.ts +1 -37
  94. package/src/calls/__tests__/voice-triage-escalate.test.ts +0 -105
  95. package/src/calls/call-constants.ts +4 -24
  96. package/src/calls/call-controller.ts +199 -499
  97. package/src/calls/call-transport.ts +0 -16
  98. package/src/calls/media-stream-output.ts +0 -32
  99. package/src/calls/media-stream-server.ts +30 -82
  100. package/src/calls/media-stream-stt-session.ts +0 -15
  101. package/src/calls/resolve-call-tts-provider.ts +2 -33
  102. package/src/calls/speaker-identification.ts +233 -0
  103. package/src/calls/tts-call-strategy.ts +5 -9
  104. package/src/calls/types.ts +0 -2
  105. package/src/calls/voice-control-protocol.ts +21 -53
  106. package/src/calls/voice-session-bridge.ts +46 -59
  107. package/src/calls/voice-triage-escalate.ts +56 -175
  108. package/src/cli/commands/apps.help.ts +13 -67
  109. package/src/cli/commands/apps.ts +0 -183
  110. package/src/cli/commands/channels/index.help.ts +2 -70
  111. package/src/cli/commands/channels/index.ts +3 -16
  112. package/src/cli/commands/credentials.help.ts +2 -6
  113. package/src/cli/commands/credentials.ts +2 -2
  114. package/src/cli/commands/mcp.help.ts +8 -8
  115. package/src/cli/commands/mcp.ts +1 -1
  116. package/src/cli/commands/roadmap.help.ts +1 -5
  117. package/src/cli/commands/roadmap.ts +1 -1
  118. package/src/cli/lib/bundled-marketplace.json +90 -103
  119. package/src/config/__tests__/default-profile-catalog.test.ts +0 -22
  120. package/src/config/__tests__/memory-retrospective-schema.test.ts +1 -15
  121. package/src/config/__tests__/platform-rehydration.test.ts +0 -93
  122. package/src/config/acp-defaults.test.ts +3 -13
  123. package/src/config/acp-defaults.ts +6 -23
  124. package/src/config/acp-schema.ts +2 -30
  125. package/src/config/bundled-skills/acp/SKILL.md +14 -22
  126. package/src/config/bundled-skills/acp/TOOLS.json +1 -5
  127. package/src/config/bundled-skills/app-builder/SKILL.md +9 -12
  128. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
  129. package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
  130. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +139 -67
  131. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
  132. package/src/config/bundled-skills/screen-annotation/SKILL.md +1 -8
  133. package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +1 -4
  134. package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
  135. package/src/config/default-profile-catalog.ts +1 -1
  136. package/src/config/env.ts +2 -2
  137. package/src/config/feature-flag-registry.json +12 -8
  138. package/src/config/memory-v3-gate.test.ts +0 -31
  139. package/src/config/memory-v3-gate.ts +16 -20
  140. package/src/config/platform-rehydration.ts +21 -75
  141. package/src/config/schema.ts +2 -2
  142. package/src/config/schemas/__tests__/live-voice.test.ts +158 -8
  143. package/src/config/schemas/calls.ts +28 -1
  144. package/src/config/schemas/live-voice.ts +173 -1
  145. package/src/config/schemas/mcp.ts +1 -1
  146. package/src/config/schemas/memory-retrospective.ts +0 -10
  147. package/src/config/schemas/skills.ts +14 -0
  148. package/src/credential-execution/process-manager.test.ts +19 -18
  149. package/src/daemon/__tests__/conversation-tool-setup.test.ts +121 -66
  150. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +1 -4
  151. package/src/daemon/app-source-watcher.ts +218 -0
  152. package/src/daemon/config-watcher.ts +194 -13
  153. package/src/daemon/conversation-process.ts +64 -35
  154. package/src/daemon/conversation-skill-tools.ts +31 -13
  155. package/src/daemon/conversation-store.ts +1 -1
  156. package/src/daemon/conversation-surfaces.ts +8 -10
  157. package/src/daemon/conversation-tool-setup.ts +43 -37
  158. package/src/daemon/conversation.ts +3 -0
  159. package/src/daemon/host-bash-proxy.ts +12 -8
  160. package/src/daemon/host-browser-proxy.ts +1 -15
  161. package/src/daemon/host-file-proxy.ts +10 -8
  162. package/src/daemon/host-transfer-proxy.ts +18 -14
  163. package/src/daemon/lifecycle.ts +5 -0
  164. package/src/daemon/mcp-reload-service.ts +5 -5
  165. package/src/daemon/message-types/sync.ts +0 -1
  166. package/src/daemon/providers-setup.ts +1 -1
  167. package/src/daemon/shutdown-handlers.ts +9 -1
  168. package/src/daemon/skill-memory-refresh.ts +0 -64
  169. package/src/daemon/tool-side-effects.ts +18 -7
  170. package/src/hooks/types.ts +0 -33
  171. package/src/inbound/platform-callback-registration.ts +5 -4
  172. package/src/live-voice/__tests__/live-voice-metrics.test.ts +3 -0
  173. package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -15
  174. package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
  175. package/src/{calls → live-voice}/__tests__/progress-narration.test.ts +4 -4
  176. package/src/{calls → live-voice}/__tests__/progress-phrases.test.ts +1 -1
  177. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +69 -13
  178. package/src/live-voice/live-voice-session.ts +693 -213
  179. package/src/{calls → live-voice}/progress-narration.ts +2 -2
  180. package/src/live-voice/protocol.ts +2 -0
  181. package/src/mcp/__tests__/effective-config.test.ts +2 -2
  182. package/src/mcp/effective-config.ts +3 -3
  183. package/src/mcp/mcp-header-store.ts +45 -29
  184. package/src/mcp/startup.ts +4 -8
  185. package/src/messaging/provider.ts +3 -10
  186. package/src/messaging/providers/callback-routing.ts +0 -18
  187. package/src/messaging/providers/channel-transport.ts +0 -56
  188. package/src/messaging/providers/discord/transport.ts +0 -17
  189. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +17 -6
  190. package/src/messaging/providers/slack/adapter.ts +44 -7
  191. package/src/messaging/providers/slack/transport.ts +0 -16
  192. package/src/messaging/providers/telegram-bot/adapter.ts +48 -1
  193. package/src/messaging/providers/telegram-bot/client.ts +26 -0
  194. package/src/messaging/providers/telegram-bot/transport.ts +0 -20
  195. package/src/messaging/providers/whatsapp/adapter.ts +48 -1
  196. package/src/messaging/providers/whatsapp/client.ts +29 -0
  197. package/src/messaging/providers/whatsapp/transport.ts +0 -18
  198. package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +0 -8
  199. package/src/notifications/__tests__/assistant-reply-producer.test.ts +0 -55
  200. package/src/notifications/__tests__/home-feed-side-effect.test.ts +3 -153
  201. package/src/notifications/assistant-reply-producer.ts +3 -8
  202. package/src/notifications/conversation-pairing.ts +16 -36
  203. package/src/notifications/delivered-post-record.ts +1 -11
  204. package/src/notifications/home-feed-side-effect.ts +7 -55
  205. package/src/outbound-proxy/certs.ts +5 -3
  206. package/src/permissions/checker.test.ts +6 -11
  207. package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
  208. package/src/persistence/conversation-crud.ts +63 -71
  209. package/src/persistence/conversation-lineage.ts +4 -12
  210. package/src/persistence/conversation-title-service.ts +26 -9
  211. package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
  212. package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
  213. package/src/persistence/message-reads.ts +0 -30
  214. package/src/persistence/migrations/157-invite-contact-id.ts +6 -18
  215. package/src/persistence/schema/memory-core.ts +0 -5
  216. package/src/platform/client.test.ts +0 -2
  217. package/src/platform/client.ts +10 -2
  218. package/src/plugin-api/constants.ts +0 -2
  219. package/src/plugin-api/index.ts +0 -1
  220. package/src/plugin-api/types.ts +0 -2
  221. package/src/plugins/defaults/index.ts +0 -2
  222. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +0 -46
  223. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +5 -9
  224. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +10 -20
  225. package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +0 -219
  226. package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +0 -31
  227. package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +0 -63
  228. package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +0 -4
  229. package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -4
  230. package/src/plugins/defaults/memory/hooks/init.ts +0 -10
  231. package/src/plugins/defaults/memory/hooks/shutdown.ts +0 -2
  232. package/src/plugins/defaults/memory/jobs-worker.ts +0 -13
  233. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +60 -34
  234. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +5 -3
  235. package/src/plugins/defaults/memory/memory-retrospective-job.ts +3 -12
  236. package/src/plugins/defaults/memory/memory-retrospective-state.ts +71 -312
  237. package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +1 -2
  238. package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +3 -5
  239. package/src/plugins/defaults/memory/startup.ts +1 -6
  240. package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +9 -7
  241. package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
  242. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +0 -89
  243. package/src/plugins/defaults/memory/worker.ts +0 -6
  244. package/src/plugins/mcp-servers.ts +75 -15
  245. package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
  246. package/src/providers/gemini/client.ts +23 -9
  247. package/src/providers/model-catalog.ts +0 -34
  248. package/src/providers/model-intents.ts +5 -10
  249. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +18 -146
  250. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +1 -83
  251. package/src/providers/openai/chat-completions-provider.ts +78 -192
  252. package/src/providers/openai/codex-models.ts +4 -0
  253. package/src/runtime/AGENTS.md +0 -2
  254. package/src/runtime/auth/same-actor.ts +3 -38
  255. package/src/runtime/http-server.ts +151 -160
  256. package/src/runtime/routes/__tests__/acp-routes.test.ts +4 -159
  257. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +1 -108
  258. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +12 -16
  259. package/src/runtime/routes/acp-routes.test.ts +0 -15
  260. package/src/runtime/routes/acp-routes.ts +9 -72
  261. package/src/runtime/routes/app-management-routes.ts +0 -73
  262. package/src/runtime/routes/app-routes.ts +1 -1
  263. package/src/runtime/routes/conversation-list-routes.ts +3 -14
  264. package/src/runtime/routes/conversation-query-routes.ts +0 -40
  265. package/src/runtime/routes/credential-routes.ts +10 -19
  266. package/src/runtime/routes/diagnostics-routes.ts +3 -55
  267. package/src/runtime/routes/index.ts +0 -4
  268. package/src/runtime/routes/log-export-routes.ts +0 -2
  269. package/src/runtime/routes/mcp-auth-routes.ts +47 -39
  270. package/src/runtime/routes/migration-routes.ts +21 -8
  271. package/src/runtime/routes/platform-routes.ts +15 -18
  272. package/src/runtime/services/__tests__/conversation-serializer.test.ts +0 -28
  273. package/src/runtime/services/conversation-serializer.ts +3 -16
  274. package/src/runtime/sync/resource-sync-events.ts +1 -2
  275. package/src/schedule/worker.ts +1 -1
  276. package/src/security/ces-rpc-record-backend.ts +0 -7
  277. package/src/subagent/consult-context.ts +25 -12
  278. package/src/tools/acp/list-agents.test.ts +1 -3
  279. package/src/tools/acp/spawn.test.ts +11 -146
  280. package/src/tools/acp/spawn.ts +3 -21
  281. package/src/tools/acp/status.ts +3 -40
  282. package/src/tools/app-control/skill-proxy-bridge.ts +1 -2
  283. package/src/tools/capability-offer.test.ts +2 -22
  284. package/src/tools/capability-offer.ts +1 -16
  285. package/src/tools/computer-use/definitions.ts +2 -4
  286. package/src/tools/computer-use/skill-proxy-bridge.ts +1 -2
  287. package/src/tools/credentials/metadata-store.ts +0 -100
  288. package/src/tools/host-filesystem/edit.test.ts +1 -2
  289. package/src/tools/host-filesystem/edit.ts +2 -2
  290. package/src/tools/host-filesystem/read.test.ts +1 -2
  291. package/src/tools/host-filesystem/read.ts +2 -2
  292. package/src/tools/host-filesystem/transfer.test.ts +1 -2
  293. package/src/tools/host-filesystem/transfer.ts +2 -2
  294. package/src/tools/host-filesystem/write.test.ts +1 -2
  295. package/src/tools/host-filesystem/write.ts +2 -2
  296. package/src/tools/host-terminal/host-shell.test.ts +0 -16
  297. package/src/tools/host-terminal/host-shell.ts +20 -4
  298. package/src/tools/policy-context.ts +7 -6
  299. package/src/tools/subagent/spawn.ts +8 -5
  300. package/src/tools/watch/watch-retro-report.ts +8 -0
  301. package/src/util/__tests__/language-subtag.test.ts +1 -23
  302. package/src/util/fs-watcher-error.ts +36 -0
  303. package/src/util/language-subtag.ts +0 -16
  304. package/src/util/platform.ts +0 -8
  305. package/src/workspace/git-service.ts +54 -5
  306. package/src/workspace/migrations/registry.ts +0 -6
  307. package/scripts/smoke-desktop-window-theme.ts +0 -186
  308. package/src/__tests__/git-porcelain.test.ts +0 -47
  309. package/src/__tests__/helpers/set-workspace-mcp.ts +0 -11
  310. package/src/__tests__/message-deleted-hook-dispatch.test.ts +0 -119
  311. package/src/__tests__/resolve-app.test.ts +0 -135
  312. package/src/__tests__/source-fingerprint.test.ts +0 -184
  313. package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +0 -336
  314. package/src/acp/__tests__/helpers/acp-model-option.ts +0 -51
  315. package/src/acp/model-config.test.ts +0 -166
  316. package/src/acp/model-config.ts +0 -138
  317. package/src/api/events/acp-session-model-update.test.ts +0 -88
  318. package/src/api/events/acp-session-model-update.ts +0 -46
  319. package/src/apps/resolve-app.ts +0 -81
  320. package/src/apps/source-fingerprint.ts +0 -266
  321. package/src/calls/__tests__/barge-in-guard.test.ts +0 -81
  322. package/src/calls/__tests__/voice-leg-coordinator.test.ts +0 -229
  323. package/src/calls/__tests__/voice-progress-cadence.test.ts +0 -274
  324. package/src/calls/barge-in-guard.ts +0 -118
  325. package/src/calls/voice-leg-coordinator.ts +0 -189
  326. package/src/calls/voice-progress-cadence.ts +0 -423
  327. package/src/cli/commands/__tests__/apps.test.ts +0 -245
  328. package/src/cli/commands/__tests__/roadmap.help.test.ts +0 -19
  329. package/src/cli/commands/channels/__tests__/send.test.ts +0 -218
  330. package/src/cli/commands/channels/send.ts +0 -128
  331. package/src/config/__tests__/platform-identity.test.ts +0 -117
  332. package/src/config/acp-agent-write.ts +0 -43
  333. package/src/config/acp-schema.test.ts +0 -81
  334. package/src/config/platform-identity.ts +0 -144
  335. package/src/config/schemas/__tests__/voice.test.ts +0 -148
  336. package/src/config/schemas/voice.ts +0 -150
  337. package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +0 -133
  338. package/src/daemon/app-change-notify.ts +0 -45
  339. package/src/desktop/__tests__/fake-desktop.ts +0 -119
  340. package/src/desktop/desktop-chrome-policy.test.ts +0 -71
  341. package/src/desktop/desktop-chrome-policy.ts +0 -42
  342. package/src/desktop/desktop-dependencies.test.ts +0 -102
  343. package/src/desktop/desktop-dependencies.ts +0 -306
  344. package/src/desktop/desktop-feature.test.ts +0 -18
  345. package/src/desktop/desktop-feature.ts +0 -19
  346. package/src/desktop/desktop-panel-config.test.ts +0 -135
  347. package/src/desktop/desktop-panel-config.ts +0 -149
  348. package/src/desktop/desktop-session-manager.test.ts +0 -627
  349. package/src/desktop/desktop-session-manager.ts +0 -814
  350. package/src/desktop/desktop-stream-bridge.test.ts +0 -395
  351. package/src/desktop/desktop-stream-bridge.ts +0 -214
  352. package/src/desktop/desktop-wallpaper.test.ts +0 -191
  353. package/src/desktop/desktop-wallpaper.ts +0 -113
  354. package/src/desktop/desktop-window-theme.ts +0 -170
  355. package/src/i18n/AGENTS.md +0 -22
  356. package/src/i18n/__tests__/i18n.test.ts +0 -108
  357. package/src/i18n/index.ts +0 -16
  358. package/src/i18n/locales.ts +0 -93
  359. package/src/i18n/messages.ts +0 -61
  360. package/src/i18n/title.ts +0 -25
  361. package/src/mcp/__tests__/workspace-mcp-config.test.ts +0 -135
  362. package/src/mcp/spec-schema.ts +0 -111
  363. package/src/mcp/workspace-mcp-config.ts +0 -188
  364. package/src/messaging/providers/__tests__/proactive-address.test.ts +0 -95
  365. package/src/persistence/conversation-title-placeholders.ts +0 -23
  366. package/src/persistence/message-cursor.ts +0 -78
  367. package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +0 -149
  368. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +0 -118
  369. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +0 -83
  370. package/src/plugins/defaults/memory/hooks/message-deleted.ts +0 -21
  371. package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +0 -126
  372. package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +0 -79
  373. package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +0 -28
  374. package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +0 -9
  375. package/src/providers/gemini-thought-signature.test.ts +0 -63
  376. package/src/providers/gemini-thought-signature.ts +0 -44
  377. package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +0 -613
  378. package/src/providers/openai/__tests__/google-thought-signature.test.ts +0 -235
  379. package/src/providers/openai/google-thought-signature.ts +0 -320
  380. package/src/runtime/__tests__/channel-send.test.ts +0 -443
  381. package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +0 -89
  382. package/src/runtime/__tests__/live-voice-upgrade.test.ts +0 -58
  383. package/src/runtime/__tests__/runtime-ws-test-utils.ts +0 -72
  384. package/src/runtime/__tests__/watch-stream-upgrade.test.ts +0 -59
  385. package/src/runtime/channel-send.ts +0 -316
  386. package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +0 -205
  387. package/src/runtime/routes/__tests__/channel-send-routes.test.ts +0 -127
  388. package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +0 -107
  389. package/src/runtime/routes/channel-send-routes.ts +0 -85
  390. package/src/runtime/routes/desktop-setup-routes.test.ts +0 -74
  391. package/src/runtime/routes/desktop-setup-routes.ts +0 -35
  392. package/src/security/__tests__/ces-http-record-backend.test.ts +0 -137
  393. package/src/security/ces-http-record-backend.ts +0 -120
  394. package/src/skills/__tests__/workspace-skill-md-signature.test.ts +0 -60
  395. package/src/skills/workspace-skill-md-signature.ts +0 -50
  396. package/src/tools/acp/status.test.ts +0 -94
  397. package/src/util/ca-bundle.ts +0 -13
  398. package/src/workspace/git-porcelain.ts +0 -66
  399. package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +0 -258
  400. package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +0 -78
  401. package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +0 -214
  402. package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +0 -102
  403. package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +0 -199
  404. /package/src/{calls → live-voice}/progress-phrases.ts +0 -0
@@ -901,32 +901,6 @@ describe("pairDeliveryWithConversation", () => {
901
901
  // body has to land there for that button to open something that contains
902
902
  // the notification the user tapped.
903
903
 
904
- test("assistant reply keeps the source conversation without appending its preview", async () => {
905
- mockExistingConversations["conv-user-chat"] = {
906
- id: "conv-user-chat",
907
- source: "user",
908
- title: "An ordinary chat",
909
- };
910
- const signal = makeSignal({
911
- requiresConversation: undefined,
912
- sourceContextId: "conv-user-chat",
913
- sourceEventName: "chat.assistant_reply",
914
- });
915
-
916
- const result = await pairDeliveryWithConversation(
917
- signal,
918
- "vellum" as NotificationChannel,
919
- makeCopy({ body: "A short push preview…" }),
920
- );
921
-
922
- expect(result.conversationId).toBe("conv-user-chat");
923
- expect(result.messageId).toBeNull();
924
- expect(result.createdNewConversation).toBe(false);
925
- expect(createConversationMock).not.toHaveBeenCalled();
926
- expect(addMessageMock).not.toHaveBeenCalled();
927
- expect(messagesInvalidated).toEqual([]);
928
- });
929
-
930
904
  test("passive vellum signal appends the body to the producing conversation", async () => {
931
905
  mockExistingConversations["conv-producer"] = {
932
906
  id: "conv-producer",
@@ -161,6 +161,12 @@ function makeFakeContext(opts: {
161
161
  trustClass: "guardian" as const,
162
162
  guardianPrincipalId: "user-1",
163
163
  },
164
+ setTrustContext(
165
+ this: { trustContext?: TrustContext },
166
+ trustContext: TrustContext | null,
167
+ ) {
168
+ this.trustContext = trustContext ?? undefined;
169
+ },
164
170
  setTransportHints() {},
165
171
  applyHostEnvFromTransport() {},
166
172
  ensureHostProxiesForTurn() {},
@@ -409,10 +415,10 @@ describe("drainQueue preactivation re-add for host-proxy interfaces", () => {
409
415
  "U-contact",
410
416
  );
411
417
  expect(ctx.currentTurnTrustContext?.sourceChannel).toBe("slack");
412
- // The slot itself is left alone; only the turn's view is corrected. A
413
- // drain stamping it would attribute the conversation to a sender whose
414
- // turn can still lose the processing lock at persist time.
415
- expect(ctx.trustContext?.trustClass).toBe("guardian");
418
+ // The drain is where a queued message commits to a run, so the resting
419
+ // slot names the sender too: history is scoped from it, and a slot still
420
+ // naming the guardian would hand the contact's turn the guardian's rows.
421
+ expect(ctx.trustContext).toBe(contactTrust);
416
422
  });
417
423
 
418
424
  test("buildPassthroughBatch refuses to coalesce two channel senders", async () => {
@@ -450,6 +450,17 @@ async function waitForCondition(
450
450
  }
451
451
  }
452
452
 
453
+ /** Count the history reloads a conversation performs from this point on. */
454
+ function countHistoryReloads(conversation: Conversation): () => number {
455
+ const originalLoad = conversation.loadFromDb.bind(conversation);
456
+ let reloads = 0;
457
+ conversation.loadFromDb = async () => {
458
+ reloads++;
459
+ return originalLoad();
460
+ };
461
+ return () => reloads;
462
+ }
463
+
453
464
  /**
454
465
  * Resolve the Nth pending AgentLoop.run() call. Fires the minimal events
455
466
  * that `runAgentLoop` expects (usage + message_complete) so the conversation
@@ -715,21 +726,22 @@ describe("Conversation message queue", () => {
715
726
  await new Promise((r) => setTimeout(r, 10));
716
727
  });
717
728
 
718
- test("the turn-context actor section describes the turn's actor, not the conversation's resting actor", async () => {
719
- // The drain carries its sender on the per-turn field and leaves the
720
- // resting slot naming the previous actor. The actor section is frozen at
721
- // turn start, so it has to read the turn's actor: reading the slot is what
722
- // told the guardian's own drained turn it was talking to a trusted
723
- // contact.
729
+ test("a drained turn commits its sender as the resting actor and re-scopes history", async () => {
730
+ // A contact's channel turn leaves the resting slot, and the resident
731
+ // history scoped from it, naming the contact. The guardian's message
732
+ // queued behind that turn must run as the guardian everywhere the slot is
733
+ // read, not only in the per-turn snapshot: the `<turn_context>` actor
734
+ // section is derived at turn start and history is scoped from the slot,
735
+ // so a drain that stamped only the snapshot rendered the guardian's own
736
+ // web turn as a trusted contact's and hid the guardian's rows from it.
724
737
  const conversation = makeConversation();
725
738
  await conversation.loadFromDb();
726
739
 
727
- const contact = {
728
- trustClass: "trusted_contact" as const,
729
- sourceChannel: "slack" as const,
740
+ conversation.setTrustContext({
741
+ trustClass: "trusted_contact",
742
+ sourceChannel: "slack",
730
743
  requesterExternalUserId: "U-contact",
731
- };
732
- conversation.setTrustContext(contact);
744
+ });
733
745
 
734
746
  const p1 = conversation.processMessage({
735
747
  content: "msg-1",
@@ -744,6 +756,7 @@ describe("Conversation message queue", () => {
744
756
  sourceChannel: "vellum" as const,
745
757
  requesterExternalUserId: "guardian-principal",
746
758
  };
759
+ const reloads = countHistoryReloads(conversation);
747
760
  conversation.enqueueMessage({
748
761
  content: "msg-2",
749
762
  requestId: "req-2",
@@ -754,30 +767,83 @@ describe("Conversation message queue", () => {
754
767
  await p1;
755
768
  await waitForPendingRun(2);
756
769
 
757
- // Guard the test itself: the slot must disagree with the turn, or the
758
- // assertion below passes for the wrong reason.
759
- expect(conversation.getTrustContext()).toBe(contact);
770
+ // Read while run 2 is in flight: the slot, the snapshot, and the frozen
771
+ // actor section all describe the guardian, and the history was reloaded
772
+ // for that scope exactly once.
773
+ expect(conversation.getTrustContext()).toBe(guardian);
760
774
  expect(conversation.currentTurnTrustContext).toBe(guardian);
761
- // A guardian turn renders no actor section, whatever the slot says.
762
775
  expect(conversation.currentTurnInboundActorContext).toBeNull();
776
+ expect(reloads()).toBe(1);
763
777
 
764
778
  await resolveRun(1);
765
779
  await new Promise((r) => setTimeout(r, 10));
766
780
  });
767
781
 
768
- test("the actor section names the drained contact while the slot holds the guardian", async () => {
769
- // The inverse direction, so the assertion above cannot pass merely because
770
- // the section is always absent: a contact's message drained behind the
771
- // guardian's turn must be described as that contact.
782
+ test("a batched drain commits the head's sender as the resting actor", async () => {
783
+ // Same commitment on the batched path, which coalesces only messages that
784
+ // share a trust identity, so the head's sender is the batch's actor.
772
785
  const conversation = makeConversation();
773
786
  await conversation.loadFromDb();
774
787
 
788
+ conversation.setTrustContext({
789
+ trustClass: "trusted_contact",
790
+ sourceChannel: "slack",
791
+ requesterExternalUserId: "U-contact",
792
+ });
793
+
794
+ const p1 = conversation.processMessage({
795
+ content: "msg-1",
796
+ attachments: [],
797
+ onEvent: () => {},
798
+ requestId: "req-1",
799
+ });
800
+ await waitForPendingRun(1);
801
+
775
802
  const guardian = {
776
803
  trustClass: "guardian" as const,
777
804
  sourceChannel: "vellum" as const,
778
805
  requesterExternalUserId: "guardian-principal",
779
806
  };
780
- conversation.setTrustContext(guardian);
807
+ const reloads = countHistoryReloads(conversation);
808
+ conversation.enqueueMessage({
809
+ content: "msg-2",
810
+ requestId: "req-2",
811
+ trustContext: guardian,
812
+ });
813
+ conversation.enqueueMessage({
814
+ content: "msg-3",
815
+ requestId: "req-3",
816
+ trustContext: guardian,
817
+ });
818
+
819
+ await resolveRun(0);
820
+ await p1;
821
+ await waitForPendingRun(2);
822
+
823
+ // One batched run for both siblings, committed as the guardian.
824
+ expect(pendingRuns.length).toBe(2);
825
+ expect(conversation.getTrustContext()).toBe(guardian);
826
+ expect(conversation.currentTurnTrustContext).toBe(guardian);
827
+ expect(conversation.currentTurnInboundActorContext).toBeNull();
828
+ expect(reloads()).toBe(1);
829
+
830
+ await resolveRun(1);
831
+ await new Promise((r) => setTimeout(r, 10));
832
+ });
833
+
834
+ test("a drained turn keeps its sender when the slot moves during the history reload", async () => {
835
+ // The commit captures the sender before the reload awaits. A writer that
836
+ // moves the slot inside that await (a wake's stamp, a pointer elevation)
837
+ // must not become the turn's actor: the history was reloaded for the
838
+ // sender, and running someone else's trust over it is the escalation.
839
+ const conversation = makeConversation();
840
+ await conversation.loadFromDb();
841
+
842
+ conversation.setTrustContext({
843
+ trustClass: "trusted_contact",
844
+ sourceChannel: "slack",
845
+ requesterExternalUserId: "U-contact",
846
+ });
781
847
 
782
848
  const p1 = conversation.processMessage({
783
849
  content: "msg-1",
@@ -787,39 +853,49 @@ describe("Conversation message queue", () => {
787
853
  });
788
854
  await waitForPendingRun(1);
789
855
 
790
- const contact = {
791
- trustClass: "trusted_contact" as const,
792
- sourceChannel: "slack" as const,
793
- requesterExternalUserId: "U-contact",
856
+ const guardian = {
857
+ trustClass: "guardian" as const,
858
+ sourceChannel: "vellum" as const,
859
+ requesterExternalUserId: "guardian-principal",
860
+ };
861
+ const intruder = {
862
+ trustClass: "unknown" as const,
863
+ sourceChannel: "telegram" as const,
864
+ requesterExternalUserId: "T-stranger",
794
865
  };
795
866
  conversation.enqueueMessage({
796
867
  content: "msg-2",
797
868
  requestId: "req-2",
798
- trustContext: contact,
869
+ trustContext: guardian,
799
870
  });
800
871
 
872
+ // The drain's reload is the await the writer lands inside.
873
+ const originalLoad = conversation.loadFromDb.bind(conversation);
874
+ let movedDuringReload = false;
875
+ conversation.loadFromDb = async () => {
876
+ const result = await originalLoad();
877
+ conversation.setTrustContext(intruder);
878
+ movedDuringReload = true;
879
+ return result;
880
+ };
881
+
801
882
  await resolveRun(0);
802
883
  await p1;
803
884
  await waitForPendingRun(2);
804
885
 
805
- expect(conversation.getTrustContext()).toBe(guardian);
806
- expect(conversation.currentTurnTrustContext).toBe(contact);
807
- expect(conversation.currentTurnInboundActorContext).toMatchObject({
808
- trustClass: "trusted_contact",
809
- sourceChannel: "slack",
810
- canonicalActorIdentity: "U-contact",
811
- });
886
+ expect(movedDuringReload).toBe(true);
887
+ expect(conversation.getTrustContext()).toBe(intruder);
888
+ expect(conversation.currentTurnTrustContext).toBe(guardian);
889
+ expect(conversation.currentTurnInboundActorContext).toBeNull();
812
890
 
813
891
  await resolveRun(1);
814
892
  await new Promise((r) => setTimeout(r, 10));
815
893
  });
816
894
 
817
- test("a processMessage turn whose history reload fails puts the resting actor back", async () => {
818
- // `processMessage` is the commitment point, so it stamps the slot before
819
- // scoping history. A reload that throws starts no turn, and leaving the
820
- // stamp would attribute the conversation to a sender that never ran: an
821
- // actorless dispatch afterwards (a deferred wake) resolves the resting
822
- // slot and would inherit it.
895
+ test("a drain whose history reload fails puts the resting actor back", async () => {
896
+ // A reload that fails starts no turn: the message is requeued for the
897
+ // next drain, so the slot must not keep naming a sender whose turn never
898
+ // began, or conversation-level readers report an owner that is not there.
823
899
  const conversation = makeConversation();
824
900
  await conversation.loadFromDb();
825
901
 
@@ -830,27 +906,110 @@ describe("Conversation message queue", () => {
830
906
  };
831
907
  conversation.setTrustContext(contact);
832
908
 
909
+ const p1 = conversation.processMessage({
910
+ content: "msg-1",
911
+ attachments: [],
912
+ onEvent: () => {},
913
+ requestId: "req-1",
914
+ });
915
+ await waitForPendingRun(1);
916
+
917
+ const guardian = {
918
+ trustClass: "guardian" as const,
919
+ sourceChannel: "vellum" as const,
920
+ requesterExternalUserId: "guardian-principal",
921
+ };
922
+ conversation.enqueueMessage({
923
+ content: "msg-2",
924
+ requestId: "req-2",
925
+ trustContext: guardian,
926
+ });
927
+
928
+ let reloadAttempts = 0;
833
929
  conversation.loadFromDb = async () => {
930
+ reloadAttempts++;
834
931
  throw new Error("history store exploded");
835
932
  };
836
933
 
934
+ // Finish the first turn; the drain (and its one retry) fail on the reload.
935
+ await resolveRun(0);
936
+ await p1;
937
+ await waitForCondition(() => reloadAttempts >= 2);
938
+ await new Promise((r) => setTimeout(r, 20));
939
+
940
+ expect(pendingRuns.length).toBe(1);
941
+ expect(conversation.getQueueDepth()).toBe(1);
942
+ expect(conversation.getTrustContext()).toBe(contact);
943
+ });
944
+
945
+ test("the turn-context actor section follows the turn's actor when the slot moves before the loop opens", async () => {
946
+ // The actor section is frozen at turn start from the turn's own actor,
947
+ // not from the resting slot. The drain stamps the slot and then awaits
948
+ // (persist) before the loop opens; an out-of-band writer landing in that
949
+ // window (pointer elevation restore, a wake's restore) moves the slot
950
+ // without owning the turn, and a read of the slot there would describe
951
+ // that writer's actor to the model.
952
+ const conversation = makeConversation();
953
+ await conversation.loadFromDb();
954
+
955
+ const contact = {
956
+ trustClass: "trusted_contact" as const,
957
+ sourceChannel: "slack" as const,
958
+ requesterExternalUserId: "U-contact",
959
+ };
960
+ conversation.setTrustContext(contact);
961
+
962
+ const p1 = conversation.processMessage({
963
+ content: "msg-1",
964
+ attachments: [],
965
+ onEvent: () => {},
966
+ requestId: "req-1",
967
+ });
968
+ await waitForPendingRun(1);
969
+
837
970
  const guardian = {
838
971
  trustClass: "guardian" as const,
839
972
  sourceChannel: "vellum" as const,
840
973
  requesterExternalUserId: "guardian-principal",
841
974
  };
842
- await expect(
843
- conversation.processMessage({
844
- content: "msg-1",
845
- attachments: [],
846
- onEvent: () => {},
847
- requestId: "req-1",
848
- trustContext: guardian,
849
- }),
850
- ).rejects.toThrow("history store exploded");
975
+ conversation.enqueueMessage({
976
+ content: "msg-2",
977
+ requestId: "req-2",
978
+ trustContext: guardian,
979
+ });
851
980
 
981
+ // Land the out-of-band slot write inside the drain's window, after its
982
+ // stamp and before the loop opens: the drain awaits persistUserMessage
983
+ // there. The resting slot alone is moved; the per-turn snapshot is the
984
+ // drain's to carry into the run.
985
+ const originalPersist = conversation.persistUserMessage.bind(conversation);
986
+ let slotMoved = false;
987
+ (
988
+ conversation as unknown as {
989
+ persistUserMessage: typeof conversation.persistUserMessage;
990
+ }
991
+ ).persistUserMessage = async (opts) => {
992
+ const result = await originalPersist(opts);
993
+ conversation.setTrustContext(contact);
994
+ slotMoved = true;
995
+ return result;
996
+ };
997
+
998
+ await resolveRun(0);
999
+ await p1;
1000
+ await waitForPendingRun(2);
1001
+
1002
+ // Guard the test itself: the injection must have run, and the slot must
1003
+ // disagree with the turn, or the assertion below passes for the wrong
1004
+ // reason.
1005
+ expect(slotMoved).toBe(true);
852
1006
  expect(conversation.getTrustContext()).toBe(contact);
853
- expect(pendingRuns.length).toBe(0);
1007
+ expect(conversation.currentTurnTrustContext).toBe(guardian);
1008
+ // The guardian's turn renders no actor section, whatever the slot says.
1009
+ expect(conversation.currentTurnInboundActorContext).toBeNull();
1010
+
1011
+ await resolveRun(1);
1012
+ await new Promise((r) => setTimeout(r, 10));
854
1013
  });
855
1014
 
856
1015
  test("a processMessage turn keeps its turn-start trust when the slot moves before the loop opens", async () => {
@@ -10,26 +10,13 @@ import {
10
10
  discardLastAssistantDisplayTurn,
11
11
  extractUserPromptText,
12
12
  } from "../daemon/conversation-history.js";
13
- import { registerPluginHooks } from "../hooks/registry.js";
14
13
  import { addMessage, getMessages } from "../persistence/conversation-crud.js";
15
- import { getDb, getMemorySqlite } from "../persistence/db-connection.js";
14
+ import { getDb } from "../persistence/db-connection.js";
16
15
  import { initializeDb } from "../persistence/db-init.js";
17
16
  import { conversations, messages } from "../persistence/schema/index.js";
18
- import memoryMessageDeleted from "../plugins/defaults/memory/hooks/message-deleted.js";
19
- import {
20
- ensureRetrospectiveCursorColumn,
21
- getRetrospectiveState,
22
- } from "../plugins/defaults/memory/memory-retrospective-state.js";
23
- import type { PluginHooks } from "../plugins/types.js";
24
17
 
25
18
  await initializeDb();
26
19
 
27
- // Register the memory plugin's `message-deleted` hook the way boot does, so
28
- // the delete primitive's dispatch reaches the cursor bookkeeping.
29
- registerPluginHooks("default-memory", {
30
- "message-deleted": memoryMessageDeleted,
31
- } as PluginHooks);
32
-
33
20
  const CONV_ID = "conv-retry-discard-test";
34
21
 
35
22
  function seedConversation(): void {
@@ -224,43 +211,3 @@ describe("extractUserPromptText", () => {
224
211
  expect(extractUserPromptText([])).toBe("");
225
212
  });
226
213
  });
227
-
228
- describe("discardLastAssistantDisplayTurn keeps the memory-retrospective cursor placeable", () => {
229
- beforeEach(() => {
230
- ensureRetrospectiveCursorColumn("test");
231
- getMemorySqlite()!.exec(`DELETE FROM memory_retrospective_state`);
232
- });
233
-
234
- test("a cursor stored without its timestamp gets it from the reply being discarded", async () => {
235
- const [, assistantId] = await seed([
236
- { role: "user", content: text("hello") },
237
- { role: "assistant", content: text("first attempt") },
238
- ]);
239
- const replyCreatedAt = getMessages(CONV_ID).find(
240
- (m) => m.id === assistantId,
241
- )!.createdAt;
242
- // A state row as a build without the timestamp column wrote it: the
243
- // retrospective reviewed through the reply, so the cursor sits on it.
244
- getMemorySqlite()!
245
- .query(
246
- `INSERT INTO memory_retrospective_state
247
- (conversation_id, last_processed_message_id, last_run_at, remembered_log, last_processed_created_at)
248
- VALUES (?, ?, 1, NULL, NULL)`,
249
- )
250
- .run(CONV_ID, assistantId);
251
-
252
- discardLastAssistantDisplayTurn(CONV_ID);
253
-
254
- expect(getMessages(CONV_ID).some((m) => m.id === assistantId)).toBe(false);
255
- // The hook chain is fire-and-forget from the delete primitive, so the
256
- // stamp lands shortly after the discard returns.
257
- const deadline = Date.now() + 2_000;
258
- let stamped =
259
- getRetrospectiveState(CONV_ID)?.lastProcessedCreatedAt ?? null;
260
- while (stamped === null && Date.now() < deadline) {
261
- await Bun.sleep(10);
262
- stamped = getRetrospectiveState(CONV_ID)?.lastProcessedCreatedAt ?? null;
263
- }
264
- expect(stamped).toBe(replyCreatedAt);
265
- });
266
- });
@@ -545,33 +545,6 @@ describe("projectSkillTools", () => {
545
545
  expect(mockRegisteredTools.has("deploy")).toBe(true);
546
546
  });
547
547
 
548
- test("catalog membership changes are visible on the next projection with a persistent cache", () => {
549
- const cache = {};
550
- mockCatalog = [];
551
- mockManifests = { deploy: makeManifest(["deploy_run"]) };
552
-
553
- const history: Message[] = [
554
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
555
- ];
556
-
557
- const result1 = projectSkillTools(history, {
558
- previouslyActiveSkillIds: sessionState,
559
- cache,
560
- });
561
- expect(result1.allowedToolNames.size).toBe(0);
562
- expect(sessionState.has("deploy")).toBe(false);
563
-
564
- mockCatalog = [makeSkill("deploy")];
565
-
566
- const result2 = projectSkillTools(history, {
567
- previouslyActiveSkillIds: sessionState,
568
- cache,
569
- });
570
- expect(result2.allowedToolNames).toEqual(new Set(["deploy_run"]));
571
- expect(sessionState.has("deploy")).toBe(true);
572
- expect(mockRegisteredTools.has("deploy")).toBe(true);
573
- });
574
-
575
548
  test("skill with manifest failure on turn 1 is registered when manifest is available on turn 2", () => {
576
549
  mockCatalog = [makeSkill("deploy")];
577
550
  // No manifest — will fail to load
@@ -146,10 +146,8 @@ describe("surfaceProxyResolver — app-control tool routing", () => {
146
146
  });
147
147
 
148
148
  expect(result.isError).toBe(true);
149
- expect(result.content).toContain(
150
- "The Vellum desktop app is needed to control apps on your computer",
151
- );
152
- expect(result.content).toContain("https://www.vellum.ai/downloads");
149
+ expect(result.content).toContain("not available");
150
+ expect(result.content).toContain("app-control");
153
151
  // No envelope dispatched.
154
152
  expect(sentMessages).toHaveLength(0);
155
153
  });
@@ -165,10 +163,7 @@ describe("surfaceProxyResolver — app-control tool routing", () => {
165
163
  });
166
164
 
167
165
  expect(result.isError).toBe(true);
168
- expect(result.content).toContain(
169
- "The Vellum desktop app is needed to control apps on your computer",
170
- );
171
- expect(result.content).toContain("https://www.vellum.ai/downloads");
166
+ expect(result.content).toContain("not available");
172
167
  expect(sentMessages).toHaveLength(0);
173
168
 
174
169
  proxy.dispose();
@@ -21,7 +21,6 @@ import {
21
21
  function makeContext(
22
22
  sent: AssistantEvent[] = [],
23
23
  channelCapabilities?: { channel: string; supportsDynamicUi: boolean },
24
- opts?: { hasNoClient?: boolean },
25
24
  ): Conversation {
26
25
  return asConversation({
27
26
  conversationId: "session-1",
@@ -38,7 +37,6 @@ function makeContext(
38
37
  accumulatedSurfaceState: new Map<string, Record<string, unknown>>(),
39
38
  surfaceActionRequestIds: new Set<string>(),
40
39
  currentTurnSurfaces: [],
41
- hasNoClient: opts?.hasNoClient ?? false,
42
40
  isProcessing: () => false,
43
41
  enqueueMessage: () => ({ queued: false, requestId: "req-1" }),
44
42
  getQueueDepth: () => 0,
@@ -67,71 +65,6 @@ describe("task_progress surface compatibility", () => {
67
65
  expect(sent).toHaveLength(0);
68
66
  });
69
67
 
70
- test("persists a clientless choice from a non-rendering channel without waiting", async () => {
71
- const sent: AssistantEvent[] = [];
72
- const ctx = makeContext(
73
- sent,
74
- {
75
- channel: "phone",
76
- supportsDynamicUi: false,
77
- },
78
- { hasNoClient: true },
79
- );
80
-
81
- const result = await surfaceProxyResolver(ctx, "ui_show", {
82
- surface_type: "choice",
83
- title: "Choose a focus",
84
- data: {
85
- options: [
86
- { id: "inbox", title: "Inbox" },
87
- { id: "calendar", title: "Calendar" },
88
- ],
89
- },
90
- });
91
-
92
- expect(result.isError).toBe(false);
93
- expect(result.yieldToUser).toBeUndefined();
94
- const { surfaceId } = JSON.parse(result.content) as { surfaceId: string };
95
- expect(ctx.currentTurnSurfaces.some((s) => s.surfaceId === surfaceId)).toBe(
96
- true,
97
- );
98
- expect(ctx.pendingSurfaceActions.has(surfaceId)).toBe(false);
99
- expect(sent.some((msg) => msg.type === "ui_surface_show")).toBe(true);
100
- });
101
-
102
- test("persists a clientless update in the current-turn surface snapshot", async () => {
103
- const sent: AssistantEvent[] = [];
104
- const ctx = makeContext(
105
- sent,
106
- {
107
- channel: "phone",
108
- supportsDynamicUi: false,
109
- },
110
- { hasNoClient: true },
111
- );
112
- const shown = await surfaceProxyResolver(ctx, "ui_show", {
113
- surface_type: "card",
114
- title: "Background work",
115
- data: {
116
- template: "task_progress",
117
- templateData: { status: "in_progress", steps: [] },
118
- },
119
- });
120
- const { surfaceId } = JSON.parse(shown.content) as { surfaceId: string };
121
-
122
- const result = await surfaceProxyResolver(ctx, "ui_update", {
123
- surface_id: surfaceId,
124
- data: { templateData: { status: "completed" } },
125
- });
126
-
127
- expect(result.isError).toBe(false);
128
- const data = ctx.currentTurnSurfaces.find((s) => s.surfaceId === surfaceId)
129
- ?.data as CardSurfaceData;
130
- expect((data.templateData as Record<string, unknown>).status).toBe(
131
- "completed",
132
- );
133
- });
134
-
135
68
  test("blocks ui_update when channel lacks dynamic UI support", async () => {
136
69
  const sent: AssistantEvent[] = [];
137
70
  const ctx = makeContext(sent, {