@gajae-code/coding-agent 0.18.11 → 0.19.0

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 (462) hide show
  1. package/CHANGELOG.md +760 -0
  2. package/dist/types/async/job-manager.d.ts +7 -3
  3. package/dist/types/async/result-formatting.d.ts +2 -0
  4. package/dist/types/autoresearch/git.d.ts +1 -1
  5. package/dist/types/capability/fs.d.ts +2 -2
  6. package/dist/types/capability/rule.d.ts +7 -2
  7. package/dist/types/cli/accounts-cli.d.ts +2 -2
  8. package/dist/types/cli/append-system-prompt.d.ts +6 -0
  9. package/dist/types/cli/args.d.ts +7 -2
  10. package/dist/types/cli/customize-doctor-precedence.d.ts +16 -0
  11. package/dist/types/cli/customize-doctor.d.ts +4 -3
  12. package/dist/types/cli/doctor/install-repairs.d.ts +4 -1
  13. package/dist/types/cli/install-activation.d.ts +10 -4
  14. package/dist/types/cli/public-command-errors.d.ts +2 -0
  15. package/dist/types/cli/root-flags.d.ts +0 -12
  16. package/dist/types/cli/update-cli.d.ts +21 -3
  17. package/dist/types/cli-main.d.ts +0 -6
  18. package/dist/types/cli-ordinary.d.ts +2 -2
  19. package/dist/types/commands/accounts.d.ts +2 -1
  20. package/dist/types/commands/launch.d.ts +0 -6
  21. package/dist/types/commands/migrate.d.ts +1 -1
  22. package/dist/types/commands/sdk.d.ts +13 -0
  23. package/dist/types/commit/changelog/index.d.ts +1 -0
  24. package/dist/types/commit/map-reduce/index.d.ts +4 -4
  25. package/dist/types/config/autorouting-tier-map.d.ts +53 -1
  26. package/dist/types/config/config-hot-reload-trust.d.ts +12 -0
  27. package/dist/types/config/config-hot-reload.d.ts +44 -0
  28. package/dist/types/config/file-lock.d.ts +52 -3
  29. package/dist/types/config/model-discovery-manager.d.ts +2 -0
  30. package/dist/types/config/model-profile-activation.d.ts +79 -9
  31. package/dist/types/config/model-registry.d.ts +72 -8
  32. package/dist/types/config/model-resolver.d.ts +7 -4
  33. package/dist/types/config/models-config-schema.d.ts +10 -0
  34. package/dist/types/config/settings-schema.d.ts +57 -4
  35. package/dist/types/config/settings.d.ts +33 -1
  36. package/dist/types/coordinator-mcp/server.d.ts +4 -0
  37. package/dist/types/customization/import.d.ts +2 -0
  38. package/dist/types/customization/types.d.ts +1 -1
  39. package/dist/types/dap/config.d.ts +8 -4
  40. package/dist/types/dap/session.d.ts +11 -2
  41. package/dist/types/debug/system-info.d.ts +6 -1
  42. package/dist/types/discovery/helpers.d.ts +7 -2
  43. package/dist/types/discovery/mcp-compat.d.ts +3 -1
  44. package/dist/types/edit/index.d.ts +5 -0
  45. package/dist/types/edit/modes/patch.d.ts +14 -0
  46. package/dist/types/edit/modes/replace.d.ts +3 -0
  47. package/dist/types/eval/py/runtime.d.ts +9 -0
  48. package/dist/types/exec/bash-executor.d.ts +12 -0
  49. package/dist/types/export/ttsr.d.ts +19 -0
  50. package/dist/types/extensibility/extensions/types.d.ts +5 -1
  51. package/dist/types/extensibility/slash-commands.d.ts +1 -0
  52. package/dist/types/gjc-runtime/managed-owner-admission.d.ts +3 -0
  53. package/dist/types/gjc-runtime/ralplan-current-final.d.ts +54 -0
  54. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +5 -0
  55. package/dist/types/gjc-runtime/session-state-lock.d.ts +2 -0
  56. package/dist/types/gjc-runtime/state-runtime.d.ts +0 -2
  57. package/dist/types/gjc-runtime/tmux-sessions.d.ts +24 -0
  58. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +6 -0
  59. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -0
  60. package/dist/types/harness-control-plane/receipt-spool.d.ts +4 -0
  61. package/dist/types/hindsight/content.d.ts +5 -6
  62. package/dist/types/internal-urls/types.d.ts +2 -0
  63. package/dist/types/lsp/clients/swiftlint-client.d.ts +0 -4
  64. package/dist/types/main.d.ts +23 -18
  65. package/dist/types/migrate/action-planner.d.ts +10 -1
  66. package/dist/types/migrate/adapters/codex.d.ts +2 -1
  67. package/dist/types/migrate/adapters/index.d.ts +42 -1
  68. package/dist/types/migrate/executor.d.ts +3 -1
  69. package/dist/types/migrate/hook-module.d.ts +23 -0
  70. package/dist/types/migrate/report.d.ts +2 -1
  71. package/dist/types/migrate/types.d.ts +82 -3
  72. package/dist/types/modes/components/agent-dashboard.d.ts +1 -1
  73. package/dist/types/modes/components/model-selector.d.ts +43 -0
  74. package/dist/types/modes/components/settings-selector.d.ts +6 -0
  75. package/dist/types/modes/controllers/selector-controller.d.ts +2 -0
  76. package/dist/types/modes/prompt-suggestion-controller.d.ts +1 -1
  77. package/dist/types/modes/theme/theme.d.ts +1 -0
  78. package/dist/types/modes/utils/host-status.d.ts +15 -11
  79. package/dist/types/runtime-mcp/content-limits.d.ts +4 -0
  80. package/dist/types/runtime-mcp/manager.d.ts +7 -0
  81. package/dist/types/runtime-mcp/plugin-mcp-name-filter.d.ts +22 -0
  82. package/dist/types/runtime-mcp/project-host-values.d.ts +11 -5
  83. package/dist/types/runtime-mcp/tool-bridge.d.ts +4 -0
  84. package/dist/types/sdk/broker/broker-exit.d.ts +3 -3
  85. package/dist/types/sdk/broker/broker.d.ts +29 -2
  86. package/dist/types/sdk/broker/daemon-entry.d.ts +11 -1
  87. package/dist/types/sdk/broker/doctor-restart.d.ts +1 -1
  88. package/dist/types/sdk/broker/ensure.d.ts +141 -9
  89. package/dist/types/sdk/broker/hop.d.ts +45 -0
  90. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +18 -10
  91. package/dist/types/sdk/broker/lifecycle.d.ts +22 -0
  92. package/dist/types/sdk/broker/lock-host-identity.d.ts +5 -0
  93. package/dist/types/sdk/broker/session-index.d.ts +6 -1
  94. package/dist/types/sdk/broker/transport.d.ts +2 -0
  95. package/dist/types/sdk/bus/chat-command-policy.d.ts +2 -1
  96. package/dist/types/sdk/bus/index.d.ts +44 -0
  97. package/dist/types/sdk/bus/reconciliation-store.d.ts +10 -0
  98. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -1
  99. package/dist/types/sdk/bus/telegram-daemon.d.ts +2 -0
  100. package/dist/types/sdk/bus/threaded-render.d.ts +2 -1
  101. package/dist/types/sdk/cli/session-cli.d.ts +0 -1
  102. package/dist/types/sdk/host/control/operations.d.ts +5 -1
  103. package/dist/types/sdk/host/events.d.ts +4 -0
  104. package/dist/types/sdk/host/host.d.ts +2 -1
  105. package/dist/types/sdk/host/prompt-image-upload.d.ts +43 -0
  106. package/dist/types/sdk/host/session-runtime.d.ts +40 -8
  107. package/dist/types/sdk/host/surface-policy.d.ts +2 -1
  108. package/dist/types/sdk/lifecycle/service.d.ts +12 -0
  109. package/dist/types/sdk/prompt-deadline-flush.d.ts +42 -4
  110. package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -0
  111. package/dist/types/sdk/prompt-status.d.ts +11 -4
  112. package/dist/types/sdk/protocol/adapter-validation.d.ts +10 -4
  113. package/dist/types/sdk/reconciliation-extensions.d.ts +1 -1
  114. package/dist/types/sdk/session-list.d.ts +1 -1
  115. package/dist/types/session/agent-session.d.ts +107 -9
  116. package/dist/types/session/internal/managed-session-scope.d.ts +2 -2
  117. package/dist/types/session/internal/managed-session-storage.d.ts +1 -1
  118. package/dist/types/session/internal/native-publish-outcome.d.ts +1 -1
  119. package/dist/types/session/messages.d.ts +5 -1
  120. package/dist/types/session/session-manager.d.ts +29 -3
  121. package/dist/types/session/startup-auth-config.d.ts +24 -2
  122. package/dist/types/session/streaming-output.d.ts +11 -0
  123. package/dist/types/session/terminal-abort.d.ts +3 -1
  124. package/dist/types/session-import/service.d.ts +1 -1
  125. package/dist/types/skill-state/workflow-mutation-guard.d.ts +20 -4
  126. package/dist/types/slash-commands/helpers/mcp.d.ts +5 -0
  127. package/dist/types/slash-commands/helpers/provider-auth-status.d.ts +8 -0
  128. package/dist/types/ssh/ssh-executor.d.ts +8 -1
  129. package/dist/types/system-prompt.d.ts +3 -2
  130. package/dist/types/task/executor.d.ts +19 -2
  131. package/dist/types/task/role-body.d.ts +11 -0
  132. package/dist/types/tools/ast-edit-write-path.d.ts +10 -0
  133. package/dist/types/tools/atomic-file-write.d.ts +9 -7
  134. package/dist/types/tools/awaiting-input.d.ts +28 -0
  135. package/dist/types/tools/bash-allowed-prefixes.d.ts +17 -0
  136. package/dist/types/tools/bash-skill-urls.d.ts +4 -0
  137. package/dist/types/tools/bash.d.ts +7 -0
  138. package/dist/types/tools/descriptors.d.ts +15 -2
  139. package/dist/types/tools/fetch.d.ts +7 -1
  140. package/dist/types/tools/index.d.ts +2 -0
  141. package/dist/types/tools/irc.d.ts +5 -3
  142. package/dist/types/tools/python.d.ts +1 -1
  143. package/dist/types/tools/report-issue.d.ts +41 -0
  144. package/dist/types/tools/subagent.d.ts +23 -0
  145. package/dist/types/tools/tmux-self-injection-guard.d.ts +2 -0
  146. package/dist/types/tools/ultragoal-ask-guard.d.ts +1 -1
  147. package/dist/types/tools/write.d.ts +2 -0
  148. package/dist/types/utils/file-mentions.d.ts +5 -0
  149. package/dist/types/utils/image-limits.d.ts +7 -0
  150. package/dist/types/utils/image-loading.d.ts +0 -1
  151. package/dist/types/utils/open.d.ts +0 -1
  152. package/dist/types/utils/pasted-image-loading.d.ts +1 -5
  153. package/dist/types/utils/prompt-suggestion.d.ts +2 -2
  154. package/dist/types/utils/shell-snapshot.d.ts +2 -0
  155. package/dist/types/utils/title-generator.d.ts +13 -3
  156. package/dist/types/web/scrapers/types.d.ts +8 -1
  157. package/dist/types/web/search/providers/codex.d.ts +6 -7
  158. package/dist/types/web/search/providers/insane.d.ts +7 -1
  159. package/dist/types/web/search/providers/searxng.d.ts +5 -0
  160. package/package.json +10 -9
  161. package/scripts/acp-conformance-agent.ts +12 -2
  162. package/scripts/build-binary.ts +2 -1
  163. package/scripts/capture-subagent-compact-panel-showcase.ts +105 -0
  164. package/scripts/generate-docs-index.ts +8 -1
  165. package/scripts/generate-sdk-operation-inventory.ts +38 -4
  166. package/src/async/job-manager.ts +356 -56
  167. package/src/async/result-formatting.ts +40 -0
  168. package/src/autoresearch/git.ts +9 -2
  169. package/src/autoresearch/prompts/prompt-setup.md +1 -1
  170. package/src/capability/fs.ts +58 -22
  171. package/src/capability/index.ts +2 -2
  172. package/src/capability/rule.ts +11 -2
  173. package/src/cli/accounts-cli.ts +176 -8
  174. package/src/cli/append-system-prompt.ts +12 -0
  175. package/src/cli/args.ts +11 -7
  176. package/src/cli/auth-broker-cli.ts +21 -11
  177. package/src/cli/customize-doctor-precedence.ts +31 -0
  178. package/src/cli/customize-doctor.ts +49 -39
  179. package/src/cli/doctor/install-repairs.ts +35 -5
  180. package/src/cli/fast-help.ts +1 -3
  181. package/src/cli/install-activation.ts +47 -4
  182. package/src/cli/migrate-cli.ts +19 -6
  183. package/src/cli/public-command-errors.ts +6 -0
  184. package/src/cli/root-flags.ts +2 -4
  185. package/src/cli/stats-cli.ts +9 -5
  186. package/src/cli/update-cli.ts +471 -13
  187. package/src/cli-ordinary.ts +7 -2
  188. package/src/commands/accounts.ts +10 -5
  189. package/src/commands/harness.ts +25 -15
  190. package/src/commands/migrate.ts +3 -3
  191. package/src/commands/sdk.ts +277 -50
  192. package/src/commands/ultragoal.ts +16 -8
  193. package/src/commit/agentic/agent.ts +6 -6
  194. package/src/commit/analysis/conventional.ts +14 -5
  195. package/src/commit/changelog/index.ts +39 -1
  196. package/src/commit/map-reduce/index.ts +7 -7
  197. package/src/commit/message.ts +10 -3
  198. package/src/commit/pipeline.ts +12 -12
  199. package/src/config/autorouting-tier-map.ts +50 -1
  200. package/src/config/config-file.ts +106 -3
  201. package/src/config/config-hot-reload-trust.ts +148 -0
  202. package/src/config/config-hot-reload.ts +742 -0
  203. package/src/config/file-lock.ts +1615 -328
  204. package/src/config/keybindings.ts +154 -7
  205. package/src/config/model-bindings-applier.ts +85 -47
  206. package/src/config/model-discovery-manager.ts +10 -0
  207. package/src/config/model-profile-activation.ts +889 -148
  208. package/src/config/model-registry.ts +1706 -205
  209. package/src/config/model-resolver.ts +52 -22
  210. package/src/config/models-config-schema.ts +6 -0
  211. package/src/config/settings-schema.ts +73 -4
  212. package/src/config/settings.ts +460 -24
  213. package/src/coordinator-mcp/policy.ts +26 -1
  214. package/src/coordinator-mcp/server.ts +37 -2
  215. package/src/crash/sanitize.ts +7 -1
  216. package/src/customization/import.ts +279 -8
  217. package/src/customization/inventory.ts +24 -8
  218. package/src/customization/types.ts +1 -0
  219. package/src/dap/client.ts +3 -2
  220. package/src/dap/config.ts +31 -11
  221. package/src/dap/defaults.json +1 -1
  222. package/src/dap/session.ts +161 -36
  223. package/src/debug/crash-diagnostics.ts +89 -6
  224. package/src/debug/report-bundle.ts +90 -2
  225. package/src/debug/system-info.ts +8 -3
  226. package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.ts +21 -7
  227. package/src/discovery/agents-md.ts +1 -1
  228. package/src/discovery/builtin.ts +4 -3
  229. package/src/discovery/helpers.ts +25 -16
  230. package/src/discovery/mcp-compat.ts +120 -17
  231. package/src/edit/apply-patch/index.ts +12 -1
  232. package/src/edit/index.ts +204 -22
  233. package/src/edit/modes/patch.ts +214 -8
  234. package/src/edit/modes/replace.ts +136 -0
  235. package/src/eval/js/context-manager.ts +29 -3
  236. package/src/eval/js/worker-entry.ts +51 -18
  237. package/src/eval/py/executor.ts +14 -4
  238. package/src/eval/py/runner.py +144 -5
  239. package/src/eval/py/runtime.ts +44 -4
  240. package/src/exec/bash-executor.ts +81 -4
  241. package/src/exec/bash-shell-guardian.ts +4 -1
  242. package/src/exec/bash-shell-supervisor.ts +8 -4
  243. package/src/exec/isolated-shell.ts +5 -1
  244. package/src/exec/non-interactive-env.ts +0 -1
  245. package/src/export/html/index.ts +86 -6
  246. package/src/export/ttsr.ts +176 -1
  247. package/src/extensibility/custom-tools/wrapper.ts +3 -1
  248. package/src/extensibility/extensions/loader.ts +10 -1
  249. package/src/extensibility/extensions/ouroboros-ooo-bridge.ts +63 -1
  250. package/src/extensibility/extensions/types.ts +5 -1
  251. package/src/extensibility/hooks/loader.ts +10 -2
  252. package/src/extensibility/plugins/marketplace/fetcher.ts +2 -2
  253. package/src/extensibility/plugins/marketplace/manager.ts +36 -10
  254. package/src/extensibility/slash-commands.ts +5 -0
  255. package/src/gjc-runtime/gc-runtime.ts +37 -7
  256. package/src/gjc-runtime/launch-tmux.ts +30 -15
  257. package/src/gjc-runtime/launch-worktree.test.ts +148 -0
  258. package/src/gjc-runtime/launch-worktree.ts +101 -14
  259. package/src/gjc-runtime/managed-owner-admission.ts +46 -2
  260. package/src/gjc-runtime/ralplan-current-final.ts +294 -0
  261. package/src/gjc-runtime/ralplan-runtime.ts +95 -1
  262. package/src/gjc-runtime/session-resolution.ts +38 -6
  263. package/src/gjc-runtime/session-state-lock.ts +59 -14
  264. package/src/gjc-runtime/state-runtime.ts +23 -19
  265. package/src/gjc-runtime/state-writer.ts +109 -5
  266. package/src/gjc-runtime/tmux-sessions.ts +93 -13
  267. package/src/gjc-runtime/ultragoal-change-set.ts +90 -11
  268. package/src/gjc-runtime/ultragoal-guard.ts +9 -1
  269. package/src/gjc-runtime/ultragoal-runtime.ts +15 -5
  270. package/src/harness-control-plane/finalize.ts +11 -0
  271. package/src/harness-control-plane/receipt-spool.ts +105 -28
  272. package/src/harness-control-plane/storage.ts +124 -6
  273. package/src/hindsight/content.ts +116 -16
  274. package/src/hindsight/mental-models.ts +22 -10
  275. package/src/hindsight/state.ts +7 -4
  276. package/src/hooks/native-skill-hook.ts +17 -12
  277. package/src/internal-urls/artifact-protocol.ts +3 -1
  278. package/src/internal-urls/docs-index.generated.ts +23 -22
  279. package/src/internal-urls/local-protocol.ts +167 -8
  280. package/src/internal-urls/types.ts +2 -0
  281. package/src/lsp/client.ts +2 -1
  282. package/src/lsp/clients/biome-client.ts +2 -0
  283. package/src/lsp/clients/swiftlint-client.ts +2 -0
  284. package/src/lsp/index.ts +29 -0
  285. package/src/lsp/workspace-path.ts +23 -0
  286. package/src/main.ts +330 -92
  287. package/src/memories/index.ts +25 -2
  288. package/src/migrate/action-planner.ts +574 -2
  289. package/src/migrate/adapters/claude-code.ts +162 -5
  290. package/src/migrate/adapters/codex.ts +77 -7
  291. package/src/migrate/adapters/index.ts +258 -5
  292. package/src/migrate/adapters/opencode.ts +10 -1
  293. package/src/migrate/executor.ts +222 -7
  294. package/src/migrate/hook-module.ts +193 -0
  295. package/src/migrate/report.ts +63 -2
  296. package/src/migrate/types.ts +84 -4
  297. package/src/modes/DESIGN.md +51 -0
  298. package/src/modes/acp/acp-agent.ts +10 -1
  299. package/src/modes/acp/permission-mode.ts +18 -1
  300. package/src/modes/components/agent-dashboard.ts +20 -4
  301. package/src/modes/components/assistant-message.ts +55 -1
  302. package/src/modes/components/customization/customization-dashboard.ts +7 -0
  303. package/src/modes/components/customization/import-wizard.ts +29 -3
  304. package/src/modes/components/model-selector.ts +157 -103
  305. package/src/modes/components/settings-selector.ts +103 -37
  306. package/src/modes/components/welcome.ts +3 -1
  307. package/src/modes/controllers/command-controller.ts +300 -9
  308. package/src/modes/controllers/event-controller.ts +1 -1
  309. package/src/modes/controllers/extension-ui-controller.ts +31 -1
  310. package/src/modes/controllers/goal-mode-controller.ts +79 -10
  311. package/src/modes/controllers/input-controller.ts +2 -0
  312. package/src/modes/controllers/plan-mode-controller.ts +66 -11
  313. package/src/modes/controllers/runtime-mcp-command-controller.ts +7 -6
  314. package/src/modes/controllers/selector-controller.ts +76 -7
  315. package/src/modes/interactive-mode.ts +123 -4
  316. package/src/modes/prompt-suggestion-controller.ts +1 -1
  317. package/src/modes/runtime-init.ts +5 -1
  318. package/src/modes/theme/theme.ts +15 -0
  319. package/src/modes/utils/host-status.ts +21 -10
  320. package/src/prompts/agent-fragments/browser-aside.md +2 -2
  321. package/src/prompts/system/irc-incoming.md +3 -1
  322. package/src/prompts/tools/debug.md +1 -1
  323. package/src/prompts/tools/irc.md +1 -1
  324. package/src/prompts/tools/read.md +1 -1
  325. package/src/prompts/tools/report-issue.md +13 -0
  326. package/src/prompts/tools/task.md +1 -1
  327. package/src/runtime-credential-selector.ts +2 -2
  328. package/src/runtime-mcp/content-limits.ts +7 -0
  329. package/src/runtime-mcp/manager-pool.test.ts +5 -4
  330. package/src/runtime-mcp/manager.ts +40 -3
  331. package/src/runtime-mcp/oauth-flow.ts +32 -6
  332. package/src/runtime-mcp/plugin-mcp-name-filter.ts +62 -0
  333. package/src/runtime-mcp/project-host-values.ts +12 -8
  334. package/src/runtime-mcp/render.ts +16 -1
  335. package/src/runtime-mcp/tool-bridge.ts +194 -21
  336. package/src/runtime-mcp/transports/stdio.ts +2 -1
  337. package/src/sdk/broker/broker-exit.ts +10 -5
  338. package/src/sdk/broker/broker.ts +484 -85
  339. package/src/sdk/broker/daemon-entry.ts +338 -116
  340. package/src/sdk/broker/doctor-restart.ts +2 -1
  341. package/src/sdk/broker/ensure.ts +1214 -268
  342. package/src/sdk/broker/hop.ts +286 -0
  343. package/src/sdk/broker/lifecycle-ledger.ts +38 -11
  344. package/src/sdk/broker/lifecycle.ts +102 -6
  345. package/src/sdk/broker/lock-host-identity.ts +29 -0
  346. package/src/sdk/broker/session-index.ts +91 -16
  347. package/src/sdk/broker/transport.ts +17 -1
  348. package/src/sdk/bus/chat-command-policy.ts +16 -3
  349. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  350. package/src/sdk/bus/index.ts +460 -35
  351. package/src/sdk/bus/reconciliation-store.ts +201 -8
  352. package/src/sdk/bus/session-control.ts +2 -0
  353. package/src/sdk/bus/slack-daemon.ts +105 -9
  354. package/src/sdk/bus/telegram-daemon-contract.ts +9 -1
  355. package/src/sdk/bus/telegram-daemon.ts +10 -2
  356. package/src/sdk/bus/threaded-render.ts +20 -1
  357. package/src/sdk/cli/session-cli.ts +23 -6
  358. package/src/sdk/host/control/dispatch.ts +13 -1
  359. package/src/sdk/host/control/operations.ts +10 -1
  360. package/src/sdk/host/events.ts +67 -4
  361. package/src/sdk/host/host.ts +8 -2
  362. package/src/sdk/host/prompt-image-upload.ts +438 -0
  363. package/src/sdk/host/sdk-surface-parity.test.ts +15 -0
  364. package/src/sdk/host/session-runtime.test.ts +1737 -129
  365. package/src/sdk/host/session-runtime.ts +684 -83
  366. package/src/sdk/host/surface-policy.ts +4 -0
  367. package/src/sdk/host/websocket-transport.lifecycle.test.ts +90 -2
  368. package/src/sdk/host/websocket-transport.ts +19 -7
  369. package/src/sdk/lifecycle/service.ts +15 -1
  370. package/src/sdk/prompt-deadline-flush.ts +78 -5
  371. package/src/sdk/prompt-deadline-manager.ts +68 -14
  372. package/src/sdk/prompt-status.ts +6 -4
  373. package/src/sdk/protocol/adapter-validation.ts +81 -12
  374. package/src/sdk/protocol/operation-inventory.generated.json +261 -2
  375. package/src/sdk/protocol/operation-registry.ts +33 -3
  376. package/src/sdk/reconciliation-extensions.ts +1 -0
  377. package/src/sdk/session-list.ts +1 -0
  378. package/src/sdk/session.ts +237 -97
  379. package/src/session/agent-session.ts +2726 -479
  380. package/src/session/history-storage.ts +182 -12
  381. package/src/session/internal/managed-session-scope.ts +173 -57
  382. package/src/session/internal/managed-session-storage.ts +85 -12
  383. package/src/session/internal/native-publish-outcome.ts +5 -1
  384. package/src/session/messages.ts +47 -6
  385. package/src/session/session-manager.ts +326 -19
  386. package/src/session/startup-auth-config.ts +116 -8
  387. package/src/session/streaming-output.ts +262 -4
  388. package/src/session/terminal-abort.ts +14 -2
  389. package/src/session-import/redact.ts +53 -2
  390. package/src/session-import/service.ts +1 -0
  391. package/src/skill-state/workflow-mutation-guard.ts +2145 -206
  392. package/src/slash-commands/builtin-registry.ts +21 -6
  393. package/src/slash-commands/helpers/credential-switch.ts +10 -5
  394. package/src/slash-commands/helpers/mcp.ts +11 -3
  395. package/src/slash-commands/helpers/provider-auth-status.ts +456 -0
  396. package/src/ssh/ssh-executor.ts +9 -2
  397. package/src/stt/recorder.ts +26 -9
  398. package/src/system-prompt.ts +61 -16
  399. package/src/task/executor.ts +115 -20
  400. package/src/task/index.ts +389 -175
  401. package/src/task/role-body.ts +84 -0
  402. package/src/task/token-log-mode.test.ts +162 -0
  403. package/src/task/token-log.ts +91 -2
  404. package/src/tools/archive-reader.ts +650 -37
  405. package/src/tools/ask.ts +1 -0
  406. package/src/tools/ast-edit-write-path.ts +64 -0
  407. package/src/tools/ast-edit.ts +18 -1
  408. package/src/tools/ast-grep.ts +7 -2
  409. package/src/tools/atomic-file-write.ts +46 -15
  410. package/src/tools/auto-generated-guard.ts +15 -3
  411. package/src/tools/awaiting-input.ts +114 -0
  412. package/src/tools/bash-allowed-prefixes.ts +214 -6
  413. package/src/tools/bash-interactive.ts +14 -2
  414. package/src/tools/bash-skill-urls.ts +58 -5
  415. package/src/tools/bash.ts +113 -26
  416. package/src/tools/browser/tab-worker.ts +39 -13
  417. package/src/tools/browser.ts +17 -7
  418. package/src/tools/cron.ts +10 -1
  419. package/src/tools/debug.ts +16 -8
  420. package/src/tools/descriptors.test.ts +44 -0
  421. package/src/tools/descriptors.ts +50 -6
  422. package/src/tools/eval.ts +15 -0
  423. package/src/tools/fetch.ts +93 -4
  424. package/src/tools/gh.ts +27 -12
  425. package/src/tools/image-gen.ts +20 -6
  426. package/src/tools/index.ts +2 -0
  427. package/src/tools/irc.ts +5 -3
  428. package/src/tools/monitor.ts +9 -1
  429. package/src/tools/output-meta.ts +74 -5
  430. package/src/tools/python.ts +1 -1
  431. package/src/tools/read.ts +26 -37
  432. package/src/tools/recipe/runner.ts +23 -12
  433. package/src/tools/render-utils.ts +2 -2
  434. package/src/tools/report-issue.ts +184 -0
  435. package/src/tools/search.ts +3 -2
  436. package/src/tools/ssh.ts +274 -8
  437. package/src/tools/subagent-render.ts +114 -12
  438. package/src/tools/subagent.ts +118 -5
  439. package/src/tools/tmux-self-injection-guard.ts +3301 -122
  440. package/src/tools/todo-write.ts +41 -3
  441. package/src/tools/tool-catalog.generated.ts +32 -4
  442. package/src/tools/ultragoal-ask-guard.ts +13 -1
  443. package/src/tools/write.ts +42 -3
  444. package/src/tui/tree-list.ts +15 -1
  445. package/src/utils/commit-message-generator.ts +12 -4
  446. package/src/utils/external-editor.ts +21 -7
  447. package/src/utils/file-mentions.ts +75 -7
  448. package/src/utils/git.ts +38 -7
  449. package/src/utils/image-limits.ts +8 -0
  450. package/src/utils/image-loading.ts +1 -1
  451. package/src/utils/open.ts +73 -4
  452. package/src/utils/pasted-image-loading.ts +9 -8
  453. package/src/utils/prompt-suggestion.ts +5 -4
  454. package/src/utils/shell-snapshot.ts +9 -1
  455. package/src/utils/title-generator.ts +72 -22
  456. package/src/web/scrapers/index.ts +3 -1
  457. package/src/web/scrapers/types.ts +8 -0
  458. package/src/web/scrapers/youtube.ts +3 -2
  459. package/src/web/search/index.ts +2 -1
  460. package/src/web/search/providers/codex.ts +29 -32
  461. package/src/web/search/providers/insane.ts +91 -28
  462. package/src/web/search/providers/searxng.ts +62 -10
