@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
@@ -335,9 +335,8 @@ describe("host_file_transfer cross-client guards", () => {
335
335
 
336
336
  expect(result.isError).toBe(true);
337
337
  expect(result.content).toContain(
338
- "The Vellum desktop app is needed to access files on your computer",
338
+ "no client with host_file capability is connected",
339
339
  );
340
- expect(result.content).toContain("https://www.vellum.ai/downloads");
341
340
  expect(toSandboxCalls.length).toBe(0);
342
341
  });
343
342
 
@@ -6,7 +6,7 @@ import { supportsHostProxy } from "../../channels/types.js";
6
6
  import { HostTransferProxy } from "../../daemon/host-transfer-proxy.js";
7
7
  import { RiskLevel } from "../../permissions/types.js";
8
8
  import { assistantEventHub } from "../../runtime/assistant-event-hub.js";
9
- import { formatDesktopAppRequired } from "../capability-offer.js";
9
+ import { desktopClientName } from "../client-os.js";
10
10
  import { declareDaemonActivityField } from "../schema-transforms.js";
11
11
  import { sandboxPolicy } from "../shared/filesystem/path-policy.js";
12
12
  import type {
@@ -126,7 +126,7 @@ export const hostFileTransferTool = {
126
126
  !HostTransferProxy.instance.isAvailable()
127
127
  ) {
128
128
  return {
129
- content: formatDesktopAppRequired("files"),
129
+ content: `Error: no client with host_file capability is connected. Connect a ${desktopClientName(context)} client to use host_file from a non-desktop interface.`,
130
130
  isError: true,
131
131
  };
132
132
  }
@@ -67,9 +67,8 @@ describe("host_file_write cross-client guards", () => {
67
67
  );
68
68
  expect(result.isError).toBe(true);
69
69
  expect(result.content).toContain(
70
- "The Vellum desktop app is needed to access files on your computer",
70
+ "no client with host_file capability is connected",
71
71
  );
72
- expect(result.content).toContain("https://www.vellum.ai/downloads");
73
72
  });
74
73
 
