@vellumai/assistant 0.11.3 → 0.11.4-dev.202608190019.b94dbf2

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 (596) hide show
  1. package/AGENTS.md +9 -3
  2. package/ARCHITECTURE.md +32 -10
  3. package/docs/architecture/memory.md +43 -5
  4. package/docs/architecture/security.md +96 -152
  5. package/docs/architecture/turn-actor.md +70 -0
  6. package/docs/browser-use-architecture-phase2.md +128 -56
  7. package/docs/flux-turn-detection-spike.md +248 -0
  8. package/docs/guardian-request-flow.md +35 -0
  9. package/docs/stt-provider-onboarding.md +3 -1
  10. package/knip.json +3 -0
  11. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  12. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  13. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  14. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  15. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
  16. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  17. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  18. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  19. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  20. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  21. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
  22. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  23. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +34 -0
  24. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +166 -1
  25. package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
  26. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  27. package/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  28. package/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  29. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  30. package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
  31. package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  32. package/openapi.yaml +289 -113
  33. package/package.json +1 -1
  34. package/scripts/voice-ttft-spike.ts +3 -3
  35. package/scripts/write-plugin-api-shim.ts +10 -0
  36. package/src/__tests__/app-compiler.test.ts +38 -3
  37. package/src/__tests__/app-control-flow.test.ts +1 -0
  38. package/src/__tests__/approval-routes-http.test.ts +2 -2
  39. package/src/__tests__/assistant-feature-flag-guard.test.ts +25 -3
  40. package/src/__tests__/attachments-store.test.ts +21 -12
  41. package/src/__tests__/byok-default-profile-ensure.test.ts +17 -0
  42. package/src/__tests__/call-setup-flow-name-capture.test.ts +0 -1
  43. package/src/__tests__/call-site-routing-provider.test.ts +1 -1
  44. package/src/__tests__/channel-availability-routes.test.ts +14 -1
  45. package/src/__tests__/channel-capabilities-dedupe.test.ts +214 -0
  46. package/src/__tests__/channel-delivery-store.test.ts +14 -14
  47. package/src/__tests__/channel-inbound-disk-pressure.test.ts +4 -4
  48. package/src/__tests__/channel-setup-panel-ack.test.ts +1 -1
  49. package/src/__tests__/checker.test.ts +20 -314
  50. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +6 -2
  51. package/src/__tests__/compaction-events.test.ts +8 -10
  52. package/src/__tests__/config-loader-backfill.test.ts +3 -3
  53. package/src/__tests__/config-schema.test.ts +25 -10
  54. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  55. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +8 -11
  56. package/src/__tests__/conversation-agent-loop-overflow.test.ts +8 -11
  57. package/src/__tests__/conversation-agent-loop.test.ts +32 -21
  58. package/src/__tests__/conversation-attachments.test.ts +0 -1
  59. package/src/__tests__/conversation-attention-store.test.ts +63 -0
  60. package/src/__tests__/conversation-confirmation-signals.test.ts +112 -0
  61. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -4
  62. package/src/__tests__/conversation-fork-crud.test.ts +69 -0
  63. package/src/__tests__/conversation-fork-referential.test.ts +67 -0
  64. package/src/__tests__/conversation-fork-retrospective.test.ts +24 -0
  65. package/src/__tests__/conversation-load-history-repair.test.ts +209 -0
  66. package/src/__tests__/conversation-notifiers-provenance.test.ts +59 -0
  67. package/src/__tests__/conversation-queue.test.ts +154 -4
  68. package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
  69. package/src/__tests__/conversation-routes-enabled-plugins.test.ts +1 -1
  70. package/src/__tests__/conversation-routes-guardian-reply.test.ts +9 -9
  71. package/src/__tests__/conversation-routes-hidden-queue.test.ts +1 -1
  72. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -1
  73. package/src/__tests__/conversation-runtime-assembly.test.ts +187 -102
  74. package/src/__tests__/conversation-runtime-workspace.test.ts +14 -10
  75. package/src/__tests__/conversation-slash-queue.test.ts +3 -0
  76. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
  77. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +1 -0
  78. package/src/__tests__/conversation-surfaces-app-control.test.ts +1 -0
  79. package/src/__tests__/conversation-surfaces-app-open.test.ts +1 -1
  80. package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -1
  81. package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +21 -14
  82. package/src/__tests__/conversation-surfaces-queued-emit.test.ts +1 -0
  83. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1 -0
  84. package/src/__tests__/conversation-surfaces-standalone.test.ts +1 -0
  85. package/src/__tests__/conversation-surfaces-state-update.test.ts +1 -1
  86. package/src/__tests__/conversation-surfaces-table-action.test.ts +1 -1
  87. package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -1
  88. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +1 -1
  89. package/src/__tests__/conversation-tool-setup-attribution.test.ts +1 -1
  90. package/src/__tests__/credential-prompt-route.test.ts +7 -10
  91. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  92. package/src/__tests__/cu-unified-flow.test.ts +1 -0
  93. package/src/__tests__/custom-profile-ensure.test.ts +5 -1
  94. package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
  95. package/src/__tests__/discord-access-request-privacy.test.ts +5 -1
  96. package/src/__tests__/discord-requester-notice-privacy.test.ts +3 -3
  97. package/src/__tests__/document-append-idempotency.test.ts +233 -0
  98. package/src/__tests__/document-sync-tags.test.ts +0 -75
  99. package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
  100. package/src/__tests__/edit-propagation.test.ts +107 -11
  101. package/src/__tests__/file-ops-service.test.ts +163 -30
  102. package/src/__tests__/filesystem-tools.test.ts +23 -24
  103. package/src/__tests__/gateway-only-guard.test.ts +2 -5
  104. package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
  105. package/src/__tests__/helpers/mock-actor-context.ts +49 -0
  106. package/src/__tests__/helpers/mock-conversation.ts +13 -1
  107. package/src/__tests__/host-file-read-tool.test.ts +16 -19
  108. package/src/__tests__/http-user-message-parity.test.ts +1 -1
  109. package/src/__tests__/init-feature-flag-overrides.test.ts +49 -0
  110. package/src/__tests__/injector-chain.test.ts +63 -41
  111. package/src/__tests__/injector-disk-pressure.test.ts +11 -23
  112. package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
  113. package/src/__tests__/llm-context-resolution.test.ts +73 -1
  114. package/src/__tests__/llm-schema.test.ts +5 -2
  115. package/src/__tests__/managed-skill-lifecycle.test.ts +7 -0
  116. package/src/__tests__/mcp-list-plugin-servers.test.ts +250 -0
  117. package/src/__tests__/media-generate-image.test.ts +131 -21
  118. package/src/__tests__/memory-retrieval-hook.test.ts +100 -7
  119. package/src/__tests__/messages-read-boundary-guard.test.ts +134 -0
  120. package/src/__tests__/mtime-cache.test.ts +3 -1
  121. package/src/__tests__/non-member-access-request.test.ts +0 -20
  122. package/src/__tests__/outbound-slack-persistence.test.ts +40 -1
  123. package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
  124. package/src/__tests__/plugin-api-store-credential.test.ts +261 -0
  125. package/src/__tests__/plugin-api-webhook-url.test.ts +10 -7
  126. package/src/__tests__/plugin-disabled-state.test.ts +2 -0
  127. package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
  128. package/src/__tests__/plugin-execution-context.test.ts +73 -0
  129. package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -1
  130. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +7 -4
  131. package/src/__tests__/plugin-secret-pattern-contribution.test.ts +1 -1
  132. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +14 -7
  133. package/src/__tests__/provider-commit-message-generator.test.ts +20 -0
  134. package/src/__tests__/proxy-approval-callback.test.ts +1 -0
  135. package/src/__tests__/qdrant-manager.test.ts +14 -1
  136. package/src/__tests__/reaction-persistence.test.ts +200 -7
  137. package/src/__tests__/require-fresh-approval.test.ts +0 -4
  138. package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
  139. package/src/__tests__/run-conversation-turn-persistence.test.ts +460 -105
  140. package/src/__tests__/run-due-schedules.test.ts +21 -0
  141. package/src/__tests__/scaffold-managed-skill-tool.test.ts +187 -18
  142. package/src/__tests__/schedule-routes.test.ts +23 -0
  143. package/src/__tests__/schedule-store.test.ts +17 -0
  144. package/src/__tests__/scoped-approval-grants.test.ts +11 -6
  145. package/src/__tests__/secret-ingress-channel.test.ts +0 -1
  146. package/src/__tests__/secret-ingress-http.test.ts +1 -1
  147. package/src/__tests__/send-endpoint-busy.test.ts +3 -3
  148. package/src/__tests__/skills.test.ts +32 -0
  149. package/src/__tests__/slack-edit-ordering-characterization.test.ts +0 -1
  150. package/src/__tests__/starter-task-flow.test.ts +5 -4
  151. package/src/__tests__/subagent-call-site-routing.test.ts +31 -19
  152. package/src/__tests__/subagent-fork-prompt-role.test.ts +1 -1
  153. package/src/__tests__/subagent-spawn-and-await.test.ts +18 -17
  154. package/src/__tests__/subagent-tool-gate-mode.test.ts +1 -1
  155. package/src/__tests__/subagent-tools.test.ts +81 -101
  156. package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +1 -0
  157. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
  158. package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
  159. package/src/__tests__/tool-executor.test.ts +68 -39
  160. package/src/__tests__/turn-events-store.test.ts +43 -0
  161. package/src/__tests__/ui-choice-copy-surfaces.test.ts +1 -1
  162. package/src/__tests__/ui-shape-teaching.test.ts +33 -0
  163. package/src/__tests__/ui-visual-surface.test.ts +1 -1
  164. package/src/__tests__/ui-voice-picker-surface.test.ts +128 -0
  165. package/src/__tests__/ui-work-result-surface.test.ts +1 -1
  166. package/src/__tests__/user-plugin-loader.test.ts +3 -1
  167. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  168. package/src/__tests__/visible-app-context.test.ts +16 -9
  169. package/src/__tests__/voice-config-update.test.ts +40 -0
  170. package/src/__tests__/voice-scoped-grant-consumer.test.ts +5 -3
  171. package/src/__tests__/voice-session-bridge.test.ts +85 -29
  172. package/src/__tests__/worker-entrypoint-guards.test.ts +54 -0
  173. package/src/__tests__/worker-plugin-surface.test.ts +77 -0
  174. package/src/__tests__/workspace-migration-142-consolidate-voice-front-door.test.ts +158 -0
  175. package/src/__tests__/workspace-migration-143-repair-deprecated-codex-model-id.test.ts +133 -0
  176. package/src/__tests__/workspace-migration-144-convert-stranded-subscription-openai-profiles.test.ts +316 -0
  177. package/src/__tests__/workspace-migration-145-collapse-profile-bindings-to-entries.test.ts +325 -0
  178. package/src/__tests__/workspace-migration-146-repair-retired-fireworks-deepseek-flash-model-id.test.ts +235 -0
  179. package/src/acp/__tests__/acp-claude-oauth.test.ts +10 -2
  180. package/src/acp/__tests__/auth-required.test.ts +161 -0
  181. package/src/acp/acp-claude-oauth.ts +19 -2
  182. package/src/acp/agent-process.test.ts +100 -0
  183. package/src/acp/agent-process.ts +29 -26
  184. package/src/acp/auth-required.ts +102 -0
  185. package/src/acp/session-manager.test.ts +119 -0
  186. package/src/acp/session-manager.ts +76 -3
  187. package/src/api/events/acp-auth-required.ts +55 -0
  188. package/src/api/events/host-file.ts +2 -2
  189. package/src/api/index.ts +7 -0
  190. package/src/api/surfaces.ts +12 -3
  191. package/src/apps/app-store.ts +3 -0
  192. package/src/bundler/package-resolver.ts +2 -30
  193. package/src/calls/__tests__/voice-session-bridge.test.ts +194 -11
  194. package/src/calls/__tests__/voice-triage-escalate.test.ts +102 -0
  195. package/src/calls/call-controller.ts +19 -3
  196. package/src/calls/call-setup-flow.ts +0 -1
  197. package/src/calls/media-stream-stt-session.ts +15 -0
  198. package/src/calls/voice-session-bridge.ts +115 -41
  199. package/src/calls/voice-triage-escalate.ts +105 -2
  200. package/src/channels/__tests__/plugin-channel-declarations.test.ts +161 -0
  201. package/src/channels/config.ts +13 -0
  202. package/src/channels/plugin-channel-declarations.ts +108 -0
  203. package/src/channels/types.ts +30 -0
  204. package/src/cli/AGENTS.md +5 -2
  205. package/src/cli/bundled-modules.ts +29 -0
  206. package/src/cli/commands/credentials.help.ts +2 -2
  207. package/src/cli/commands/db/repair.ts +4 -8
  208. package/src/cli/commands/domain.ts +6 -3
  209. package/src/cli/commands/email.ts +6 -3
  210. package/src/cli/commands/inference-providers.ts +1 -1
  211. package/src/cli/commands/keys.ts +8 -3
  212. package/src/cli/commands/mcp.help.ts +13 -4
  213. package/src/cli/commands/mcp.ts +9 -0
  214. package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
  215. package/src/cli/commands/memory/__tests__/memory-v3.test.ts +128 -5
  216. package/src/cli/commands/memory/index.help.ts +84 -28
  217. package/src/cli/commands/memory/index.ts +2 -0
  218. package/src/cli/commands/memory/memory-v2.ts +58 -54
  219. package/src/cli/commands/memory/memory-v3.ts +64 -0
  220. package/src/cli/commands/memory/memory-validate.ts +18 -0
  221. package/src/cli/commands/plugins.ts +85 -36
  222. package/src/cli/commands/schedules.ts +35 -1
  223. package/src/cli/commands/stt.help.ts +27 -2
  224. package/src/cli/commands/trust.ts +3 -14
  225. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +39 -0
  226. package/src/cli/lib/bundled-marketplace.json +14 -1
  227. package/src/cli/lib/upgrade-plugin.ts +42 -0
  228. package/src/config/__tests__/balanced-model-experiment.test.ts +278 -0
  229. package/src/config/__tests__/default-profile-catalog.test.ts +34 -2
  230. package/src/config/__tests__/default-provider.test.ts +6 -1
  231. package/src/config/__tests__/profile-materialization.test.ts +75 -19
  232. package/src/config/assistant-feature-flags.ts +36 -15
  233. package/src/config/balanced-model-experiment.ts +35 -0
  234. package/src/config/bundled-skills/acp/SKILL.md +6 -7
  235. package/src/config/bundled-skills/document-editor/SKILL.md +2 -2
  236. package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
  237. package/src/config/bundled-skills/image-studio/SKILL.md +5 -4
  238. package/src/config/bundled-skills/image-studio/TOOLS.json +1 -1
  239. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +101 -0
  240. package/src/config/bundled-skills/media-processing/services/preprocess.ts +14 -4
  241. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  242. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.test.ts +65 -0
  243. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +7 -1
  244. package/src/config/bundled-skills/settings/tools/shared.ts +16 -0
  245. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -1
  246. package/src/config/bundled-skills/skill-management/TOOLS.json +9 -3
  247. package/src/config/bundled-skills/subagent/SKILL.md +17 -12
  248. package/src/config/bundled-skills/subagent/TOOLS.json +4 -4
  249. package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +22 -1
  250. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +9 -2
  251. package/src/config/call-site-defaults.ts +11 -5
  252. package/src/config/default-profile-catalog.ts +179 -16
  253. package/src/config/default-profile-names.ts +4 -1
  254. package/src/config/default-provider-resolution.ts +4 -0
  255. package/src/config/feature-flag-registry.json +12 -12
  256. package/src/config/llm-context-resolution.ts +11 -3
  257. package/src/config/llm-resolver.ts +28 -1
  258. package/src/config/profile-materialization.ts +70 -22
  259. package/src/config/schemas/__tests__/live-voice.test.ts +107 -4
  260. package/src/config/schemas/call-site-catalog.ts +4 -4
  261. package/src/config/schemas/live-voice.ts +57 -23
  262. package/src/config/schemas/llm.ts +59 -32
  263. package/src/config/schemas/mcp.ts +23 -0
  264. package/src/config/schemas/plugin-updates.ts +6 -2
  265. package/src/config/schemas/stt.ts +1 -0
  266. package/src/config/skills.ts +9 -2
  267. package/src/context/outbound-sanitize.ts +96 -1
  268. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +1 -1
  269. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +82 -0
  270. package/src/daemon/conversation-agent-loop-handlers.ts +15 -10
  271. package/src/daemon/conversation-agent-loop.ts +31 -19
  272. package/src/daemon/conversation-messaging.ts +5 -1
  273. package/src/daemon/conversation-notifiers.ts +20 -10
  274. package/src/daemon/conversation-process.ts +20 -6
  275. package/src/daemon/conversation-runtime-assembly.ts +12 -6
  276. package/src/daemon/conversation-store.ts +14 -4
  277. package/src/daemon/conversation-surfaces.ts +75 -26
  278. package/src/daemon/conversation-tool-setup.ts +3 -6
  279. package/src/daemon/conversation.ts +154 -51
  280. package/src/daemon/doordash-steps.ts +2 -2
  281. package/src/daemon/lifecycle.ts +14 -1
  282. package/src/daemon/mcp-reload-service.ts +36 -6
  283. package/src/daemon/process-message.ts +13 -26
  284. package/src/daemon/providers-setup.ts +6 -3
  285. package/src/daemon/trust-context-types.ts +29 -0
  286. package/src/daemon/trust-context.ts +12 -7
  287. package/src/daemon/wake-conversation-ops.ts +3 -2
  288. package/src/daemon/windows-compiled-entry.ts +4 -0
  289. package/src/documents/document-store.ts +143 -240
  290. package/src/hooks/hook-loader.ts +3 -3
  291. package/src/hooks/registry.ts +103 -45
  292. package/src/hooks/types.ts +5 -0
  293. package/src/inbound/__tests__/oauth-callback-url.test.ts +83 -0
  294. package/src/inbound/oauth-callback-url.ts +61 -0
  295. package/src/ipc/gateway-client.test.ts +1 -1
  296. package/src/ipc/gateway-client.ts +14 -21
  297. package/src/ipc/gateway-flag-listener.ts +17 -3
  298. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +1 -104
  299. package/src/live-voice/__tests__/live-voice-events.test.ts +7 -8
  300. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +1050 -0
  301. package/src/live-voice/__tests__/live-voice-metrics.test.ts +115 -8
  302. package/src/live-voice/__tests__/live-voice-photo.test.ts +100 -0
  303. package/src/live-voice/__tests__/live-voice-progress.test.ts +60 -194
  304. package/src/live-voice/__tests__/live-voice-stt.test.ts +14 -0
  305. package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +29 -0
  306. package/src/live-voice/__tests__/live-voice-tts-session.test.ts +0 -483
  307. package/src/live-voice/__tests__/live-voice-vad.test.ts +0 -16
  308. package/src/live-voice/__tests__/progress-narration.test.ts +214 -0
  309. package/src/live-voice/live-voice-archive.ts +2 -0
  310. package/src/live-voice/live-voice-manager.ts +16 -3
  311. package/src/live-voice/live-voice-metrics.ts +57 -32
  312. package/src/live-voice/live-voice-photo.ts +1 -2
  313. package/src/live-voice/live-voice-session.ts +598 -323
  314. package/src/live-voice/progress-narration.ts +277 -0
  315. package/src/live-voice/protocol.ts +21 -1
  316. package/src/live-voice/windows-compiled-live-voice.ts +4 -0
  317. package/src/mcp/__tests__/effective-config.test.ts +238 -0
  318. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
  319. package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +200 -0
  320. package/src/mcp/__tests__/mcp-oauth-provider.test.ts +9 -9
  321. package/src/mcp/__tests__/plugin-server-credential-isolation.test.ts +95 -0
  322. package/src/mcp/client.ts +16 -11
  323. package/src/mcp/effective-config.ts +113 -0
  324. package/src/mcp/manager.ts +11 -6
  325. package/src/mcp/mcp-auth-orchestrator.ts +13 -22
  326. package/src/mcp/mcp-oauth-provider.ts +205 -240
  327. package/src/monitoring/__tests__/plugin-auto-update.test.ts +166 -3
  328. package/src/monitoring/control.ts +1 -0
  329. package/src/monitoring/db-integrity-sample.ts +4 -5
  330. package/src/monitoring/plugin-auto-update.ts +128 -24
  331. package/src/notifications/AGENTS.md +2 -0
  332. package/src/notifications/approval-card-data.ts +33 -0
  333. package/src/notifications/signal.ts +1 -0
  334. package/src/permissions/AGENTS.md +16 -0
  335. package/src/permissions/checker.test.ts +75 -122
  336. package/src/permissions/checker.ts +44 -560
  337. package/src/permissions/confirmation-guardian-request.test.ts +15 -11
  338. package/src/permissions/confirmation-guardian-request.ts +2 -2
  339. package/src/permissions/prompter.ts +1 -5
  340. package/src/permissions/question-guardian-request.test.ts +14 -6
  341. package/src/permissions/question-guardian-request.ts +1 -2
  342. package/src/persistence/attachments-store.ts +8 -1
  343. package/src/persistence/bookmark-crud.ts +3 -7
  344. package/src/persistence/conversation-attention-store.ts +16 -45
  345. package/src/persistence/conversation-crud.ts +33 -4
  346. package/src/persistence/conversation-lineage.ts +9 -0
  347. package/src/persistence/conversation-queries.ts +182 -52
  348. package/src/persistence/delivery-crud.ts +150 -36
  349. package/src/persistence/embeddings/qdrant-manager.ts +84 -49
  350. package/src/persistence/external-conversation-store.ts +32 -4
  351. package/src/persistence/llm-request-log-store.ts +4 -10
  352. package/src/persistence/llm-usage-store.ts +8 -3
  353. package/src/persistence/message-reads.test.ts +197 -0
  354. package/src/persistence/message-reads.ts +211 -0
  355. package/src/persistence/migrations/360-add-document-workspace-path.ts +5 -14
  356. package/src/persistence/migrations/366-chatgpt-subscription-row-identity.test.ts +120 -0
  357. package/src/persistence/migrations/366-chatgpt-subscription-row-identity.ts +62 -0
  358. package/src/persistence/real-user-turn-filter.ts +27 -3
  359. package/src/persistence/schema/documents.ts +4 -4
  360. package/src/persistence/steps.ts +9 -0
  361. package/src/plugin-api/__tests__/oauth-callback-url-export.test.ts +29 -0
  362. package/src/plugin-api/constants.ts +8 -0
  363. package/src/plugin-api/conversation-turn.ts +195 -5
  364. package/src/plugin-api/index.ts +38 -6
  365. package/src/plugin-api/resolve-credential.ts +3 -2
  366. package/src/plugin-api/store-credential.ts +146 -0
  367. package/src/plugin-api/vision-support.test.ts +1 -1
  368. package/src/plugin-api/webhook-url.ts +13 -11
  369. package/src/plugins/__tests__/mcp-servers.test.ts +371 -0
  370. package/src/plugins/defaults/main.ts +21 -7
  371. package/src/plugins/defaults/memory/AGENTS.md +15 -3
  372. package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +204 -0
  373. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +72 -0
  374. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +4 -1
  375. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +4 -1
  376. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
  377. package/src/plugins/defaults/memory/buffer-format.ts +165 -0
  378. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +6 -0
  379. package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +77 -0
  380. package/src/plugins/defaults/memory/graph/conversation-graph-memory.ts +24 -6
  381. package/src/plugins/defaults/memory/graph/image-ref-utils.ts +3 -0
  382. package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -30
  383. package/src/plugins/defaults/memory/graph-topology/pending-buffer.test.ts +34 -0
  384. package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +17 -24
  385. package/src/plugins/defaults/memory/hooks/post-compact.ts +1 -4
  386. package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +53 -5
  387. package/src/plugins/defaults/memory/indexer.ts +3 -1
  388. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +19 -7
  389. package/src/plugins/defaults/memory/memory-retrospective-job.ts +4 -4
  390. package/src/plugins/defaults/memory/src/__tests__/memory-v3-gate-stats.test.ts +281 -0
  391. package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
  392. package/src/plugins/defaults/memory/src/memory-v3-routes.ts +207 -0
  393. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +147 -0
  394. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
  395. package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
  396. package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
  397. package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
  398. package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
  399. package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
  400. package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +195 -58
  401. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +93 -24
  402. package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
  403. package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
  404. package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
  405. package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
  406. package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
  407. package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
  408. package/src/plugins/defaults/memory/substrate/skill-content.ts +8 -1
  409. package/src/plugins/defaults/memory/substrate/static-context.ts +160 -33
  410. package/src/plugins/defaults/memory/substrate/sweep-job.ts +2 -4
  411. package/src/plugins/defaults/memory/v1/graph/extraction.ts +3 -1
  412. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +18 -0
  413. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +66 -1
  414. package/src/plugins/defaults/memory/v3/card.ts +9 -9
  415. package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
  416. package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
  417. package/src/plugins/defaults/memory/v3/edge.ts +13 -57
  418. package/src/plugins/defaults/memory/v3/injector.ts +8 -0
  419. package/src/plugins/defaults/memory/v3/prune.ts +2 -0
  420. package/src/plugins/defaults/memory/v3/selection-log-store.ts +2 -0
  421. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +23 -9
  422. package/src/plugins/defaults/memory/worker-control.ts +1 -0
  423. package/src/plugins/defaults/memory/worker.ts +6 -3
  424. package/src/plugins/defaults/worker-entrypoints.ts +3 -0
  425. package/src/plugins/external-plugin-loader.ts +47 -0
  426. package/src/plugins/mcp-servers.ts +361 -0
  427. package/src/plugins/mtime-cache.ts +37 -46
  428. package/src/plugins/pipeline.ts +14 -7
  429. package/src/plugins/plugin-execution-context.ts +35 -11
  430. package/src/plugins/worker-plugin-surface.ts +33 -0
  431. package/src/prompts/templates/system-sections.ts +0 -7
  432. package/src/providers/__tests__/connection-model-compat.test.ts +1 -1
  433. package/src/providers/__tests__/context-overflow-error.test.ts +24 -0
  434. package/src/providers/__tests__/dispatch-connection-routing.test.ts +214 -2
  435. package/src/providers/__tests__/preflight-resolved-config.test.ts +57 -0
  436. package/src/providers/__tests__/retry-callsite.test.ts +25 -2
  437. package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
  438. package/src/providers/anthropic/client.ts +332 -241
  439. package/src/providers/call-site-routing.ts +30 -3
  440. package/src/providers/connection-resolution.ts +194 -11
  441. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +4 -0
  442. package/src/providers/inference/adapter-factory.ts +3 -0
  443. package/src/providers/inference/auth.ts +6 -0
  444. package/src/providers/inference/connection-availability.ts +24 -2
  445. package/src/providers/inference/connections.ts +2 -0
  446. package/src/providers/model-catalog.ts +3 -3
  447. package/src/providers/model-intents.ts +28 -8
  448. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +163 -40
  449. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
  450. package/src/providers/openai/chat-completions-provider.ts +133 -36
  451. package/src/providers/openai/codex-models.ts +2 -1
  452. package/src/providers/provider-send-message.ts +32 -3
  453. package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +433 -0
  454. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +625 -0
  455. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +34 -0
  456. package/src/providers/speech-to-text/__tests__/resolve.test.ts +285 -6
  457. package/src/providers/speech-to-text/deepgram-flux-frames.ts +395 -0
  458. package/src/providers/speech-to-text/deepgram-flux-realtime.ts +679 -0
  459. package/src/providers/speech-to-text/provider-catalog.ts +99 -8
  460. package/src/providers/speech-to-text/resolve.ts +25 -2
  461. package/src/routes/control.ts +1 -0
  462. package/src/routes/route-host-client.ts +1 -0
  463. package/src/routes/route-host-protocol.ts +7 -0
  464. package/src/routes/worker.ts +48 -15
  465. package/src/runtime/AGENTS.md +16 -17
  466. package/src/runtime/access-request-helper.ts +9 -12
  467. package/src/runtime/agent-wake.ts +18 -15
  468. package/src/runtime/effective-capabilities.test.ts +43 -0
  469. package/src/runtime/effective-capabilities.ts +27 -0
  470. package/src/runtime/guardian-reply-router.ts +6 -2
  471. package/src/runtime/pre-first-message-gate.ts +4 -0
  472. package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +12 -4
  473. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +522 -2
  474. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +52 -0
  475. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +61 -0
  476. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +44 -0
  477. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +102 -1
  478. package/src/runtime/routes/__tests__/plugins-routes.test.ts +132 -35
  479. package/src/runtime/routes/__tests__/schedule-routes-disarm-reason.test.ts +215 -0
  480. package/src/runtime/routes/__tests__/stt-routes.test.ts +25 -0
  481. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
  482. package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
  483. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +133 -1
  484. package/src/runtime/routes/channel-availability-routes.ts +32 -14
  485. package/src/runtime/routes/channel-route-shared.ts +0 -6
  486. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +6 -6
  487. package/src/runtime/routes/conversation-list-routes.ts +181 -23
  488. package/src/runtime/routes/conversation-management-routes.ts +17 -12
  489. package/src/runtime/routes/conversation-query-routes.ts +40 -27
  490. package/src/runtime/routes/conversation-routes.ts +43 -29
  491. package/src/runtime/routes/credential-prompt-routes.ts +4 -7
  492. package/src/runtime/routes/credential-routes.ts +26 -82
  493. package/src/runtime/routes/default-provider-routes.ts +15 -0
  494. package/src/runtime/routes/documents-routes.ts +3 -222
  495. package/src/runtime/routes/inbound-message-handler.ts +21 -45
  496. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +0 -1
  497. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -9
  498. package/src/runtime/routes/inbound-stages/admission-policy.ts +1 -17
  499. package/src/runtime/routes/inbound-stages/bootstrap-intercept.test.ts +0 -1
  500. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -3
  501. package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -79
  502. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +0 -1
  503. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +12 -7
  504. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -8
  505. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -79
  506. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +2 -3
  507. package/src/runtime/routes/inference-profiles-routes.ts +20 -11
  508. package/src/runtime/routes/inference-provider-connection-routes.ts +77 -15
  509. package/src/runtime/routes/log-export-routes.ts +3 -0
  510. package/src/runtime/routes/mcp-auth-routes.ts +148 -57
  511. package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +2 -0
  512. package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +3 -0
  513. package/src/runtime/routes/playground/inject-failures.ts +2 -2
  514. package/src/runtime/routes/playground/reset-circuit.ts +1 -1
  515. package/src/runtime/routes/plugins-routes.ts +38 -8
  516. package/src/runtime/routes/schedule-routes.ts +94 -7
  517. package/src/runtime/routes/settings-routes.ts +6 -6
  518. package/src/runtime/routes/stt-routes.ts +31 -25
  519. package/src/runtime/routes/surface-action-routes.ts +20 -19
  520. package/src/runtime/routes/surface-conversation-resolver.ts +3 -0
  521. package/src/runtime/routes/user-route-dispatcher.ts +69 -15
  522. package/src/runtime/routes/user-route-import.ts +108 -0
  523. package/src/runtime/routes/user-route-resolution.ts +18 -1
  524. package/src/runtime/routes/workspace-routes.ts +0 -9
  525. package/src/runtime/routes/workspace-utils.ts +3 -13
  526. package/src/runtime/services/conversation-serializer.ts +7 -2
  527. package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +68 -5
  528. package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +81 -0
  529. package/src/schedule/plugin-schedule-availability.ts +58 -0
  530. package/src/schedule/plugin-schedule-declarations.ts +23 -27
  531. package/src/schedule/plugin-schedule-reconciler.ts +12 -3
  532. package/src/schedule/schedule-store.ts +5 -1
  533. package/src/schedule/scheduler.ts +9 -4
  534. package/src/schedule/worker-control.ts +1 -0
  535. package/src/schedule/worker.ts +6 -0
  536. package/src/security/oauth2.ts +6 -22
  537. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +22 -0
  538. package/src/stt/__tests__/types.test.ts +94 -0
  539. package/src/stt/daemon-batch-transcriber.ts +10 -0
  540. package/src/stt/stt-stream-session.ts +8 -4
  541. package/src/stt/types.ts +103 -0
  542. package/src/subagent/__tests__/consult-prompt.test.ts +26 -15
  543. package/src/subagent/consult-context.ts +11 -11
  544. package/src/subagent/consult-prompt.ts +26 -35
  545. package/src/subagent/manager.ts +21 -40
  546. package/src/subagent/notify.ts +7 -1
  547. package/src/subagent/types.ts +15 -12
  548. package/src/tools/__tests__/tool-input-schemas.test.ts +7 -7
  549. package/src/tools/acp/spawn.test.ts +97 -0
  550. package/src/tools/acp/spawn.ts +38 -4
  551. package/src/tools/credentials/ref-parse.ts +35 -0
  552. package/src/tools/credentials/resolve.ts +4 -10
  553. package/src/tools/credentials/store.ts +168 -0
  554. package/src/tools/document/document-tool.ts +12 -3
  555. package/src/tools/executor.ts +53 -15
  556. package/src/tools/filesystem/read.ts +27 -10
  557. package/src/tools/host-filesystem/read.ts +27 -15
  558. package/src/tools/network/url-safety.ts +9 -16
  559. package/src/tools/permission-checker.ts +43 -52
  560. package/src/tools/registry.ts +2 -1
  561. package/src/tools/shared/filesystem/file-ops-service.ts +63 -35
  562. package/src/tools/shared/filesystem/legacy-read-args.ts +22 -0
  563. package/src/tools/shared/filesystem/types.ts +5 -5
  564. package/src/tools/skills/scaffold-managed.ts +25 -7
  565. package/src/tools/subagent/spawn.ts +28 -88
  566. package/src/tools/tool-types.ts +26 -13
  567. package/src/tools/types.ts +6 -6
  568. package/src/tools/ui-surface/surface-shape-docs.ts +12 -1
  569. package/src/tools/workflows/run-workflow.ts +1 -2
  570. package/src/tts/__tests__/reasoning-tag-filter.test.ts +78 -0
  571. package/src/tts/reasoning-tag-filter.ts +89 -0
  572. package/src/util/__tests__/worker-process-command.test.ts +37 -0
  573. package/src/util/logger.ts +16 -0
  574. package/src/util/think-tag-stream.ts +95 -0
  575. package/src/util/worker-process.ts +37 -4
  576. package/src/windows-compiled-cli.ts +32 -0
  577. package/src/windows-compiled-entry.ts +4 -0
  578. package/src/windows-compiled-logger.ts +6 -0
  579. package/src/windows-compiled-worker-entry.ts +29 -0
  580. package/src/workspace/byok-default-profile-ensure.ts +76 -24
  581. package/src/workspace/custom-profile-ensure.ts +4 -24
  582. package/src/workspace/migrations/142-consolidate-voice-front-door.ts +70 -0
  583. package/src/workspace/migrations/143-repair-deprecated-codex-model-id.ts +134 -0
  584. package/src/workspace/migrations/144-convert-stranded-subscription-openai-profiles.ts +265 -0
  585. package/src/workspace/migrations/145-collapse-profile-bindings-to-entries.ts +328 -0
  586. package/src/workspace/migrations/146-repair-retired-fireworks-deepseek-flash-model-id.ts +195 -0
  587. package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +0 -10
  588. package/src/workspace/migrations/registry.ts +10 -0
  589. package/src/workspace/provider-commit-message-generator.ts +7 -5
  590. package/src/__tests__/document-workspace-file.test.ts +0 -467
  591. package/src/live-voice/__tests__/front-decision.test.ts +0 -645
  592. package/src/live-voice/front-decision.ts +0 -476
  593. package/src/permissions/ipc-risk-types.ts +0 -143
  594. package/src/permissions/risk-types.ts +0 -76
  595. package/src/subagent/__tests__/consult-transcript.test.ts +0 -184
  596. package/src/subagent/consult-transcript.ts +0 -90
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Shared primitives for streaming think-tag scanning.
3
+ *
4
+ * Two consumers parse inline `<think>` spans out of streamed model content
5
+ * and must not drift apart (single source of truth):
6
+ *
7
+ * - `providers/openai/chat-completions-provider.ts` ROUTES the spans to
8
+ * `thinking_delta` events (content preserved, case-sensitive `<think>`
9
+ * only, matching the models that emit it).
10
+ * - `tts/reasoning-tag-filter.ts` DROPS the spans from the spoken stream
11
+ * (case-insensitive, `<think>` and `<thinking>` variants).
12
+ *
13
+ * The state machines stay separate because they do different things with
14
+ * the reasoning; what they share is the tag scanning below. All scanning is
15
+ * positionally exact on the ORIGINAL string: case-insensitive mode folds
16
+ * A-Z per character code and never calls `toLowerCase()` on the haystack,
17
+ * because Unicode lowercasing can change string length (e.g. dotted capital
18
+ * I, U+0130, lowers to two code units) and shift every subsequent index.
19
+ * Tags must be given in lowercase ASCII.
20
+ */
21
+
22
+ function foldedCharCode(code: number, caseInsensitive: boolean): number {
23
+ return caseInsensitive && code >= 65 && code <= 90 ? code + 32 : code;
24
+ }
25
+
26
+ function tagMatchesAt(
27
+ haystack: string,
28
+ start: number,
29
+ tag: string,
30
+ caseInsensitive: boolean,
31
+ ): boolean {
32
+ if (start + tag.length > haystack.length) {
33
+ return false;
34
+ }
35
+ for (let k = 0; k < tag.length; k += 1) {
36
+ if (
37
+ foldedCharCode(haystack.charCodeAt(start + k), caseInsensitive) !==
38
+ tag.charCodeAt(k)
39
+ ) {
40
+ return false;
41
+ }
42
+ }
43
+ return true;
44
+ }
45
+
46
+ /**
47
+ * First occurrence of any tag in `haystack`, by original-string index.
48
+ * When several tags match at the same index the longest wins, so
49
+ * `<thinking>` is not consumed as `<think>` plus stray text.
50
+ */
51
+ export function indexOfTag(
52
+ haystack: string,
53
+ tags: readonly string[],
54
+ caseInsensitive: boolean,
55
+ ): { index: number; tag: string } | null {
56
+ for (let i = 0; i < haystack.length; i += 1) {
57
+ let bestTag: string | null = null;
58
+ for (const tag of tags) {
59
+ if (tagMatchesAt(haystack, i, tag, caseInsensitive)) {
60
+ if (bestTag === null || tag.length > bestTag.length) {
61
+ bestTag = tag;
62
+ }
63
+ }
64
+ }
65
+ if (bestTag !== null) {
66
+ return { index: i, tag: bestTag };
67
+ }
68
+ }
69
+ return null;
70
+ }
71
+
72
+ /**
73
+ * Length of the longest suffix of `text` that is a proper prefix of any
74
+ * listed tag. Streaming scanners hold that many characters back so a tag
75
+ * split across deltas is not emitted before it can be recognized.
76
+ */
77
+ export function partialTagSuffix(
78
+ text: string,
79
+ tags: readonly string[],
80
+ caseInsensitive: boolean,
81
+ ): number {
82
+ const longest = Math.max(...tags.map((tag) => tag.length)) - 1;
83
+ for (let len = Math.min(text.length, longest); len > 0; len -= 1) {
84
+ const start = text.length - len;
85
+ for (const tag of tags) {
86
+ if (
87
+ len < tag.length &&
88
+ tagMatchesAt(text, start, tag.slice(0, len), caseInsensitive)
89
+ ) {
90
+ return len;
91
+ }
92
+ }
93
+ }
94
+ return 0;
95
+ }
@@ -16,7 +16,7 @@ import {
16
16
  readFileSync,
17
17
  unlinkSync,
18
18
  } from "node:fs";