@@ -1,4 +1,17 @@
1
- import { copyFileSync, existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
1
+ import {
2
+ closeSync,
3
+ constants,
4
+ existsSync,
5
+ fchmodSync,
6
+ fstatSync,
7
+ ftruncateSync,
8
+ lstatSync,
9
+ openSync,
10
+ readFileSync,
11
+ renameSync,
12
+ unlinkSync,
13
+ writeSync,
14
+ } from "node:fs";
2
15
  import * as path from "node:path";
3
16
  import {
4
17
  type Keybinding,
@@ -469,22 +482,156 @@ function loadRawConfig(filePath: string): unknown {
469
482
  }
470
483
  }
471
484
 
485
+ const KEYBINDINGS_MARKER_MODE = 0o600;
486
+
487
+ type KeybindingsLeaf = "absent" | "file" | "symlink" | "other";
488
+
489
+ function keybindingsLeaf(filePath: string): KeybindingsLeaf {
490
+ try {
491
+ const stat = lstatSync(filePath);
492
+ if (stat.isSymbolicLink()) return "symlink";
493
+ if (stat.isFile()) return "file";
494
+ return "other";
495
+ } catch (error) {
496
+ if (isEnoent(error)) return "absent";
497
+ throw error;
498
+ }
499
+ }
500
+
501
+ function writeAllSync(fd: number, content: string | Buffer): void {
502
+ const buffer = typeof content === "string" ? Buffer.from(content) : content;
503
+ let offset = 0;
504
+ while (offset < buffer.length) {
505
+ const wrote = writeSync(fd, buffer, offset, buffer.length - offset);
506
+ if (wrote <= 0) throw new Error("keybindings migration write was incomplete");
507
+ offset += wrote;
508
+ }
509
+ }
510
+
511
+ /** Create a new file. O_NOFOLLOW refuses a final symlink. A set mode is fchmod'd. */
512
+ function writeExclusiveNoFollow(filePath: string, content: string | Buffer, mode: number | undefined): void {
513
+ const noFollow = process.platform === "win32" ? 0 : (constants.O_NOFOLLOW ?? 0);
514
+ const fd = openSync(filePath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | noFollow, mode ?? 0o666);
515
+ try {
516
+ writeAllSync(fd, content);
517
+ if (mode !== undefined && process.platform !== "win32") fchmodSync(fd, mode);
518
+ } finally {
519
+ closeSync(fd);
520
+ }
521
+ }
522
+
523
+ /** Copy bytes to the backup. A final symlink at either path is not opened for write. */
524
+ function copyKeybindingsBackup(
525
+ source: string,
526
+ dest: string,
527
+ sourceKind: KeybindingsLeaf,
528
+ destKind: KeybindingsLeaf,
529
+ ): void {
530
+ const noFollow = process.platform === "win32" ? 0 : (constants.O_NOFOLLOW ?? 0);
531
+ // A symlinked config is still backed up from its target. The backup path itself stays no-follow.
532
+ const sourceFlags = constants.O_RDONLY | (sourceKind === "file" ? noFollow : 0);
533
+ const srcFd = openSync(source, sourceFlags);
534
+ try {
535
+ const stat = fstatSync(srcFd);
536
+ if (!stat.isFile()) throw new Error("keybindings migration target is not a regular file");
537
+ const bytes = readFileSync(srcFd);
538
+ const mode = stat.mode & 0o777;
539
+ if (destKind === "absent") {
540
+ writeExclusiveNoFollow(dest, bytes, mode);
541
+ return;
542
+ }
543
+ const fd = openSync(dest, constants.O_WRONLY | noFollow);
544
+ try {
545
+ const destStat = fstatSync(fd);
546
+ if (!destStat.isFile()) throw new Error("keybindings migration target is not a regular file");
547
+ if (destStat.dev === stat.dev && destStat.ino === stat.ino) return;
548
+ ftruncateSync(fd, 0);
549
+ writeAllSync(fd, bytes);
550
+ if (process.platform !== "win32") fchmodSync(fd, mode);
551
+ } finally {
552
+ closeSync(fd);
553
+ }
554
+ } finally {
555
+ closeSync(srcFd);
556
+ }
557
+ }
558
+
559
+ function migrationMarkerState(markerPath: string): { present: boolean; kind: KeybindingsLeaf } {
560
+ const kind = keybindingsLeaf(markerPath);
561
+ // A symlink is not a completed marker. existsSync only preserves the historical
562
+ // skip when that link already names a real target. The write path never opens it.
563
+ const present = kind === "file" || kind === "other" || (kind === "symlink" && existsSync(markerPath));
564
+ return { present, kind };
565
+ }
566
+
567
+ function writeMigratedKeybindings(
568
+ filePath: string,
569
+ markerPath: string,
570
+ markerKind: KeybindingsLeaf,
571
+ body: string,
572
+ ): void {
573
+ const tempPath = `${filePath}.${process.pid}.tmp`;
574
+ const bakPath = `${filePath}.bak`;
575
+ const bakKind = keybindingsLeaf(bakPath);
576
+ const tempKind = keybindingsLeaf(tempPath);
577
+ const sourceKind = keybindingsLeaf(filePath);
578
+ if (
579
+ bakKind === "symlink" ||
580
+ bakKind === "other" ||
581
+ tempKind === "symlink" ||
582
+ tempKind === "other" ||
583
+ (sourceKind !== "file" && sourceKind !== "symlink")
584
+ ) {
585
+ throw new Error("keybindings migration target is not a regular file");
586
+ }
587
+ if (tempKind === "file") unlinkSync(tempPath);
588
+ if (keybindingsLeaf(filePath) !== sourceKind) {
589
+ throw new Error("keybindings migration target is not a regular file");
590
+ }
591
+ const mode = sourceKind === "file" ? lstatSync(filePath).mode & 0o777 : undefined;
592
+ try {
593
+ copyKeybindingsBackup(filePath, bakPath, sourceKind, bakKind);
594
+ writeExclusiveNoFollow(tempPath, body, mode);
595
+ const current = lstatSync(filePath);
596
+ if (sourceKind === "file" && (current.isSymbolicLink() || !current.isFile())) {
597
+ throw new Error("keybindings migration target is not a regular file");
598
+ }
599
+ renameSync(tempPath, filePath);
600
+ if (markerKind === "absent") writeExclusiveNoFollow(markerPath, "v1\n", KEYBINDINGS_MARKER_MODE);
601
+ } catch (error) {
602
+ if (keybindingsLeaf(tempPath) === "file") {
603
+ try {
604
+ unlinkSync(tempPath);
605
+ } catch {
606
+ // Best-effort removal of the unreplaced temp file.
607
+ }
608
+ }
609
+ throw error;
610
+ }
611
+ }
612
+
472
613
  /** Migrate legacy keybindings once, preserving the original file as .bak. */
473
614
  function loadKeybindingsConfig(filePath: string, writeBack: boolean): KeybindingsConfig {
474
615
  const rawConfig = loadRawConfig(filePath);
475
616
  if (rawConfig === null) return {};
476
617
  const markerPath = `${filePath}.migration-v1`;
477
- const { config: migratedConfig, migrated } = existsSync(markerPath)
618
+ let markerKind: KeybindingsLeaf = "absent";
619
+ let markerPresent = false;
620
+ try {
621
+ const marker = migrationMarkerState(markerPath);
622
+ markerKind = marker.kind;
623
+ markerPresent = marker.present;
624
+ } catch (error) {
625
+ logger.warn("Failed to read keybindings migration marker", { path: markerPath, error: String(error) });
626
+ return toKeybindingsConfig(rawConfig);
627
+ }
628
+ const { config: migratedConfig, migrated } = markerPresent
478
629
  ? { config: toKeybindingsConfig(rawConfig), migrated: false }
479
630
  : migrateKeybindingNames(rawConfig);
480
631
  if (writeBack && migrated) {
481
632
  const ordered = orderKeybindingsConfig(migratedConfig);
482
- const tempPath = `${filePath}.${process.pid}.tmp`;
483
633
  try {
484
- copyFileSync(filePath, `${filePath}.bak`);
485
- writeFileSync(tempPath, `${JSON.stringify(ordered, null, 2)}\n`, "utf-8");
486
- renameSync(tempPath, filePath);
487
- writeFileSync(markerPath, "v1\n", "utf-8");
634
+ writeMigratedKeybindings(filePath, markerPath, markerKind, `${JSON.stringify(ordered, null, 2)}\n`);
488
635
  logger.debug("Migrated keybindings config", { path: filePath });
489
636
  } catch (error) {
490
637
  logger.warn("Failed to write migrated keybindings config", { path: filePath, error: String(error) });
@@ -10,12 +10,14 @@ export interface ConfiguredModelBindings {
10
10
  export class ModelBindingsApplier {
11
11
  #targetSettings: Settings | undefined;
12
12
  #bindings: ConfiguredModelBindings | undefined;
13
+ #lastAppliedRoles = new Map<string, ModelSelectorValue>();
14
+ #lastAppliedAgentOverrides = new Map<string, ModelSelectorValue>();
15
+ #manualRoles = new Map<string, ModelSelectorValue | undefined>();
16
+ #manualAgentOverrides = new Map<string, ModelSelectorValue | undefined>();
13
17
  #appliedRoles = new Set<string>();
14
18
  #appliedAgentOverrides = new Set<string>();
15
19
  #roleBaselines = new Map<string, ModelSelectorValue | undefined>();
16
20
  #agentBaselines = new Map<string, ModelSelectorValue | undefined>();
17
- #lastAppliedRoles = new Map<string, ModelSelectorValue>();
18
- #lastAppliedAgentOverrides = new Map<string, ModelSelectorValue>();
19
21
 
20
22
  /** The currently configured bindings (as installed at startup), for baseline lookup. */
21
23
  getBindings(): ConfiguredModelBindings | undefined {
@@ -71,6 +73,8 @@ export class ModelBindingsApplier {
71
73
  this.#targetSettings = targetSettings;
72
74
  const bindings = this.#bindings;
73
75
  if (!targetSettings) return;
76
+ for (const role of Object.keys(bindings?.modelRoles ?? {})) this.#manualRoles.delete(role);
77
+ for (const role of Object.keys(bindings?.agentModelOverrides ?? {})) this.#manualAgentOverrides.delete(role);
74
78
  const modelRoles = { ...(targetSettings.get("modelRoles") ?? {}) };
75
79
  this.#forceSync(
76
80
  modelRoles,
@@ -98,6 +102,13 @@ export class ModelBindingsApplier {
98
102
  baselines: Map<string, ModelSelectorValue | undefined>,
99
103
  lastApplied: Map<string, ModelSelectorValue>,
100
104
  ): void {
105
+ const configuredKeys = new Set(Object.keys(configured));
106
+ for (const key of applied) {
107
+ if (configuredKeys.has(key)) continue;
108
+ const baseline = baselines.get(key);
109
+ if (baseline === undefined) delete target[key];
110
+ else target[key] = this.#clone(baseline)!;
111
+ }
101
112
  for (const [key, value] of Object.entries(configured)) {
102
113
  if (!baselines.has(key)) baselines.set(key, this.#clone(target[key]));
103
114
  target[key] = this.#clone(value)!;
@@ -111,82 +122,109 @@ export class ModelBindingsApplier {
111
122
  const targetSettings = this.#targetSettings;
112
123
  if (!targetSettings) return;
113
124
  const bindings = this.#bindings;
114
- const nextModelRoles = { ...(targetSettings.get("modelRoles") ?? {}) };
115
125
  this.#sync(
116
- nextModelRoles,
126
+ targetSettings,
127
+ "modelRoles",
117
128
  bindings?.modelRoles ?? {},
118
- this.#appliedRoles,
119
- this.#roleBaselines,
120
129
  this.#lastAppliedRoles,
130
+ this.#manualRoles,
131
+ this.#roleBaselines,
121
132
  );
122
- targetSettings.override("modelRoles", nextModelRoles);
123
-
124
- const nextAgentOverrides = { ...(targetSettings.get("task.agentModelOverrides") ?? {}) };
125
133
  this.#sync(
126
- nextAgentOverrides,
134
+ targetSettings,
135
+ "task.agentModelOverrides",
127
136
  bindings?.agentModelOverrides ?? {},
128
- this.#appliedAgentOverrides,
129
- this.#agentBaselines,
130
137
  this.#lastAppliedAgentOverrides,
138
+ this.#manualAgentOverrides,
139
+ this.#agentBaselines,
131
140
  );
132
- targetSettings.override("task.agentModelOverrides", nextAgentOverrides);
133
141
  }
134
142
 
135
143
  #restoreTarget(targetSettings: Settings): void {
136
- const modelRoles = { ...(targetSettings.get("modelRoles") ?? {}) };
137
- this.#sync(modelRoles, {}, this.#appliedRoles, this.#roleBaselines, this.#lastAppliedRoles);
138
- targetSettings.override("modelRoles", modelRoles);
139
-
140
- const agentOverrides = { ...(targetSettings.get("task.agentModelOverrides") ?? {}) };
141
144
  this.#sync(
142
- agentOverrides,
145
+ targetSettings,
146
+ "task.agentModelOverrides",
143
147
  {},
144
- this.#appliedAgentOverrides,
145
- this.#agentBaselines,
146
148
  this.#lastAppliedAgentOverrides,
149
+ this.#manualAgentOverrides,
150
+ this.#agentBaselines,
147
151
  );
148
- targetSettings.override("task.agentModelOverrides", agentOverrides);
152
+ this.#sync(targetSettings, "modelRoles", {}, this.#lastAppliedRoles, this.#manualRoles, this.#roleBaselines);
149
153
  }
150
154
 
151
155
  #clearTargetLifecycle(): void {
152
- this.#appliedRoles.clear();
153
- this.#appliedAgentOverrides.clear();
154
- this.#roleBaselines.clear();
155
- this.#agentBaselines.clear();
156
156
  this.#lastAppliedRoles.clear();
157
157
  this.#lastAppliedAgentOverrides.clear();
158
+ this.#manualRoles.clear();
159
+ this.#manualAgentOverrides.clear();
160
+ this.#roleBaselines.clear();
161
+ this.#agentBaselines.clear();
158
162
  }
159
163
 
160
164
  #sync(
161
- target: Record<string, ModelSelectorValue>,
165
+ targetSettings: Settings,
166
+ settingPath: "modelRoles" | "task.agentModelOverrides",
162
167
  configured: Record<string, ModelSelectorValue>,
163
- applied: Set<string>,
164
- baselines: Map<string, ModelSelectorValue | undefined>,
165
168
  lastApplied: Map<string, ModelSelectorValue>,
169
+ manualOverrides: Map<string, ModelSelectorValue | undefined>,
170
+ baselines: Map<string, ModelSelectorValue | undefined>,
166
171
  ): void {
167
172
  const configuredKeys = new Set(Object.keys(configured));
168
- for (const key of applied) {
169
- if (configuredKeys.has(key)) continue;
170
- const previous = lastApplied.get(key);
171
- if (previous !== undefined && this.#equal(target[key], previous)) {
172
- const baseline = baselines.get(key);
173
- if (baseline === undefined) delete target[key];
174
- else target[key] = this.#clone(baseline)!;
173
+ const current = (targetSettings.get(settingPath) ?? {}) as Record<string, ModelSelectorValue>;
174
+ const global = (targetSettings.getGlobal(settingPath) ?? {}) as Record<string, ModelSelectorValue>;
175
+ const runtime = targetSettings.getOverride(settingPath) ?? {};
176
+ for (const key of configuredKeys) {
177
+ if (!lastApplied.has(key) && !baselines.has(key)) baselines.set(key, this.#clone(runtime[key]));
178
+ }
179
+
180
+ // Manual bindings remain authoritative, but their cached values must follow
181
+ // later edits and removals from the runtime override slots.
182
+ for (const key of manualOverrides.keys()) {
183
+ manualOverrides.set(key, this.#clone(runtime[key]));
184
+ }
185
+
186
+ // Keep existing non-global values that belong to another runtime layer.
187
+ for (const [key, value] of Object.entries(current)) {
188
+ if (configuredKeys.has(key) || lastApplied.has(key) || manualOverrides.has(key)) continue;
189
+ if (!this.#equal(value, global[key])) manualOverrides.set(key, this.#clone(value)!);
190
+ }
191
+
192
+ // A value changed while our previous overlay was active is a manual edit.
193
+ for (const [key, previous] of lastApplied) {
194
+ const currentValue = current[key];
195
+ if (!Object.hasOwn(runtime, key) || !Object.hasOwn(current, key)) {
196
+ manualOverrides.set(key, undefined);
197
+ } else if (!this.#equal(runtime[key], previous)) {
198
+ manualOverrides.set(key, this.#clone(runtime[key])!);
199
+ } else if (!this.#equal(currentValue, previous)) {
200
+ manualOverrides.set(key, this.#clone(currentValue)!);
175
201
  }
176
- baselines.delete(key);
177
- lastApplied.delete(key);
178
202
  }
179
- for (const [key, value] of Object.entries(configured)) {
180
- const previous = lastApplied.get(key);
181
- if (!baselines.has(key)) baselines.set(key, this.#clone(target[key]));
182
- if (previous === undefined || this.#equal(target[key], previous)) {
183
- const appliedValue = this.#clone(value)!;
184
- target[key] = appliedValue;
185
- lastApplied.set(key, this.#clone(appliedValue)!);
203
+
204
+ const nextOverrides: Record<string, ModelSelectorValue> = {};
205
+ for (const [key, value] of Object.entries(runtime)) {
206
+ if (!lastApplied.has(key) && !configuredKeys.has(key)) nextOverrides[key] = this.#clone(value)!;
207
+ }
208
+ for (const [key, baseline] of baselines) {
209
+ if (!configuredKeys.has(key)) {
210
+ if (baseline !== undefined) nextOverrides[key] = this.#clone(baseline)!;
211
+ baselines.delete(key);
186
212
  }
187
213
  }
188
- applied.clear();
189
- for (const key of configuredKeys) applied.add(key);
214
+ for (const [key, value] of manualOverrides) {
215
+ if (value === undefined) delete nextOverrides[key];
216
+ else nextOverrides[key] = this.#clone(value)!;
217
+ }
218
+ lastApplied.clear();
219
+ for (const [key, value] of Object.entries(configured)) {
220
+ if (manualOverrides.has(key)) continue;
221
+ const appliedValue = this.#clone(value)!;
222
+ nextOverrides[key] = appliedValue;
223
+ lastApplied.set(key, this.#clone(appliedValue)!);
224
+ }
225
+
226
+ if (Object.keys(nextOverrides).length === 0) targetSettings.clearOverride(settingPath);
227
+ else targetSettings.override(settingPath, nextOverrides);
190
228
  }
191
229
 
192
230
  #cloneBindings(
@@ -100,6 +100,16 @@ export class ModelDiscoveryManager<TProvider extends DiscoveryProvider> {
100
100
  const state = this.#states.get(provider);
101
101
  return state === undefined ? undefined : this.#snapshot(state);
102
102
  }
103
+
104
+ /** Retain accepted discovery evidence for unchanged providers during a local reload. */
105
+ retainStatesFrom(source: ModelDiscoveryManager<TProvider>, providers: ReadonlySet<string>): void {
106
+ for (const provider of this.#providers) {
107
+ if (!providers.has(provider.provider)) continue;
108
+ const state = source.#states.get(provider.provider);
109
+ if (state) this.#states.set(provider.provider, this.#snapshot(state));
110
+ }
111
+ }
112
+
103
113
  invalidate(provider: string): void {
104
114
  this.#invalidate(provider);
105
115
  this.#states.delete(provider);