@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
@@ -13,11 +13,8 @@ describe("forwardComputerUseProxyTool", () => {
13
13
  );
14
14
 
15
15
  expect(result.isError).toBe(true);
16
- expect(result.content).toContain(
17
- "The Vellum desktop app is needed to view or control your screen",
18
- );
19
- expect(result.content).toContain("https://www.vellum.ai/downloads");
20
- expect(result.content).not.toContain("computer_use_click");
16
+ expect(result.content).toContain("no proxy resolver available");
17
+ expect(result.content).toContain("computer_use_click");
21
18
  });
22
19
 
23
20
  test("forwards to proxyToolResolver when present", async () => {
@@ -104,10 +104,7 @@ describe("computer_use_click (unified)", () => {
104
104
  test("execute returns isError when no proxy resolver is configured", async () => {
105
105
  const result = await computerUseClickTool.execute({}, ctx);
106
106
  expect(result.isError).toBe(true);
107
- expect(result.content).toContain(
108
- "The Vellum desktop app is needed to view or control your screen",
109
- );
110
- expect(result.content).toContain("https://www.vellum.ai/downloads");
107
+ expect(result.content).toMatch(/No proxy resolver/);
111
108
  });
112
109
  });
113
110
 
@@ -122,10 +119,7 @@ describe("computer_use_type_text", () => {
122
119
  test("execute returns isError when no proxy resolver is configured", async () => {
123
120
  const result = await computerUseTypeTextTool.execute({}, ctx);
124
121
  expect(result.isError).toBe(true);
125
- expect(result.content).toContain(
126
- "The Vellum desktop app is needed to view or control your screen",
127
- );
128
- expect(result.content).toContain("https://www.vellum.ai/downloads");
122
+ expect(result.content).toMatch(/No proxy resolver/);
129
123
  });
130
124
  });
131
125
 
@@ -140,10 +134,7 @@ describe("computer_use_key", () => {
140
134
  test("execute returns isError when no proxy resolver is configured", async () => {
141
135
  const result = await computerUseKeyTool.execute({}, ctx);
142
136
  expect(result.isError).toBe(true);
143
- expect(result.content).toContain(
144
- "The Vellum desktop app is needed to view or control your screen",
145
- );
146
- expect(result.content).toContain("https://www.vellum.ai/downloads");
137
+ expect(result.content).toMatch(/No proxy resolver/);
147
138
  });
148
139
  });
149
140
 
@@ -250,11 +241,8 @@ describe("forwardComputerUseProxyTool", () => {
250
241
  );
251
242
 
252
243
  expect(result.isError).toBe(true);
253
- expect(result.content).toContain(
254
- "The Vellum desktop app is needed to view or control your screen",
255
- );
256
- expect(result.content).toContain("https://www.vellum.ai/downloads");
257
- expect(result.content).not.toContain("computer_use_click");
244
+ expect(result.content).toContain("no proxy resolver available");
245
+ expect(result.content).toContain("computer_use_click");
258
246
  });
259
247
 
260
248
  test("delegates to proxy resolver when available", async () => {
@@ -130,7 +130,7 @@ describe("getSchemaAtPath", () => {
130
130
  expect(properties).toBeDefined();
131
131
  expect(properties.enabled).toBeDefined();
132
132
  expect(properties.voice).toBeDefined();
133
- expect(properties.disclosure).toBeDefined();
133
+ expect(properties.safety).toBeDefined();
134
134
  });
135
135
 
136
136
  test("navigates through .transform() wrappers (ingress → object schema)", () => {
@@ -230,7 +230,7 @@ describe("z.toJSONSchema integration", () => {
230
230
  expect(properties).toBeDefined();
231
231
  expect(properties!.enabled).toBeDefined();
232
232
  expect(properties!.voice).toBeDefined();
233
- expect(properties!.disclosure).toBeDefined();
233
+ expect(properties!.safety).toBeDefined();
234
234
  });
235
235
 
236
236
  test("sub-schema at a leaf like llm.profileSession.defaultTtlSeconds produces integer schema", () => {
@@ -952,7 +952,11 @@ describe("AssistantConfigSchema", () => {
952
952
  enabled: true,
953
953
  text: 'At the very beginning of the call, introduce yourself as an assistant calling on behalf of the person you represent. Do not say "AI assistant".',
954
954
  },
955
+ safety: {
956
+ denyCategories: [],
957
+ },
955
958
  voice: {
959
+ interruptSensitivity: "low",
956
960
  telephonyStreaming: true,
957
961
  utteranceEndMs: 1000,
958
962
  },
@@ -974,6 +978,7 @@ describe("AssistantConfigSchema", () => {
974
978
  maxDurationSeconds: 1800,
975
979
  userConsultTimeoutSeconds: 60,
976
980
  disclosure: { enabled: false, text: "Custom disclosure" },
981
+ safety: { denyCategories: ["spam"] },
977
982
  },
978
983
  });
979
984
  expect(result.calls.enabled).toBe(false);
@@ -981,6 +986,7 @@ describe("AssistantConfigSchema", () => {
981
986
  expect(result.calls.userConsultTimeoutSeconds).toBe(60);
982
987
  expect(result.calls.disclosure.enabled).toBe(false);
983
988
  expect(result.calls.disclosure.text).toBe("Custom disclosure");
989
+ expect(result.calls.safety.denyCategories).toEqual(["spam"]);
984
990
  });
985
991
 
986
992
  // ── Live voice config ───────────────────────────────────────────────
@@ -988,6 +994,7 @@ describe("AssistantConfigSchema", () => {
988
994
  test("applies liveVoice defaults", () => {
989
995
  const result = AssistantConfigSchema.parse({});
990
996
  expect(result.liveVoice).toEqual({
997
+ mode: "open-mic",
991
998
  vad: {
992
999
  speechEnergyThreshold: 800,
993
1000
  noiseFloorMargin: 3,
@@ -998,18 +1005,6 @@ describe("AssistantConfigSchema", () => {
998
1005
  echoEmaHalfLifeMs: 400,
999
1006
  echoDrainSlackMs: 300,
1000
1007
  },
1001
- flux: {
1002
- turnEnd: { enabled: true },
1003
- eotThreshold: 0.7,
1004
- eotTimeoutMs: 5000,
1005
- },
1006
- archiveAudio: false,
1007
- });
1008
- });
1009
-
1010
- test("applies voice defaults", () => {
1011
- const result = AssistantConfigSchema.parse({});
1012
- expect(result.voice).toEqual({
1013
1008
  frontModel: {
1014
1009
  endpointDecisionTimeoutMs: 1200,
1015
1010
  endpointExtensionMs: 1500,
@@ -1024,24 +1019,35 @@ describe("AssistantConfigSchema", () => {
1024
1019
  generationTimeoutMs: 1500,
1025
1020
  },
1026
1021
  },
1022
+ flux: {
1023
+ turnEnd: { enabled: true },
1024
+ eotThreshold: 0.7,
1025
+ eotTimeoutMs: 5000,
1026
+ },
1027
+ maxSessionDurationSeconds: 1800,
1028
+ archiveAudio: false,
1027
1029
  });
1028
1030
  });
1029
1031
 
1030
1032
  test("accepts valid liveVoice config overrides", () => {
1031
1033
  const result = AssistantConfigSchema.parse({
1032
1034
  liveVoice: {
1035
+ mode: "ptt",
1033
1036
  vad: {
1034
1037
  speechEnergyThreshold: 1500,
1035
1038
  silenceThresholdMs: 1000,
1036
1039
  bargeInMinSpeechMs: 120,
1037
1040
  },
1041
+ maxSessionDurationSeconds: 900,
1038
1042
  },
1039
1043
  });
1044
+ expect(result.liveVoice.mode).toBe("ptt");
1040
1045
  expect(result.liveVoice.vad.speechEnergyThreshold).toBe(1500);
1041
1046
  expect(result.liveVoice.vad.silenceThresholdMs).toBe(1000);
1042
1047
  expect(result.liveVoice.vad.bargeInMinSpeechMs).toBe(120);
1043
1048
  // Unspecified vad fields still get defaults
1044
1049
  expect(result.liveVoice.vad.maxTurnDurationMs).toBe(30000);
1050
+ expect(result.liveVoice.maxSessionDurationSeconds).toBe(900);
1045
1051
  // A partial liveVoice override leaves Flux turn-end disabled by default.
1046
1052
  expect(result.liveVoice.flux.turnEnd.enabled).toBe(true);
1047
1053
  });
@@ -1130,10 +1136,18 @@ describe("AssistantConfigSchema", () => {
1130
1136
  expect(result.success).toBe(false);
1131
1137
  });
1132
1138
 
1139
+ test("rejects non-array calls.safety.denyCategories", () => {
1140
+ const result = AssistantConfigSchema.safeParse({
1141
+ calls: { safety: { denyCategories: "spam" } },
1142
+ });
1143
+ expect(result.success).toBe(false);
1144
+ });
1145
+
1133
1146
  // ── Calls voice config ──────────────────────────────────────────────
1134
1147
 
1135
1148
  test("config without calls.voice parses correctly and produces defaults", () => {
1136
1149
  const result = AssistantConfigSchema.parse({});
1150
+ expect(result.calls.voice.interruptSensitivity).toBe("low");
1137
1151
  expect(result.calls.voice.telephonyStreaming).toBe(true);
1138
1152
  expect(result.calls.voice.utteranceEndMs).toBe(1000);
1139
1153
  });
@@ -1151,23 +1165,9 @@ describe("AssistantConfigSchema", () => {
1151
1165
  expect(result.calls.voice.utteranceEndMs).toBe(2500);
1152
1166
  });
1153
1167
 
1154
- test("persisted calls configs carrying retired keys keep parsing", () => {
1155
- // Zod strips unrecognized keys, so a persisted config that still carries
1156
- // `calls.safety.denyCategories` or `calls.voice.interruptSensitivity`
1157
- // parses.
1158
- const result = AssistantConfigSchema.parse({
1159
- calls: {
1160
- safety: { denyCategories: ["spam"] },
1161
- voice: { interruptSensitivity: "high" },
1162
- },
1163
- });
1164
- expect(result.calls).not.toHaveProperty("safety");
1165
- expect(result.calls.voice).not.toHaveProperty("interruptSensitivity");
1166
- });
1167
-
1168
1168
  test("language is no longer part of the voice config schema", () => {
1169
- // Zod strips the unrecognized key, so persisted configs that still carry
1170
- // it parse.
1169
+ // The retired knob was read by nothing; Zod strips the unrecognized key
1170
+ // so persisted configs that still carry it keep parsing.
1171
1171
  const result = AssistantConfigSchema.parse({
1172
1172
  calls: { voice: { language: "es-ES" } },
1173
1173
  });
@@ -2633,6 +2633,7 @@ describe("loadConfig with schema validation", () => {
2633
2633
  expect(config.calls.maxDurationSeconds).toBe(3600);
2634
2634
  expect(config.calls.userConsultTimeoutSeconds).toBe(120);
2635
2635
  expect(config.calls.disclosure.enabled).toBe(true);
2636
+ expect(config.calls.safety.denyCategories).toEqual([]);
2636
2637
  expect(
2637
2638
  (config.calls.voice as Record<string, unknown>).language,
2638
2639
  ).toBeUndefined();
@@ -0,0 +1,359 @@
1
+ import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import {
5
+ afterAll,
6
+ afterEach,
7
+ beforeEach,
8
+ describe,
9
+ expect,
10
+ mock,
11
+ test,
12
+ } from "bun:test";
13
+
14
+ const WORKSPACE_DIR = mkdtempSync(join(tmpdir(), "vellum-skills-watch-"));
15
+ const SKILLS_DIR = join(WORKSPACE_DIR, "skills");
16
+ process.env.VELLUM_WORKSPACE_DIR = WORKSPACE_DIR;
17
+
18
+ type WatchCallback = (eventType: string, filename: string | null) => void;
19
+
20
+ interface CapturedWatcher {
21
+ dir: string;
22
+ callback: WatchCallback;
23
+ closed: boolean;
24
+ }
25
+
26
+ const capturedWatchers: CapturedWatcher[] = [];
27
+ let recursiveWatchAvailable = false;
28
+
29
+ // The skills watcher reacts by calling these directly; count the calls so tests
30
+ // can assert dispatch without driving real eviction or skill-memory reseeding.
31
+ let evictCalls = 0;
32
+ let skillsChangedCalls = 0;
33
+
34
+ mock.module("node:fs", () => {
35
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
36
+ const actual = require("node:fs");
37
+ return {
38
+ ...actual,
39
+ watch: (dir: string, ...args: unknown[]) => {
40
+ let callback: WatchCallback;
41
+
42
+ if (typeof args[0] === "function") {
43
+ callback = args[0] as WatchCallback;
44
+ } else {
45
+ if (
46
+ (args[0] as { recursive?: boolean } | undefined)?.recursive &&
47
+ !recursiveWatchAvailable
48
+ ) {
49
+ throw new Error("recursive watch unavailable");
50
+ }
51
+ callback = args[1] as WatchCallback;
52
+ }
53
+
54
+ const capturedWatcher: CapturedWatcher = {
55
+ dir,
56
+ callback,
57
+ closed: false,
58
+ };
59
+ capturedWatchers.push(capturedWatcher);
60
+ return {
61
+ close: () => {
62
+ capturedWatcher.closed = true;
63
+ },
64
+ on: () => {},
65
+ };
66
+ },
67
+ };
68
+ });
69
+
70
+ mock.module("../persistence/embeddings/embedding-backend.js", () => ({
71
+ clearEmbeddingBackendCache: () => {},
72
+ }));
73
+
74
+ mock.module("../persistence/cleanup-schedule-state.js", () => ({
75
+ resetCleanupScheduleThrottle: () => {},
76
+ }));
77
+
78
+ mock.module("../providers/registry.js", () => ({
79
+ initializeProviders: async () => {},
80
+ }));
81
+
82
+ mock.module("../daemon/mcp-reload-service.js", () => ({
83
+ reloadMcpServers: async () => {},
84
+ }));
85
+
86
+ mock.module("../signals/bash.js", () => ({
87
+ handleBashSignal: () => {},
88
+ }));
89
+
90
+ mock.module("../signals/cancel.js", () => ({
91
+ handleCancelSignal: () => {},
92
+ }));
93
+
94
+ mock.module("../signals/conversation-undo.js", () => ({
95
+ handleConversationUndoSignal: () => {},
96
+ }));
97
+
98
+ mock.module("../signals/emit-event.js", () => ({
99
+ handleEmitEventSignal: () => {},
100
+ }));
101
+
102
+ mock.module("../signals/user-message.js", () => ({
103
+ handleUserMessageSignal: () => {},
104
+ }));
105
+
106
+ mock.module("../daemon/conversation-store.js", () => ({
107
+ evictConversationsForReload: () => {
108
+ evictCalls++;
109
+ },
110
+ }));
111
+
112
+ mock.module("../daemon/skill-memory-refresh.js", () => ({
113
+ refreshSkillCapabilityMemories: () => {
114
+ skillsChangedCalls++;
115
+ },
116
+ }));
117
+
118
+ mock.module("../runtime/sync/resource-sync-events.js", () => ({
119
+ publishIdentityChanged: () => {},
120
+ publishConfigChanged: () => {},
121
+ publishSoundsConfigUpdated: () => {},
122
+ publishAvatarChanged: () => {},
123
+ publishWorkspaceThemeChanged: () => {},
124
+ }));
125
+
126
+ mock.module("../platform/sync-identity.js", () => ({
127
+ syncIdentityNameToPlatform: () => {},
128
+ }));
129
+
130
+ const { ConfigWatcher } = await import("../daemon/config-watcher.js");
131
+
132
+ function findWatcher(dir: string): CapturedWatcher | undefined {
133
+ return capturedWatchers.find((w) => w.dir === dir && !w.closed);
134
+ }
135
+
136
+ function findCapturedWatcher(dir: string): CapturedWatcher | undefined {
137
+ return capturedWatchers.find((w) => w.dir === dir);
138
+ }
139
+
140
+ function capturedWatcherCount(dir: string): number {
141
+ return capturedWatchers.filter((w) => w.dir === dir).length;
142
+ }
143
+
144
+ describe("ConfigWatcher skills watcher reseeding", () => {
145
+ let watcher: InstanceType<typeof ConfigWatcher>;
146
+
147
+ beforeEach(() => {
148
+ capturedWatchers.length = 0;
149
+ recursiveWatchAvailable = false;
150
+ rmSync(SKILLS_DIR, { recursive: true, force: true });
151
+ mkdirSync(SKILLS_DIR, { recursive: true });
152
+ evictCalls = 0;
153
+ skillsChangedCalls = 0;
154
+ watcher = new ConfigWatcher();
155
+ });
156
+
157
+ afterEach(() => {
158
+ watcher.stop();
159
+ });
160
+
161
+ afterAll(() => {
162
+ rmSync(WORKSPACE_DIR, { recursive: true, force: true });
163
+ });
164
+
165
+ describe("recursive watcher", () => {
166
+ beforeEach(() => {
167
+ recursiveWatchAvailable = true;
168
+ });
169
+
170
+ test("reloads on SKILL.md changes", async () => {
171
+ watcher.start();
172
+
173
+ const skillsWatcher = findWatcher(SKILLS_DIR);
174
+ expect(skillsWatcher).toBeDefined();
175
+ skillsWatcher!.callback("change", "example-skill/SKILL.md");
176
+
177
+ await new Promise((resolve) => setTimeout(resolve, 300));
178
+
179
+ expect(evictCalls).toBe(1);
180
+ expect(skillsChangedCalls).toBe(1);
181
+ });
182
+
183
+ test("reloads on TOOLS.json changes", async () => {
184
+ watcher.start();
185
+
186
+ const skillsWatcher = findWatcher(SKILLS_DIR);
187
+ skillsWatcher!.callback("change", "example-skill/TOOLS.json");
188
+
189
+ await new Promise((resolve) => setTimeout(resolve, 300));
190
+
191
+ expect(evictCalls).toBe(1);
192
+ expect(skillsChangedCalls).toBe(1);
193
+ });
194
+
195
+ test("ignores non-catalog files (scripts, references, build output)", async () => {
196
+ watcher.start();
197
+
198
+ const skillsWatcher = findWatcher(SKILLS_DIR);
199
+ skillsWatcher!.callback("change", "example-skill/references/foo.md");
200
+ skillsWatcher!.callback("change", "example-skill/dist/index.js");
201
+ skillsWatcher!.callback("change", "example-skill/package.json");
202
+ skillsWatcher!.callback("change", "example-skill/scripts/run.py");
203
+
204
+ await new Promise((resolve) => setTimeout(resolve, 300));
205
+
206
+ expect(evictCalls).toBe(0);
207
+ expect(skillsChangedCalls).toBe(0);
208
+ });
209
+
210
+ test("ignores catalog files under skipped dependency and staging paths", async () => {
211
+ watcher.start();
212
+
213
+ const skillsWatcher = findWatcher(SKILLS_DIR);
214
+ skillsWatcher!.callback(
215
+ "change",
216
+ "example-skill/node_modules/pkg/SKILL.md",
217
+ );
218
+ skillsWatcher!.callback(
219
+ "change",
220
+ ".install-staging/example-skill/SKILL.md",
221
+ );
222
+
223
+ await new Promise((resolve) => setTimeout(resolve, 300));
224
+
225
+ expect(evictCalls).toBe(0);
226
+ expect(skillsChangedCalls).toBe(0);
227
+ });
228
+
229
+ test("coalesces multiple catalog changes into one reload", async () => {
230
+ watcher.start();
231
+
232
+ const skillsWatcher = findWatcher(SKILLS_DIR);
233
+ skillsWatcher!.callback("change", "example-skill/SKILL.md");
234
+ skillsWatcher!.callback("change", "example-skill/package.json");
235
+ skillsWatcher!.callback("change", "example-skill/TOOLS.json");
236
+ skillsWatcher!.callback("change", "other-skill/SKILL.md");
237
+
238
+ await new Promise((resolve) => setTimeout(resolve, 300));
239
+
240
+ expect(evictCalls).toBe(1);
241
+ expect(skillsChangedCalls).toBe(1);
242
+ });
243
+ });
244
+
245
+ describe("per-skill-directory fallback", () => {
246
+ test("watches the root and each immediate skill directory, not nested subdirectories", () => {
247
+ const skillDir = join(SKILLS_DIR, "example-skill");
248
+ mkdirSync(join(skillDir, "references"), { recursive: true });
249
+ mkdirSync(join(skillDir, "dist"), { recursive: true });
250
+
251
+ watcher.start();
252
+
253
+ expect(findWatcher(SKILLS_DIR)).toBeDefined();
254
+ expect(findWatcher(skillDir)).toBeDefined();
255
+ expect(findWatcher(join(skillDir, "references"))).toBeUndefined();
256
+ expect(findWatcher(join(skillDir, "dist"))).toBeUndefined();
257
+ });
258
+
259
+ test("does not watch dependency or staging directories", () => {
260
+ const skillDir = join(SKILLS_DIR, "example-skill");
261
+ mkdirSync(join(skillDir, "node_modules", "pkg"), { recursive: true });
262
+ mkdirSync(join(SKILLS_DIR, ".install-staging", "staged"), {
263
+ recursive: true,
264
+ });
265
+
266
+ watcher.start();
267
+
268
+ expect(findWatcher(join(skillDir, "node_modules"))).toBeUndefined();
269
+ expect(findWatcher(join(SKILLS_DIR, ".install-staging"))).toBeUndefined();
270
+ expect(findWatcher(skillDir)).toBeDefined();
271
+ });
272
+
273
+ test("reloads on SKILL.md / TOOLS.json edits within a skill directory", async () => {
274
+ const skillDir = join(SKILLS_DIR, "example-skill");
275
+ mkdirSync(skillDir, { recursive: true });
276
+
277
+ watcher.start();
278
+ const skillWatcher = findWatcher(skillDir);
279
+ expect(skillWatcher).toBeDefined();
280
+
281
+ skillWatcher!.callback("change", "SKILL.md");
282
+ await new Promise((resolve) => setTimeout(resolve, 300));
283
+ expect(evictCalls).toBe(1);
284
+ expect(skillsChangedCalls).toBe(1);
285
+
286
+ skillWatcher!.callback("change", "TOOLS.json");
287
+ await new Promise((resolve) => setTimeout(resolve, 300));
288
+ expect(evictCalls).toBe(2);
289
+ expect(skillsChangedCalls).toBe(2);
290
+ });
291
+
292
+ test("ignores non-catalog file edits within a skill directory", async () => {
293
+ const skillDir = join(SKILLS_DIR, "example-skill");
294
+ mkdirSync(skillDir, { recursive: true });
295
+
296
+ watcher.start();
297
+ const skillWatcher = findWatcher(skillDir);
298
+
299
+ skillWatcher!.callback("change", "README.md");
300
+ skillWatcher!.callback("change", "package.json");
301
+ await new Promise((resolve) => setTimeout(resolve, 300));
302
+
303
+ expect(evictCalls).toBe(0);
304
+ expect(skillsChangedCalls).toBe(0);
305
+ });
306
+
307
+ test("watches and reloads when a skill is installed", async () => {
308
+ watcher.start();
309
+ const rootWatcher = findWatcher(SKILLS_DIR);
310
+ expect(rootWatcher).toBeDefined();
311
+
312
+ const newSkillDir = join(SKILLS_DIR, "new-skill");
313
+ mkdirSync(newSkillDir, { recursive: true });
314
+ rootWatcher!.callback("rename", "new-skill");
315
+
316
+ await new Promise((resolve) => setTimeout(resolve, 300));
317
+
318
+ expect(findWatcher(newSkillDir)).toBeDefined();
319
+ expect(evictCalls).toBe(1);
320
+ expect(skillsChangedCalls).toBe(1);
321
+ });
322
+
323
+ test("closes the watcher and reloads when a skill is removed", async () => {
324
+ const skillDir = join(SKILLS_DIR, "example-skill");
325
+ mkdirSync(skillDir, { recursive: true });
326
+
327
+ watcher.start();
328
+ expect(findWatcher(skillDir)).toBeDefined();
329
+
330
+ rmSync(skillDir, { recursive: true, force: true });
331
+ const rootWatcher = findWatcher(SKILLS_DIR);
332
+ rootWatcher!.callback("rename", "example-skill");
333
+
334
+ await new Promise((resolve) => setTimeout(resolve, 300));
335
+
336
+ expect(findWatcher(skillDir)).toBeUndefined();
337
+ expect(findCapturedWatcher(skillDir)?.closed).toBe(true);
338
+ expect(evictCalls).toBe(1);
339
+ expect(skillsChangedCalls).toBe(1);
340
+ });
341
+
342
+ test("does not duplicate a watcher or reload when the root changes but the skill set does not", async () => {
343
+ const skillDir = join(SKILLS_DIR, "example-skill");
344
+ mkdirSync(skillDir, { recursive: true });
345
+
346
+ watcher.start();
347
+ expect(capturedWatcherCount(skillDir)).toBe(1);
348
+
349
+ // A stray file event at the root: no skill directory added or removed.
350
+ const rootWatcher = findWatcher(SKILLS_DIR);
351
+ rootWatcher!.callback("change", "README.md");
352
+ await new Promise((resolve) => setTimeout(resolve, 300));
353
+
354
+ expect(capturedWatcherCount(skillDir)).toBe(1);
355
+ expect(evictCalls).toBe(0);
356
+ expect(skillsChangedCalls).toBe(0);
357
+ });
358
+ });
359
+ });
@@ -113,11 +113,8 @@ mock.module("../providers/registry.js", () => ({
113
113
  resolveProviderFromConnection: async () => null,
114
114
  }));
115
115
 
116
- let mcpReloadCallCount = 0;
117
116
  mock.module("../daemon/mcp-reload-service.js", () => ({
118
- reloadMcpServers: async () => {
119
- mcpReloadCallCount += 1;
120
- },
117
+ reloadMcpServers: async () => {},
121
118
  }));
122
119
 
123
120
  mock.module("../signals/conversation-undo.js", () => ({
@@ -157,6 +154,10 @@ mock.module("../runtime/sync/resource-sync-events.js", () => ({
157
154
  },
158
155
  }));
159
156
 
157
+ mock.module("../daemon/skill-memory-refresh.js", () => ({
158
+ refreshSkillCapabilityMemories: () => {},
159
+ }));
160
+
160
161
  mock.module("../platform/sync-identity.js", () => ({
161
162
  syncIdentityNameToPlatform: () => {},
162
163
  }));
@@ -182,7 +183,6 @@ function findFileWatch(filePath: string): CapturedFileWatch | undefined {
182
183
 
183
184
  const WORKSPACE_FILES = new Set([
184
185
  "config.json",
185
- "mcp.json",
186
186
  "SOUL.md",
187
187
  "IDENTITY.md",
188
188
  "ui/theme.json",
@@ -239,7 +239,6 @@ beforeEach(() => {
239
239
  evictCallCount = 0;
240
240
  identityCallCount = 0;
241
241
  themeCallCount = 0;
242
- mcpReloadCallCount = 0;
243
242
  watcher = new ConfigWatcher(undefined, TEST_DEBOUNCE_MS);
244
243
  });
245
244
 
@@ -271,9 +270,9 @@ describe("ConfigWatcher workspace file handlers", () => {
271
270
 
272
271
  test("unregistered workspace files are not subscribed (only the registered handler set is)", () => {
273
272
  watcher.start();
274
- // Per-file watching only registers config.json, mcp.json, SOUL.md,
275
- // IDENTITY.md, and ui/theme.json. The whole workspace dir must not
276
- // be watched either: that was the ENXIO-on-Unix-sockets bug.
273
+ // Per-file watching only registers config.json, SOUL.md, IDENTITY.md,
274
+ // and ui/theme.json. The whole workspace dir must not be watched
275
+ // either — that was the ENXIO-on-Unix-sockets bug.
277
276
  expect(findFileWatch(join(WORKSPACE_DIR, "OTHER.md"))).toBeUndefined();
278
277
  expect(findWatcher(WORKSPACE_DIR)).toBeUndefined();
279
278
  });
@@ -286,14 +285,6 @@ describe("ConfigWatcher workspace file handlers", () => {
286
285
  expect(evictCallCount).toBe(0);
287
286
  });
288
287
 
289
- test("mcp.json change reloads MCP servers without evicting conversations", async () => {
290
- watcher.start();
291
- simulateFileChange(WORKSPACE_DIR, "mcp.json");
292
- await new Promise((r) => setTimeout(r, WAIT_MS));
293
- expect(mcpReloadCallCount).toBe(1);
294
- expect(evictCallCount).toBe(0);
295
- });
296
-
297
288
  test("config.json change calls refreshConfigFromSources", async () => {
298
289
  let refreshCalled = false;
299
290
  watcher.refreshConfigFromSources = async () => {
@@ -341,12 +332,6 @@ describe("ConfigWatcher watcher lifecycle", () => {
341
332
  expect(findFileWatch(join(WORKSPACE_DIR, "IDENTITY.md"))).toBeDefined();
342
333
  });
343
334
 
344
- test("start does not watch the skills directory", () => {
345
- mkdirSync(join(WORKSPACE_DIR, "skills"), { recursive: true });
346
- watcher.start();
347
- expect(findWatcher(join(WORKSPACE_DIR, "skills"))).toBeUndefined();
348
- });
349
-
350
335
  test("stop cancels pending debounce work, no eviction fires after", async () => {
351
336
  watcher.start();
352
337
  simulateFileChange(WORKSPACE_DIR, "SOUL.md");
@@ -19,7 +19,7 @@ describe("isConnectionCompatibleWithModel", () => {
19
19
  expect(
20
20
  isConnectionCompatibleWithModel(
21
21
  { auth: { type: "oauth_subscription" } as never },
22
- "gpt-5.5",
22
+ "gpt-5.4",
23
23
  ),
24
24
  ).toBe(true);
25
25
  });
@@ -79,7 +79,7 @@ describe("describeSubscriptionModelIncompatibility", () => {
79
79
 
80
80
  test("returns null when model is Codex-compatible", () => {
81
81
  expect(
82
- describeSubscriptionModelIncompatibility([subscriptionConn], "gpt-5.5"),
82
+ describeSubscriptionModelIncompatibility([subscriptionConn], "gpt-5.4"),
83
83
  ).toBeNull();
84
84
  });
85
85
  });
@@ -59,7 +59,7 @@ describe("conversation-key-store disk view", () => {
59
59
 
60
60
  const meta = JSON.parse(readFileSync(metaPath, "utf-8"));
61
61
  expect(meta.id).toBe(first.conversationId);
62
- expect(meta.title).toBe("conversation.title.generating");
62
+ expect(meta.title).toBe("Generating title...");
63
63
  expect(meta.type).toBe("standard");
64
64
  expect(meta.channel).toBeNull();
65
65
  expect(readdirSync(conversationsDir)).toEqual([expectedDirName]);