75
74
  test("returns 'specified client disconnected' error when targetClientId set but proxy unavailable on web", async () => {
@@ -4,7 +4,7 @@ import { supportsHostProxy } from "../../channels/types.js";
4
4
  import { HostFileProxy } from "../../daemon/host-file-proxy.js";
5
5
  import { RiskLevel } from "../../permissions/types.js";
6
6
  import { assistantEventHub } from "../../runtime/assistant-event-hub.js";
7
- import { formatDesktopAppRequired } from "../capability-offer.js";
7
+ import { desktopClientName } from "../client-os.js";
8
8
  import { FileSystemOps } from "../shared/filesystem/file-ops-service.js";
9
9
  import { formatWriteSummary } from "../shared/filesystem/format-diff.js";
10
10
  import { hostPolicy } from "../shared/filesystem/path-policy.js";
@@ -90,7 +90,7 @@ export const hostFileWriteTool = {
90
90
  !HostFileProxy.instance.isAvailable()
91
91
  ) {
92
92
  return {
93
- content: formatDesktopAppRequired("files"),
93
+ content: `Error: no client with host_file capability is connected. Connect a ${desktopClientName(context)} client to use host_file from a non-desktop interface.`,
94
94
  isError: true,
95
95
  };
96
96
  }
@@ -170,22 +170,6 @@ afterEach(() => {
170
170
  mockProxyAvailable = false;
171
171
  });
172
172
 
173
- describe("host_bash unavailable desktop-app recovery", () => {
174
- test("offers the desktop app download when a web turn has no host connection", async () => {
175
- const result = await hostShellTool.execute(
176
- { command: "pwd" },
177
- makeContext({ transportInterface: "web" }),
178
- );
179
-
180
- expect(result.isError).toBe(true);
181
- expect(result.content).toContain(
182
- "The Vellum desktop app is needed to run commands on your computer",
183
- );
184
- expect(result.content).toContain("https://www.vellum.ai/downloads");
185
- expect(result.content).toContain("Open it, then retry.");
186
- });
187
- });
188
-
189
173
  // ---------------------------------------------------------------------------
190
174
  // Proxy path — background: true
191
175
  // ---------------------------------------------------------------------------
@@ -18,7 +18,10 @@ import { getConfig } from "../../config/loader.js";
18
18
  import { HostBashProxy } from "../../daemon/host-bash-proxy.js";
19
19
  import { RiskLevel } from "../../permissions/types.js";
20
20
  import { wakeAgentForOpportunity } from "../../runtime/agent-wake.js";
21
- import { broadcastMessage } from "../../runtime/assistant-event-hub.js";
21
+ import {
22
+ assistantEventHub,
23
+ broadcastMessage,
24
+ } from "../../runtime/assistant-event-hub.js";
22
25
  import { conversationRevealNonce } from "../../runtime/reveal-nonce.js";
23
26
  import { redactSecrets } from "../../security/secret-scanner.js";
24
27
  import {
@@ -36,7 +39,7 @@ import {
36
39
  registerBackgroundTool,
37
40
  removeBackgroundTool,
38
41
  } from "../background-tool-registry.js";
39
- import { formatDesktopAppRequired } from "../capability-offer.js";
42
+ import { desktopClientName } from "../client-os.js";
40
43
  import {
41
44
  attachBoundedStdio,
42
45
  MAX_OUTPUT_LENGTH,
@@ -139,7 +142,7 @@ export const hostShellInputSchema = z.looseObject({
139
142
  target_client_id: z
140
143
  .string()
141
144
  .describe(
142
- "Optional ID of the specific client to execute this command on. Without it, the most recently active eligible client is used. Obtain IDs from `assistant clients list --capability host_bash`.",
145
+ "ID of the specific client to execute this command on. Required when multiple clients support host_bash; omit when only one client is connected. Obtain IDs from `assistant clients list --capability host_bash`.",
143
146
  )
144
147
  .optional()
145
148
  .catch(undefined),
@@ -200,7 +203,20 @@ export const hostShellTool = {
200
203
  const config = getConfig();
201
204
  const { shellDefaultTimeoutSec, shellMaxTimeoutSec } = config.timeouts;
202
205
 
206
+ // Guard: non-host-proxy interfaces need an explicit target when multiple
207
+ // capable clients are connected to avoid ambiguous untargeted broadcasts.
203
208
  const transportInterface = context.transportInterface;
209
+ if (
210
+ targetClientId == null &&
211
+ transportInterface != null &&
212
+ !supportsHostProxy(transportInterface) &&
213
+ assistantEventHub.listClientsByCapability("host_bash").length > 1
214
+ ) {
215
+ return {
216
+ content: `Error: multiple clients support host_bash. Specify which client to use with \`target_client_id\`. Run \`assistant clients list --capability host_bash\` to see client IDs and labels.`,
217
+ isError: true,
218
+ };
219
+ }
204
220
 
205
221
  // Guard: non-host-proxy interfaces with no capable clients connected.
206
222
  if (
@@ -210,7 +226,7 @@ export const hostShellTool = {
210
226
  !HostBashProxy.instance.isAvailable()
211
227
  ) {
212
228
  return {
213
- content: formatDesktopAppRequired("shell"),
229
+ content: `Error: no client with host_bash capability is connected. Connect a ${desktopClientName(context)} client to use host_bash from a non-desktop interface.`,
214
230
  isError: true,
215
231
  };
216
232
  }
@@ -1,5 +1,5 @@
1
1
  import { getConfig } from "../config/loader.js";
2
- import { isSkillImprovementActive } from "../config/memory-v3-gate.js";
2
+ import { isV3TierActive } from "../config/memory-v3-gate.js";
3
3
  import type { ExecutionContext } from "../permissions/approval-policy.js";
4
4
  import type { ChannelPermissionCoordinates } from "../permissions/channel-permission-query.js";
5
5
  import type { PolicyContext } from "../permissions/types.js";
@@ -63,11 +63,12 @@ export function buildPolicyContext(
63
63
  requestOrigin: context?.requestOrigin,
64
64
  ...channelCoordinatesFromToolContext(context),
65
65
  requesterContactId: context?.requesterContactId,
66
- // Precompute the retrospective skill-improvement gate here so the
67
- // permission checker, a leaf module that must not read config, can deny
68
- // the memory-retrospective skill-authoring grant whenever the feature is
69
- // inactive just by reading this boolean.
70
- procToSkillsActive: isSkillImprovementActive(getConfig()),
66
+ // Precompute the proc-to-skills gate — the v3 tier being active, i.e.
67
+ // memory on AND v3 live — here so the permission checker, a leaf module
68
+ // that must not read config, can deny the memory-retrospective
69
+ // skill-authoring grant whenever the feature is inactive just by reading
70
+ // this boolean.
71
+ procToSkillsActive: isV3TierActive(getConfig()),
71
72
  };
72
73
 
73
74
  const ownerKind = getToolOwner(tool.name)?.kind;
@@ -484,9 +484,9 @@ function spawnCancelledResult(label: string): ToolExecutionResult {
484
484
  * repeat-spawn guard folds, so a consult has to record it the same way every
485
485
  * other spawn does or the guard could never match one consult against another.
486
486
  *
487
- * The context pack is best effort: each section independently falls back to
488
- * null on failure, so a missing workspace or empty catalog costs only that
489
- * section, never the consult.
487
+ * The context pack is best effort: the parent conversation is looked up only
488
+ * for its warm skill catalog, so an unresolvable one (e.g. evicted) costs the
489
+ * skills section of the pack and nothing else.
490
490
  *
491
491
  * The budget is the advisor's alone. It is the only spawn the agent may issue
492
492
  * unprompted and the only one that runs on the premium profile, so it is the
@@ -502,18 +502,21 @@ async function buildAdvisorFields(
502
502
  maxRuntimeMs: number;
503
503
  maxToolCalls: number;
504
504
  }> {
505
+ const parentConversation = findConversation(context.conversationId);
505
506
  // Situational awareness for the advisor: the parent's live tool set, the
506
507
  // full skill catalog, and its workspace. Assembled off the per-turn
507
508
  // ToolContext snapshot (trust, channel) so the personal-memory sections are
508
509
  // gated exactly like the runtime injectors. A null pack just means the
509
- // consult runs on the brief alone. Skills come from a fresh
510
- // `loadSkillCatalog()` inside the context pack, not a conversation snapshot.
510
+ // consult runs on the brief alone.
511
511
  const situationalContext = await buildAdvisorContext({
512
512
  conversationId: context.conversationId,
513
513
  workingDir: context.workingDir,
514
514
  allowedToolNames: context.allowedToolNames,
515
515
  trustClass: context.trustClass,
516
516
  enabledPluginSet: context.enabledPluginSet,
517
+ // The parent's warm per-turn catalog keeps the synchronous on-disk catalog
518
+ // scan out of the spawn path.
519
+ skillCatalog: parentConversation?.skillProjectionCache?.catalog,
517
520
  });
518
521
  return {
519
522
  requestText: advisorRequestText(objective, situationalContext),
@@ -14,6 +14,14 @@ import type {
14
14
  /**
15
15
  * How a watch retrospective hands its report to the daemon.
16
16
  *
17
+ * **The card cannot come from `ui_show`.** A retrospective runs as a
18
+ * `clientless` wake, which pins the turn non-interactive, and
19
+ * `conversation-tool-setup` gates the whole `ui_surface` tool family on a
20
+ * client being present (`return channelCapabilities?.supportsDynamicUi ??
21
+ * !hasNoClient`). So `ui_show` is not merely denied in this turn, it is absent
22
+ * from the tool set, and a retrospective told to call it can only report that
23
+ * it cannot. This tool is an ordinary one and passes that gate untouched.
24
+ *
17
25
  * **It records; it does not render.** The executor validates the payload and
18
26
  * returns, and nothing here writes to the conversation. What makes the card is
19
27
  * `watch-retro.ts` reading this call back out of the turn's own history once
@@ -1,10 +1,6 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
 
3
- import {
4
- baseLanguageSubtag,
5
- fixedPhraseLanguage,
6
- localizedOrDefault,
7
- } from "../language-subtag.js";
3
+ import { baseLanguageSubtag, localizedOrDefault } from "../language-subtag.js";
8
4
 
9
5
  describe("baseLanguageSubtag", () => {
10
6
  test("lowercases a bare tag", () => {
@@ -56,21 +52,3 @@ describe("localizedOrDefault", () => {
56
52
  expect(localizedOrDefault(table, "__proto__", "fallback")).toBe("fallback");
57
53
  });
58
54
  });
59
-
60
- describe("fixedPhraseLanguage", () => {
61
- const table = { en: "one", es: "uno" };
62
-
63
- test("pins English when the speaker's language has no table entry", () => {
64
- expect(fixedPhraseLanguage(table, "ko")).toBe("en");
65
- expect(fixedPhraseLanguage(table, "ar-EG")).toBe("en");
66
- });
67
-
68
- test("rides the speaker's language when the table covers it", () => {
69
- expect(fixedPhraseLanguage(table, "es")).toBeUndefined();
70
- expect(fixedPhraseLanguage(table, "es-419")).toBeUndefined();
71
- });
72
-
73
- test("carries no hint when the language is unknown", () => {
74
- expect(fixedPhraseLanguage(table, undefined)).toBeUndefined();
75
- });
76
- });
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Shared `'error'`-event handler for `fs.watch()` FSWatchers.
3
+ *
4
+ * An `FSWatcher` is an EventEmitter. When the underlying inotify/FSEvents
5
+ * backend fails *after* the watch was established — e.g. ENOSPC when the
6
+ * kernel's `fs.inotify.max_user_watches` limit is exhausted while walking a
7
+ * recursive watch into a large subtree (a plugin's `node_modules`), or ENXIO
8
+ * when a Unix socket file appears in a watched directory — the failure is
9
+ * delivered asynchronously as an `'error'` event rather than a synchronous
10
+ * throw from `watch()`. An emitter with no `'error'` listener rethrows, which
11
+ * surfaces as an `uncaughtException` and takes the whole daemon down (→
12
+ * CrashLoopBackOff).
13
+ *
14
+ * Attaching this handler degrades the failure to "this watcher stops
15
+ * delivering events", in line with the daemon startup philosophy: a subsystem
16
+ * failure must never crash the process.
17
+ */
18
+
19
+ import type { FSWatcher } from "node:fs";
20
+
21
+ import type { Logger } from "pino";
22
+
23
+ /**
24
+ * Attach a resilient `'error'` listener so async FSWatcher failures are logged
25
+ * instead of crashing the process. Pass the owning module's logger and the
26
+ * watched directory for diagnostic context.
27
+ */
28
+ export function attachFsWatcherErrorHandler(
29
+ watcher: FSWatcher,
30
+ log: Logger,
31
+ dir: string,
32
+ ): void {
33
+ watcher.on("error", (err) => {
34
+ log.warn({ err, dir }, "FSWatcher error (non-fatal, continuing)");
35
+ });
36
+ }
@@ -41,19 +41,3 @@ export function localizedOrDefault<T>(
41
41
  ? (table[baseLanguageSubtag(language)!] as T)
42
42
  : fallback;
43
43
  }
44
-
45
- /**
46
- * The TTS language hint for a fixed phrase picked from a localized table:
47
- * "en" when the speaker has a language the table does not cover (the
48
- * picker fell back to English text, which must not be synthesized under
49
- * an ar/ko/ta hint), undefined otherwise (the phrase rides the speaker's
50
- * language, or carries no hint at all when the language is unknown).
51
- */
52
- export function fixedPhraseLanguage(
53
- table: Readonly<Record<string, unknown>>,
54
- language: string | undefined,
55
- ): string | undefined {
56
- return language !== undefined && !hasLocalizedEntry(table, language)
57
- ? "en"
58
- : undefined;
59
- }
@@ -470,14 +470,6 @@ export function getWorkspaceConfigPath(): string {
470
470
  return join(getWorkspaceDir(), "config.json");
471
471
  }
472
472
 
473
- /** Filename for the user-owned Agent Plugins MCP document. */
474
- export const WORKSPACE_MCP_FILENAME = "mcp.json";
475
-
476
- /** Returns $VELLUM_WORKSPACE_DIR/mcp.json */
477
- export function getWorkspaceMcpConfigPath(): string {
478
- return join(getWorkspaceDir(), WORKSPACE_MCP_FILENAME);
479
- }
480
-
481
473
  /** Returns $VELLUM_WORKSPACE_DIR/skills */
482
474
  export function getWorkspaceSkillsDir(): string {
483
475
  return join(getWorkspaceDir(), "skills");
@@ -17,10 +17,6 @@ import { getLogger } from "../util/logger.js";
17
17
  import { Mutex } from "../util/mutex.js";
18
18
  import { addToPathEnv, getExtraToolPathDirs } from "../util/platform.js";
19
19
  import { PromiseGuard } from "../util/promise-guard.js";
20
- import {
21
- collectDirtyPathsFromPorcelain,
22
- parsePorcelainZ,
23
- } from "./git-porcelain.js";
24
20
 
25
21
  const execFileAsync = promisify(execFile);
26
22
  const log = getLogger("workspace-git");
@@ -230,6 +226,59 @@ done
230
226
  exit 0
231
227
  `;
232
228
 
229
+ /**
230
+ * Parse NUL-terminated `git status --porcelain -z` output into status/path
231
+ * pairs. NUL termination is required so paths with special characters
232
+ * (non-ASCII, quotes, newlines) arrive verbatim instead of C-style quoted.
233
+ * A rename/copy record is followed by a bare origin-path entry, which is
234
+ * skipped.
235
+ */
236
+ function parsePorcelainZ(
237
+ stdout: string,
238
+ ): Array<{ status: string; path: string }> {
239
+ const entries = stdout.split("\0");
240
+ const parsed: Array<{ status: string; path: string }> = [];
241
+ for (let i = 0; i < entries.length; i++) {
242
+ const entry = entries[i] ?? "";
243
+ if (entry.length < 4) {
244
+ continue;
245
+ }
246
+ const status = entry.substring(0, 2);
247
+ parsed.push({ status, path: entry.substring(3) });
248
+ if (status[0] === "R" || status[0] === "C") {
249
+ i++;
250
+ }
251
+ }
252
+ return parsed;
253
+ }
254
+
255
+ /**
256
+ * Every path git status knows about, including rename/copy origins.
257
+ * Origins are skipped by {@link parsePorcelainZ} (they have no status
258
+ * prefix) but still need to be staged so a deletion is not left behind
259
+ * when a rename is split across add batches.
260
+ */
261
+ function collectDirtyPathsFromPorcelain(stdout: string): string[] {
262
+ const entries = stdout.split("\0");
263
+ const paths: string[] = [];
264
+ for (let i = 0; i < entries.length; i++) {
265
+ const entry = entries[i] ?? "";
266
+ if (entry.length < 4) {
267
+ continue;
268
+ }
269
+ const status = entry.substring(0, 2);
270
+ paths.push(entry.substring(3));
271
+ if (status[0] === "R" || status[0] === "C") {
272
+ i++;
273
+ const origin = entries[i];
274
+ if (origin && origin.length > 0) {
275
+ paths.push(origin);
276
+ }
277
+ }
278
+ }
279
+ return paths;
280
+ }
281
+
233
282
  /** Properties added by Node's child_process errors. */
234
283
  interface ExecError extends Error {
235
284
  killed?: boolean;
@@ -384,7 +433,7 @@ export class WorkspaceGitService {
384
433
  *
385
434
  * This method is always called inside the mutex, so no git operation from
386
435
  * our code can be concurrently holding the lock. However, an external git
387
- * git process (user running `git add`, IDE tooling, etc.) could legitimately
436
+ * process (user running `git add`, IDE tooling, etc.) could legitimately
388
437
  * hold the lock. We use `lsof` to check — if any process has the file
389
438
  * open, we leave it alone. If no process holds it, it's stale (crashed
390
439
  * process) and safe to remove.
@@ -151,9 +151,6 @@ import { sttFluxProviderToModelFamilyMigration } from "./150-stt-flux-provider-t
151
151
  import { repairRenamedFireworksDeepseekProModelIdMigration } from "./151-repair-renamed-fireworks-deepseek-pro-model-id.js";
152
152
  import { repairRetiredFireworksMinimaxM2p7ModelIdMigration } from "./152-repair-retired-fireworks-minimax-m2p7-model-id.js";
153
153
  import { stripMcpPolicyFieldsMigration } from "./153-strip-mcp-policy-fields.js";
154
- import { repairRetiredCodexGpt54ModelIdsMigration } from "./154-repair-retired-codex-gpt-5-4-model-ids.js";
155
- import { moveFrontModelConfigToVoiceMigration } from "./155-move-front-model-config-to-voice.js";
156
- import { extractWorkspaceMcpJsonMigration } from "./156-extract-workspace-mcp-json.js";
157
154
  import { migrateToWorkspaceVolumeMigration } from "./migrate-to-workspace-volume.js";
158
155
  import type { WorkspaceMigration } from "./types.js";
159
156
 
@@ -320,7 +317,4 @@ export const WORKSPACE_MIGRATIONS: WorkspaceMigration[] = [
320
317
  repairRenamedFireworksDeepseekProModelIdMigration,
321
318
  repairRetiredFireworksMinimaxM2p7ModelIdMigration,
322
319
  stripMcpPolicyFieldsMigration,
323
- repairRetiredCodexGpt54ModelIdsMigration,
324
- moveFrontModelConfigToVoiceMigration,
325
- extractWorkspaceMcpJsonMigration,
326
320
  ];
@@ -1,186 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
3
- import { tmpdir } from "node:os";
4
- import { join } from "node:path";
5
-
6
- import { writeDesktopWindowTheme } from "../src/desktop/desktop-window-theme.js";
7
-
8
- if (process.platform !== "linux") {
9
- throw new Error(
10
- "Run in a disposable Linux environment with Xvfb, Openbox, xterm, xdotool, xwininfo and xmllint installed",
11
- );
12
- }
13
-
14
- const directory = await mkdtemp(join(tmpdir(), "window-theme-smoke-"));
15
- const env = { ...process.env, DISPLAY: ":97" };
16
- const children: ReturnType<typeof Bun.spawn>[] = [];
17
- const start = (cmd: string[]) => {
18
- const child = Bun.spawn(cmd, {
19
- env,
20
- stdout: "ignore",
21
- stderr: "inherit",
22
- windowsHide: true,
23
- });
24
- children.push(child);
25
- return child;
26
- };
27
- const run = (...cmd: string[]) => {
28
- const result = Bun.spawnSync(cmd, {
29
- env,
30
-
31
- windowsHide: true,
32
- });
33
- assert.equal(result.exitCode, 0, result.stderr.toString());
34
- return result.stdout.toString();
35
- };
36
- const waitFor = async (predicate: () => boolean) => {
37
- for (let attempt = 0; attempt < 100; attempt++) {
38
- try {
39
- if (predicate()) {
40
- return;
41
- }
42
- } catch {}
43
- await Bun.sleep(50);
44
- }
45
- throw new Error("Window theme smoke condition timed out");
46
- };
47
- const pointer = async (...args: string[]) => {
48
- run("xdotool", ...args);
49
- await Bun.sleep(200);
50
- };
51
- const geometry = () => {
52
- const info = run("xwininfo", "-name", "Theme smoke");
53
- return [
54
- "Absolute upper-left X",
55
- "Absolute upper-left Y",
56
- "Width",
57
- "Height",
58
- ].map((label) =>
59
- Number(new RegExp(`${label}:\\s+(-?\\d+)`).exec(info)?.[1]),
60
- ) as [number, number, number, number];
61
- };
62
- const state = () => run("xprop", "-name", "Theme smoke", "_NET_WM_STATE");
63
- const button = async (offset: number) => {
64
- const [x, y, width] = geometry();
65
- await pointer(
66
- "mousemove",
67
- String(x + width - offset),
68
- String(y - 16),
69
- "click",
70
- "1",
71
- );
72
- };
73
-
74
- try {
75
- const home = join(directory, "home");
76
- const sourceDir = join(home, ".config", "openbox");
77
- await mkdir(sourceDir, { recursive: true });
78
- const original = await readFile("/etc/xdg/openbox/rc.xml", "utf8");
79
- const mouse = original.match(/<mouse>[^]*?<\/mouse>/)![0];
80
- const keyboard = original.match(/<keyboard>[^]*?<\/keyboard>/)![0];
81
- const source = original
82
- .replace(
83
- mouse,
84
- `<xi:include href="bindings.xml" xpointer="xpointer(/*/*)"/>`,
85
- )
86
- .replace(keyboard, "");
87
- const sourcePath = join(sourceDir, "rc.xml");
88
- await writeFile(sourcePath, source);
89
- await writeFile(
90
- join(sourceDir, "bindings.xml"),
91
- `<bindings xmlns="http://openbox.org/3.4/rc">${mouse}${keyboard}</bindings>`,
92
- );
93
- const configDir = join(directory, "managed & theme");
94
- const config = writeDesktopWindowTheme(configDir, home, "#53b7aa");
95
- const expanded = run("xmllint", "--xinclude", config);
96
- assert(
97
- expanded.includes("NextWindow") && expanded.includes("Move"),
98
- "Relative includes must retain the actual keyboard and mouse bindings",
99
- );
100
- assert.equal(await readFile(sourcePath, "utf8"), source);
101
- writeDesktopWindowTheme(configDir, home, "#bc7799");
102
- writeDesktopWindowTheme(configDir, home, "#53b7aa");
103
- assert.equal(await readFile(sourcePath, "utf8"), source);
104
- const blocked = join(directory, "blocked");
105
- await writeFile(blocked, "not a directory");
106
- assert.throws(() => writeDesktopWindowTheme(blocked, home));
107
- assert.equal(await readFile(sourcePath, "utf8"), source);
108
- const customBase = source.replace(
109
- "<openbox_config",
110
- '<openbox_config xml:base="./custom/"',
111
- );
112
- await writeFile(sourcePath, customBase);
113
- assert.throws(() => writeDesktopWindowTheme(configDir, home));
114
- assert.equal(await readFile(sourcePath, "utf8"), customBase);
115
- await writeFile(sourcePath, source);
116
-
117
- start(["Xvfb", ":97", "-screen", "0", "1100x700x24"]);
118
- await waitFor(
119
- () => run("xdotool", "getdisplaygeometry").trim() === "1100 700",
120
- );
121
- start(["openbox", "--config-file", config]);
122
- await waitFor(() =>
123
- run("xprop", "-root", "_OB_THEME").includes("window-theme"),
124
- );
125
- start([
126
- "xterm",
127
- "-title",
128
- "Theme smoke",
129
- "-geometry",
130
- "48x12+450+250",
131
- "-e",
132
- "sleep",
133
- "60",
134
- ]);
135
- await waitFor(
136
- () => run("xdotool", "search", "--name", "^Theme smoke$").trim().length > 0,
137
- );
138
- await Bun.sleep(300);
139
- const id = run("xdotool", "search", "--name", "^Theme smoke$").trim();
140
- await button(39);
141
- assert(
142
- state().includes("MAXIMIZED_HORZ") && state().includes("MAXIMIZED_VERT"),
143
- );
144
- await button(39);
145
- assert(!state().includes("MAXIMIZED"));
146
- await button(62);
147
- assert(state().includes("HIDDEN"));
148
- await pointer("windowactivate", id);
149
- assert(!state().includes("HIDDEN"));
150
- let [x, y, width, height] = geometry();
151
- await pointer("mousemove", String(x + 100), String(y - 16), "mousedown", "1");
152
- await pointer("mousemove", String(x + 90), String(y - 26));
153
- await pointer("mousemove", String(x + 45), String(y - 65));
154
- await pointer("mouseup", "1");
155
- const [movedX, movedY] = geometry();
156
- assert(Math.abs(movedX - x) >= 30 && Math.abs(movedY - y) >= 30);
157
- [x, y, width, height] = geometry();
158
- await pointer(
159
- "mousemove",
160
- String(x + width - 2),
161
- String(y + height + 2),
162
- "mousedown",
163
- "1",
164
- );
165
- await pointer("mousemove", String(x + width + 10), String(y + height + 10));
166
- await pointer("mousemove", String(x + width + 60), String(y + height + 40));
167
- await pointer("mouseup", "1");
168
- const [, , resizedWidth, resizedHeight] = geometry();
169
- assert(resizedWidth > width && resizedHeight > height);
170
- await pointer("key", "alt+F4");
171
- await waitFor(
172
- () =>
173
- !run("xprop", "-root", "_NET_CLIENT_LIST").includes(
174
- `0x${Number(id).toString(16)}`,
175
- ),
176
- );
177
- console.log(
178
- "PASS: relative includes, preserved source, repeated theme updates, maximize/restore, minimize/activation, dragging, resizing, and keyboard close",
179
- );
180
- } finally {
181
- for (const child of children.reverse()) {
182
- child.kill();
183
- }
184
- await Promise.all(children.map((child) => child.exited));
185
- await rm(directory, { recursive: true, force: true });
186
- }
@@ -1,47 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
-
3
- import {
4
- collectDirtyPathsFromPorcelain,
5
- parsePorcelainZ,
6
- } from "../workspace/git-porcelain.js";
7
-
8
- describe("parsePorcelainZ", () => {
9
- test("parses ordinary modified and untracked records", () => {
10
- expect(parsePorcelainZ("M tracked.txt\0?? new.txt\0")).toEqual([
11
- { status: "M ", path: "tracked.txt" },
12
- { status: "??", path: "new.txt" },
13
- ]);
14
- });
15
-
16
- test("captures rename origin on the same record", () => {
17
- expect(parsePorcelainZ("R dest.txt\0src.txt\0")).toEqual([
18
- { status: "R ", path: "dest.txt", origin: "src.txt" },
19
- ]);
20
- });
21
-
22
- test("captures copy origin on the same record", () => {
23
- expect(parsePorcelainZ("C dest.txt\0src.txt\0")).toEqual([
24
- { status: "C ", path: "dest.txt", origin: "src.txt" },
25
- ]);
26
- });
27
-
28
- test("skips short leftover NUL entries", () => {
29
- expect(parsePorcelainZ("M a.txt\0\0")).toEqual([
30
- { status: "M ", path: "a.txt" },
31
- ]);
32
- });
33
- });
34
-
35
- describe("collectDirtyPathsFromPorcelain", () => {
36
- test("flattens rename origin so both paths are staged", () => {
37
- expect(
38
- collectDirtyPathsFromPorcelain("R dest.txt\0src.txt\0?? extra.txt\0"),
39
- ).toEqual(["dest.txt", "src.txt", "extra.txt"]);
40
- });
41
-
42
- test("dedupes a path that appears as both origin and destination", () => {
43
- expect(collectDirtyPathsFromPorcelain("R same.txt\0same.txt\0")).toEqual([
44
- "same.txt",
45
- ]);
46
- });
47
- });