19
- import { dirname } from "node:path";
19
+ import { dirname, join } from "node:path";
20
20
  import { fileURLToPath } from "node:url";
21
21
 
22
22
  import { getCurrentLogFilePath } from "./logger.js";
@@ -122,6 +122,37 @@ export interface SpawnWorkerProcessOptions {
122
122
  detached?: boolean;
123
123
  }
124
124
 
125
+ export type PackagedWorkerEntry =
126
+ | "monitoring"
127
+ | "schedule"
128
+ | "memory"
129
+ | "routes"
130
+ | "db-integrity";
131
+
132
+ interface WorkerCommandRuntime {
133
+ platform: NodeJS.Platform;
134
+ execPath: string;
135
+ executableExists: (path: string) => boolean;
136
+ }
137
+
138
+ export function resolveWorkerCommand(
139
+ entry: URL,
140
+ packagedEntry: PackagedWorkerEntry | undefined,
141
+ runtime: WorkerCommandRuntime = {
142
+ platform: process.platform,
143
+ execPath: process.execPath,
144
+ executableExists: existsSync,
145
+ },
146
+ ): string[] {
147
+ if (runtime.platform === "win32" && packagedEntry) {
148
+ const executable = join(dirname(runtime.execPath), "vellum-worker.exe");
149
+ if (runtime.executableExists(executable)) {
150
+ return [executable, packagedEntry];
151
+ }
152
+ }
153
+ return ["bun", "--smol", "run", fileURLToPath(entry)];
154
+ }
155
+
125
156
  type WorkerReadyOutcome = "ready" | "exited" | "timeout";
