@gajae-code/coding-agent 0.18.7 → 0.18.9

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 (344) hide show
  1. package/CHANGELOG.md +377 -1
  2. package/dist/types/async/endpoint-id.d.ts +16 -0
  3. package/dist/types/async/job-manager.d.ts +1 -1
  4. package/dist/types/async/support.d.ts +0 -14
  5. package/dist/types/capability/mcp.d.ts +2 -0
  6. package/dist/types/capability/skill.d.ts +2 -0
  7. package/dist/types/capability/types.d.ts +2 -0
  8. package/dist/types/cli/public-command-errors.d.ts +3 -1
  9. package/dist/types/cli/setup-cli.d.ts +1 -1
  10. package/dist/types/commands/deep-interview.d.ts +3 -0
  11. package/dist/types/commands/mcp-serve.d.ts +4 -0
  12. package/dist/types/config/autorouting-tier-map.d.ts +348 -9
  13. package/dist/types/config/file-lock.d.ts +1 -0
  14. package/dist/types/config/model-bindings-applier.d.ts +2 -0
  15. package/dist/types/config/model-profile-activation.d.ts +11 -3
  16. package/dist/types/config/model-registry.d.ts +4 -9
  17. package/dist/types/config/skill-settings-defaults.d.ts +2 -0
  18. package/dist/types/cursor-delete-path.d.ts +1 -0
  19. package/dist/types/cursor-mcp-name.d.ts +8 -0
  20. package/dist/types/customization/inventory.d.ts +2 -0
  21. package/dist/types/debug/report-bundle-parent.d.ts +2 -0
  22. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.d.ts +1 -0
  23. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.d.ts +31 -0
  24. package/dist/types/defaults/gjc-skills.generated.d.ts +5 -6
  25. package/dist/types/discovery/helpers.d.ts +6 -1
  26. package/dist/types/discovery/mcp-json.d.ts +1 -0
  27. package/dist/types/eval/py/kernel.d.ts +18 -0
  28. package/dist/types/export/html/css-color.d.ts +2 -0
  29. package/dist/types/export/html/html-export-destination.d.ts +2 -0
  30. package/dist/types/export/html/template.generated.d.ts +1 -1
  31. package/dist/types/extensibility/extensions/function-hooks-internal.d.ts +5 -0
  32. package/dist/types/extensibility/extensions/types.d.ts +7 -0
  33. package/dist/types/extensibility/gjc-plugins/runtime-adapters.d.ts +14 -1
  34. package/dist/types/extensibility/plugins/marketplace/cache.d.ts +2 -0
  35. package/dist/types/extensibility/plugins/plugin-link-path.d.ts +2 -0
  36. package/dist/types/extensibility/runtime-skill-discovery.d.ts +3 -1
  37. package/dist/types/extensibility/skill-management.d.ts +4 -1
  38. package/dist/types/extensibility/skills.d.ts +2 -0
  39. package/dist/types/gjc-runtime/deep-interview-crystallize.d.ts +74 -0
  40. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +11 -0
  41. package/dist/types/gjc-runtime/gc-runtime.d.ts +5 -0
  42. package/dist/types/gjc-runtime/psmux-detect.d.ts +3 -3
  43. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +0 -5
  44. package/dist/types/gjc-runtime/sidecar-bootstrap-env.d.ts +6 -0
  45. package/dist/types/gjc-runtime/state-argv.d.ts +1 -1
  46. package/dist/types/gjc-runtime/state-migrations.d.ts +8 -0
  47. package/dist/types/gjc-runtime/state-runtime.d.ts +95 -0
  48. package/dist/types/gjc-runtime/state-writer.d.ts +14 -1
  49. package/dist/types/gjc-runtime/tmux-common.d.ts +8 -5
  50. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -0
  51. package/dist/types/internal-urls/skill-path.d.ts +1 -0
  52. package/dist/types/lsp/config.d.ts +8 -5
  53. package/dist/types/lsp/workspace-path.d.ts +1 -0
  54. package/dist/types/modes/acp/acp-agent.d.ts +7 -0
  55. package/dist/types/modes/components/custom-editor.d.ts +6 -0
  56. package/dist/types/modes/components/customization/customization-dashboard.d.ts +1 -0
  57. package/dist/types/modes/components/extensions/state-manager.d.ts +4 -3
  58. package/dist/types/modes/controllers/input-controller.d.ts +9 -0
  59. package/dist/types/modes/interactive-mode.d.ts +1 -0
  60. package/dist/types/modes/utils/interrupt-ownership.d.ts +3 -0
  61. package/dist/types/modes/utils/keybinding-matchers.d.ts +1 -0
  62. package/dist/types/modes/utils/macos-option-forwarding.d.ts +17 -0
  63. package/dist/types/progress/collect-project-progress.d.ts +38 -0
  64. package/dist/types/progress/progress-contract.d.ts +154 -0
  65. package/dist/types/progress/progress-snapshot.d.ts +11 -0
  66. package/dist/types/progress/project-progress.d.ts +141 -0
  67. package/dist/types/progress/render-progress.d.ts +28 -0
  68. package/dist/types/runtime-mcp/manager.d.ts +2 -1
  69. package/dist/types/runtime-mcp/mcp-redirect.d.ts +9 -0
  70. package/dist/types/runtime-mcp/oauth-public-url.d.ts +3 -0
  71. package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +1 -0
  72. package/dist/types/runtime-mcp/project-host-values.d.ts +27 -0
  73. package/dist/types/runtime-mcp/smithery-registry.d.ts +24 -0
  74. package/dist/types/runtime-mcp/types.d.ts +6 -0
  75. package/dist/types/sdk/acp/adapter.d.ts +1 -1
  76. package/dist/types/sdk/broker/broker-exit.d.ts +6 -2
  77. package/dist/types/sdk/broker/broker.d.ts +24 -2
  78. package/dist/types/sdk/broker/doctor-restart.d.ts +5 -0
  79. package/dist/types/sdk/broker/ensure.d.ts +14 -1
  80. package/dist/types/sdk/broker/session-index.d.ts +12 -6
  81. package/dist/types/sdk/bus/index.d.ts +12 -1
  82. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -1
  83. package/dist/types/sdk/client/client.d.ts +2 -0
  84. package/dist/types/sdk/host/query/handlers.d.ts +3 -0
  85. package/dist/types/sdk/index.d.ts +1 -0
  86. package/dist/types/sdk/router/session-router.d.ts +2 -0
  87. package/dist/types/sdk/session-list.d.ts +1 -1
  88. package/dist/types/sdk/session.d.ts +8 -0
  89. package/dist/types/session/agent-session.d.ts +38 -9
  90. package/dist/types/session/artifacts.d.ts +2 -2
  91. package/dist/types/session/internal/managed-gc-retirement-codec.d.ts +32 -0
  92. package/dist/types/session/internal/managed-session-scope.d.ts +79 -2
  93. package/dist/types/session/internal/managed-session-storage.d.ts +26 -1
  94. package/dist/types/session/internal/managed-task-owner-cleanup.d.ts +62 -0
  95. package/dist/types/session/internal/task-artifact-owner-access.d.ts +2 -0
  96. package/dist/types/session/internal/task-artifact-owner-transcript.d.ts +9 -1
  97. package/dist/types/session/session-manager.d.ts +31 -5
  98. package/dist/types/session/session-retirement.d.ts +31 -7
  99. package/dist/types/session/session-storage.d.ts +69 -2
  100. package/dist/types/session/task-artifact-owner-codec.d.ts +2 -0
  101. package/dist/types/setup/credential-auto-import.d.ts +1 -1
  102. package/dist/types/setup/credential-import.d.ts +6 -4
  103. package/dist/types/skill-state/active-state.d.ts +38 -0
  104. package/dist/types/skill-state/workflow-mutation-guard.d.ts +3 -0
  105. package/dist/types/slash-commands/helpers/progress-report.d.ts +8 -0
  106. package/dist/types/task/executor.d.ts +3 -0
  107. package/dist/types/task/isolation-dir.d.ts +26 -0
  108. package/dist/types/tools/ast-edit-write-path.d.ts +13 -0
  109. package/dist/types/tools/bash-skill-urls.d.ts +1 -1
  110. package/dist/types/tools/browser/cdp-url.d.ts +2 -0
  111. package/dist/types/tools/github-side-effect.d.ts +22 -0
  112. package/dist/types/tools/index.d.ts +3 -0
  113. package/dist/types/tools/monitor-mutation.d.ts +9 -0
  114. package/dist/types/tools/python.d.ts +9 -1
  115. package/dist/types/tools/recipe/recipe-guard.d.ts +7 -0
  116. package/dist/types/tools/restricted-cwd.d.ts +1 -0
  117. package/dist/types/tools/todo-write.d.ts +6 -0
  118. package/dist/types/utils/tools-checksum.d.ts +7 -0
  119. package/package.json +7 -7
  120. package/scripts/generate-sdk-operation-inventory.ts +21 -0
  121. package/scripts/verify-managed-empty-stop.ts +28 -0
  122. package/src/async/endpoint-id.ts +26 -0
  123. package/src/async/job-manager.ts +247 -132
  124. package/src/async/support.ts +0 -25
  125. package/src/capability/index.ts +15 -0
  126. package/src/capability/mcp.ts +2 -0
  127. package/src/capability/skill.ts +2 -0
  128. package/src/capability/types.ts +2 -0
  129. package/src/cli/auth-broker-cli.ts +1 -1
  130. package/src/cli/customize-doctor.ts +15 -2
  131. package/src/cli/doctor/repairs.ts +4 -1
  132. package/src/cli/doctor-supervisor.ts +3 -0
  133. package/src/cli/public-command-errors.ts +21 -1
  134. package/src/cli/public-command-registry.ts +28 -6
  135. package/src/cli/setup-cli.ts +4 -4
  136. package/src/cli/skills-cli.ts +5 -0
  137. package/src/commands/deep-interview.ts +47 -8
  138. package/src/commands/mcp-serve.ts +7 -0
  139. package/src/commands/sdk.ts +455 -397
  140. package/src/config/autorouting-tier-map.ts +117 -7
  141. package/src/config/file-lock.ts +316 -79
  142. package/src/config/mcp-schema.json +4 -0
  143. package/src/config/model-bindings-applier.ts +20 -0
  144. package/src/config/model-profile-activation.ts +240 -46
  145. package/src/config/model-registry.ts +47 -153
  146. package/src/config/skill-settings-defaults.ts +7 -0
  147. package/src/coordinator-mcp/policy.ts +3 -2
  148. package/src/coordinator-mcp/server.ts +2 -0
  149. package/src/cursor-delete-path.ts +10 -0
  150. package/src/cursor-mcp-name.ts +15 -0
  151. package/src/cursor.ts +13 -2
  152. package/src/customization/inventory.ts +4 -13
  153. package/src/dap/config.ts +2 -2
  154. package/src/debug/report-bundle-parent.ts +22 -0
  155. package/src/debug/report-bundle.ts +3 -0
  156. package/src/debug/system-info.ts +13 -1
  157. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +10 -6
  158. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.ts +67 -4
  159. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.ts +80 -0
  160. package/src/defaults/gjc/skills/deep-interview/SKILL.md +105 -10
  161. package/src/defaults/gjc/skills/ralplan/SKILL.md +6 -4
  162. package/src/defaults/gjc-defaults.test.ts +18 -1
  163. package/src/defaults/gjc-defaults.ts +3 -0
  164. package/src/defaults/gjc-skills.generated.ts +58 -36
  165. package/src/discovery/agents.ts +6 -1
  166. package/src/discovery/builtin.ts +13 -1
  167. package/src/discovery/helpers.ts +171 -29
  168. package/src/discovery/mcp-json.ts +26 -12
  169. package/src/discovery/ssh.ts +1 -1
  170. package/src/discovery/substitute-plugin-root.ts +3 -1
  171. package/src/edit/file-read-cache.ts +1 -1
  172. package/src/eval/py/executor.ts +796 -154
  173. package/src/eval/py/kernel.ts +79 -13
  174. package/src/eval/py/tool-bridge.ts +4 -0
  175. package/src/export/html/css-color.ts +9 -0
  176. package/src/export/html/html-export-destination.ts +16 -0
  177. package/src/export/html/index.ts +7 -10
  178. package/src/export/html/template.generated.ts +1 -1
  179. package/src/extensibility/extensions/function-hooks-internal.ts +27 -0
  180. package/src/extensibility/extensions/runner.ts +15 -1
  181. package/src/extensibility/extensions/types.ts +7 -0
  182. package/src/extensibility/gjc-plugins/mcp-policy.ts +2 -1
  183. package/src/extensibility/gjc-plugins/runtime-adapters.ts +85 -8
  184. package/src/extensibility/plugins/manager.ts +3 -2
  185. package/src/extensibility/plugins/marketplace/cache.ts +15 -0
  186. package/src/extensibility/plugins/marketplace/manager.ts +11 -3
  187. package/src/extensibility/plugins/plugin-link-path.ts +12 -0
  188. package/src/extensibility/runtime-skill-discovery.ts +20 -2
  189. package/src/extensibility/skill-management.ts +9 -2
  190. package/src/extensibility/skills.ts +6 -1
  191. package/src/gjc-runtime/deep-interview-crystallize.ts +1814 -0
  192. package/src/gjc-runtime/deep-interview-recorder.ts +20 -2
  193. package/src/gjc-runtime/deep-interview-runtime.ts +1415 -13
  194. package/src/gjc-runtime/deep-interview-stage.ts +98 -21
  195. package/src/gjc-runtime/gc-runtime.ts +206 -9
  196. package/src/gjc-runtime/launch-worktree.ts +70 -9
  197. package/src/gjc-runtime/psmux-detect.ts +28 -4
  198. package/src/gjc-runtime/ralplan-runtime.ts +447 -52
  199. package/src/gjc-runtime/session-state-sidecar.ts +3 -2
  200. package/src/gjc-runtime/sidecar-bootstrap-env.ts +19 -0
  201. package/src/gjc-runtime/state-argv.ts +2 -0
  202. package/src/gjc-runtime/state-migrations.ts +91 -29
  203. package/src/gjc-runtime/state-runtime.ts +3716 -135
  204. package/src/gjc-runtime/state-writer.ts +105 -16
  205. package/src/gjc-runtime/tmux-common.ts +8 -5
  206. package/src/gjc-runtime/ultragoal-runtime.ts +16 -4
  207. package/src/gjc-runtime/workflow-manifest.generated.json +70 -42
  208. package/src/gjc-runtime/workflow-manifest.ts +54 -21
  209. package/src/harness-control-plane/operate.ts +12 -4
  210. package/src/harness-control-plane/owner.ts +5 -1
  211. package/src/hashline/parser.ts +48 -9
  212. package/src/hashline/recovery.ts +13 -4
  213. package/src/internal-urls/docs-index.generated.ts +20 -19
  214. package/src/internal-urls/skill-path.ts +19 -0
  215. package/src/internal-urls/skill-protocol.ts +2 -0
  216. package/src/lsp/config.ts +12 -63
  217. package/src/lsp/edits.ts +6 -0
  218. package/src/lsp/workspace-path.ts +51 -0
  219. package/src/main.ts +195 -31
  220. package/src/memories/index.ts +88 -7
  221. package/src/modes/acp/acp-agent.ts +88 -4
  222. package/src/modes/components/custom-editor.ts +14 -0
  223. package/src/modes/components/customization/customization-dashboard.ts +12 -0
  224. package/src/modes/components/extensions/extension-dashboard.ts +2 -2
  225. package/src/modes/components/extensions/state-manager.ts +15 -5
  226. package/src/modes/components/hook-editor.ts +1 -1
  227. package/src/modes/components/hook-selector.ts +1 -1
  228. package/src/modes/components/model-selector.ts +1 -4
  229. package/src/modes/components/provider-onboarding-selector.ts +1 -1
  230. package/src/modes/components/status-line/segments.ts +4 -0
  231. package/src/modes/controllers/event-controller.ts +11 -4
  232. package/src/modes/controllers/input-controller.ts +76 -31
  233. package/src/modes/controllers/runtime-mcp-command-controller.ts +29 -16
  234. package/src/modes/controllers/selector-controller.ts +24 -15
  235. package/src/modes/controllers/todo-command-controller.ts +1 -1
  236. package/src/modes/interactive-mode.ts +5 -1
  237. package/src/modes/runtime-init.ts +4 -0
  238. package/src/modes/utils/abort-message.ts +3 -1
  239. package/src/modes/utils/interrupt-ownership.ts +12 -0
  240. package/src/modes/utils/keybinding-matchers.ts +4 -0
  241. package/src/modes/utils/macos-option-forwarding.ts +80 -0
  242. package/src/progress/collect-project-progress.ts +153 -0
  243. package/src/progress/progress-contract.ts +172 -0
  244. package/src/progress/progress-snapshot.ts +140 -0
  245. package/src/progress/project-progress.ts +446 -0
  246. package/src/progress/render-progress.ts +209 -0
  247. package/src/prompts/system/master-mode.md +1 -0
  248. package/src/prompts/tools/skill-discovery.md +1 -1
  249. package/src/runtime-mcp/client.ts +4 -2
  250. package/src/runtime-mcp/config-writer.ts +10 -6
  251. package/src/runtime-mcp/config.ts +1 -0
  252. package/src/runtime-mcp/json-rpc.ts +2 -1
  253. package/src/runtime-mcp/manager.ts +29 -11
  254. package/src/runtime-mcp/mcp-redirect.ts +73 -0
  255. package/src/runtime-mcp/oauth-flow.ts +14 -2
  256. package/src/runtime-mcp/oauth-public-url.ts +14 -0
  257. package/src/runtime-mcp/plugin-network-boundary.ts +4 -0
  258. package/src/runtime-mcp/pool.ts +6 -0
  259. package/src/runtime-mcp/project-host-values.ts +46 -0
  260. package/src/runtime-mcp/smithery-registry.ts +22 -3
  261. package/src/runtime-mcp/transports/http.ts +4 -3
  262. package/src/runtime-mcp/types.ts +6 -0
  263. package/src/sdk/acp/adapter.ts +95 -8
  264. package/src/sdk/broker/broker-exit.ts +52 -23
  265. package/src/sdk/broker/broker.ts +81 -6
  266. package/src/sdk/broker/doctor-restart.ts +4 -0
  267. package/src/sdk/broker/ensure.ts +31 -2
  268. package/src/sdk/broker/lifecycle.ts +711 -66
  269. package/src/sdk/broker/session-index.ts +191 -81
  270. package/src/sdk/bus/chat-daemon-cli.ts +38 -1
  271. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  272. package/src/sdk/bus/index.ts +238 -43
  273. package/src/sdk/bus/telegram-daemon-contract.ts +6 -1
  274. package/src/sdk/cli/session-cli.ts +17 -3
  275. package/src/sdk/client/client.ts +28 -8
  276. package/src/sdk/host/query/handlers.ts +9 -1
  277. package/src/sdk/host/session-runtime.test.ts +2705 -303
  278. package/src/sdk/host/session-runtime.ts +490 -143
  279. package/src/sdk/host/surface-policy.ts +1 -0
  280. package/src/sdk/index.ts +8 -0
  281. package/src/sdk/lifecycle-session.ts +3 -1
  282. package/src/sdk/prompt-deadline-manager.ts +15 -0
  283. package/src/sdk/protocol/operation-inventory.generated.json +146 -0
  284. package/src/sdk/protocol/operation-registry.ts +9 -2
  285. package/src/sdk/router/session-router.ts +82 -18
  286. package/src/sdk/session-list.ts +1 -0
  287. package/src/sdk/session.ts +69 -45
  288. package/src/session/agent-session.ts +679 -238
  289. package/src/session/artifacts.ts +176 -71
  290. package/src/session/internal/managed-gc-retirement-codec.ts +319 -0
  291. package/src/session/internal/managed-session-scope.ts +3779 -462
  292. package/src/session/internal/managed-session-storage.ts +501 -78
  293. package/src/session/internal/managed-task-owner-cleanup.ts +473 -0
  294. package/src/session/internal/task-artifact-owner-access.ts +19 -3
  295. package/src/session/internal/task-artifact-owner-transcript.ts +547 -1
  296. package/src/session/session-manager.ts +951 -133
  297. package/src/session/session-retirement.ts +573 -14
  298. package/src/session/session-storage.ts +447 -15
  299. package/src/session/task-artifact-owner-codec.ts +71 -8
  300. package/src/session/task-artifact-owner-retirement.ts +21 -19
  301. package/src/setup/credential-auto-import.ts +12 -3
  302. package/src/setup/credential-import.ts +176 -5
  303. package/src/setup/paseo/announce.ts +6 -1
  304. package/src/skill-state/active-state.ts +145 -2
  305. package/src/skill-state/workflow-mutation-guard.ts +102 -10
  306. package/src/slash-commands/builtin-registry.ts +39 -9
  307. package/src/slash-commands/helpers/mcp.ts +8 -4
  308. package/src/slash-commands/helpers/progress-report.ts +23 -0
  309. package/src/stt/setup.ts +3 -5
  310. package/src/task/commands.ts +3 -1
  311. package/src/task/executor.ts +4 -0
  312. package/src/task/index.ts +6 -0
  313. package/src/task/isolation-dir.ts +143 -0
  314. package/src/task/worktree.ts +16 -7
  315. package/src/tools/ask.ts +205 -6
  316. package/src/tools/ast-edit-write-path.ts +68 -0
  317. package/src/tools/ast-edit.ts +10 -1
  318. package/src/tools/bash-allowed-prefixes.ts +15 -0
  319. package/src/tools/bash-skill-urls.ts +56 -3
  320. package/src/tools/bash.ts +48 -79
  321. package/src/tools/browser/cdp-url.ts +18 -0
  322. package/src/tools/browser.ts +8 -1
  323. package/src/tools/descriptors.ts +5 -1
  324. package/src/tools/eval.ts +56 -36
  325. package/src/tools/gh.ts +10 -0
  326. package/src/tools/github-side-effect.ts +112 -0
  327. package/src/tools/image-gen.ts +1 -1
  328. package/src/tools/index.ts +3 -0
  329. package/src/tools/monitor-mutation.ts +27 -0
  330. package/src/tools/monitor.ts +7 -0
  331. package/src/tools/python.ts +188 -66
  332. package/src/tools/recipe/index.ts +9 -0
  333. package/src/tools/recipe/recipe-guard.ts +19 -0
  334. package/src/tools/restricted-cwd.ts +10 -0
  335. package/src/tools/skill-discovery.ts +5 -0
  336. package/src/tools/skill.ts +28 -1
  337. package/src/tools/todo-write.ts +10 -1
  338. package/src/tools/tool-catalog.generated.ts +1 -1
  339. package/src/tools/write.ts +1 -0
  340. package/src/utils/external-editor.ts +3 -2
  341. package/src/utils/tools-checksum.ts +34 -0
  342. package/src/utils/tools-manager.ts +24 -6
  343. package/src/web/insane/url-guard.ts +2 -1
  344. package/src/web/search/providers/codex.ts +3 -2
