@gajae-code/coding-agent 0.14.2 → 0.15.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 (345) hide show
  1. package/CHANGELOG.md +120 -1
  2. package/dist/types/async/job-manager.d.ts +4 -1
  3. package/dist/types/autoresearch/dashboard.d.ts +29 -4
  4. package/dist/types/autoresearch/data-context.d.ts +25 -0
  5. package/dist/types/autoresearch/git.d.ts +65 -7
  6. package/dist/types/autoresearch/harness.d.ts +58 -0
  7. package/dist/types/autoresearch/prompts.d.ts +51 -0
  8. package/dist/types/autoresearch/runs.d.ts +173 -0
  9. package/dist/types/autoresearch/types.d.ts +9 -139
  10. package/dist/types/capability/types.d.ts +20 -0
  11. package/dist/types/cli/accounts-cli.d.ts +1 -0
  12. package/dist/types/commands/{rlm.d.ts → autoresearch.d.ts} +2 -4
  13. package/dist/types/config/autorouting-contract.d.ts +146 -0
  14. package/dist/types/config/autorouting-generator.d.ts +40 -0
  15. package/dist/types/config/autorouting-tier-map.d.ts +16844 -0
  16. package/dist/types/config/autorouting.d.ts +35 -0
  17. package/dist/types/config/model-discovery-manager.d.ts +8 -1
  18. package/dist/types/config/model-registry.d.ts +30 -1
  19. package/dist/types/config/provider-selection-policy.d.ts +23 -0
  20. package/dist/types/config/settings-schema.d.ts +61 -3
  21. package/dist/types/config/settings.d.ts +3 -0
  22. package/dist/types/coordinator/contract.d.ts +1 -1
  23. package/dist/types/coordinator-mcp/durability.d.ts +29 -0
  24. package/dist/types/coordinator-mcp/event-webhook.d.ts +50 -0
  25. package/dist/types/coordinator-mcp/question-state.d.ts +1 -1
  26. package/dist/types/coordinator-mcp/server.d.ts +14 -0
  27. package/dist/types/crash/index-store.d.ts +3 -0
  28. package/dist/types/crash/record-loader.d.ts +2 -0
  29. package/dist/types/crash/upstream/relay.d.ts +5 -3
  30. package/dist/types/cursor.d.ts +12 -0
  31. package/dist/types/defaults/gjc-defaults.d.ts +35 -1
  32. package/dist/types/defaults/gjc-skills.generated.d.ts +1 -1
  33. package/dist/types/discovery/helpers.d.ts +1 -1
  34. package/dist/types/edit/index.d.ts +2 -1
  35. package/dist/types/eval/py/executor.d.ts +2 -0
  36. package/dist/types/extensibility/extensions/loader.d.ts +32 -0
  37. package/dist/types/extensibility/gjc-plugins/types.d.ts +1 -1
  38. package/dist/types/extensibility/skills.d.ts +2 -0
  39. package/dist/types/gjc-runtime/autoresearch-runtime.d.ts +194 -0
  40. package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
  41. package/dist/types/gjc-runtime/psmux-detect.d.ts +1 -1
  42. package/dist/types/gjc-runtime/python-transcript.d.ts +19 -0
  43. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +2 -8
  44. package/dist/types/gjc-runtime/session-layout.d.ts +6 -3
  45. package/dist/types/gjc-runtime/state-renderer.d.ts +0 -22
  46. package/dist/types/gjc-runtime/state-schema.d.ts +7 -7
  47. package/dist/types/gjc-runtime/tmux-common.d.ts +2 -2
  48. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +38 -1
  49. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +2 -24
  50. package/dist/types/gjc-runtime/ultragoal-validation-policy.d.ts +73 -0
  51. package/dist/types/gjc-runtime/workflow-recovery-projection.d.ts +100 -0
  52. package/dist/types/hooks/skill-keywords.d.ts +1 -1
  53. package/dist/types/main.d.ts +0 -10
  54. package/dist/types/modes/acp/acp-agent.d.ts +13 -6
  55. package/dist/types/modes/components/model-selector.d.ts +14 -0
  56. package/dist/types/modes/components/skill-hud/render.d.ts +6 -1
  57. package/dist/types/modes/components/smart-routing-panel.d.ts +82 -0
  58. package/dist/types/modes/controllers/selector-controller.d.ts +14 -0
  59. package/dist/types/modes/interactive-mode.d.ts +3 -0
  60. package/dist/types/modes/types.d.ts +9 -0
  61. package/dist/types/rlm/artifacts.d.ts +4 -4
  62. package/dist/types/runtime/optional-runtime-services.d.ts +2 -1
  63. package/dist/types/runtime/workspace-tree-service.d.ts +5 -1
  64. package/dist/types/runtime-mcp/config.d.ts +6 -0
  65. package/dist/types/runtime-mcp/manager.d.ts +10 -0
  66. package/dist/types/sdk/acp/adapter.d.ts +0 -4
  67. package/dist/types/sdk/broker/broker.d.ts +1 -1
  68. package/dist/types/sdk/broker/lifecycle.d.ts +57 -2
  69. package/dist/types/sdk/broker/session-index.d.ts +15 -0
  70. package/dist/types/sdk/bus/chat-daemon-control.d.ts +6 -0
  71. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -0
  72. package/dist/types/sdk/bus/index.d.ts +8 -0
  73. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +2 -0
  74. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -2
  75. package/dist/types/sdk/client/client.d.ts +39 -4
  76. package/dist/types/sdk/host/host.d.ts +1 -0
  77. package/dist/types/sdk/host/internal-autorouting-state.d.ts +3 -0
  78. package/dist/types/sdk/host/model-pin.d.ts +29 -0
  79. package/dist/types/sdk/host/session-runtime.d.ts +2 -1
  80. package/dist/types/sdk/lifecycle-session.d.ts +13 -0
  81. package/dist/types/sdk/router/session-router.d.ts +27 -1
  82. package/dist/types/sdk/session.d.ts +11 -1
  83. package/dist/types/session/agent-session.d.ts +36 -6
  84. package/dist/types/session/artifacts.d.ts +16 -1
  85. package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
  86. package/dist/types/session/session-manager.d.ts +84 -2
  87. package/dist/types/session/session-staging-paths.d.ts +3 -0
  88. package/dist/types/skill-state/canonical-skills.d.ts +1 -1
  89. package/dist/types/skill-state/workflow-hud.d.ts +12 -21
  90. package/dist/types/skill-state/workflow-mutation-guard.d.ts +1 -0
  91. package/dist/types/slash-commands/helpers/autorouting-status.d.ts +8 -0
  92. package/dist/types/system-prompt.d.ts +23 -0
  93. package/dist/types/task/executor.d.ts +35 -4
  94. package/dist/types/task/index.d.ts +17 -3
  95. package/dist/types/task/receipt.d.ts +3 -1
  96. package/dist/types/task/types.d.ts +144 -0
  97. package/dist/types/tools/atomic-file-write.d.ts +72 -0
  98. package/dist/types/tools/descriptors.d.ts +1 -0
  99. package/dist/types/tools/index.d.ts +36 -1
  100. package/dist/types/tools/move-session.d.ts +40 -0
  101. package/dist/types/tools/python.d.ts +12 -0
  102. package/dist/types/tools/resource-gc.d.ts +0 -3
  103. package/dist/types/tools/search.d.ts +8 -1
  104. package/dist/types/tools/skill.d.ts +9 -4
  105. package/dist/types/tools/subagent.d.ts +33 -3
  106. package/dist/types/utils/xml-escape.d.ts +2 -0
  107. package/dist/types/workflow/workflow-intent-diff.d.ts +2 -2
  108. package/dist/types/workflow/workflow-intent-report-consumer.d.ts +1 -1
  109. package/package.json +11 -12
  110. package/scripts/build-sdk-package-smoke.ts +7 -5
  111. package/scripts/check-autorouting-tier-map.ts +183 -0
  112. package/scripts/generate-sdk-operation-inventory.ts +12 -0
  113. package/scripts/generate-tool-catalog.ts +40 -5
  114. package/src/async/job-manager.ts +5 -2
  115. package/src/autoresearch/dashboard.ts +134 -314
  116. package/src/autoresearch/data-context.ts +35 -0
  117. package/src/autoresearch/git.ts +186 -26
  118. package/src/autoresearch/{helpers.ts → harness.ts} +65 -40
  119. package/src/autoresearch/{prompt-setup.md → prompts/prompt-setup.md} +7 -7
  120. package/src/autoresearch/prompts/prompt.md +103 -0
  121. package/src/autoresearch/prompts.ts +126 -0
  122. package/src/autoresearch/runs.ts +708 -0
  123. package/src/autoresearch/types.ts +9 -150
  124. package/src/capability/index.ts +4 -4
  125. package/src/capability/types.ts +20 -0
  126. package/src/cli/accounts-cli.ts +21 -5
  127. package/src/cli/config-cli.ts +25 -1
  128. package/src/cli/fast-help.ts +1 -2
  129. package/src/cli/list-models.ts +5 -8
  130. package/src/cli/setup-cli.ts +8 -0
  131. package/src/cli.ts +2 -21
  132. package/src/commands/autoresearch.ts +33 -0
  133. package/src/commands/coordinator.ts +15 -0
  134. package/src/commands/launch.ts +2 -4
  135. package/src/commands/quick-lane.ts +1 -1
  136. package/src/commands/sdk.ts +24 -0
  137. package/src/commands/state.ts +1 -1
  138. package/src/config/autorouting-contract.ts +474 -0
  139. package/src/config/autorouting-generator.ts +238 -0
  140. package/src/config/autorouting-tier-map.ts +6108 -0
  141. package/src/config/autorouting.ts +120 -0
  142. package/src/config/model-discovery-manager.ts +42 -11
  143. package/src/config/model-registry.ts +348 -68
  144. package/src/config/provider-selection-policy.ts +60 -9
  145. package/src/config/settings-schema.ts +195 -6
  146. package/src/config/settings.ts +62 -1
  147. package/src/config.ts +2 -3
  148. package/src/coordinator/contract.ts +0 -1
  149. package/src/coordinator-mcp/codex-handoff.ts +68 -42
  150. package/src/coordinator-mcp/durability.ts +200 -0
  151. package/src/coordinator-mcp/event-webhook.ts +278 -0
  152. package/src/coordinator-mcp/question-state.ts +109 -46
  153. package/src/coordinator-mcp/server.ts +932 -504
  154. package/src/crash/index-store.ts +126 -22
  155. package/src/crash/record-loader.ts +7 -0
  156. package/src/crash/upstream/relay.ts +82 -34
  157. package/src/cursor.ts +84 -1
  158. package/src/debug/index.ts +26 -0
  159. package/src/defaults/gjc/skills/autoresearch/SKILL.md +116 -0
  160. package/src/defaults/gjc/skills/autoresearch/auto-critic.md +46 -0
  161. package/src/defaults/gjc/skills/autoresearch/auto-iterate.md +42 -0
  162. package/src/defaults/gjc/skills/deep-interview/SKILL.md +17 -37
  163. package/src/defaults/gjc/skills/ralplan/SKILL.md +21 -18
  164. package/src/defaults/gjc/skills/ultragoal/SKILL.md +6 -17
  165. package/src/defaults/gjc/skills/ultragoal/ai-slop-cleaner.md +2 -2
  166. package/src/defaults/gjc-defaults.ts +89 -2
  167. package/src/defaults/gjc-skills.generated.ts +24 -17
  168. package/src/discovery/builtin.ts +18 -5
  169. package/src/discovery/helpers.ts +6 -6
  170. package/src/edit/index.ts +5 -2
  171. package/src/eval/py/executor.ts +8 -0
  172. package/src/extensibility/extensions/loader.ts +135 -13
  173. package/src/extensibility/extensions/wrapper.ts +1 -2
  174. package/src/extensibility/gjc-plugins/schema.ts +1 -1
  175. package/src/extensibility/runtime-skill-discovery.ts +2 -2
  176. package/src/extensibility/skill-management.ts +2 -3
  177. package/src/extensibility/skills.ts +16 -8
  178. package/src/gjc-runtime/autoresearch-runtime.ts +1509 -0
  179. package/src/gjc-runtime/gc-render.ts +0 -1
  180. package/src/gjc-runtime/gc-runtime.ts +1 -11
  181. package/src/gjc-runtime/launch-tmux.ts +3 -3
  182. package/src/gjc-runtime/psmux-detect.ts +2 -2
  183. package/src/gjc-runtime/python-transcript.ts +58 -0
  184. package/src/gjc-runtime/ralplan-runtime.ts +6 -18
  185. package/src/gjc-runtime/session-layout.ts +18 -7
  186. package/src/gjc-runtime/session-state-sidecar.ts +58 -9
  187. package/src/gjc-runtime/state-renderer.ts +0 -38
  188. package/src/gjc-runtime/state-runtime.ts +43 -22
  189. package/src/gjc-runtime/state-schema.ts +1 -1
  190. package/src/gjc-runtime/tmux-common.ts +7 -7
  191. package/src/gjc-runtime/ultragoal-change-set.ts +232 -11
  192. package/src/gjc-runtime/ultragoal-runtime.ts +309 -114
  193. package/src/gjc-runtime/ultragoal-validation-policy.ts +247 -0
  194. package/src/gjc-runtime/workflow-command-ref.ts +15 -11
  195. package/src/gjc-runtime/workflow-manifest.generated.json +413 -532
  196. package/src/gjc-runtime/workflow-manifest.ts +43 -83
  197. package/src/gjc-runtime/workflow-recovery-projection.ts +616 -0
  198. package/src/hooks/skill-keywords.ts +3 -3
  199. package/src/hooks/skill-state.ts +2 -2
  200. package/src/internal-urls/docs-index.generated.ts +26 -24
  201. package/src/lsp/index.ts +25 -5
  202. package/src/main.ts +2 -27
  203. package/src/modes/acp/acp-agent.ts +200 -75
  204. package/src/modes/components/model-selector.ts +216 -13
  205. package/src/modes/components/oauth-selector.ts +5 -5
  206. package/src/modes/components/skill-hud/render.ts +173 -35
  207. package/src/modes/components/smart-routing-panel.ts +531 -0
  208. package/src/modes/components/tool-status-header.ts +6 -3
  209. package/src/modes/controllers/command-controller.ts +17 -7
  210. package/src/modes/controllers/input-controller.ts +44 -9
  211. package/src/modes/controllers/runtime-mcp-command-controller.ts +4 -0
  212. package/src/modes/controllers/selector-controller.ts +298 -65
  213. package/src/modes/interactive-mode.ts +8 -1
  214. package/src/modes/shared/agent-wire/workflow-gate-types.ts +1 -1
  215. package/src/modes/types.ts +9 -1
  216. package/src/prompts/agents/planner.md +1 -1
  217. package/src/prompts/system/system-prompt.md +1 -1
  218. package/src/prompts/system/volatile-project-context.md +2 -1
  219. package/src/prompts/tools/move-session.md +15 -0
  220. package/src/prompts/tools/python.md +12 -0
  221. package/src/prompts/tools/skill-discovery.md +1 -1
  222. package/src/prompts/tools/skill.md +2 -2
  223. package/src/prompts/tools/task-summary.md +1 -0
  224. package/src/prompts/tools/task.md +6 -0
  225. package/src/rlm/artifacts.ts +12 -10
  226. package/src/runtime/optional-runtime-services.ts +3 -2
  227. package/src/runtime/workspace-tree-service.ts +11 -3
  228. package/src/runtime-mcp/config.ts +8 -1
  229. package/src/runtime-mcp/manager.ts +33 -0
  230. package/src/sdk/acp/adapter.ts +48 -1
  231. package/src/sdk/broker/broker.ts +30 -2
  232. package/src/sdk/broker/discovery.ts +217 -2
  233. package/src/sdk/broker/lifecycle.ts +385 -59
  234. package/src/sdk/broker/session-index.ts +243 -48
  235. package/src/sdk/broker/transport.ts +1 -0
  236. package/src/sdk/bus/chat-daemon-control.ts +8 -2
  237. package/src/sdk/bus/chat-daemon-runtime.ts +5 -0
  238. package/src/sdk/bus/index.ts +208 -11
  239. package/src/sdk/bus/kind-aware-reconciliation.ts +16 -0
  240. package/src/sdk/bus/reconciliation-store.ts +27 -5
  241. package/src/sdk/bus/telegram-daemon-contract.ts +12 -2
  242. package/src/sdk/client/client.ts +204 -22
  243. package/src/sdk/host/host.ts +16 -0
  244. package/src/sdk/host/internal-autorouting-state.ts +13 -0
  245. package/src/sdk/host/model-pin.ts +101 -0
  246. package/src/sdk/host/reverse-leases.ts +2 -1
  247. package/src/sdk/host/session-runtime.test.ts +224 -1
  248. package/src/sdk/host/session-runtime.ts +125 -37
  249. package/src/sdk/lifecycle/service.ts +3 -1
  250. package/src/sdk/lifecycle-session.ts +42 -1
  251. package/src/sdk/protocol/operation-inventory.generated.json +66 -0
  252. package/src/sdk/router/session-router.ts +375 -36
  253. package/src/sdk/session.ts +547 -47
  254. package/src/session/account-inventory.ts +1 -1
  255. package/src/session/agent-session.ts +765 -189
  256. package/src/session/artifacts.ts +234 -4
  257. package/src/session/internal/managed-session-storage.ts +91 -4
  258. package/src/session/provider-safety-stop-hint.ts +3 -2
  259. package/src/session/session-manager.ts +775 -16
  260. package/src/session/session-staging-paths.ts +8 -0
  261. package/src/setup/hermes/templates/operator-instructions.v1.md +0 -1
  262. package/src/setup/host-plugin-setup.ts +2 -2
  263. package/src/skill-state/active-state.ts +2 -2
  264. package/src/skill-state/canonical-skills.ts +1 -1
  265. package/src/skill-state/initial-phase.ts +1 -1
  266. package/src/skill-state/workflow-hud.ts +41 -41
  267. package/src/skill-state/workflow-mutation-guard.ts +26 -7
  268. package/src/slash-commands/builtin-registry.ts +77 -0
  269. package/src/slash-commands/helpers/autorouting-status.ts +55 -0
  270. package/src/system-prompt.ts +102 -5
  271. package/src/task/discovery.ts +2 -3
  272. package/src/task/executor.ts +617 -43
  273. package/src/task/index.ts +262 -15
  274. package/src/task/receipt.ts +30 -0
  275. package/src/task/render.ts +11 -1
  276. package/src/task/types.ts +316 -0
  277. package/src/tools/atomic-file-write.ts +594 -0
  278. package/src/tools/bisect.ts +3 -2
  279. package/src/tools/descriptors.test.ts +3 -0
  280. package/src/tools/descriptors.ts +24 -0
  281. package/src/tools/image-gen.ts +5 -1
  282. package/src/tools/index.ts +62 -5
  283. package/src/tools/move-session.ts +107 -0
  284. package/src/tools/python.ts +205 -0
  285. package/src/tools/read.ts +88 -15
  286. package/src/tools/renderers.ts +2 -0
  287. package/src/tools/resource-gc.ts +8 -177
  288. package/src/tools/search.ts +30 -6
  289. package/src/tools/skill-discovery.ts +1 -0
  290. package/src/tools/skill.ts +37 -7
  291. package/src/tools/subagent-render.ts +119 -13
  292. package/src/tools/subagent.ts +91 -74
  293. package/src/tools/tool-catalog.generated.ts +64 -2
  294. package/src/tools/write.ts +16 -12
  295. package/src/utils/xml-escape.ts +14 -0
  296. package/src/web/search/providers/codex.ts +2 -2
  297. package/src/workflow/workflow-intent-diff.ts +13 -12
  298. package/src/workflow/workflow-intent-report-consumer.ts +6 -6
  299. package/dist/types/autoresearch/helpers.d.ts +0 -24
  300. package/dist/types/autoresearch/index.d.ts +0 -2
  301. package/dist/types/autoresearch/state.d.ts +0 -17
  302. package/dist/types/autoresearch/storage.d.ts +0 -142
  303. package/dist/types/autoresearch/tools/init-experiment.d.ts +0 -31
  304. package/dist/types/autoresearch/tools/log-experiment.d.ts +0 -23
  305. package/dist/types/autoresearch/tools/run-experiment.d.ts +0 -8
  306. package/dist/types/autoresearch/tools/update-notes.d.ts +0 -12
  307. package/dist/types/commands/team.d.ts +0 -24
  308. package/dist/types/gjc-runtime/team-gc.d.ts +0 -7
  309. package/dist/types/gjc-runtime/team-launch.d.ts +0 -61
  310. package/dist/types/gjc-runtime/team-notify.d.ts +0 -45
  311. package/dist/types/gjc-runtime/team-runtime.d.ts +0 -336
  312. package/dist/types/gjc-runtime/team-store.d.ts +0 -168
  313. package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +0 -43
  314. package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +0 -92
  315. package/dist/types/gjc-runtime/team-workers.d.ts +0 -83
  316. package/dist/types/rlm/index.d.ts +0 -47
  317. package/dist/types/rlm/preset.d.ts +0 -23
  318. package/dist/types/rlm/python-tool.d.ts +0 -16
  319. package/src/autoresearch/command-resume.md +0 -14
  320. package/src/autoresearch/index.ts +0 -536
  321. package/src/autoresearch/prompt.md +0 -103
  322. package/src/autoresearch/resume-message.md +0 -10
  323. package/src/autoresearch/state.ts +0 -273
  324. package/src/autoresearch/storage.ts +0 -699
  325. package/src/autoresearch/tools/init-experiment.ts +0 -272
  326. package/src/autoresearch/tools/log-experiment.ts +0 -524
  327. package/src/autoresearch/tools/run-experiment.ts +0 -475
  328. package/src/autoresearch/tools/update-notes.ts +0 -109
  329. package/src/commands/rlm.ts +0 -19
  330. package/src/commands/team.ts +0 -238
  331. package/src/defaults/gjc/skills/deep-interview/auto-research-greenfield.md +0 -42
  332. package/src/defaults/gjc/skills/team/SKILL.md +0 -467
  333. package/src/gjc-runtime/team-gc.ts +0 -49
  334. package/src/gjc-runtime/team-launch.ts +0 -404
  335. package/src/gjc-runtime/team-notify.ts +0 -326
  336. package/src/gjc-runtime/team-runtime.ts +0 -5923
  337. package/src/gjc-runtime/team-store.ts +0 -878
  338. package/src/gjc-runtime/team-worker-heartbeat.ts +0 -149
  339. package/src/gjc-runtime/team-worker-memory-guard.ts +0 -363
  340. package/src/gjc-runtime/team-workers.ts +0 -1123
  341. package/src/prompts/system/rlm-report-command.md +0 -1
  342. package/src/prompts/system/rlm-research.md +0 -23
  343. package/src/rlm/index.ts +0 -365
  344. package/src/rlm/preset.ts +0 -76
  345. package/src/rlm/python-tool.ts +0 -68