126
157
 
127
158
  /**
@@ -178,6 +209,8 @@ export async function spawnWorkerProcess(args: {
178
209
  pidPath: string;
179
210
  /** Worker entry script, e.g. `new URL("./worker.ts", import.meta.url)`. */
180
211
  entry: URL;
212
+ /** Entry bundled into vellum-worker.exe for packaged Windows runtimes. */
213
+ packagedEntry?: PackagedWorkerEntry;
181
214
  /** Human-readable name used in spawn-failure messages, e.g. "Memory worker". */
182
215
  workerLabel: string;
183
216
  options?: SpawnWorkerProcessOptions;
@@ -208,15 +241,15 @@ export async function spawnWorkerProcess(args: {
208
241
  // output is at least visible to the spawning process.
209
242
  }
210
243
 
211
- // Workers are latency-insensitive, so run them in bun's small-heap mode
212
- // with a RAM-size hint sized to the container — see worker-memory.ts.
244
+ // Source workers use bun's small-heap mode. Packaged Windows workers use the
245
+ // compiled worker executable. Both receive the RAM hint from worker-memory.
213
246
  //
214
247
  // `fileURLToPath`, not `.pathname`: a URL's pathname is percent-encoded, so
215
248
  // an install path containing a space (every macOS desktop install lives
216
249
  // under "Application Support") would reach bun as "Application%20Support"
217
250
  // and the entry would not be found.
218
251
  const child = Bun.spawn({
219
- cmd: ["bun", "--smol", "run", fileURLToPath(args.entry)],
252
+ cmd: resolveWorkerCommand(args.entry, args.packagedEntry),
220
253
  env: workerMemoryEnv(),
221
254
  stdio: ["ignore", "ignore", stderrFd],
222
255
  detached,
@@ -0,0 +1,32 @@
1
+ import { setBundledCliModules } from "./cli/bundled-modules.js";
2
+ import * as pluginDiff from "./cli/lib/diff-plugin.js";
3
+ import * as pluginInspect from "./cli/lib/inspect-plugin.js";
4
+ import * as pluginInstallGitHub from "./cli/lib/install-from-github.js";
5
+ import * as pluginInstallPlatform from "./cli/lib/install-from-platform.js";
6
+ import * as pluginInstalled from "./cli/lib/list-installed-plugins.js";
7
+ import * as pluginCatalogCache from "./cli/lib/plugin-catalog-cache.js";
8
+ import * as pluginCatalogLocal from "./cli/lib/plugin-catalog-local.js";
9
+ import * as pluginPinHistory from "./cli/lib/plugin-pin-history.js";
10
+ import * as pluginSurfaces from "./cli/lib/plugin-surfaces.js";
11
+ import * as pluginSearch from "./cli/lib/search-plugins.js";
12
+ import * as pluginUninstall from "./cli/lib/uninstall-plugin.js";
13
+ import * as pluginUpgrade from "./cli/lib/upgrade-plugin.js";
14
+ import * as configEnv from "./config/env.js";
15
+ import * as providerSecretCatalog from "./providers/provider-secret-catalog.js";
16
+
17
+ setBundledCliModules({
18
+ configEnv,
19
+ providerSecretCatalog,
20
+ pluginCatalogCache,
21
+ pluginCatalogLocal,
22
+ pluginDiff,
23
+ pluginInspect,
24
+ pluginInstallGitHub,
25
+ pluginInstallPlatform,
26
+ pluginInstalled,
27
+ pluginPinHistory,
28
+ pluginSearch,
29
+ pluginSurfaces,
30
+ pluginUninstall,
31
+ pluginUpgrade,
32
+ });
@@ -0,0 +1,4 @@
1
+ import "./windows-compiled-logger.js";
2
+ import "./windows-compiled-cli.js";
3
+
4
+ await import("./index.js");
@@ -0,0 +1,6 @@
1
+ import pino from "pino";
2
+ import pinoPretty from "pino-pretty";
3
+
4
+ import { setBundledLoggerModules } from "./util/logger.js";
5
+
6
+ setBundledLoggerModules(pino, pinoPretty);
@@ -0,0 +1,29 @@
1
+ import "./windows-compiled-logger.js";
2
+
3
+ const worker = process.argv[2];
4
+
5
+ switch (worker) {
6
+ case "monitoring":
7
+ await import("./monitoring/worker.js");
8
+ break;
9
+ case "schedule":
10
+ await import("./schedule/worker.js");
11
+ break;
12
+ case "memory":
13
+ await (
14
+ await import("./plugins/defaults/worker-entrypoints.js")
15
+ ).loadDefaultMemoryWorker();
16
+ break;
17
+ case "routes":
18
+ await import("./embedded/plugin-api.js");
19
+ await import("./routes/worker.js");
20
+ break;
21
+ case "db-integrity": {
22
+ const { runIntegrityCheck } =
23
+ await import("./monitoring/db-integrity-check.js");
24
+ console.log(JSON.stringify(runIntegrityCheck(process.argv[3] ?? "")));
25
+ break;
26
+ }
27
+ default:
28
+ throw new Error(`Unknown Windows worker entry: ${worker ?? "missing"}`);
29
+ }
@@ -18,6 +18,7 @@ import {
18
18
  type LLMProvider,
19
19
  type ProfileEntry,
20
20
  } from "../config/schemas/llm.js";
21
+ import { ROUTING_IDENTITY_PROVIDERS } from "../providers/inference/auth.js";
21
22
  import { getLogger } from "../util/logger.js";
22
23
  import { completedProfileBody } from "./custom-profile-ensure.js";
23
24
 
@@ -135,10 +136,26 @@ const ERA_COPY_LABEL_SUFFIX = " (Custom Provider)";
135
136
  /**
136
137
  * Comparison ignores `label` and `status` (user overlay state, preserved via
137
138
  * `userOverlayState`) and `model` (checked separately against the current
138
- * intent resolution and `HISTORICAL_INTENT_MODELS`).
139
+ * intent resolution and `HISTORICAL_INTENT_MODELS`). The CONVENTIONAL
140
+ * `<vendor>-personal` binding is normalized away before comparison (it was
141
+ * machinery-written across several eras); a non-conventional binding is a
142
+ * user selection and keeps blocking conversion.
139
143
  */
140
144
  const IGNORED_COMPARISON_KEYS = new Set(["label", "status", "model"]);
141
145
 
146
+ /**
147
+ * Fold the conventional personal entry name back to its vendor. The entries
148
+ * collapse (migration 145) rewrites a hatch copy's binding into its
149
+ * provider as `<vendor>-personal`; the matcher folds that shape so every
150
+ * era of stored copy keeps converting (the same discipline as
151
+ * `HISTORICAL_INTENT_MODELS`).
152
+ */
153
+ function foldPersonalEntryName(provider: string): string {
154
+ return provider.endsWith("-personal")
155
+ ? provider.slice(0, -"-personal".length)
156
+ : provider;
157
+ }
158
+
142
159
  /**
143
160
  * Per-provider model ids that earlier intent eras pinned onto `custom-*`
144
161
  * copies, verified against `providers/model-intents.ts` git history. Hatch
@@ -146,9 +163,10 @@ const IGNORED_COMPARISON_KEYS = new Set(["label", "status", "model"]);
146
163
  * 2026-05-05, #29755), and the profile-model migrations (100, 103, 109, 113,
147
164
  * 123) deliberately rewrite only the managed default entries (`custom-*` is
148
165
  * the user's to manage), so an untouched copy still carries whichever value
149
- * its hatch-era intent resolved to. Migration 136 is the one exception: it
150
- * rewrote kimi-k2p5 pins (`custom-*` included) to deepseek-v4-flash in
151
- * place. A model listed here counts as unedited only when every non-model
166
+ * its hatch-era intent resolved to. Migrations 136 and 146 are the
167
+ * exceptions: they rewrite stale Fireworks pins (`custom-*` included) in
168
+ * place, so their targets are machinery-authored values too. A model listed
169
+ * here counts as unedited only when every non-model
152
170
  * field still matches the template.
153
171
  */
154
172
  const HISTORICAL_INTENT_MODELS: Record<
@@ -156,6 +174,10 @@ const HISTORICAL_INTENT_MODELS: Record<
156
174
  Partial<Record<string, readonly string[]>>
157
175
  > = {
158
176
  balanced: {
177
+ openai: [
178
+ // balanced intent 2026-05-05 (#29755) to the 2026-08-10 gpt-5.6 repoint.
179
+ "gpt-5.4-mini",
180
+ ],
159
181
  fireworks: [
160
182
  // balanced intent 2026-05-05 (#29755) to 2026-05-19 (#31068).
161
183
  "accounts/fireworks/models/kimi-k2p5",
@@ -163,9 +185,15 @@ const HISTORICAL_INTENT_MODELS: Record<
163
185
  "accounts/fireworks/models/kimi-k2p6",
164
186
  // migration 136's in-place rewrite of a kimi-k2p5 pin.
165
187
  "accounts/fireworks/models/deepseek-v4-flash",
188
+ // the ID migration 146 writes over stale deepseek-v4-flash pins.
189
+ "accounts/fireworks/models/deepseek-v4-flash-0731",
166
190
  ],
167
191
  },
168
192
  "quality-optimized": {
193
+ openai: [
194
+ // quality intent 2026-05-05 (#29755) to the 2026-08-10 gpt-5.6 repoint.
195
+ "gpt-5.4",
196
+ ],
169
197
  anthropic: [
170
198
  // quality intent 2026-05-05 (#29755) to 2026-06-11 (#34498).
171
199
  "claude-opus-4-7",
@@ -183,6 +211,8 @@ const HISTORICAL_INTENT_MODELS: Record<
183
211
  "accounts/fireworks/models/kimi-k2p5",
184
212
  // migration 136's in-place rewrite of a kimi-k2p5 pin.
185
213
  "accounts/fireworks/models/deepseek-v4-flash",
214
+ // the ID migration 146 writes over stale deepseek-v4-flash pins.
215
+ "accounts/fireworks/models/deepseek-v4-flash-0731",
186
216
  ],
187
217
  },
188
218
  "cost-optimized": {
@@ -196,10 +226,14 @@ const HISTORICAL_INTENT_MODELS: Record<
196
226
  ],
197
227
  fireworks: [
198
228
  // latency intent 2026-05-05 (#29755) to 2026-07-28 (#39446). On live
199
- // configs migration 136 rewrote it to deepseek-v4-flash (the current
200
- // intent), so this survives only in configs restored from pre-136
201
- // backups.
229
+ // configs migration 136 rewrites it to deepseek-v4-flash, so this
230
+ // survives only in configs restored from pre-136 backups.
202
231
  "accounts/fireworks/models/kimi-k2p5",
232
+ // latency intent 2026-07-28 (#39446) to the 2026-08-17 dated-ID
233
+ // repoint; also the ID migration 136 writes. On live configs migration
234
+ // 146 rewrites it to the dated ID, so this survives only in pre-146
235
+ // backups.
236
+ "accounts/fireworks/models/deepseek-v4-flash",
203
237
  ],
204
238
  },
205
239
  };
@@ -259,11 +293,13 @@ export function ensureByokDefaultProfiles(workspaceDir: string): void {
259
293
  const completionBase = LLMConfigBase.safeParse(llm.default ?? {}).data;
260
294
 
261
295
  // The default provider on a BYOK default, or the uniform hatch provider
262
- // across the complete copy set on a vellum default; null converts nothing.
263
- const candidateProvider =
264
- parsedDefault.data.provider !== "vellum"
265
- ? parsedDefault.data.provider
266
- : uniformCopyProvider(profiles);
296
+ // across the complete copy set on a routing-identity default (vellum,
297
+ // chatgpt); null converts nothing.
298
+ const candidateProvider = ROUTING_IDENTITY_PROVIDERS.has(
299
+ parsedDefault.data.provider,
300
+ )
301
+ ? uniformCopyProvider(profiles)
302
+ : parsedDefault.data.provider;
267
303
  const convertibleProvider =
268
304
  candidateProvider !== null && isByokDefaultProviderChoice(candidateProvider)
269
305
  ? candidateProvider
@@ -339,7 +375,11 @@ function isKnownUneditedBody(
339
375
  // (hatch materialized it once; the default provider may have changed
340
376
  // since); equality with the corroborated provider rules out a user
341
377
  // re-provision.
342
- if (convertibleProvider === null || entry.provider !== convertibleProvider) {
378
+ if (
379
+ convertibleProvider === null ||
380
+ typeof entry.provider !== "string" ||
381
+ foldPersonalEntryName(entry.provider) !== convertibleProvider
382
+ ) {
343
383
  return false;
344
384
  }
345
385
  const copyProvider = convertibleProvider;
@@ -347,11 +387,13 @@ function isKnownUneditedBody(
347
387
  if (template === undefined) {
348
388
  return false;
349
389
  }
350
- const materialized = materializeProfile(
351
- template,
352
- copyProvider,
353
- `${copyProvider}-personal`,
354
- ) as Record<string, unknown>;
390
+ // No connection binding: the bare provider means the default entry of
391
+ // that kind, and a stamped binding would re-introduce the collapsed
392
+ // field on disk.
393
+ const materialized = materializeProfile(template, copyProvider) as Record<
394
+ string,
395
+ unknown
396
+ >;
355
397
  if (
356
398
  entry.model !== materialized.model &&
357
399
  !(HISTORICAL_INTENT_MODELS[key][copyProvider] ?? []).includes(entry.model)
@@ -361,11 +403,20 @@ function isKnownUneditedBody(
361
403
  // Completion skips managed-source bodies, so a copy from the era whose
362
404
  // templates wrote `source: "managed"` (#29755 to #29768, 2026-05-05) is
363
405
  // compared as if user-source to keep both sides normalized identically.
406
+ const normalizedEntry: Record<string, unknown> = {
407
+ ...entry,
408
+ provider: copyProvider,
409
+ ...(entry.source === "managed" ? { source: "user" } : {}),
410
+ };
411
+ // The conventional personal binding is machinery-written (hatch stamps,
412
+ // completion re-stamps); normalize it away. Any other binding is user
413
+ // state and stays in the comparison, where the unbound template side
414
+ // makes it block conversion.
415
+ if (normalizedEntry.provider_connection === `${copyProvider}-personal`) {
416
+ delete normalizedEntry.provider_connection;
417
+ }
364
418
  const body = comparableBody(
365
- withCompletionBaked(
366
- entry.source === "managed" ? { ...entry, source: "user" } : entry,
367
- completionBase,
368
- ),
419
+ withCompletionBaked(normalizedEntry, completionBase),
369
420
  );
370
421
  const known = comparableBody(
371
422
  withCompletionBaked(materialized, completionBase),
@@ -392,9 +443,10 @@ function uniformCopyProvider(profiles: Record<string, unknown>): string | null {
392
443
  if (typeof entry.provider !== "string") {
393
444
  return null;
394
445
  }
446
+ const entryProvider = foldPersonalEntryName(entry.provider);
395
447
  if (provider === null) {
396
- provider = entry.provider;
397
- } else if (provider !== entry.provider) {
448
+ provider = entryProvider;
449
+ } else if (provider !== entryProvider) {
398
450
  return null;
399
451
  }
400
452
  }
@@ -3,7 +3,10 @@ import { join } from "node:path";
3
3
  import { isDeepStrictEqual } from "node:util";
4
4
 
5
5
  import { invalidateConfigCache } from "../config/loader.js";
6
- import { completeCustomProfile } from "../config/profile-materialization.js";
6
+ import {
7
+ completeCustomProfile,
8
+ mergePreservingUnknownKeys,
9
+ } from "../config/profile-materialization.js";
7
10
  import { LLMConfigBase, ProfileEntry } from "../config/schemas/llm.js";
8
11
  import { getLogger } from "../util/logger.js";
9
12
 
@@ -113,29 +116,6 @@ export function completedProfileBody(
113
116
  );
114
117
  }
115
118
 
116
- /**
117
- * `{...raw, ...completed}` recursively: completed (schema-known) values win,
118
- * raw keys the schema stripped survive at every depth. (Duplicated in the
119
- * write-path normalization in conversation-query-routes.ts — the two land in
120
- * independent PRs; consolidate when either next changes.)
121
- */
122
- function mergePreservingUnknownKeys(
123
- raw: Record<string, unknown>,
124
- completed: Record<string, unknown>,
125
- ): Record<string, unknown> {
126
- const out: Record<string, unknown> = { ...raw, ...completed };
127
- for (const [key, value] of Object.entries(completed)) {
128
- const rawValue = raw[key];
129
- if (readObject(value) !== null && readObject(rawValue) !== null) {
130
- out[key] = mergePreservingUnknownKeys(
131
- rawValue as Record<string, unknown>,
132
- value as Record<string, unknown>,
133
- );
134
- }
135
- }
136
- return out;
137
- }
138
-
139
119
  function readObject(value: unknown): Record<string, unknown> | null {
140
120
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
141
121
  return null;
@@ -0,0 +1,70 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ import type { WorkspaceMigration } from "./types.js";
5
+
6
+ const OLD_CALLSITE = "voiceFrontDecision";
7
+ const PROGRESS_CALLSITE = "voiceProgressNarration";
8
+ const RETIRED_ACK_KEYS = [
9
+ "ackFirstDeltaTimeoutMs",
10
+ "ackGenerationTimeoutMs",
11
+ ] as const;
12
+
13
+ export const consolidateVoiceFrontDoorMigration: WorkspaceMigration = {
14
+ id: "142-consolidate-voice-front-door",
15
+ description:
16
+ "Rename the progress narration callsite and remove retired generated-ack tuning",
17
+ run(workspaceDir: string): void {
18
+ const configPath = join(workspaceDir, "config.json");
19
+ if (!existsSync(configPath)) {
20
+ return;
21
+ }
22
+
23
+ let config: Record<string, unknown>;
24
+ try {
25
+ const raw = JSON.parse(readFileSync(configPath, "utf-8"));
26
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
27
+ return;
28
+ }
29
+ config = raw as Record<string, unknown>;
30
+ } catch {
31
+ return;
32
+ }
33
+
34
+ let changed = false;
35
+ const llm = readObject(config.llm);
36
+ const callSites = readObject(llm?.callSites);
37
+ if (callSites && Object.hasOwn(callSites, OLD_CALLSITE)) {
38
+ if (!Object.hasOwn(callSites, PROGRESS_CALLSITE)) {
39
+ callSites[PROGRESS_CALLSITE] = callSites[OLD_CALLSITE];
40
+ }
41
+ delete callSites[OLD_CALLSITE];
42
+ changed = true;
43
+ }
44
+
45
+ const liveVoice = readObject(config.liveVoice);
46
+ const frontModel = readObject(liveVoice?.frontModel);
47
+ if (frontModel) {
48
+ for (const key of RETIRED_ACK_KEYS) {
49
+ if (Object.hasOwn(frontModel, key)) {
50
+ delete frontModel[key];
51
+ changed = true;
52
+ }
53
+ }
54
+ }
55
+
56
+ if (changed) {
57
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
58
+ }
59
+ },
60
+ down(_workspaceDir: string): void {
61
+ // Forward-only.
62
+ },
63
+ };
64
+
65
+ function readObject(value: unknown): Record<string, unknown> | null {
66
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
67
+ return null;
68
+ }
69
+ return value as Record<string, unknown>;
70
+ }
@@ -0,0 +1,134 @@
1
+ import { existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ import type { WorkspaceMigration } from "./types.js";
5
+
6
+ /**
7
+ * Repair the deprecated `gpt-5.3-codex` model ID on ChatGPT-subscription
8
+ * config fragments.
9
+ *
10
+ * OpenAI deprecated `gpt-5.3-codex` under ChatGPT sign-in (the Codex
11
+ * endpoint rejects it with HTTP 400), so it is being removed from
12
+ * `CODEX_SUBSCRIPTION_MODEL_IDS`. A workspace can still pin it on
13
+ * `provider: "chatgpt"` fragments (migration 133 wrote exactly that shape
14
+ * for `chatgpt-subscription` entries), and once the ID leaves the allowlist,
15
+ * `LLMSchema.superRefine` rejects such a fragment. The loader's leaf
16
+ * recovery then deletes the fragment's `model`, which still fails ("chatgpt"
17
+ * requires an explicit model), and per-section salvage resets the whole
18
+ * `llm` section, discarding the user's other LLM settings.
19
+ *
20
+ * Repair rewrites the model to `gpt-5.6-terra` (OpenAI's recommended
21
+ * everyday Codex model) on an exact `provider: "chatgpt"` + model match in
22
+ * `llm.default`, `llm.callSites.*`, and `llm.profiles.*`. A call-site
23
+ * fragment is also repaired when it pins the model with no provider of its
24
+ * own: at resolve time such a fragment overlays the winning profile, so the
25
+ * stale pin rides whatever provider and connection the winner supplies
26
+ * (including the subscription connection, which routes it to the Codex
27
+ * endpoint). Fragments with any other explicit provider are left untouched:
28
+ * the allowlist only gates the "chatgpt" routing identity, and a provider
29
+ * like `openai-compatible` may legitimately serve this id.
30
+ */
31
+ export const repairDeprecatedCodexModelIdMigration: WorkspaceMigration = {
32
+ id: "143-repair-deprecated-codex-model-id",
33
+ description:
34
+ 'Repair deprecated gpt-5.3-codex model ID on provider "chatgpt" fragments in workspace LLM config',
35
+ run(workspaceDir: string): void {
36
+ const configPath = join(workspaceDir, "config.json");
37
+ if (!existsSync(configPath)) {
38
+ return;
39
+ }
40
+
41
+ // Read outside the parse catch: a transient filesystem error (EIO,
42
+ // EACCES) must reach the runner so the migration retries, while
43
+ // malformed JSON is a permanent state this migration cannot repair.
44
+ const rawText = readFileSync(configPath, "utf-8");
45
+
46
+ let config: Record<string, unknown>;
47
+ try {
48
+ const raw = JSON.parse(rawText);
49
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
50
+ return;
51
+ }
52
+ config = raw as Record<string, unknown>;
53
+ } catch {
54
+ return;
55
+ }
56
+
57
+ const llm = readObject(config.llm);
58
+ if (llm === null) {
59
+ return;
60
+ }
61
+
62
+ let changed = false;
63
+
64
+ changed = repairFragment(readObject(llm.default)) || changed;
65
+
66
+ const callSites = readObject(llm.callSites);
67
+ if (callSites !== null) {
68
+ for (const rawConfig of Object.values(callSites)) {
69
+ changed =
70
+ repairFragment(readObject(rawConfig), {
71
+ repairMissingProvider: true,
72
+ }) || changed;
73
+ }
74
+ }
75
+
76
+ const profiles = readObject(llm.profiles);
77
+ if (profiles !== null) {
78
+ for (const rawProfile of Object.values(profiles)) {
79
+ changed = repairFragment(readObject(rawProfile)) || changed;
80
+ }
81
+ }
82
+
83
+ if (!changed) {
84
+ return;
85
+ }
86
+
87
+ // Write-then-rename so an interrupted write cannot leave config.json
88
+ // truncated: a torn in-place write would parse as invalid JSON on the
89
+ // retry, which the catch above treats as "nothing to do", letting the
90
+ // runner checkpoint the migration as completed against a corrupt file.
91
+ const tmpPath = `${configPath}.migration-143.tmp`;
92
+ writeFileSync(tmpPath, JSON.stringify(config, null, 2) + "\n");
93
+ renameSync(tmpPath, configPath);
94
+ },
95
+ // The exact-match rewrite is idempotent, so a transient failure (full
96
+ // disk, I/O error) is safe to retry on later startups.
97
+ retryFailedCheckpoint: true,
98
+ down(_workspaceDir: string): void {
99
+ // Forward-only: reintroducing the deprecated model ID would fail schema
100
+ // validation once the allowlist no longer carries it.
101
+ },
102
+ };
103
+
104
+ // ---------------------------------------------------------------------------
105
+ // Helpers: self-contained per workspace migrations AGENTS.md
106
+ // ---------------------------------------------------------------------------
107
+
108
+ const DEPRECATED_MODEL_ID = "gpt-5.3-codex";
109
+ const REPLACEMENT_MODEL_ID = "gpt-5.6-terra";
110
+
111
+ function repairFragment(
112
+ fragment: Record<string, unknown> | null,
113
+ options?: { repairMissingProvider?: boolean },
114
+ ): boolean {
115
+ if (fragment === null || fragment.model !== DEPRECATED_MODEL_ID) {
116
+ return false;
117
+ }
118
+ const providerRepairable =
119
+ fragment.provider === "chatgpt" ||
120
+ (options?.repairMissingProvider === true &&
121
+ fragment.provider === undefined);
122
+ if (!providerRepairable) {
123
+ return false;
124
+ }
125
+ fragment.model = REPLACEMENT_MODEL_ID;
126
+ return true;
127
+ }
128
+
129
+ function readObject(value: unknown): Record<string, unknown> | null {
130
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
131
+ return null;
132
+ }
133
+ return value as Record<string, unknown>;
134
+ }