@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
@@ -9,6 +9,7 @@ import {
9
9
  import {
10
10
  Container,
11
11
  fuzzyFilter,
12
+ fuzzyMatch,
12
13
  getKeybindings,
13
14
  Input,
14
15
  matchesKey,
@@ -99,6 +100,150 @@ function computeModelRank(model: Model, roles: Record<string, RoleAssignment | u
99
100
  return roles.default && modelsAreEqual(roles.default.model, model) ? 0 : 1;
100
101
  }
101
102
 
103
+ /**
104
+ * Compute and assign fuzzy scores to model items based on a query.
105
+ * Scores are based on fuzzy matching against the model's searchable text.
106
+ */
107
+ function fuzzyTokensScore(query: string, text: string): number | undefined {
108
+ const tokens = query
109
+ .trim()
110
+ .split(/\s+/)
111
+ .filter(token => token.length > 0);
112
+ if (tokens.length === 0) return undefined;
113
+ let total = 0;
114
+ for (const token of tokens) {
115
+ const match = fuzzyMatch(token, text);
116
+ if (!match.matches) return undefined;
117
+ total += match.score;
118
+ }
119
+ return total;
120
+ }
121
+
122
+ export function assignFuzzyScoresToModels(items: ModelItem[], query: string): void {
123
+ if (!query.trim()) {
124
+ for (const item of items) item.fuzzyScore = undefined;
125
+ return;
126
+ }
127
+ for (const item of items) {
128
+ // Overwrite every item so a raw-query miss cannot retain an earlier score.
129
+ item.fuzzyScore = fuzzyTokensScore(query, `${item.id} ${item.provider}`);
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Compute and assign fuzzy scores to canonical model items based on a query.
135
+ */
136
+ export function assignFuzzyScoresToCanonicalModels(items: CanonicalModelItem[], query: string): void {
137
+ if (!query.trim()) {
138
+ for (const item of items) item.fuzzyScore = undefined;
139
+ return;
140
+ }
141
+ for (const item of items) {
142
+ item.fuzzyScore = fuzzyTokensScore(query, item.searchText);
143
+ }
144
+ }
145
+
146
+ export interface RankedModelOrderContext {
147
+ roles: RoleAssignments;
148
+ mruIndex: ReadonlyMap<string, number>;
149
+ providerAuthStateById: ReadonlyMap<string, ProviderAuthState>;
150
+ }
151
+
152
+ const MODEL_DATE_RE = /-(\d{8})$/;
153
+ const MODEL_LATEST_RE = /-latest$/;
154
+
155
+ /** Ranking shared by the provider view: fuzzy quality, default, MRU, provider, recency. */
156
+ export function compareModelItemsForRanking(a: ModelItem, b: ModelItem, context: RankedModelOrderContext): number {
157
+ const { roles, mruIndex, providerAuthStateById } = context;
158
+ const aHasFuzzyScore = a.fuzzyScore !== undefined;
159
+ const bHasFuzzyScore = b.fuzzyScore !== undefined;
160
+ if (aHasFuzzyScore || bHasFuzzyScore) {
161
+ const aScore = a.fuzzyScore ?? Number.MAX_SAFE_INTEGER;
162
+ const bScore = b.fuzzyScore ?? Number.MAX_SAFE_INTEGER;
163
+ if (aScore !== bScore) return aScore - bScore;
164
+ // Ties keep the established default-first, then MRU/provider ordering.
165
+ }
166
+
167
+ const aRank = computeModelRank(a.model, roles);
168
+ const bRank = computeModelRank(b.model, roles);
169
+ if (aRank !== bRank) return aRank - bRank;
170
+
171
+ const aMru = mruIndex.get(a.selector) ?? Number.MAX_SAFE_INTEGER;
172
+ const bMru = mruIndex.get(b.selector) ?? Number.MAX_SAFE_INTEGER;
173
+ if (aMru !== bMru) return aMru - bMru;
174
+
175
+ const providerCmp = compareRankedProviders(
176
+ { id: a.provider, label: a.provider, authState: providerAuthStateById.get(a.provider) ?? "none" },
177
+ { id: b.provider, label: b.provider, authState: providerAuthStateById.get(b.provider) ?? "none" },
178
+ );
179
+ if (providerCmp !== 0) return providerCmp;
180
+
181
+ // Priority field (lower = better, e.g. OpenAI code backend priority values)
182
+ const aPri = a.model.priority ?? Number.MAX_SAFE_INTEGER;
183
+ const bPri = b.model.priority ?? Number.MAX_SAFE_INTEGER;
184
+ if (aPri !== bPri) return aPri - bPri;
185
+
186
+ // Version number descending (higher version = better model)
187
+ const aVer = extractVersionNumber(a.id);
188
+ const bVer = extractVersionNumber(b.id);
189
+ if (aVer !== bVer) return bVer - aVer;
190
+
191
+ const aIsLatest = MODEL_LATEST_RE.test(a.id);
192
+ const bIsLatest = MODEL_LATEST_RE.test(b.id);
193
+ const aDate = a.id.match(MODEL_DATE_RE)?.[1] ?? "";
194
+ const bDate = b.id.match(MODEL_DATE_RE)?.[1] ?? "";
195
+
196
+ const aHasRecency = aIsLatest || aDate !== "";
197
+ const bHasRecency = bIsLatest || bDate !== "";
198
+ if (aHasRecency !== bHasRecency) return aHasRecency ? -1 : 1;
199
+ if (!aHasRecency) return a.id.localeCompare(b.id);
200
+ if (aIsLatest !== bIsLatest) return aIsLatest ? -1 : 1;
201
+ if (aDate && bDate) return bDate.localeCompare(aDate);
202
+ return aIsLatest ? -1 : bIsLatest ? 1 : a.id.localeCompare(b.id);
203
+ }
204
+
205
+ /** Ranking shared by the canonical view: fuzzy quality, default, MRU, provider, then id. */
206
+ export function compareCanonicalModelItemsForRanking(
207
+ a: CanonicalModelItem,
208
+ b: CanonicalModelItem,
209
+ context: RankedModelOrderContext,
210
+ ): number {
211
+ const { roles, mruIndex, providerAuthStateById } = context;
212
+ const aHasFuzzyScore = a.fuzzyScore !== undefined;
213
+ const bHasFuzzyScore = b.fuzzyScore !== undefined;
214
+ if (aHasFuzzyScore || bHasFuzzyScore) {
215
+ const aScore = a.fuzzyScore ?? Number.MAX_SAFE_INTEGER;
216
+ const bScore = b.fuzzyScore ?? Number.MAX_SAFE_INTEGER;
217
+ if (aScore !== bScore) return aScore - bScore;
218
+ // Ties keep the established default-first, then MRU/provider ordering.
219
+ }
220
+
221
+ const aRank = computeModelRank(a.model, roles);
222
+ const bRank = computeModelRank(b.model, roles);
223
+ if (aRank !== bRank) return aRank - bRank;
224
+
225
+ const aKey = `${a.model.provider}/${a.model.id}`;
226
+ const bKey = `${b.model.provider}/${b.model.id}`;
227
+ const aMru = mruIndex.get(aKey) ?? Number.MAX_SAFE_INTEGER;
228
+ const bMru = mruIndex.get(bKey) ?? Number.MAX_SAFE_INTEGER;
229
+ if (aMru !== bMru) return aMru - bMru;
230
+
231
+ const providerCmp = compareRankedProviders(
232
+ {
233
+ id: a.model.provider,
234
+ label: a.model.provider,
235
+ authState: providerAuthStateById.get(a.model.provider) ?? "none",
236
+ },
237
+ {
238
+ id: b.model.provider,
239
+ label: b.model.provider,
240
+ authState: providerAuthStateById.get(b.model.provider) ?? "none",
241
+ },
242
+ );
243
+ if (providerCmp !== 0) return providerCmp;
244
+ return a.id.localeCompare(b.id);
245
+ }
246
+
102
247
  interface ModelItem {
103
248
  kind: "provider";
104
249
  provider: string;
@@ -107,6 +252,7 @@ interface ModelItem {
107
252
  selector: string;
108
253
  thinkingLevel?: ThinkingLevel;
109
254
  explicitThinkingLevel?: boolean;
255
+ fuzzyScore?: number;
110
256
  }
111
257
 
112
258
  interface CanonicalModelItem {
@@ -120,6 +266,7 @@ interface CanonicalModelItem {
120
266
  compactSearchText: string;
121
267
  thinkingLevel?: ThinkingLevel;
122
268
  explicitThinkingLevel?: boolean;
269
+ fuzzyScore?: number;
123
270
  }
124
271
 
125
272
  type ScopedModelItem = ScopedModelSelection;
@@ -646,7 +793,7 @@ export class ModelSelectorComponent extends Container {
646
793
  this.#configuredDefaultChain ?? normalizeModelSelectorValue(this.#settings.getModelRole("default"));
647
794
  if (configuredChain.length < 2) return false;
648
795
 
649
- const allModels = this.#modelRegistry.getAll();
796
+ const allModels = this.#modelRegistry.getAll(this.#authSessionId);
650
797
  const matchPreferences = { usageOrder: this.#settings.getStorage()?.getModelUsageOrder() };
651
798
  return configuredChain.slice(1).some(selector => {
652
799
  const resolved = resolveModelRoleValue(selector, allModels, {
@@ -662,7 +809,7 @@ export class ModelSelectorComponent extends Container {
662
809
 
663
810
  #loadRoleModels(): RoleAssignments {
664
811
  const roles: RoleAssignments = {};
665
- const allModels = this.#modelRegistry.getAll();
812
+ const allModels = this.#modelRegistry.getAll(this.#authSessionId);
666
813
  const matchPreferences = { usageOrder: this.#settings.getStorage()?.getModelUsageOrder() };
667
814
  const agentModelOverrides = this.#settings.get("task.agentModelOverrides");
668
815
  const activeProfile = this.#activeModelProfile
@@ -738,7 +885,6 @@ export class ModelSelectorComponent extends Container {
738
885
  }
739
886
 
740
887
  #sortModels(models: ModelItem[], roles: RoleAssignments = this.#roles): void {
741
- // Sort: default-tagged model first, then MRU, then provider ranking
742
888
  const mruOrder = this.#settings.getStorage()?.getModelUsageOrder() ?? [];
743
889
  const mruIndex = new Map(mruOrder.map((key, i) => [key, i]));
744
890
  const providerAuthStateById = new Map<string, ProviderAuthState>();
@@ -747,74 +893,7 @@ export class ModelSelectorComponent extends Container {
747
893
  providerAuthStateById.set(item.provider, this.#resolveProviderAuthState(item.provider));
748
894
  }
749
895
  }
750
-
751
- const modelRank = (item: ModelItem) => computeModelRank(item.model, roles);
752
-
753
- const dateRe = /-(\d{8})$/;
754
- const latestRe = /-latest$/;
755
-
756
- models.sort((a, b) => {
757
- const aKey = a.selector;
758
- const bKey = b.selector;
759
-
760
- const aRank = modelRank(a);
761
- const bRank = modelRank(b);
762
- if (aRank !== bRank) return aRank - bRank;
763
-
764
- // Then MRU order (models in mruIndex come before those not in it)
765
- const aMru = mruIndex.get(aKey) ?? Number.MAX_SAFE_INTEGER;
766
- const bMru = mruIndex.get(bKey) ?? Number.MAX_SAFE_INTEGER;
767
- if (aMru !== bMru) return aMru - bMru;
768
-
769
- // By provider, then recency within provider
770
- const providerCmp = compareRankedProviders(
771
- {
772
- id: a.provider,
773
- label: a.provider,
774
- authState: providerAuthStateById.get(a.provider) ?? "none",
775
- },
776
- {
777
- id: b.provider,
778
- label: b.provider,
779
- authState: providerAuthStateById.get(b.provider) ?? "none",
780
- },
781
- );
782
- if (providerCmp !== 0) return providerCmp;
783
-
784
- // Priority field (lower = better, e.g. OpenAI code backend priority values)
785
- const aPri = a.model.priority ?? Number.MAX_SAFE_INTEGER;
786
- const bPri = b.model.priority ?? Number.MAX_SAFE_INTEGER;
787
- if (aPri !== bPri) return aPri - bPri;
788
-
789
- // Version number descending (higher version = better model)
790
- const aVer = extractVersionNumber(a.id);
791
- const bVer = extractVersionNumber(b.id);
792
- if (aVer !== bVer) return bVer - aVer;
793
-
794
- const aIsLatest = latestRe.test(a.id);
795
- const bIsLatest = latestRe.test(b.id);
796
- const aDate = a.id.match(dateRe)?.[1] ?? "";
797
- const bDate = b.id.match(dateRe)?.[1] ?? "";
798
-
799
- // Both have dates or latest tags — sort by recency
800
- const aHasRecency = aIsLatest || aDate !== "";
801
- const bHasRecency = bIsLatest || bDate !== "";
802
-
803
- // Models with recency info come before those without
804
- if (aHasRecency !== bHasRecency) return aHasRecency ? -1 : 1;
805
-
806
- // If neither has recency info, fall back to alphabetical
807
- if (!aHasRecency) return a.id.localeCompare(b.id);
808
-
809
- // -latest always sorts first within recency group
810
- if (aIsLatest !== bIsLatest) return aIsLatest ? -1 : 1;
811
-
812
- // Both have dates — descending (newest first)
813
- if (aDate && bDate) return bDate.localeCompare(aDate);
814
-
815
- // One has date, other is latest — latest first
816
- return aIsLatest ? -1 : bIsLatest ? 1 : a.id.localeCompare(b.id);
817
- });
896
+ models.sort((a, b) => compareModelItemsForRanking(a, b, { roles, mruIndex, providerAuthStateById }));
818
897
  }
819
898
 
820
899
  #sortCanonicalModels(models: CanonicalModelItem[], roles: RoleAssignments = this.#roles): void {
@@ -826,34 +905,7 @@ export class ModelSelectorComponent extends Container {
826
905
  providerAuthStateById.set(item.model.provider, this.#resolveProviderAuthState(item.model.provider));
827
906
  }
828
907
  }
829
-
830
- const modelRank = (item: CanonicalModelItem) => computeModelRank(item.model, roles);
831
-
832
- models.sort((a, b) => {
833
- const aRank = modelRank(a);
834
- const bRank = modelRank(b);
835
- if (aRank !== bRank) return aRank - bRank;
836
-
837
- const aMru = mruIndex.get(`${a.model.provider}/${a.model.id}`) ?? Number.MAX_SAFE_INTEGER;
838
- const bMru = mruIndex.get(`${b.model.provider}/${b.model.id}`) ?? Number.MAX_SAFE_INTEGER;
839
- if (aMru !== bMru) return aMru - bMru;
840
-
841
- const providerCmp = compareRankedProviders(
842
- {
843
- id: a.model.provider,
844
- label: a.model.provider,
845
- authState: providerAuthStateById.get(a.model.provider) ?? "none",
846
- },
847
- {
848
- id: b.model.provider,
849
- label: b.model.provider,
850
- authState: providerAuthStateById.get(b.model.provider) ?? "none",
851
- },
852
- );
853
- if (providerCmp !== 0) return providerCmp;
854
-
855
- return a.id.localeCompare(b.id);
856
- });
908
+ models.sort((a, b) => compareCanonicalModelItemsForRanking(a, b, { roles, mruIndex, providerAuthStateById }));
857
909
  }
858
910
 
859
911
  #buildScopedModelItems(): ModelItem[] {
@@ -869,7 +921,7 @@ export class ModelSelectorComponent extends Container {
869
921
  }
870
922
 
871
923
  #buildAvailableModelItems(): ModelItem[] {
872
- return this.#modelRegistry.getAvailable().map((model: Model) => ({
924
+ return this.#modelRegistry.getAvailable(this.#authSessionId).map((model: Model) => ({
873
925
  kind: "provider",
874
926
  provider: model.provider,
875
927
  id: model.id,
@@ -1205,10 +1257,12 @@ export class ModelSelectorComponent extends Container {
1205
1257
  ? alphaFiltered
1206
1258
  : baseCanonicalModels;
1207
1259
  const fuzzyMatches = fuzzyFilter(fuzzySource, query, ({ searchText }) => searchText);
1260
+ assignFuzzyScoresToCanonicalModels(fuzzyMatches, query);
1208
1261
  this.#sortCanonicalModels(fuzzyMatches);
1209
1262
  this.#filteredCanonicalModels = fuzzyMatches;
1210
1263
  } else {
1211
1264
  const fuzzyMatches = fuzzyFilter(baseModels, query, ({ id, provider }) => `${id} ${provider}`);
1265
+ assignFuzzyScoresToModels(fuzzyMatches, query);
1212
1266
  this.#sortModels(fuzzyMatches);
1213
1267
  this.#filteredModels = fuzzyMatches;
1214
1268
  }
@@ -1256,7 +1310,7 @@ export class ModelSelectorComponent extends Container {
1256
1310
  if (isInheritedRoleSelector(configuredSelector)) continue;
1257
1311
  const selectors = resolveConfiguredModelPatterns(configuredSelector, this.#settings);
1258
1312
  for (const selector of selectors.length > 0 ? selectors : [configuredSelector]) {
1259
- const resolved = resolveModelRoleValue(selector, this.#modelRegistry.getAll(), {
1313
+ const resolved = resolveModelRoleValue(selector, this.#modelRegistry.getAll(this.#authSessionId), {
1260
1314
  settings: this.#settings,
1261
1315
  matchPreferences: { usageOrder: this.#settings.getStorage()?.getModelUsageOrder() },
1262
1316
  modelRegistry: this.#modelRegistry,
@@ -1369,7 +1423,7 @@ export class ModelSelectorComponent extends Container {
1369
1423
  }
1370
1424
 
1371
1425
  #getProfileAvailableModels(): Model[] {
1372
- return this.#modelRegistry.getAvailable();
1426
+ return this.#modelRegistry.getAvailable(this.#authSessionId);
1373
1427
  }
1374
1428
 
1375
1429
  #createProfileResolutionRegistry(availableModels: readonly Model[]) {
@@ -2108,7 +2162,7 @@ export class ModelSelectorComponent extends Container {
2108
2162
  for (const role of PROFILE_ROLE_PREVIEW_ORDER) {
2109
2163
  const selector = profile.modelMapping[role];
2110
2164
  if (!selector) continue;
2111
- const resolved = resolveModelRoleValue(selector, this.#modelRegistry.getAll(), {
2165
+ const resolved = resolveModelRoleValue(selector, this.#modelRegistry.getAll(this.#authSessionId), {
2112
2166
  settings: this.#settings,
2113
2167
  matchPreferences: { usageOrder: this.#settings.getStorage()?.getModelUsageOrder() },
2114
2168
  modelRegistry: this.#modelRegistry,
@@ -108,9 +108,9 @@ class SelectSubmenu extends Container {
108
108
  description: string,
109
109
  options: ReadonlyArray<SelectItem>,
110
110
  currentValue: string,
111
- onSelect: (value: string) => void,
112
- onCancel: () => void,
113
- onSelectionChange?: (value: string) => void | Promise<void>,
111
+ private readonly onSelect: (value: string) => void,
112
+ private readonly onCancel: () => void,
113
+ private readonly onSelectionChange?: (value: string) => void | Promise<void>,
114
114
  private readonly getPreview?: () => string,
115
115
  ) {
116
116
  super();
@@ -137,46 +137,54 @@ class SelectSubmenu extends Container {
137
137
  // Spacer
138
138
  this.addChild(new Spacer(1));
139
139
 
140
- // Select list
141
- this.#selectList = new SelectList(options, Math.min(options.length, 10), () => getSelectListTheme());
142
-
143
- // Pre-select current value
144
- const currentIndex = options.findIndex(o => o.value === currentValue);
145
- if (currentIndex !== -1) {
146
- this.#selectList.setSelectedIndex(currentIndex);
147
- }
148
-
149
- this.#selectList.onSelect = item => {
150
- onSelect(item.value);
151
- };
140
+ this.#selectList = this.#createSelectList(options, currentValue);
141
+ this.addChild(this.#selectList);
152
142
 
153
- this.#selectList.onCancel = onCancel;
143
+ // Hint
144
+ this.addChild(new Spacer(1));
145
+ this.addChild(
146
+ new DynamicThemeText(() => theme.fg("dim", uiString(settings.get("ui.language"), "settings.selectHint"))),
147
+ );
148
+ }
154
149
 
155
- if (onSelectionChange) {
156
- this.#selectList.onSelectionChange = item => {
150
+ #createSelectList(options: ReadonlyArray<SelectItem>, selectedValue?: string): SelectList {
151
+ const selectList = new SelectList(options, Math.min(options.length, 10), () => getSelectListTheme());
152
+ const selectedIndex = options.findIndex(option => option.value === selectedValue);
153
+ if (selectedIndex !== -1) selectList.setSelectedIndex(selectedIndex);
154
+ selectList.onSelect = item => this.onSelect(item.value);
155
+ selectList.onCancel = this.onCancel;
156
+ if (this.onSelectionChange) {
157
+ selectList.onSelectionChange = item => {
157
158
  const requestId = ++this.#previewUpdateRequestId;
158
- const result = onSelectionChange(item.value);
159
+ const result = this.onSelectionChange?.(item.value);
159
160
  if (result && typeof (result as Promise<void>).then === "function") {
160
161
  void (result as Promise<void>).finally(() => {
161
- if (requestId === this.#previewUpdateRequestId) {
162
- this.#updatePreview();
163
- }
162
+ if (requestId === this.#previewUpdateRequestId) this.#updatePreview();
164
163
  });
165
164
  return;
166
165
  }
167
- if (requestId === this.#previewUpdateRequestId) {
168
- this.#updatePreview();
169
- }
166
+ if (requestId === this.#previewUpdateRequestId) this.#updatePreview();
170
167
  };
171
168
  }
169
+ return selectList;
170
+ }
172
171
 
173
- this.addChild(this.#selectList);
172
+ refreshOptions(options: ReadonlyArray<SelectItem>, retainMissingSelection = false): void {
173
+ const previous = this.#selectList;
174
+ const selectedItem = previous.getSelectedItem();
175
+ if (retainMissingSelection && selectedItem && !options.some(option => option.value === selectedItem.value)) {
176
+ const label = selectedItem.label.endsWith(" (unavailable)")
177
+ ? selectedItem.label
178
+ : `${selectedItem.label} (unavailable)`;
179
+ options = [...options, { ...selectedItem, label }];
180
+ }
181
+ const selectedValue = selectedItem?.value;
182
+ this.#selectList = this.#createSelectList(options, selectedValue);
183
+ this.replaceChildren(this.children.map(child => (child === previous ? this.#selectList : child)));
184
+ }
174
185
 
175
- // Hint
176
- this.addChild(new Spacer(1));
177
- this.addChild(
178
- new DynamicThemeText(() => theme.fg("dim", uiString(settings.get("ui.language"), "settings.selectHint"))),
179
- );
186
+ refreshPreview(): void {
187
+ this.#updatePreview();
180
188
  }
181
189
 
182
190
  #updatePreview(): void {
@@ -1528,6 +1536,10 @@ export interface SettingsCallbacks {
1528
1536
  onPetCommit?: (mode: string) => boolean;
1529
1537
  /** Called for status line preview while configuring */
1530
1538
  onStatusLinePreview?: (settings: StatusLinePreviewSettings) => void;
1539
+ /** Called when a status-line submenu begins an uncommitted preview. */
1540
+ onStatusLinePreviewStart?: () => void;
1541
+ /** Called when a status-line preview is committed, cancelled, or disposed. */
1542
+ onStatusLinePreviewEnd?: () => void;
1531
1543
  /** Get current rendered status line for inline preview */
1532
1544
  getStatusLinePreview?: (width?: number) => string;
1533
1545
  /** Render a status-line preview for supplied draft settings without mutating the live status line. */
@@ -1582,6 +1594,8 @@ export class SettingsSelectorComponent extends Container {
1582
1594
  #currentTabId: SettingTab | "plugins" | "gjc-bundles" = "appearance";
1583
1595
  #textInputActive = false;
1584
1596
  #activeProviderOrderEditor: Container | null = null;
1597
+ #activeSelectSubmenu: SelectSubmenu | null = null;
1598
+ #activeModelProfileSubmenu: SelectSubmenu | null = null;
1585
1599
 
1586
1600
  constructor(
1587
1601
  private readonly context: SettingsRuntimeContext,
@@ -1634,9 +1648,15 @@ export class SettingsSelectorComponent extends Container {
1634
1648
  }
1635
1649
 
1636
1650
  #switchToTab(tabId: SettingTab | "plugins" | "gjc-bundles"): void {
1651
+ if (this.#currentTabId === "appearance" && tabId !== "appearance") {
1652
+ this.callbacks.onStatusLinePreviewEnd?.();
1653
+ }
1637
1654
  if (this.#currentTabId === "notifications" && tabId !== "notifications" && !this.#disposeNotificationsEditor()) {
1638
1655
  return;
1639
1656
  }
1657
+ this.#activeSelectSubmenu?.dispose();
1658
+ this.#activeSelectSubmenu = null;
1659
+ this.#activeModelProfileSubmenu = null;
1640
1660
  // Release an open provider-order editor (and its context subscriptions)
1641
1661
  // before switching tabs; the submenu's done() never runs on tab change,
1642
1662
  // so without this the abandoned editor's listeners would survive.
@@ -1833,6 +1853,18 @@ export class SettingsSelectorComponent extends Container {
1833
1853
  // Preview handlers
1834
1854
  let onPreview: ((value: string) => void | Promise<void>) | undefined;
1835
1855
  let onPreviewCancel: (() => void) | undefined;
1856
+ let onPreviewEnd: (() => void) | undefined;
1857
+ let statusLinePreviewStarted = false;
1858
+ const startStatusLinePreview = (): void => {
1859
+ if (statusLinePreviewStarted) return;
1860
+ statusLinePreviewStarted = true;
1861
+ this.callbacks.onStatusLinePreviewStart?.();
1862
+ };
1863
+ const endStatusLinePreview = (): void => {
1864
+ if (!statusLinePreviewStarted) return;
1865
+ statusLinePreviewStarted = false;
1866
+ this.callbacks.onStatusLinePreviewEnd?.();
1867
+ };
1836
1868
 
1837
1869
  if (def.path === "theme.dark" || def.path === "theme.light") {
1838
1870
  const activeThemeBeforePreview = getCurrentThemeName() ?? currentValue;
@@ -1844,6 +1876,7 @@ export class SettingsSelectorComponent extends Container {
1844
1876
  };
1845
1877
  } else if (def.path === "statusLine.preset") {
1846
1878
  onPreview = value => {
1879
+ startStatusLinePreview();
1847
1880
  const presetDef = getPreset(value as StatusLinePreset);
1848
1881
  this.callbacks.onStatusLinePreview?.({
1849
1882
  preset: value as StatusLinePreset,
@@ -1854,6 +1887,7 @@ export class SettingsSelectorComponent extends Container {
1854
1887
  });
1855
1888
  this.#updateStatusPreview();
1856
1889
  };
1890
+ onPreviewEnd = endStatusLinePreview;
1857
1891
  onPreviewCancel = () => {
1858
1892
  const currentPreset = settings.get("statusLine.preset");
1859
1893
  const presetDef = getPreset(currentPreset);
@@ -1880,9 +1914,11 @@ export class SettingsSelectorComponent extends Container {
1880
1914
  };
1881
1915
  } else if (def.path === "statusLine.separator") {
1882
1916
  onPreview = value => {
1917
+ startStatusLinePreview();
1883
1918
  this.callbacks.onStatusLinePreview?.({ separator: value as StatusLineSeparatorStyle });
1884
1919
  this.#updateStatusPreview();
1885
1920
  };
1921
+ onPreviewEnd = endStatusLinePreview;
1886
1922
  onPreviewCancel = () => {
1887
1923
  const separator = settings.get("statusLine.separator");
1888
1924
  this.callbacks.onStatusLinePreview?.({ separator, previewHighlightSegment: undefined });
@@ -1890,9 +1926,11 @@ export class SettingsSelectorComponent extends Container {
1890
1926
  };
1891
1927
  } else if (def.path === "statusLine.maxRows") {
1892
1928
  onPreview = value => {
1929
+ startStatusLinePreview();
1893
1930
  this.callbacks.onStatusLinePreview?.({ maxRows: Number(value) });
1894
1931
  this.#updateStatusPreview();
1895
1932
  };
1933
+ onPreviewEnd = endStatusLinePreview;
1896
1934
  onPreviewCancel = () => {
1897
1935
  this.callbacks.onStatusLinePreview?.({
1898
1936
  maxRows: settings.get("statusLine.maxRows"),
@@ -1914,7 +1952,14 @@ export class SettingsSelectorComponent extends Container {
1914
1952
  const isThemeSetting = def.path === "theme.dark" || def.path === "theme.light";
1915
1953
  const getPreview = isThemeSetting ? this.callbacks.getStatusLinePreview : undefined;
1916
1954
 
1917
- return new SelectSubmenu(
1955
+ let submenu: SelectSubmenu;
1956
+ const finish = (value?: string): void => {
1957
+ if (this.#activeSelectSubmenu === submenu) this.#activeSelectSubmenu = null;
1958
+ if (this.#activeModelProfileSubmenu === submenu) this.#activeModelProfileSubmenu = null;
1959
+ submenu.dispose();
1960
+ done(value);
1961
+ };
1962
+ submenu = new SelectSubmenu(
1918
1963
  title,
1919
1964
  description,
1920
1965
  options,
@@ -1922,7 +1967,7 @@ export class SettingsSelectorComponent extends Container {
1922
1967
  value => {
1923
1968
  if (def.path === "modelProfile.default") {
1924
1969
  this.callbacks.onChange(def.path, value);
1925
- done(value);
1970
+ finish(value);
1926
1971
  return;
1927
1972
  }
1928
1973
  if (def.path === "theme.dark" || def.path === "theme.light") {
@@ -1937,7 +1982,7 @@ export class SettingsSelectorComponent extends Container {
1937
1982
  }
1938
1983
  if (!this.callbacks.onThemeCommit) return;
1939
1984
  void this.callbacks.onThemeCommit(def.path, value, currentValue).then(accepted => {
1940
- if (accepted) done(value);
1985
+ if (accepted) finish(value);
1941
1986
  });
1942
1987
  return;
1943
1988
  }
@@ -1953,21 +1998,26 @@ export class SettingsSelectorComponent extends Container {
1953
1998
  ) {
1954
1999
  return;
1955
2000
  }
1956
- done(accepted ? value : undefined);
2001
+ finish(accepted ? value : undefined);
1957
2002
  return;
1958
2003
  }
1959
2004
  if (!commitInteractiveSettings(this.callbacks, () => this.#setSettingValue(def.path, value))) return;
1960
2005
  this.callbacks.onChange(def.path, value);
1961
2006
  if (def.path === "ui.language") this.#refreshTabBarLanguage();
1962
- done(value);
2007
+ onPreviewEnd?.();
2008
+ finish(value);
1963
2009
  },
1964
2010
  () => {
1965
2011
  onPreviewCancel?.();
1966
- done();
2012
+ onPreviewEnd?.();
2013
+ finish();
1967
2014
  },
1968
2015
  onPreview,
1969
2016
  getPreview,
1970
2017
  );
2018
+ this.#activeSelectSubmenu = submenu;
2019
+ if (def.path === "modelProfile.default") this.#activeModelProfileSubmenu = submenu;
2020
+ return submenu;
1971
2021
  }
1972
2022
 
1973
2023
  /**
@@ -2277,8 +2327,24 @@ export class SettingsSelectorComponent extends Container {
2277
2327
  return (this.#currentList || this.#pluginComponent || this.#gjcBundleComponent || this.#notificationsEditor)!;
2278
2328
  }
2279
2329
 
2330
+ /** Refresh saved rows and model-profile choices without replacing active editors. */
2331
+ refreshFromConfiguration(availableModelProfiles: readonly string[]): void {
2332
+ this.context.availableModelProfiles = [...availableModelProfiles];
2333
+ this.#refreshCurrentTabItems();
2334
+ this.#activeModelProfileSubmenu?.refreshOptions(
2335
+ this.context.availableModelProfiles.map(profile => ({ value: profile, label: profile })),
2336
+ true,
2337
+ );
2338
+ this.#activeSelectSubmenu?.refreshPreview();
2339
+ this.#updateStatusPreview();
2340
+ }
2341
+
2280
2342
  override dispose(): void {
2281
2343
  if (this.#notificationsEditor?.navigationLocked) return;
2344
+ this.callbacks.onStatusLinePreviewEnd?.();
2345
+ this.#activeSelectSubmenu?.dispose();
2346
+ this.#activeSelectSubmenu = null;
2347
+ this.#activeModelProfileSubmenu = null;
2282
2348
  // Release an open provider-order editor (and its context subscriptions)
2283
2349
  // when the whole selector is torn down without a normal close.
2284
2350
  this.#activeProviderOrderEditor?.dispose();
@@ -3,6 +3,7 @@ import { APP_NAME } from "@gajae-code/utils";
3
3
  import { formatBuildLabel } from "../../build-metadata";
4
4
  import { formatKeyHint, type KeyDisplayContext } from "../../config/keybindings";
5
5
  import { type ThemeColor, theme } from "../../modes/theme/theme";
6
+ import { sanitizeStatusText } from "../shared";
6
7
 
7
8
  export interface RecentSession {
8
9
  name: string;
@@ -536,7 +537,8 @@ export class WelcomeComponent implements Component {
536
537
  }
537
538
 
538
539
  #pill(icon: string, text: string, color: ThemeColor): string {
539
- return `${theme.fg("borderMuted", "[")} ${theme.fg(color, icon)} ${theme.fg("muted", text)} ${theme.fg(
540
+ const safeText = sanitizeStatusText(text);
541
+ return `${theme.fg("borderMuted", "[")} ${theme.fg(color, icon)} ${theme.fg("muted", safeText)} ${theme.fg(
540
542
  "borderMuted",
541
543
  "]",
542
544
  )}`;