@@ -504,6 +504,10 @@ export class MCPCommandController {
504
504
  );
505
505
 
506
506
  // Replace editor with wizard
507
+ // The composer is reusable across overlays; detach it before clearing so
508
+ // clear() disposes only the transient wizard, not the editor's
509
+ // tab-width listener / paste state (disposal is terminal).
510
+ this.ctx.editorContainer.detachChild(this.ctx.editor);
507
511
  this.ctx.editorContainer.clear();
508
512
  this.ctx.editorContainer.addChild(wizard);
509
513
  this.ctx.ui.setFocus(wizard);
@@ -12,6 +12,16 @@ import type { OAuthProvider } from "@gajae-code/ai/utils/oauth/types";
12
12
  import type { Component, OverlayHandle, SlashCommand } from "@gajae-code/tui";
13
13
  import { Input, Loader, resolvePetMode, Spacer, Text } from "@gajae-code/tui";
14
14
  import { getAgentDbPath, getProjectDir, logger, VERSION } from "@gajae-code/utils";
15
+ import {
16
+ type AutoroutingProvenance,
17
+ type AutoroutingSetup,
18
+ buildAutoroutingEnabledPatch,
19
+ buildAutoroutingSettingsBatch,
20
+ evaluateAutoroutingProvenanceState,
21
+ validateAutoroutingSetup,
22
+ } from "../../config/autorouting-contract";
23
+ import { canonicalJsonBytes, generateTierChains } from "../../config/autorouting-generator";
24
+ import { CURATED_TIER_MAP } from "../../config/autorouting-tier-map";
15
25
  import type { AppKeybinding } from "../../config/keybindings";