@@ -40,7 +40,9 @@ import { CommandPalette, type CommandPaletteAction, type CommandPaletteEntry } f
40
40
  import type { PasteTextContext } from "../components/custom-editor";
41
41
  import { QueuePaneComponent } from "../components/queue-pane";
42
42
  import { type QueuedMessageMoveDirection, QueuedMessageSelectorComponent } from "../components/queued-message-selector";
43
+ import { focusedUiOwnsInterrupt } from "../utils/interrupt-ownership";
43
44
  import { matchesAppInterrupt } from "../utils/keybinding-matchers";
45
+ import { createUnforwardedOptionChordWarner } from "../utils/macos-option-forwarding";
44
46
 
45
47
  const QUEUE_SELECTOR_NAVIGATION_ACTIONS = [
46
48
  "tui.select.up",
@@ -95,6 +97,8 @@ function isExpandable(obj: unknown): obj is Expandable {
95
97
  export class InputController {
96
98
  readonly actionRegistry: ActionRegistry<void>;
97
99
  readonly #loadPastedImageBatch: typeof loadPastedImageBatch;
100
+ /** Session id with an automatic-title request pending, if any. */
101
+ #autoTitleInFlightSessionId: string | undefined;
98
102
  #deferredSubmission?: {
99
103
  text: string;
100
104
  images?: InteractiveModeContext["pendingImages"];
@@ -546,15 +550,23 @@ export class InputController {
546
550
  if (!isInterruptKey && !isClearKey) {
547
551
  return undefined;
548
552
  }
549
- if (isClearKey && !isInterruptKey && this.ctx.hasActiveBtw() && this.ctx.handleBtwEscape()) {
553
+ // Listeners run before focused handleInput. Yield interrupt/back to the
554
+ // focused UI before touching BTW, maintenance, retries or workflow state.
555
+ // Clear (Ctrl+C by default) remains a global abort, even if the same
556
+ // key is also configured as interrupt.
557
+ if (isInterruptKey && !isClearKey && focusedUiOwnsInterrupt(this.ctx)) {
558
+ this.#resetEscapeGestures();
559
+ return undefined;
560
+ }
561
+ if (isClearKey && this.ctx.hasActiveBtw() && this.ctx.handleBtwEscape()) {
550
562
  this.#resetEscapeGestures();
551
563
  return { consume: true };
552
564
  }
553
- if (isClearKey && !isInterruptKey && this.ctx.hookSelector?.hasActiveInlineInput?.() === true) {
565
+ if (isClearKey && this.ctx.hookSelector?.hasActiveInlineInput?.() === true) {
554
566
  this.#resetEscapeGestures();
555
567
  return undefined;
556
568
  }
557
- if (isClearKey && !isInterruptKey) {
569
+ if (isClearKey) {
558
570
  if (this.#handlePendingSteerInterrupt()) return { consume: true };
559
571
  if (
560
572
  this.#handleCancellableWorkEscape({
@@ -807,6 +819,12 @@ export class InputController {
807
819
  this.ctx.editor.setActionKeys("app.message.queue", this.ctx.keybindings.getKeys("app.message.queue"));
808
820
  this.ctx.editor.onQueue = () => this.#executeAction("app.message.queue");
809
821
  this.#registerCommandPaletteAction("app.message.queue", queue, true);
822
+ this.ctx.editor.onUnforwardedOptionChord ??= createUnforwardedOptionChordWarner({
823
+ platform: process.platform,
824
+ terminalProgram: Bun.env.TERM_PROGRAM,
825
+ formatKeyHint: key => this.ctx.keybindings.formatKeyHint(key),
826
+ showWarning: message => this.ctx.showWarning(message),
827
+ });
810
828
 
811
829
  this.ctx.editor.onViewportPageScroll = direction => this.ctx.ui.scrollViewportPages(direction);
812
830
  this.ctx.editor.onViewportFollowLive = () => {
@@ -1163,6 +1181,7 @@ export class InputController {
1163
1181
  return;
1164
1182
  }
1165
1183
  this.ctx.queueCompactionMessage(text, "steer", composer);
1184
+ this.#titleQueuedCompactionText(text);
1166
1185
  return;
1167
1186
  }
1168
1187
 
@@ -1172,6 +1191,9 @@ export class InputController {
1172
1191
  // This handles extension commands (execute immediately), prompt template expansion, and queueing
1173
1192
  if (this.ctx.session.isStreaming) {
1174
1193
  invalidateSessionTitleGeneration(this.ctx.sessionManager);
1194
+ // A turn started by a skill command has no user message yet, so the
1195
+ // first typed message can arrive here instead of the idle path.
1196
+ this.maybeGenerateSessionTitle(text);
1175
1197
  if (this.#canModifyComposer(composer)) {
1176
1198
  this.ctx.editor.addToHistory(text);
1177
1199
  this.ctx.editor.setText("");
@@ -1200,32 +1222,7 @@ export class InputController {
1200
1222
  // First, move any pending bash components to chat
1201
1223
  this.ctx.flushPendingBashComponents();
1202
1224
 
1203
- // Generate session title on first message
1204
- const hasUserMessages = this.ctx.session.messages.some((m: AgentMessage) => m.role === "user");
1205
- if (!hasUserMessages && !this.ctx.sessionManager.getSessionName() && !$pickenv("GJC_NO_TITLE", "PI_NO_TITLE")) {
1206
- const registry = this.ctx.session.modelRegistry;
1207
- generateSessionTitle(
1208
- text,
1209
- registry,
1210
- this.ctx.settings,
1211
- this.ctx.session.credentialSessionId,
1212
- this.ctx.session.model,
1213
- provider => this.ctx.session.agent.metadataForProvider(provider),
1214
- )
1215
- .then(async title => {
1216
- if (title) {
1217
- const applied = await this.ctx.sessionManager.setSessionName(title, "auto");
1218
- if (applied) {
1219
- setSessionTerminalTitle(
1220
- this.ctx.sessionManager.getSessionName()!,
1221
- this.ctx.sessionManager.getCwd(),
1222
- );
1223
- this.ctx.updateEditorBorderColor();
1224
- }
1225
- }
1226
- })
1227
- .catch(() => {});
1228
- }
1225
+ this.maybeGenerateSessionTitle(text);
1229
1226
 
1230
1227
  if (this.ctx.onInputCallback) {
1231
1228
  // Include any pending images from clipboard paste
@@ -1303,7 +1300,7 @@ export class InputController {
1303
1300
  });
1304
1301
 
1305
1302
  // Stop the TUI (restore terminal to normal mode)
1306
- this.ctx.ui.stop();
1303
+ this.ctx.ui.suspend();
1307
1304
 
1308
1305
  // Send SIGTSTP to process group (pid=0 means all processes in group)
1309
1306
  process.kill(0, "SIGTSTP");
@@ -1628,6 +1625,51 @@ export class InputController {
1628
1625
  return this.ctx.settings.get("busyPromptMode") === "steer" ? "steer" : "followUp";
1629
1626
  }
1630
1627
 
1628
+ /**
1629
+ * Generate the automatic session title from the first user message. Called
1630
+ * from the editor submit and queue-shortcut paths (idle and streaming) and
1631
+ * from interactive startup messages; it is a no-op once the session has a
1632
+ * user message, a name, or a title request already in flight. The result is
1633
+ * bound to the session that requested it: a completion that lands after the
1634
+ * session was replaced (New Session, switch, resume) is discarded.
1635
+ */
1636
+ maybeGenerateSessionTitle(text: string): void {
1637
+ if (this.ctx.sessionManager.getSessionName()) return;
1638
+ const sessionId = this.ctx.sessionManager.getSessionId();
1639
+ if (this.#autoTitleInFlightSessionId === sessionId) return;
1640
+ if ($pickenv("GJC_NO_TITLE", "PI_NO_TITLE")) return;
1641
+ if (this.ctx.session.messages.some((m: AgentMessage) => m.role === "user")) return;
1642
+ // Keep locally intercepted command arguments away from the title model.
1643
+ if (this.ctx.session.isLocallyHandledSlashCommand(text)) return;
1644
+ this.#autoTitleInFlightSessionId = sessionId;
1645
+ generateSessionTitle(
1646
+ text,
1647
+ this.ctx.session.modelRegistry,
1648
+ this.ctx.settings,
1649
+ this.ctx.session.credentialSessionId,
1650
+ this.ctx.session.model,
1651
+ provider => this.ctx.session.agent.metadataForProvider(provider),
1652
+ )
1653
+ .then(async title => {
1654
+ if (!title || this.ctx.sessionManager.getSessionId() !== sessionId) return;
1655
+ const applied = await this.ctx.sessionManager.setSessionName(title, "auto");
1656
+ if (applied) {
1657
+ setSessionTerminalTitle(this.ctx.sessionManager.getSessionName()!, this.ctx.sessionManager.getCwd());
1658
+ this.ctx.updateEditorBorderColor();
1659
+ }
1660
+ })
1661
+ .catch(() => {})
1662
+ .finally(() => {
1663
+ if (this.#autoTitleInFlightSessionId === sessionId) this.#autoTitleInFlightSessionId = undefined;
1664
+ });
1665
+ }
1666
+
1667
+ /** Title text accepted into the compaction queue; skill commands replay through the skill path and are not titled. */
1668
+ #titleQueuedCompactionText(text: string): void {
1669
+ if (parseSkillInvocations(text, this.ctx.skillCommands ?? new Map()).length > 0) return;
1670
+ this.maybeGenerateSessionTitle(text);
1671
+ }
1672
+
1631
1673
  /**
1632
1674
  * Dispatch skill slash invocation(s) (`/skill:<name>`) through custom messages
1633
1675
  * using the supplied `streamingBehavior`. Returns true if the text contains a
@@ -1736,6 +1778,7 @@ export class InputController {
1736
1778
  return;
1737
1779
  }
1738
1780
  this.ctx.queueCompactionMessage(text, "followUp");
1781
+ this.#titleQueuedCompactionText(text);
1739
1782
  return;
1740
1783
  }
1741
1784
 
@@ -1750,6 +1793,7 @@ export class InputController {
1750
1793
  followUpQueuePolicy: "sequential",
1751
1794
  }),
1752
1795
  );
1796
+ this.maybeGenerateSessionTitle(text);
1753
1797
  this.ctx.updatePendingMessagesDisplay();
1754
1798
  this.ctx.ui.requestRender();
1755
1799
  return;
@@ -1761,6 +1805,7 @@ export class InputController {
1761
1805
  return;
1762
1806
  }
1763
1807
 
1808
+ this.maybeGenerateSessionTitle(text);
1764
1809
  if (this.ctx.session.isStreaming) {
1765
1810
  this.ctx.editor.addToHistory(text);
1766
1811
  this.ctx.editor.setText("");
@@ -2517,7 +2562,7 @@ export class InputController {
2517
2562
  let ttyHandle: fs.FileHandle | null = null;
2518
2563
  try {
2519
2564
  ttyHandle = await this.#openEditorTerminalHandle();
2520
- this.ctx.ui.stop();
2565
+ this.ctx.ui.suspend();
2521
2566
 
2522
2567
  const stdio: [number | "inherit", number | "inherit", number | "inherit"] = ttyHandle
2523
2568
  ? [ttyHandle.fd, ttyHandle.fd, ttyHandle.fd]
@@ -19,6 +19,7 @@ import {
19
19
  updateMCPServer,
20
20
  } from "../../runtime-mcp/config-writer";
21
21
  import { canonicalMCPResourceUri, type MCPOAuthConfig, MCPOAuthFlow } from "../../runtime-mcp/oauth-flow";
22
+ import { nativeMcpSource } from "../../runtime-mcp/project-host-values";
22
23
  import {
23
24
  clearSmitheryApiKey,
24
25
  createSmitheryCliAuthSession,
@@ -44,7 +45,8 @@ import { parseCommandArgs } from "../shared";
44
45
  import { buildOAuthLoginAnchor, createOAuthUrlCopyLease } from "../shared/oauth-url-copy";
45
46
  import { theme } from "../theme/theme";
46
47
  import type { InteractiveModeContext } from "../types";
47
- import { matchesAppInterrupt } from "../utils/keybinding-matchers";
48
+ import { focusedUiOwnsInterrupt } from "../utils/interrupt-ownership";
49
+ import { matchesAppClear, matchesAppInterrupt } from "../utils/keybinding-matchers";
48
50
  import { groupBySource, parseRemoveArgs, readScopeFlag, showCommandMessage } from "./command-controller-shared";
49
51
 
50
52
  function withTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string): Promise<T> {
@@ -645,7 +647,9 @@ export class MCPCommandController {
645
647
  const cancellationUnsubscribe =
646
648
  signal === undefined && typeof this.ctx.ui.addInputListener === "function"
647
649
  ? this.ctx.ui.addInputListener(data => {
648
- if (data !== "\x03" && !matchesAppInterrupt(data)) return;
650
+ const isClearKey = data === "\x03" || matchesAppClear(data);
651
+ if (!isClearKey && !matchesAppInterrupt(data)) return;
652
+ if (!isClearKey && focusedUiOwnsInterrupt(this.ctx)) return;
649
653
  userController.abort(new Error("OAuth flow cancelled"));
650
654
  return { consume: true };
651
655
  })
@@ -812,7 +816,7 @@ export class MCPCommandController {
812
816
  * Test connection to an MCP server.
813
817
  * Throws an error if connection fails (used for auto-detection).
814
818
  */
815
- async #handleTestConnection(config: MCPServerConfig): Promise<void> {
819
+ async #handleTestConnection(config: MCPServerConfig, source?: SourceMeta): Promise<void> {
816
820
  const testName = `test_${Date.now()}`;
817
821
  const manager =
818
822
  this.ctx.mcpManager ??
@@ -820,7 +824,7 @@ export class MCPCommandController {
820
824
  sharedPoolIdleMs: this.ctx.settings.get("mcp.sharedPoolIdleMs"),
821
825
  });
822
826
  if (!this.ctx.mcpManager) manager.setAuthStorage(this.ctx.session.modelRegistry.authStorage);
823
- await manager.withPreparedLease(testName, config, async () => {});
827
+ await manager.withPreparedLease(testName, config, async () => {}, { source });
824
828
  }
825
829
 
826
830
  async #findConfiguredServer(
@@ -877,7 +881,10 @@ export class MCPCommandController {
877
881
  return next;
878
882
  }
879
883
 
880
- async #resolveOAuthEndpointsFromServer(config: MCPServerConfig): Promise<{
884
+ async #resolveOAuthEndpointsFromServer(
885
+ config: MCPServerConfig,
886
+ source?: SourceMeta,
887
+ ): Promise<{
881
888
  authorizationUrl: string;
882
889
  tokenUrl: string;
883
890
  clientId?: string;
@@ -885,11 +892,12 @@ export class MCPCommandController {
885
892
  issuer?: string;
886
893
  authorizationResponseIssSupported?: boolean;
887
894
  }> {
888
- // First test if server actually needs auth by connecting without OAuth
895
+ // First test if server actually needs auth by connecting without OAuth.
896
+ // A stored file keeps its source so project `!` and secret names stay gated.
889
897
  let connectionSucceeded = false;
890
898
  let connectionError: Error | undefined;
891
899
  try {
892
- await this.#handleTestConnection(this.#stripOAuthAuth(config));
900
+ await this.#handleTestConnection(this.#stripOAuthAuth(config), source);
893
901
  connectionSucceeded = true;
894
902
  } catch (error) {
895
903
  connectionError = error as Error;
@@ -964,10 +972,10 @@ export class MCPCommandController {
964
972
  }
965
973
  }
966
974
 
967
- async #syncManagerConnection(name: string, config: MCPServerConfig): Promise<void> {
975
+ async #syncManagerConnection(name: string, config: MCPServerConfig, source?: SourceMeta): Promise<void> {
968
976
  if (!this.ctx.mcpManager) return;
969
977
  if (this.ctx.mcpManager.getConnectionStatus(name) !== "disconnected") return;
970
- await this.ctx.mcpManager.connectServers({ [name]: config }, {});
978
+ await this.ctx.mcpManager.connectServers({ [name]: config }, source ? { [name]: source } : {});
971
979
  if (this.ctx.mcpManager.getConnectionStatus(name) === "connected") {
972
980
  await this.ctx.session.refreshMCPTools(this.ctx.mcpManager.getTools());
973
981
  }
@@ -995,9 +1003,10 @@ export class MCPCommandController {
995
1003
  // report as connected to avoid false-negative messaging.
996
1004
  if (!isConnected && !isConnecting && config.enabled !== false) {
997
1005
  try {
998
- await this.#handleTestConnection(config);
1006
+ const source = nativeMcpSource(scope, filePath);
1007
+ await this.#handleTestConnection(config, source);
999
1008
  isConnected = true;
1000
- await this.#syncManagerConnection(name, config);
1009
+ await this.#syncManagerConnection(name, config, source);
1001
1010
  } catch {
1002
1011
  // Keep disconnected status
1003
1012
  }
@@ -1273,7 +1282,8 @@ export class MCPCommandController {
1273
1282
  return;
1274
1283
  }
1275
1284
 
1276
- const { config } = found;
1285
+ const { config, scope, filePath } = found;
1286
+ const source = nativeMcpSource(scope, filePath);
1277
1287
  if (config.enabled === false) {
1278
1288
  this.ctx.showError(`Server "${name}" is disabled. Run /mcp enable ${name} first.`);
1279
1289
  return;
@@ -1310,10 +1320,10 @@ export class MCPCommandController {
1310
1320
  }
1311
1321
 
1312
1322
  lines.push("");
1313
- await this.#syncManagerConnection(name, config);
1323
+ await this.#syncManagerConnection(name, config, source);
1314
1324
  this.#showMessage(lines.join("\n"));
1315
1325
  },
1316
- { signal: abortController.signal },
1326
+ { signal: abortController.signal, source },
1317
1327
  );
1318
1328
  } catch (error) {
1319
1329
  if (abortController.signal.aborted || (error instanceof Error && error.name === "AbortError")) {
@@ -1489,7 +1499,8 @@ export class MCPCommandController {
1489
1499
  }
1490
1500
 
1491
1501
  const baseConfig = this.#stripOAuthAuth(found.config);
1492
- const oauth = await this.#resolveOAuthEndpointsFromServer(baseConfig);
1502
+ const source = nativeMcpSource(found.scope, found.filePath);
1503
+ const oauth = await this.#resolveOAuthEndpointsFromServer(baseConfig, source);
1493
1504
  const oauthClientSecret = found.config.oauth?.clientSecret ?? currentAuth?.clientSecret ?? "";
1494
1505
 
1495
1506
  this.#showMessage(["", theme.fg("muted", `Reauthorizing "${name}"...`), ""].join("\n"));
@@ -1839,7 +1850,9 @@ export class MCPCommandController {
1839
1850
  const oauthUrlCopyLease = createOAuthUrlCopyLease(this.ctx);
1840
1851
  const abortController = new AbortController();
1841
1852
  const interruptUnsubscribe = this.ctx.ui.addInputListener?.(data => {
1842
- if (data !== "\x03" && !matchesAppInterrupt(data)) return;
1853
+ const isClearKey = data === "\x03" || matchesAppClear(data);
1854
+ if (!isClearKey && !matchesAppInterrupt(data)) return;
1855
+ if (!isClearKey && focusedUiOwnsInterrupt(this.ctx)) return;
1843
1856
  abortController.abort(new Error("Smithery authorization cancelled."));
1844
1857
  return { consume: true };
1845
1858
  });
@@ -1612,7 +1612,7 @@ export class SelectorController {
1612
1612
  }
1613
1613
 
1614
1614
  async #handleCredentialImport(): Promise<void> {
1615
- this.ctx.showStatus("Scanning for existing Claude Code / Codex CLI credentials…");
1615
+ this.ctx.showStatus("Scanning for existing Claude Code / Codex CLI / Kiro CLI credentials…");
1616
1616
  const preview = await runExternalCredentialAutoImport({
1617
1617
  authStorage: {
1618
1618
  importCredentialIfAbsent: async () => ({
@@ -2494,17 +2494,20 @@ export class SelectorController {
2494
2494
  * default) then refresh the status surfaces. Rethrows so callers surface errors.
2495
2495
  */
2496
2496
  async #applyModelProfile(profileName: string, persistDefault: boolean): Promise<void> {
2497
+ this.ctx.session.markUserModelSelection();
2497
2498
  const profileLabel = formatModelProfileDisplayLabel(
2498
2499
  this.ctx.session.modelRegistry.getModelProfile(profileName) ?? { name: profileName },
2499
2500
  );
2500
- await activateModelProfile(
2501
- {
2502
- session: this.ctx.session,
2503
- modelRegistry: this.ctx.session.modelRegistry,
2504
- settings: this.ctx.settings,
2505
- profileName,
2506
- },
2507
- { persistDefault },
2501
+ await this.ctx.session.withSdkControlMutation(() =>
2502
+ activateModelProfile(
2503
+ {
2504
+ session: this.ctx.session,
2505
+ modelRegistry: this.ctx.session.modelRegistry,
2506
+ settings: this.ctx.settings,
2507
+ profileName,
2508
+ },
2509
+ { persistDefault },
2510
+ ),
2508
2511
  );
2509
2512
  this.ctx.statusLine.invalidate();
2510
2513
  this.ctx.updateEditorBorderColor();
@@ -2779,6 +2782,7 @@ export class SelectorController {
2779
2782
  await this.ctx.session.setModelTemporary(model, thinkingLevel, {
2780
2783
  cause: "temporary-operation",
2781
2784
  reason: "other",
2785
+ onMutationStarted: () => this.ctx.session.markUserModelSelection(),
2782
2786
  });
2783
2787
  this.ctx.session.setDefaultFallbackRuntimeModel(
2784
2788
  selectedSelector ?? formatModelSelectorValue(`${model.provider}/${model.id}`, thinkingLevel),
@@ -2816,6 +2820,7 @@ export class SelectorController {
2816
2820
  selectedSelector && thinkingLevel && selectedSelector.endsWith(`:${thinkingLevel}`)
2817
2821
  ? selectedSelector.slice(0, -thinkingLevel.length - 1)
2818
2822
  : selectedSelector;
2823
+ if (includesRoleAgent && !includesDefault) this.ctx.session.markUserModelSelection();
2819
2824
 
2820
2825
  const rollbackSnapshot = this.#captureDefaultAssignmentRollback();
2821
2826
  let defaultMutationStarted = false;
@@ -2938,6 +2943,7 @@ export class SelectorController {
2938
2943
  }
2939
2944
  const value =
2940
2945
  selectedSelector ?? formatModelSelectorValue(`${model.provider}/${model.id}`, thinkingLevel);
2946
+ this.ctx.session.markUserModelSelection();
2941
2947
  const assignments = new Map<GjcModelAssignmentTargetId, string>([[role, value]]);
2942
2948
  const materializedProfile = materializeActiveModelProfileAssignments({
2943
2949
  session: this.ctx.session,
@@ -3677,12 +3683,15 @@ export class SelectorController {
3677
3683
  return;
3678
3684
  }
3679
3685
 
3680
- await activateModelProfile({
3681
- session: this.ctx.session,
3682
- modelRegistry: this.ctx.session.modelRegistry,
3683
- settings: this.ctx.settings,
3684
- profileName: recommendedProfile.name,
3685
- });
3686
+ this.ctx.session.markUserModelSelection();
3687
+ await this.ctx.session.withSdkControlMutation(() =>
3688
+ activateModelProfile({
3689
+ session: this.ctx.session,
3690
+ modelRegistry: this.ctx.session.modelRegistry,
3691
+ settings: this.ctx.settings,
3692
+ profileName: recommendedProfile.name,
3693
+ }),
3694
+ );
3686
3695
  }
3687
3696
 
3688
3697
  async #handleOAuthLogin(providerId: string, options?: OAuthSelectorOptions): Promise<void> {
@@ -409,7 +409,7 @@ export class TodoCommandController {
409
409
  current.length > 0 ? phasesToMarkdown(current) : "# Todos\n- [ ] (replace this with your tasks)\n";
410
410
 
411
411
  const fileHandle = await this.#openTtyHandle();
412
- this.ctx.ui.stop();
412
+ this.ctx.ui.suspend();
413
413
  try {
414
414
  const stdio: [number | "inherit", number | "inherit", number | "inherit"] = fileHandle
415
415
  ? [fileHandle.fd, fileHandle.fd, fileHandle.fd]
@@ -679,7 +679,7 @@ export class InteractiveMode implements InteractiveModeContext {
679
679
  startPendingSubmission: input => thisMode.startPendingSubmission(input),
680
680
  addChatChild: child => addChatChild(thisMode, child),
681
681
  requestRender: full => thisMode.ui.requestRender(full),
682
- stopUi: () => thisMode.ui.stop(),
682
+ stopUi: () => thisMode.ui.suspend(),
683
683
  startUi: () => thisMode.ui.start(),
684
684
  showStatus: message => thisMode.showStatus(message),
685
685
  showWarning: message => thisMode.showWarning(message),
@@ -2642,6 +2642,10 @@ export class InteractiveMode implements InteractiveModeContext {
2642
2642
  this.#inputController.handleBackgroundCommand();
2643
2643
  }
2644
2644
 
2645
+ maybeGenerateSessionTitle(text: string): void {
2646
+ this.#inputController.maybeGenerateSessionTitle(text);
2647
+ }
2648
+
2645
2649
  handleImagePaste(): Promise<boolean> {
2646
2650
  return this.#inputController.handleImagePaste();
2647
2651
  }
@@ -11,6 +11,8 @@ import { ThinkingLevel } from "@gajae-code/agent-core";
11
11
  import { runExtensionCompact, runExtensionSetModel } from "../extensibility/extensions/compact-handler";
12
12
  import { getSessionSlashCommands } from "../extensibility/extensions/get-commands-handler";
13
13
  import type { ExtensionError, ExtensionUIContext } from "../extensibility/extensions/types";
14
+ import { buildSessionProjectProgress } from "../progress/collect-project-progress";
15
+ import { toProjectProgressSnapshot } from "../progress/progress-snapshot";
14
16
  import {
15
17
  parseSyntheticModelId,
16
18
  resolveSyntheticModelSelection,
@@ -201,6 +203,8 @@ export async function initializeExtensions(session: AgentSession, options: Initi
201
203
  return { bytes: new Uint8Array(await file.slice(start, end).arrayBuffer()), totalBytes: file.size };
202
204
  },
203
205
  getJobs: () => session.getAsyncJobSnapshot(),
206
+ getProjectProgress: async () =>
207
+ toProjectProgressSnapshot(await buildSessionProjectProgress(session, session.sessionManager)),
204
208
  onJobFold: listener => session.onJobFold(listener),
205
209
  onSessionEvent: listener => session.subscribe(listener),
206
210
  setSdkPermissionProvider: provider => session.setSdkPermissionProvider(provider),
@@ -1,3 +1,5 @@
1
+ import { getStreamIdleTimeoutRemediationMs } from "@gajae-code/ai/utils/idle-iterator";
2
+
1
3
  const STREAM_IDLE_TIMEOUT_PATTERN = /\bstream stalled while waiting for the next event\b/i;
2
4
  const GENERIC_ABORT_PATTERN = /^Request was aborted\.?$/i;
3
5
  const ABORT_DISPLAY_LABEL_PATTERN = /^(?:Operation aborted|Aborted after \d+ retry attempts?)(?::|$)/;
@@ -37,5 +39,5 @@ function normalizeAbortCause(errorMessage: string | undefined): string {
37
39
  function streamIdleTimeoutHint(cause: string): string {
38
40
  if (!STREAM_IDLE_TIMEOUT_PATTERN.test(cause)) return "";
39
41
  const separator = /[.!?]$/.test(cause) ? " " : ". ";
40
- return `${separator}Hint: set PI_STREAM_IDLE_TIMEOUT_MS=300000 for slow reasoning/proxy streams, or PI_STREAM_IDLE_TIMEOUT_MS=0 to disable the watchdog.`;
42
+ return `${separator}Hint: set PI_STREAM_IDLE_TIMEOUT_MS=${getStreamIdleTimeoutRemediationMs()} for slow reasoning/proxy streams, or PI_STREAM_IDLE_TIMEOUT_MS=0 to disable the watchdog.`;
41
43
  }
@@ -0,0 +1,12 @@
1
+ import type { InteractiveModeContext } from "../types";
2
+
3
+ /** Ordinary focused UI owns interrupt/back before background work does. */
4
+ export function focusedUiOwnsInterrupt(
5
+ ctx: Pick<InteractiveModeContext, "ui" | "editor" | "hookSelector" | "hookInput" | "hookEditor">,
6
+ ): boolean {
7
+ const focused = ctx.ui.getFocusedComponent?.();
8
+ if (!focused?.handleInput || focused === ctx.editor) return false;
9
+ // Non-inline hook prompts deliberately retain workflow-level interruption.
10
+ if (focused === ctx.hookSelector) return ctx.hookSelector.hasActiveInlineInput();
11
+ return focused !== ctx.hookInput && focused !== ctx.hookEditor;
12
+ }
@@ -16,6 +16,10 @@ export function matchesAppInterrupt(data: string): boolean {
16
16
  return matchesKey(data, "escape") || matchesKey(data, "esc");
17
17
  }
18
18
 
19
+ export function matchesAppClear(data: string): boolean {
20
+ return getKeybindings().matches(data, "app.clear");
21
+ }
22
+
19
23
  export function matchesSelectCancel(data: string): boolean {
20
24
  return getKeybindings().matches(data, "tui.select.cancel");
21
25
  }
@@ -0,0 +1,80 @@
1
+ import type { KeyId } from "@gajae-code/tui";
2
+
3
+ /**
4
+ * Characters macOS composes for Option+<letter> on the US/ABC layout when the
5
+ * terminal sends Option as text instead of Alt/Meta. Dead-key letters (e, i, n,
6
+ * u) compose nothing on their own and are absent.
7
+ */
8
+ const MACOS_OPTION_COMPOSED_KEYS: ReadonlyMap<string, KeyId> = new Map<string, KeyId>([
9
+ ["å", "alt+a"],
10
+ ["∫", "alt+b"],
11
+ ["ç", "alt+c"],
12
+ ["∂", "alt+d"],
13
+ ["ƒ", "alt+f"],
14
+ ["©", "alt+g"],
15
+ ["˙", "alt+h"],
16
+ ["∆", "alt+j"],
17
+ ["˚", "alt+k"],
18
+ ["¬", "alt+l"],
19
+ ["µ", "alt+m"],
20
+ ["ø", "alt+o"],
21
+ ["π", "alt+p"],
22
+ ["œ", "alt+q"],
23
+ ["®", "alt+r"],
24
+ ["ß", "alt+s"],
25
+ ["†", "alt+t"],
26
+ ["√", "alt+v"],
27
+ ["∑", "alt+w"],
28
+ ["≈", "alt+x"],
29
+ ["¥", "alt+y"],
30
+ ["Ω", "alt+z"],
31
+ ]);
32
+
33
+ /** The Option chord that composes `text` on the macOS US/ABC layout, if any. */
34
+ export function macosOptionChordForComposedText(text: string): KeyId | undefined {
35
+ return MACOS_OPTION_COMPOSED_KEYS.get(text);
36
+ }
37
+
38
+ /** Terminal-specific instruction for sending Option as Alt/Meta, when the terminal is known. */
39
+ export function macosOptionForwardingInstruction(terminalProgram: string | undefined): string | undefined {
40
+ const program = terminalProgram?.toLowerCase() ?? "";
41
+ if (program.includes("ghostty")) {
42
+ return "Ghostty: set macos-option-as-alt = true in its config, then reload the config or restart Ghostty.";
43
+ }
44
+ if (program === "apple_terminal") {
45
+ return "Terminal.app: enable Settings > Profiles > Keyboard > Use Option as Meta key.";
46
+ }
47
+ if (program === "iterm.app") {
48
+ return "iTerm2: set Settings > Profiles > Keys > Left and Right Option key to Esc+.";
49
+ }
50
+ return undefined;
51
+ }
52
+
53
+ export interface UnforwardedOptionChordWarnerOptions {
54
+ platform: NodeJS.Platform;
55
+ terminalProgram: string | undefined;
56
+ formatKeyHint: (key: KeyId) => string;
57
+ showWarning: (message: string) => void;
58
+ }
59
+
60
+ /**
61
+ * Builds the composer callback that explains, once, why a bound Option shortcut
62
+ * produced a character instead of firing. Returns undefined off macOS, where the
63
+ * composition table does not apply.
64
+ */
65
+ export function createUnforwardedOptionChordWarner(
66
+ options: UnforwardedOptionChordWarnerOptions,
67
+ ): ((key: KeyId, text: string) => void) | undefined {
68
+ if (options.platform !== "darwin") return undefined;
69
+ let warned = false;
70
+ return (key, text) => {
71
+ if (warned) return;
72
+ warned = true;
73
+ const instruction =
74
+ macosOptionForwardingInstruction(options.terminalProgram) ??
75
+ "Configure the terminal to send Option as Alt/Meta (Esc+).";
76
+ options.showWarning(
77
+ `${options.formatKeyHint(key)} arrived as "${text}": the terminal is sending Option as text, so Option shortcuts cannot reach GJC. ${instruction} Or remap the shortcut to a Control chord in keybindings.json.`,
78
+ );
79
+ };
80
+ }