16
26
  import {
17
27
  activateModelProfile,
@@ -26,7 +36,7 @@ import { GJC_MODEL_ASSIGNMENT_TARGETS, type GjcModelAssignmentTargetId } from ".
26
36
  import { formatModelSelectorValue } from "../../config/model-resolver";
27
37
  import { selectorHead } from "../../config/model-selector-value";
28
38
  import type { ModelProfileConfig } from "../../config/models-config-schema";
29
- import { type Settings, type SettingsAtomicReceipt, settings } from "../../config/settings";
39
+ import { type RawSettings, type Settings, type SettingsAtomicReceipt, settings } from "../../config/settings";
30
40
  import type { SettingValue } from "../../config/settings-schema";
31
41
  import { DebugSelectorComponent } from "../../debug";
32
42
  import { disableProvider, enableProvider } from "../../discovery";
@@ -167,7 +177,6 @@ import {
167
177
  PlanPreviewOverlay,
168
178
  type PlanPreviewResult,
169
179
  } from "../components/plan-preview-overlay";
170
-
171
180
  import { PluginSelectorComponent } from "../components/plugin-selector";
172
181
  import {
173
182
  type ProviderOnboardingAction,
@@ -179,6 +188,7 @@ import { SessionObserverOverlayComponent } from "../components/session-observer-
179
188
  import { SessionSelectorComponent } from "../components/session-selector";
180
189
  import { dashboardSessions, SessionsDashboardComponent } from "../components/sessions-dashboard";
181
190
  import { SettingsSelectorComponent } from "../components/settings-selector";
191
+ import type { SmartRoutingPreview } from "../components/smart-routing-panel";
182
192
  import { TasksPaneComponent } from "../components/tasks-pane";
183
193
  import { ThemeSelectorComponent } from "../components/theme-selector";
184
194
  import { ThinkingSelectorComponent } from "../components/thinking-selector";
@@ -1176,7 +1186,36 @@ interface DefaultAssignmentRollbackSnapshot {
1176
1186
  resumeDefaultSelector: string | undefined;
1177
1187
  fallbackRuntimeState: DefaultFallbackRuntimeState;
1178
1188
  }
1189
+
1190
+ function sameCanonicalAutoroutingValue(left: unknown, right: unknown): boolean {
1191
+ const leftBytes = canonicalJsonBytes(left);
1192
+ const rightBytes = canonicalJsonBytes(right);
1193
+ if (leftBytes.length !== rightBytes.length) return false;
1194
+ for (let index = 0; index < leftBytes.length; index++) {
1195
+ if (leftBytes[index] !== rightBytes[index]) return false;
1196
+ }
1197
+ return true;
1198
+ }
1199
+
1200
+ function rawAutoroutingState(current: RawSettings): {
1201
+ provenance?: AutoroutingProvenance;
1202
+ setup?: AutoroutingSetup;
1203
+ tiers?: unknown;
1204
+ } {
1205
+ const task = current.task;
1206
+ if (!task || typeof task !== "object" || Array.isArray(task)) return {};
1207
+ const autorouting = (task as Record<string, unknown>).autorouting;
1208
+ if (!autorouting || typeof autorouting !== "object" || Array.isArray(autorouting)) return {};
1209
+ const record = autorouting as Record<string, unknown>;
1210
+ return {
1211
+ provenance: record.provenance as AutoroutingProvenance | undefined,
1212
+ setup: record.setup as AutoroutingSetup | undefined,
1213
+ tiers: record.tiers,
1214
+ };
1215
+ }
1216
+
1179
1217
  export class SelectorController {
1218
+ #smartRoutingInFlight?: Promise<unknown>;
1180
1219
  #transcriptViewerOpen = false;
1181
1220
  #transcriptViewer?: TranscriptViewerOverlay;
1182
1221
  #sessionsDashboardOpen = false;
@@ -1338,6 +1377,10 @@ export class SelectorController {
1338
1377
  }
1339
1378
  };
1340
1379
  const { component, focus } = create(done);
1380
+ // The composer is reusable across overlays; detach it before clearing so
1381
+ // clear() disposes only the transient overlay, not the editor's
1382
+ // tab-width listener / paste state (disposal is terminal).
1383
+ this.ctx.editorContainer.detachChild(this.ctx.editor);
1341
1384
  this.ctx.editorContainer.clear();
1342
1385
  this.ctx.editorContainer.addChild(component);
1343
1386
  this.ctx.ui.setFocus(focus);
@@ -2274,7 +2317,208 @@ export class SelectorController {
2274
2317
  this.ctx.showStatus(persistDefault ? `Default model profile: ${profileLabel}` : `Model profile: ${profileLabel}`);
2275
2318
  }
2276
2319
 
2277
- showModelSelector(options?: { temporaryOnly?: boolean }): void {
2320
+ /** Generate the immutable preview used by the smart-routing panel. */
2321
+ previewSmartRouting(draft: AutoroutingSetup): SmartRoutingPreview {
2322
+ const issues = validateAutoroutingSetup(draft);
2323
+ if (issues.length > 0)
2324
+ throw new Error(issues.map(issue => `${issue.path || "setup"}: ${issue.detail}`).join("; "));
2325
+ const setup = structuredClone(draft);
2326
+ const generated = generateTierChains(setup, CURATED_TIER_MAP, [...this.ctx.session.modelRegistry.getAll()]);
2327
+ const provenance: AutoroutingProvenance = {
2328
+ schema: 1,
2329
+ source: structuredClone(generated.sourceIdentity),
2330
+ declarationFingerprint: generated.declarationFingerprint,
2331
+ tiersFingerprint: generated.tiersFingerprint,
2332
+ };
2333
+ return {
2334
+ setup,
2335
+ tiers: structuredClone(generated.tiers),
2336
+ provenance,
2337
+ sourceIdentity: structuredClone(generated.sourceIdentity),
2338
+ };
2339
+ }
2340
+
2341
+ #assertSmartRoutingWritable(): void {
2342
+ if ((this.ctx.session.scopedModels?.length ?? 0) > 0) {
2343
+ throw new Error("Smart-routing settings are read-only in a --models-scoped session.");
2344
+ }
2345
+ if (!this.ctx.settings.canWriteDurableConfig()) {
2346
+ throw new Error("Cannot change smart-routing settings while durable config is unavailable.");
2347
+ }
2348
+ }
2349
+
2350
+ #assertSmartRoutingNotHandEdited(
2351
+ preview: SmartRoutingPreview,
2352
+ allowHandEdit: boolean,
2353
+ current?: { provenance?: AutoroutingProvenance; tiers?: unknown },
2354
+ ): void {
2355
+ if (allowHandEdit) return;
2356
+ const provenance = current?.provenance ?? this.ctx.settings.get("task.autorouting.provenance");
2357
+ const currentTiers = current?.tiers ?? this.ctx.settings.get("task.autorouting.tiers");
2358
+ if (!provenance || currentTiers === undefined) return;
2359
+ const state = evaluateAutoroutingProvenanceState(provenance, {
2360
+ catalogFingerprint: preview.sourceIdentity.catalogFingerprint,
2361
+ mapFingerprint: preview.sourceIdentity.mapFingerprint,
2362
+ tiers: currentTiers,
2363
+ });
2364
+ if (!state.handEdited) return;
2365
+ throw Object.assign(
2366
+ new Error("Generated autorouting tiers were hand-edited; explicit confirmation is required."),
2367
+ { code: "autorouting-hand-edited" },
2368
+ );
2369
+ }
2370
+
2371
+ async #runSmartRoutingIntent<T>(label: string, operation: () => Promise<T>): Promise<T> {
2372
+ if (this.#smartRoutingInFlight) throw new Error("Another smart-routing operation is already in progress.");
2373
+ const task = (async () => {
2374
+ this.ctx.showStatus(`${label} smart-routing settings…`);
2375
+ try {
2376
+ const result = await operation();
2377
+ await this.ctx.notifyConfigChanged?.();
2378
+ this.ctx.showStatus(`${label} smart-routing settings saved.`);
2379
+ this.ctx.ui.requestRender();
2380
+ return result;
2381
+ } catch (error) {
2382
+ this.ctx.showError(error instanceof Error ? error.message : String(error));
2383
+ this.ctx.ui.requestRender();
2384
+ throw error;
2385
+ } finally {
2386
+ this.#smartRoutingInFlight = undefined;
2387
+ }
2388
+ })();
2389
+ this.#smartRoutingInFlight = task;
2390
+ return task;
2391
+ }
2392
+ #reportSmartRoutingValidationError(error: unknown): never {
2393
+ this.ctx.showError(error instanceof Error ? error.message : String(error));
2394
+ this.ctx.ui.requestRender();
2395
+ throw error;
2396
+ }
2397
+
2398
+ async applySmartRouting(
2399
+ draft: AutoroutingSetup,
2400
+ options?: { preview?: SmartRoutingPreview; confirmHandEdit?: boolean },
2401
+ ): Promise<SmartRoutingPreview> {
2402
+ let preview: SmartRoutingPreview;
2403
+ try {
2404
+ this.#assertSmartRoutingWritable();
2405
+ const issues = validateAutoroutingSetup(draft);
2406
+ if (issues.length > 0) {
2407
+ throw new Error(issues.map(issue => `${issue.path || "setup"}: ${issue.detail}`).join("; "));
2408
+ }
2409
+ preview = options?.preview ?? this.previewSmartRouting(draft);
2410
+ const regenerated = this.previewSmartRouting(draft);
2411
+ if (!sameCanonicalAutoroutingValue(preview.setup, regenerated.setup)) {
2412
+ throw new Error("Smart-routing preview does not match the draft being applied.");
2413
+ }
2414
+ if (
2415
+ !sameCanonicalAutoroutingValue(preview.tiers, regenerated.tiers) ||
2416
+ !sameCanonicalAutoroutingValue(preview.provenance, regenerated.provenance)
2417
+ ) {
2418
+ // Never persist caller-supplied tier/provenance bytes that diverge from
2419
+ // the declaration. Continue with the fresh canonical payload so Apply
2420
+ // remains exactly the generated preview for legitimate callers.
2421
+ preview = regenerated;
2422
+ }
2423
+ this.#assertSmartRoutingNotHandEdited(preview, options?.confirmHandEdit === true);
2424
+ } catch (error) {
2425
+ return this.#reportSmartRoutingValidationError(error);
2426
+ }
2427
+ return this.#runSmartRoutingIntent("Apply", async () => {
2428
+ await this.ctx.settings.commitAtomicBatchWithCurrent(current => {
2429
+ this.#assertSmartRoutingNotHandEdited(
2430
+ preview,
2431
+ options?.confirmHandEdit === true,
2432
+ rawAutoroutingState(current),
2433
+ );
2434
+ return buildAutoroutingSettingsBatch({
2435
+ tiers: preview.tiers,
2436
+ setup: preview.setup,
2437
+ provenance: preview.provenance,
2438
+ });
2439
+ });
2440
+ return preview;
2441
+ });
2442
+ }
2443
+
2444
+ /**
2445
+ * Reorder a recorded declaration into current provider priority and drop entries
2446
+ * the catalog no longer offers. Comparison uses normalized ids while the result
2447
+ * keeps the catalog's spelling, because the generator matches provider prefixes
2448
+ * case-sensitively.
2449
+ */
2450
+ #reseedProvidersFromPolicy(setup: AutoroutingSetup): AutoroutingSetup {
2451
+ const order = this.ctx.session.modelRegistry.autoroutingProviderOrder();
2452
+ const declared = new Set(setup.providers.map(provider => provider.trim().toLowerCase()));
2453
+ const providers = order.filter(provider => declared.has(provider.trim().toLowerCase()));
2454
+ return { ...structuredClone(setup), providers };
2455
+ }
2456
+
2457
+ async refreshSmartRouting(options?: { confirmHandEdit?: boolean }): Promise<SmartRoutingPreview> {
2458
+ let preview: SmartRoutingPreview;
2459
+ let expectedSetup: AutoroutingSetup;
2460
+ try {
2461
+ this.#assertSmartRoutingWritable();
2462
+ const setup = this.ctx.settings.get("task.autorouting.setup");
2463
+ const issues = validateAutoroutingSetup(setup);
2464
+ if (issues.length > 0 || setup === undefined) {
2465
+ throw new Error("Cannot refresh smart routing without a valid recorded setup.");
2466
+ }
2467
+ expectedSetup = setup;
2468
+ // Reseed the recorded declaration against the current provider priority:
2469
+ // reorder to policy order and drop providers the catalog no longer offers.
2470
+ // Refusing an empty result keeps a dead declaration from being persisted.
2471
+ const reseeded = this.#reseedProvidersFromPolicy(setup);
2472
+ if (reseeded.providers.length === 0) {
2473
+ throw new Error("No declared providers remain in the catalog; smart routing was not updated.");
2474
+ }
2475
+ preview = this.previewSmartRouting(reseeded);
2476
+ this.#assertSmartRoutingNotHandEdited(preview, options?.confirmHandEdit === true);
2477
+ } catch (error) {
2478
+ return this.#reportSmartRoutingValidationError(error);
2479
+ }
2480
+ return this.#runSmartRoutingIntent("Refresh", async () => {
2481
+ await this.ctx.settings.commitAtomicBatchWithCurrent(current => {
2482
+ const currentState = rawAutoroutingState(current);
2483
+ if (!sameCanonicalAutoroutingValue(currentState.setup, expectedSetup)) {
2484
+ throw new Error(
2485
+ "Smart-routing setup changed while refresh was preparing; refresh again to use current settings.",
2486
+ );
2487
+ }
2488
+ this.#assertSmartRoutingNotHandEdited(preview, options?.confirmHandEdit === true, currentState);
2489
+ return buildAutoroutingSettingsBatch({
2490
+ tiers: preview.tiers,
2491
+ setup: preview.setup,
2492
+ provenance: preview.provenance,
2493
+ });
2494
+ });
2495
+ return preview;
2496
+ });
2497
+ }
2498
+
2499
+ async clearGeneratedSetup(): Promise<void> {
2500
+ try {
2501
+ this.#assertSmartRoutingWritable();
2502
+ } catch (error) {
2503
+ return this.#reportSmartRoutingValidationError(error);
2504
+ }
2505
+ return this.#runSmartRoutingIntent("Clear", async () => {
2506
+ await this.ctx.settings.commitAtomicBatchWithCurrent(() => buildAutoroutingSettingsBatch({ clear: true }));
2507
+ });
2508
+ }
2509
+
2510
+ async setAutoroutingEnabled(enabled: boolean): Promise<void> {
2511
+ try {
2512
+ this.#assertSmartRoutingWritable();
2513
+ } catch (error) {
2514
+ return this.#reportSmartRoutingValidationError(error);
2515
+ }
2516
+ return this.#runSmartRoutingIntent("Toggle", async () => {
2517
+ await this.ctx.settings.commitAtomicBatchWithCurrent(() => [buildAutoroutingEnabledPatch(enabled)]);
2518
+ });
2519
+ }
2520
+
2521
+ showModelSelector(options?: { temporaryOnly?: boolean; smartRoutingOnly?: boolean }): void {
2278
2522
  this.showSelector(done => {
2279
2523
  let modelSelector: ModelSelectorComponent;
2280
2524
  const refreshRoleAssignments = () => {
@@ -2292,6 +2536,27 @@ export class SelectorController {
2292
2536
  this.ctx.session.modelRegistry,
2293
2537
  this.ctx.session.scopedModels,
2294
2538
  async selection => {
2539
+ if (selection.kind === "smartRouting") {
2540
+ switch (selection.intent.kind) {
2541
+ case "apply":
2542
+ await this.applySmartRouting(selection.intent.draft, {
2543
+ preview: selection.intent.preview,
2544
+ confirmHandEdit: selection.intent.confirmHandEdit,
2545
+ });
2546
+ break;
2547
+ case "refresh":
2548
+ await this.refreshSmartRouting({ confirmHandEdit: selection.intent.confirmHandEdit });
2549
+ break;
2550
+ case "clear":
2551
+ await this.clearGeneratedSetup();
2552
+ break;
2553
+ case "toggle":
2554
+ await this.setAutoroutingEnabled(selection.intent.enabled);
2555
+ break;
2556
+ }
2557
+ modelSelector.refreshSmartRoutingState();
2558
+ return;
2559
+ }
2295
2560
  const isTrackedSingleAssignment =
2296
2561
  selection.kind === "assignment" && selection.role !== null && selection.roles === undefined;
2297
2562
  try {
@@ -2525,6 +2790,8 @@ export class SelectorController {
2525
2790
  isFastForSubagentProvider: (provider, supportsServiceTier) =>
2526
2791
  this.ctx.session.isFastForSubagentProvider(provider, supportsServiceTier),
2527
2792
  isCurrentModelFastModeActive: () => this.ctx.session.isFastModeActive(),
2793
+ smartRoutingPreview: draft => this.previewSmartRouting(draft),
2794
+ smartRoutingOnly: options?.smartRoutingOnly,
2528
2795
  },
2529
2796
  );
2530
2797
  return { component: modelSelector, focus: modelSelector };
@@ -3161,6 +3428,10 @@ export class SelectorController {
3161
3428
  this.ctx.ui.setFocus(this.ctx.editor);
3162
3429
  resolve(code);
3163
3430
  };
3431
+ // The composer is reusable across overlays; detach it before
3432
+ // clearing so clear() disposes only the transient code input,
3433
+ // not the editor's tab-width listener (disposal is terminal).
3434
+ this.ctx.editorContainer.detachChild(this.ctx.editor);
3164
3435
  this.ctx.editorContainer.clear();
3165
3436
  this.ctx.editorContainer.addChild(codeInput);
3166
3437
  this.ctx.ui.setFocus(codeInput);
@@ -3198,51 +3469,29 @@ export class SelectorController {
3198
3469
  }
3199
3470
  }
3200
3471
 
3201
- async #handleOAuthLogout(providerId: string, targets?: readonly CredentialRemovalTarget[]): Promise<void> {
3472
+ async #handleLogout(providerId: string, targets?: readonly CredentialRemovalTarget[]): Promise<void> {
3202
3473
  try {
3203
3474
  const authStorage = this.ctx.session.modelRegistry.authStorage;
3204
- const inventory = authStorage.listCredentialInventory(providerId);
3205
- const oauthRows = inventory.filter(row => row.provider === providerId && row.credentialKind === "oauth");
3475
+ const inventory = authStorage.listCredentialInventory(providerId).filter(row => row.provider === providerId);
3206
3476
  const removalTargetsById = new Map(
3207
3477
  authStorage.listCredentialRemovalTargets(providerId).map(target => [target.id, target]),
3208
3478
  );
3209
- const oauthRemovalTargets = oauthRows
3479
+ const removable = inventory
3210
3480
  .map(row => removalTargetsById.get(row.id))
3211
3481
  .filter((target): target is CredentialRemovalTarget => target !== undefined);
3212
- if (oauthRows.length > 0 && oauthRemovalTargets.length === 0) {
3482
+ // Credentials stored locally but not removable locally are broker-managed.
3483
+ if (inventory.length > 0 && removable.length === 0) {
3213
3484
  this.ctx.showError(
3214
3485
  `Logout is broker-managed for ${providerId}; run \`gjc auth-broker logout ${providerId}\` on the broker host.`,
3215
3486
  );
3216
3487
  return;
3217
3488
  }
3218
- if (targets) {
3219
- if (targets.length === 0) {
3220
- this.ctx.showError(
3221
- `No OAuth accounts to remove for ${providerId}; API-key credentials are not managed here.`,
3222
- );
3223
- return;
3224
- }
3225
- const result = authStorage.removeAuthCredentialsHard(providerId, targets);
3226
- if (result.kind !== "removed") {
3227
- this.ctx.showError(
3228
- "Logout failed: account inventory changed; no credentials were removed. Retry /logout.",
3229
- );
3230
- return;
3231
- }
3232
- await clearPersistentPinForRemovedRows(this.ctx.settings, providerId, inventory, result.ids);
3233
- await this.ctx.session.modelRegistry.refresh();
3234
- this.ctx.showStatus(
3235
- `Successfully removed ${result.ids.length} OAuth account${result.ids.length === 1 ? "" : "s"} from ${providerId}.`,
3236
- );
3489
+ const selected = targets ? targets : removable;
3490
+ if (selected.length === 0) {
3491
+ this.ctx.showError(`No stored credentials to remove for ${providerId}.`);
3237
3492
  return;
3238
3493
  }
3239
- if (oauthRemovalTargets.length === 0) {
3240
- this.ctx.showError(
3241
- `No OAuth accounts to remove for ${providerId}; API-key credentials are not managed here.`,
3242
- );
3243
- return;
3244
- }
3245
- const result = authStorage.removeAuthCredentialsHard(providerId, oauthRemovalTargets);
3494
+ const result = authStorage.removeAuthCredentialsHard(providerId, selected);
3246
3495
  if (result.kind !== "removed") {
3247
3496
  this.ctx.showError("Logout failed: account inventory changed; no credentials were removed. Retry /logout.");
3248
3497
  return;
@@ -3250,7 +3499,7 @@ export class SelectorController {
3250
3499
  await clearPersistentPinForRemovedRows(this.ctx.settings, providerId, inventory, result.ids);
3251
3500
  await this.ctx.session.modelRegistry.refresh();
3252
3501
  this.ctx.showStatus(
3253
- `Successfully removed ${result.ids.length} OAuth account${result.ids.length === 1 ? "" : "s"} from ${providerId}.`,
3502
+ `Successfully removed ${result.ids.length} stored credential${result.ids.length === 1 ? "" : "s"} from ${providerId}.`,
3254
3503
  );
3255
3504
  } catch (error: unknown) {
3256
3505
  this.ctx.showError(`Logout failed: ${error instanceof Error ? error.message : String(error)}`);
@@ -3273,11 +3522,11 @@ export class SelectorController {
3273
3522
  const hasOAuthInventory = authStorage
3274
3523
  .listCredentialInventory(selectedProviderId)
3275
3524
  .some(row => row.provider === selectedProviderId && row.credentialKind === "oauth");
3276
- const hasRemovableOAuth = authStorage
3525
+ const hasRemovableCredential = authStorage
3277
3526
  .listCredentialRemovalTargets(selectedProviderId)
3278
3527
  .some(target => target.provider === selectedProviderId);
3279
- if (mode === "logout" && hasOAuthInventory && !hasRemovableOAuth) {
3280
- await this.#handleOAuthLogout(selectedProviderId);
3528
+ if (mode === "logout" && hasOAuthInventory && !hasRemovableCredential) {
3529
+ await this.#handleLogout(selectedProviderId);
3281
3530
  return;
3282
3531
  }
3283
3532
  if (hasOAuthInventory && !options?.manualCode) {
@@ -3312,7 +3561,7 @@ export class SelectorController {
3312
3561
  await this.#handleOAuthLogin(selectedProviderId, options);
3313
3562
  },
3314
3563
  onAccountRemove: async targets => {
3315
- await this.#handleOAuthLogout(selectedProviderId, targets);
3564
+ await this.#handleLogout(selectedProviderId, targets);
3316
3565
  selector.stopValidation();
3317
3566
  done();
3318
3567
  },
@@ -3326,32 +3575,8 @@ export class SelectorController {
3326
3575
  await this.#handleOAuthLogin(selectedProviderId, options);
3327
3576
  return;
3328
3577
  }
3329
- if (!hasRemovableOAuth) {
3330
- await this.#handleOAuthLogout(selectedProviderId);
3331
- return;
3332
- }
3333
- this.showSelector(done => {
3334
- let selector: OAuthSelectorComponent;
3335
- selector = new OAuthSelectorComponent(
3336
- "logout",
3337
- authStorage,
3338
- () => undefined,
3339
- () => {
3340
- selector.stopValidation();
3341
- done();
3342
- this.ctx.ui.requestRender();
3343
- },
3344
- {
3345
- accountProviderId: selectedProviderId,
3346
- onAccountRemove: async targets => {
3347
- await this.#handleOAuthLogout(selectedProviderId, targets);
3348
- selector.stopValidation();
3349
- done();
3350
- },
3351
- },
3352
- );
3353
- return { component: selector, focus: selector };
3354
- });
3578
+ // mode === "logout" with no OAuth accounts: remove stored api-key credentials directly.
3579
+ await this.#handleLogout(selectedProviderId);
3355
3580
  return;
3356
3581
  }
3357
3582
 
@@ -3662,6 +3887,10 @@ export class SelectorController {
3662
3887
  this.ctx.ui.requestRender();
3663
3888
  },
3664
3889
  });
3890
+ // The composer is reusable across overlays; detach it before clearing so
3891
+ // clear() disposes only the transient overlay, not the editor's
3892
+ // tab-width listener / paste state (disposal is terminal).
3893
+ this.ctx.editorContainer.detachChild(this.ctx.editor);
3665
3894
  this.ctx.editorContainer.clear();
3666
3895
  this.ctx.editorContainer.addChild(overlay);
3667
3896
  this.ctx.ui.setFocus(overlay.getFocus());
@@ -3692,6 +3921,10 @@ export class SelectorController {
3692
3921
  },
3693
3922
  });
3694
3923
  unsubscribe = aggregator.onChange(() => this.#tasksPane?.refresh());
3924
+ // The composer is reusable across overlays; detach it before clearing so
3925
+ // clear() disposes only the transient pane, not the editor's
3926
+ // tab-width listener / paste state (disposal is terminal).
3927
+ this.ctx.editorContainer.detachChild(this.ctx.editor);
3695
3928
  this.ctx.editorContainer.clear();
3696
3929
  this.ctx.editorContainer.addChild(this.#tasksPane);
3697
3930
  this.ctx.ui.setFocus(this.#tasksPane.getFocus());
@@ -1255,6 +1255,9 @@ export class InteractiveMode implements InteractiveModeContext {
1255
1255
  this.ui.requestRender();
1256
1256
  return true;
1257
1257
  }
1258
+ hasPendingSubmission(): boolean {
1259
+ return this.#pendingSubmittedInput !== undefined;
1260
+ }
1258
1261
 
1259
1262
  markPendingSubmissionStarted(input: SubmittedUserInput): boolean {
1260
1263
  if (this.#pendingSubmittedInput !== input || input.cancelled) {
@@ -2348,10 +2351,14 @@ export class InteractiveMode implements InteractiveModeContext {
2348
2351
  void this.#selectorController.showAgentsDashboard();
2349
2352
  }
2350
2353
 
2351
- showModelSelector(options?: { temporaryOnly?: boolean }): void {
2354
+ showModelSelector(options?: { temporaryOnly?: boolean; smartRoutingOnly?: boolean }): void {
2352
2355
  this.#selectorController.showModelSelector(options);
2353
2356
  }
2354
2357
 
2358
+ setAutoroutingEnabled(enabled: boolean): Promise<void> {
2359
+ return this.#selectorController.setAutoroutingEnabled(enabled);
2360
+ }
2361
+
2355
2362
  showEffortSelector(): void {
2356
2363
  this.#selectorController.showEffortSelector();
2357
2364
  }
@@ -9,7 +9,7 @@ export type WorkflowStage = "deep-interview" | "ralplan" | "ultragoal";
9
9
  export const WORKFLOW_GATE_V1_STAGES: readonly WorkflowStage[] = ["deep-interview", "ralplan", "ultragoal"];
10
10
 
11
11
  /** Reserved stage names that are explicitly not part of the v1 contract. */
12
- export const RESERVED_WORKFLOW_STAGES: readonly string[] = ["team"];
12
+ export const RESERVED_WORKFLOW_STAGES: readonly string[] = ["autoresearch"];
13
13
 
14
14
  export type WorkflowGateKind = "question" | "approval" | "execution";
15
15
 
@@ -302,6 +302,13 @@ export interface InteractiveModeContext {
302
302
  options?: ComposerSubmissionOptions,
303
303
  ): SubmittedUserInput;
304
304
  cancelPendingSubmission(): boolean;
305
+ /**
306
+ * True while a submission is pending: not yet started, or started but still
307
+ * awaiting prompt delivery (`session.prompt()` has not flipped streaming yet).
308
+ * Interrupt handling uses this to keep a started-preflight submission
309
+ * cancellable instead of misclassifying its loader as stale (#4741).
310
+ */
311
+ hasPendingSubmission(): boolean;
305
312
  markPendingSubmissionStarted(input: SubmittedUserInput): boolean;
306
313
  finishPendingSubmission(input: SubmittedUserInput): void;
307
314
  /**
@@ -404,7 +411,8 @@ export interface InteractiveModeContext {
404
411
  showExtensionsDashboard(): void;
405
412
  showCustomizationDashboard(): void;
406
413
  showAgentsDashboard(): void;
407
- showModelSelector(options?: { temporaryOnly?: boolean }): void;
414
+ showModelSelector(options?: { temporaryOnly?: boolean; smartRoutingOnly?: boolean }): void;
415
+ setAutoroutingEnabled(enabled: boolean): Promise<void>;
408
416
  showEffortSelector(): void;
409
417
  showProviderOnboarding(): void;
410
418
  showPluginSelector(mode?: "install" | "uninstall"): void;
@@ -43,7 +43,7 @@ Inspect relevant files, classify the task, identify resources/constraints/depend
43
43
  - Codebase facts are backed by inspected files.
44
44
  - Thin specs are expanded with explicit assumptions, additive options, missed sub-scope, and verification detail.
45
45
  - Risks and verification commands are concrete.
46
- - Handoff identifies when to use executor, architect, critic, team, or ultragoal.
46
+ - Handoff identifies when to use executor, architect, critic, autoresearch, or ultragoal.
47
47
  </success_criteria>
48
48
 
49
49
  <output_contract>
@@ -28,7 +28,7 @@ Optimize for correctness first, maintainability second, and brevity third. Prefe
28
28
  - Informational questions are answer-only/read-only unless the user explicitly requests a change, command, or execution.
29
29
  - Vague or underspecified requirements: recommend `/skill:deep-interview` via `ask` (options: run the interview / proceed with stated assumptions) before mutating anything. Deep-interview is requirements-only and must not mutate product code. Its spec hands off as deep-interview → ralplan consensus → final receipt → approval or valid non-off runtime admission → separately authorized execution.
30
30
  - Clear work with demonstrated architecture or sequencing risk suggests `/skill:ralplan --deliberate`; reconciliation must persist its final receipt before choosing approval or an admitted handoff. A valid non-off final runtime receipt enters the existing handoff chain; otherwise it stops pending approval.
31
- - Use `/skill:ultragoal` for durable goal ledgers and `/skill:team` for approved coordinated persistent work.
31
+ - Use `/skill:ultragoal` for durable goal ledgers and `/skill:autoresearch` for goal-directed research missions that end on a structured verdict rather than an implementation.
32
32
  - Delegate large implementation slices to `executor`; use `planner`, `architect`, or `critic` for bounded planning and review.
33
33
  - An explicit user request to use a worktree (for example, "use worktree") overrides direct editing: delegate implementation through `task` with `isolated: true`. This is the in-session counterpart of launching `gjc --worktree`; if task isolation is unavailable, report that conflict instead of editing in the parent session.
34
34
  - Active skills are authoritative: never ignore an invoked skill; read the full skill text and follow it exactly.
@@ -6,5 +6,6 @@ Working directory layout (sorted by mtime, recent first; depth ≤ 3):
6
6
  (some entries elided to keep the tree short — use `find`/`read` to drill in)
7
7
  {{/if}}
8
8
  </workspace-tree>
9
- {{/if}}Today is {{date}}, and the current working directory is '{{cwd}}'.
9
+ {{/if}}Today is {{date}}, the local time is {{localTime}}, and the current working directory is '{{cwd}}'.
10
+ Timestamps in files, logs, git history, and API responses are often UTC; convert timestamps that are explicitly UTC to the local timezone above when reporting times to the user. Preserve timestamps that already include an explicit offset or timezone, and do not treat this host wall clock as authenticated event-time evidence. The timezone follows the runtime's process timezone configuration (which may include project environment settings), so treat it as display context rather than trusted location evidence. The full IANA zone may reveal geographic information; do not infer more about the user than the displayed context states.
10
11
  </system-reminder>
@@ -0,0 +1,15 @@
1
+ Rescope the session to a narrower working directory.
2
+
3
+ Use this only when the session's working directory is a broad launcher root (for example a
4
+ multi-repo workspace like `~/Projects`) and the task has clearly converged on one subdirectory
5
+ or repository: after this call, every later turn resolves relative paths and the bash default
6
+ cwd from the new directory, and project-scoped plugins/capabilities reload for it.
7
+
8
+ - `path` must be an existing directory; relative paths resolve against the current session cwd.
9
+ The canonical target must be strictly inside the current session directory — moves to a
10
+ parent, a sibling project, or an unrelated absolute path are refused.
11
+ - A session can be moved this way at most once, and never while another move is running; a
12
+ rejected call does not consume the move. Use it once the target repo is identified — not
13
+ speculatively — because the session file and caches move with the session.
14
+ - This tool is unavailable in subagent sessions and restricted profiles; ask the top-level
15
+ session to rescope instead.
@@ -0,0 +1,12 @@
1
+ Execute Python in a persistent per-session REPL kernel.
2
+
3
+ Variables, imports, and loaded data persist across calls in the current GJC session. The kernel runs in the session working directory. Each `execute` call is appended to a JSONL transcript under `.gjc/_session-{sessionid}/ipykernels/`; display artifacts are stored under `.gjc/_session-{sessionid}/ipykernels/artifacts/`.
4
+
5
+ ## Actions
6
+
7
+ - `execute` (default) — run `code` in the persistent REPL. Requires `code`.
8
+ - `clear` — dispose this session's Python kernel. The next `execute` starts a fresh kernel with no retained state.
9
+
10
+ ## Use
11
+
12
+ Use this tool for stateful Python work. It is distinct from `eval`: each has a separate kernel and owner, so state is not shared between them.
@@ -1,7 +1,7 @@
1
1
  Discover project and user runtime skills without loading full skill content.
2
2
 
3
3
  <instruction>
4
- - Searches canonical GJC skill locations in precedence order: project `.gjc/skills` (ancestors from cwd to repo root, closest first), then user locations under the home directory: canonical `<config>/agent/skills`, configured legacy `<config>/skills`, and historical legacy `.gjc/skills`. `<config>` is the home-relative directory name from `GJC_CONFIG_DIR`, then `PI_CONFIG_DIR`, then `.gjc`; even an absolute-looking configured name is joined beneath `<home>`. Project scope shadows user scope; within a scope, earlier locations above win. Bundled GJC workflow skills (`deep-interview`, `ralplan`, `team`, `ultragoal`) are always available and cannot be replaced by filesystem skills.
4
+ - Searches canonical GJC skill locations in precedence order: project `.gjc/skills` (ancestors from cwd to repo root, closest first), then user locations under the home directory: canonical `<config>/agent/skills`, configured legacy `<config>/skills`, and historical legacy `.gjc/skills`. `<config>` is the home-relative directory name from `GJC_CONFIG_DIR`, then `PI_CONFIG_DIR`, then `.gjc`; even an absolute-looking configured name is joined beneath `<home>`. Project scope shadows user scope; within a scope, earlier locations above win. Bundled GJC workflow skills (`autoresearch`, `deep-interview`, `ralplan`, `ultragoal`) are always available and cannot be replaced by filesystem skills.
5
5
  - Returns thin metadata only: name, description, source scope, path, and use conditions when present.
6
6
  - Claude Code (`.claude/skills`) and Codex (`.codex/skills`) layouts are explicit import sources into `.gjc`, never invokable candidates. They are not returned as candidates; instead, each convention skill found in a trusted scope is reported in `diagnostics` with the exact copy command that enables it (copy into `.gjc/skills`), so a skill placed in a documented convention location is discoverable in a normal session without being silently loaded.
7
7
  - Discovery is on by default in a normal session. When zero candidates are returned because discovery config is disabled (`skills.enabled` master switch, or `skills.trustProjectSkills` / `skills.trustUserSkills` scope trust), the result carries a `notice` explaining which setting blocked the search — an empty result without a `notice` means the searched scopes genuinely contain no matching skills.
@@ -6,10 +6,10 @@ Invoke another available skill in the current turn.
6
6
  </conditions>
7
7
 
8
8
  <instruction>
9
- - `name` is the skill name as it appears in `/skill:<name>` (e.g. `ralplan`, `ultragoal`, `team`, `deep-interview`)
9
+ - `name` is the skill name as it appears in `/skill:<name>` (e.g. `ralplan`, `ultragoal`, `autoresearch`, `deep-interview`)
10
10
  - `args` is the free-form argument string the skill would receive after `/skill:<name>` on the command line
11
11
  - The tool loads the callee's SKILL.md into the current turn and handles native workflow caller→callee state handoff when the caller is one of the built-in GJC workflows.
12
- - The chain is refused while a native workflow caller is still active. If your current skill is one of `deep-interview`, `ralplan`, `ultragoal`, or `team` and has not yet reached a terminal phase, prepare it first with `gjc state <skill> write --input '{"current_phase":"handoff"}' --json`; no other handoff command is needed. Runtime project/user skills do not use `gjc state <skill>`.
12
+ - The chain is refused while a native workflow caller is still mid-flight. `autoresearch` chains from any of its phases (`intake`/`research`/`verdict`) — a research mission is always handoff-ready. `deep-interview` chains once its final spec is persisted (phase `handoff`), and `ralplan` chains from `final` or `handoff`. Only a mid-flight `ralplan` or `ultragoal` needs preparation first: `gjc state <skill> write --input '{"current_phase":"handoff"}' --json`; no other handoff command is needed. Runtime project/user skills do not use `gjc state <skill>`.
13
13
  - Call once per chain step. To chain `A → B → C`, A calls `skill(B)`; B's next agent turn calls `skill(C)`.
14
14
  </instruction>
15
15