@gajae-code/coding-agent 0.13.3 → 0.14.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 (599) hide show
  1. package/CHANGELOG.md +184 -2
  2. package/README.md +2 -0
  3. package/bin/gajaessi.js +4 -0
  4. package/dist/types/async/job-manager.d.ts +55 -2
  5. package/dist/types/async/support.d.ts +14 -0
  6. package/dist/types/capability/mcp.d.ts +6 -0
  7. package/dist/types/capability/types.d.ts +6 -0
  8. package/dist/types/cli/accounts-cli.d.ts +19 -0
  9. package/dist/types/cli/args.d.ts +3 -0
  10. package/dist/types/cli/config-cli.d.ts +6 -0
  11. package/dist/types/cli/crash-cli.d.ts +10 -0
  12. package/dist/types/cli/customize-doctor.d.ts +145 -0
  13. package/dist/types/cli/daemon-cli.d.ts +3 -1
  14. package/dist/types/cli/mcp-cli.d.ts +11 -0
  15. package/dist/types/cli/notify-cli.d.ts +1 -1
  16. package/dist/types/cli/root-flags.d.ts +12 -0
  17. package/dist/types/cli/setup-cli.d.ts +3 -1
  18. package/dist/types/cli/skills-cli.d.ts +2 -1
  19. package/dist/types/cli/update-cli.d.ts +19 -1
  20. package/dist/types/cli.d.ts +6 -0
  21. package/dist/types/commands/accounts.d.ts +40 -0
  22. package/dist/types/commands/crash.d.ts +24 -0
  23. package/dist/types/commands/customize.d.ts +22 -0
  24. package/dist/types/commands/daemon.d.ts +1 -1
  25. package/dist/types/commands/launch.d.ts +6 -0
  26. package/dist/types/commands/setup.d.ts +6 -0
  27. package/dist/types/commands/skills.d.ts +6 -1
  28. package/dist/types/commands/update.d.ts +7 -0
  29. package/dist/types/config/atomic-yaml-patch-worker.d.ts +25 -0
  30. package/dist/types/config/atomic-yaml-patch.d.ts +62 -2
  31. package/dist/types/config/config-schema-version.d.ts +10 -0
  32. package/dist/types/config/file-lock.d.ts +18 -0
  33. package/dist/types/config/model-profile-activation.d.ts +3 -1
  34. package/dist/types/config/model-registry.d.ts +9 -1
  35. package/dist/types/config/models-config-schema.d.ts +2 -0
  36. package/dist/types/config/settings-schema.d.ts +70 -6
  37. package/dist/types/config/settings.d.ts +63 -0
  38. package/dist/types/config/skill-settings-defaults.d.ts +26 -0
  39. package/dist/types/config/task-delegation.d.ts +39 -0
  40. package/dist/types/config/update-channel.d.ts +10 -0
  41. package/dist/types/coordinator-mcp/server.d.ts +5 -1
  42. package/dist/types/crash/index-store.d.ts +87 -0
  43. package/dist/types/crash/nudge.d.ts +50 -0
  44. package/dist/types/crash/record-loader.d.ts +17 -0
  45. package/dist/types/crash/report.d.ts +134 -0
  46. package/dist/types/crash/sanitize.d.ts +25 -0
  47. package/dist/types/cursor.d.ts +13 -2
  48. package/dist/types/customization/import.d.ts +37 -0
  49. package/dist/types/customization/index.d.ts +4 -0
  50. package/dist/types/customization/inventory.d.ts +25 -0
  51. package/dist/types/customization/mutations.d.ts +32 -0
  52. package/dist/types/customization/types.d.ts +115 -0
  53. package/dist/types/daemon/control-types.d.ts +8 -1
  54. package/dist/types/daemon/operator-contract.d.ts +4 -4
  55. package/dist/types/discovery/claude.d.ts +39 -1
  56. package/dist/types/discovery/codex.d.ts +39 -1
  57. package/dist/types/discovery/index.d.ts +2 -0
  58. package/dist/types/discovery/mcp-compat.d.ts +30 -0
  59. package/dist/types/edit/renderer.d.ts +23 -0
  60. package/dist/types/extensibility/extensions/runner.d.ts +3 -0
  61. package/dist/types/extensibility/extensions/types.d.ts +21 -4
  62. package/dist/types/extensibility/gjc-plugins/observability.d.ts +6 -1
  63. package/dist/types/extensibility/gjc-plugins/registry.d.ts +8 -1
  64. package/dist/types/extensibility/hooks/loader.d.ts +19 -5
  65. package/dist/types/extensibility/runtime-skill-discovery.d.ts +22 -1
  66. package/dist/types/extensibility/skill-management.d.ts +107 -0
  67. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +3 -1
  68. package/dist/types/gjc-runtime/launch-worktree.d.ts +8 -0
  69. package/dist/types/gjc-runtime/linux-proc.d.ts +3 -0
  70. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +10 -6
  71. package/dist/types/gjc-runtime/session-state-lock.d.ts +104 -0
  72. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +120 -2
  73. package/dist/types/gjc-runtime/state-writer.d.ts +10 -1
  74. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +4 -2
  75. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -6
  76. package/dist/types/gjc-runtime/workflow-settings.d.ts +109 -0
  77. package/dist/types/hooks/events.d.ts +60 -0
  78. package/dist/types/hooks/normalize.d.ts +83 -0
  79. package/dist/types/hooks/skill-state.d.ts +9 -2
  80. package/dist/types/main.d.ts +1 -1
  81. package/dist/types/modes/acp/abort-scope.d.ts +13 -0
  82. package/dist/types/modes/acp/acp-agent.d.ts +7 -4
  83. package/dist/types/modes/components/customization/customization-dashboard.d.ts +44 -0
  84. package/dist/types/modes/components/customization/import-wizard.d.ts +21 -0
  85. package/dist/types/modes/components/customization/index.d.ts +1 -0
  86. package/dist/types/modes/components/gajae-pet-widget.d.ts +8 -30
  87. package/dist/types/modes/components/irc-sidebar.d.ts +1 -0
  88. package/dist/types/modes/components/iterm-pet-transport.d.ts +122 -0
  89. package/dist/types/modes/components/oauth-selector.d.ts +10 -4
  90. package/dist/types/modes/components/pet-capability.d.ts +11 -25
  91. package/dist/types/modes/components/status-line/gh.d.ts +6 -2
  92. package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
  93. package/dist/types/modes/controllers/selector-controller.d.ts +7 -8
  94. package/dist/types/modes/controllers/stt-controller.d.ts +1 -1
  95. package/dist/types/modes/interactive-mode.d.ts +1 -0
  96. package/dist/types/modes/types.d.ts +1 -0
  97. package/dist/types/modes/utils/injected-user-submission.d.ts +10 -0
  98. package/dist/types/runtime-mcp/config.d.ts +9 -0
  99. package/dist/types/runtime-mcp/manager.d.ts +26 -1
  100. package/dist/types/runtime-mcp/oauth-discovery.d.ts +4 -0
  101. package/dist/types/runtime-mcp/oauth-flow.d.ts +24 -1
  102. package/dist/types/runtime-mcp/pool-key.d.ts +2 -0
  103. package/dist/types/runtime-mcp/protocol.d.ts +278 -0
  104. package/dist/types/runtime-mcp/scope-config.d.ts +29 -0
  105. package/dist/types/runtime-mcp/tool-bridge.d.ts +15 -13
  106. package/dist/types/runtime-mcp/transports/http.d.ts +9 -0
  107. package/dist/types/runtime-mcp/types.d.ts +102 -10
  108. package/dist/types/sdk/acp/adapter.d.ts +26 -5
  109. package/dist/types/sdk/broker/broker.d.ts +3 -55
  110. package/dist/types/sdk/broker/lifecycle.d.ts +18 -12
  111. package/dist/types/sdk/broker/session-index.d.ts +14 -5
  112. package/dist/types/sdk/broker/startup-failure.d.ts +16 -1
  113. package/dist/types/sdk/bus/chat-daemon-control.d.ts +51 -1
  114. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -41
  115. package/dist/types/sdk/bus/config.d.ts +11 -0
  116. package/dist/types/sdk/bus/conversation-store.d.ts +0 -1
  117. package/dist/types/sdk/bus/daemon-paths.d.ts +6 -0
  118. package/dist/types/sdk/bus/discord-conversation.d.ts +2 -0
  119. package/dist/types/sdk/bus/discord-daemon.d.ts +17 -12
  120. package/dist/types/sdk/bus/existing-thread-readiness.d.ts +4 -34
  121. package/dist/types/sdk/bus/inbound-reaction-ordering.d.ts +61 -0
  122. package/dist/types/sdk/bus/index.d.ts +85 -99
  123. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +29 -7
  124. package/dist/types/sdk/bus/managed-daemon.d.ts +2 -32
  125. package/dist/types/sdk/bus/notification-service.d.ts +52 -0
  126. package/dist/types/sdk/bus/operator-runtime.d.ts +1 -1
  127. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +16 -5
  128. package/dist/types/sdk/bus/reconciliation-store.d.ts +113 -5
  129. package/dist/types/sdk/bus/session-control.d.ts +25 -1
  130. package/dist/types/sdk/bus/slack-conversation.d.ts +4 -0
  131. package/dist/types/sdk/bus/slack-daemon.d.ts +9 -17
  132. package/dist/types/sdk/bus/slack-live-provider.d.ts +3 -1
  133. package/dist/types/sdk/bus/slack-provider.d.ts +4 -0
  134. package/dist/types/sdk/bus/slack-thread-binding.d.ts +6 -40
  135. package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +30 -104
  136. package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +10 -0
  137. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +262 -19
  138. package/dist/types/sdk/bus/telegram-daemon-orphan-reap.d.ts +125 -0
  139. package/dist/types/sdk/bus/telegram-daemon-owner-registry.d.ts +22 -0
  140. package/dist/types/sdk/bus/telegram-daemon.d.ts +119 -310
  141. package/dist/types/sdk/bus/telegram-reference.d.ts +1 -47
  142. package/dist/types/sdk/bus/threaded-render.d.ts +1 -0
  143. package/dist/types/sdk/bus/topic-registry.d.ts +33 -17
  144. package/dist/types/sdk/cli/rows.d.ts +2 -11
  145. package/dist/types/sdk/cli/session-cli.d.ts +15 -25
  146. package/dist/types/sdk/client/client.d.ts +97 -1
  147. package/dist/types/sdk/host/control/dispatch.d.ts +0 -7
  148. package/dist/types/sdk/host/control/index.d.ts +1 -1
  149. package/dist/types/sdk/host/control/operations.d.ts +18 -5
  150. package/dist/types/sdk/host/control/runtime-gate.d.ts +19 -0
  151. package/dist/types/sdk/host/host.d.ts +15 -1
  152. package/dist/types/sdk/host/query/handlers.d.ts +5 -0
  153. package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
  154. package/dist/types/sdk/host/session-runtime.d.ts +127 -7
  155. package/dist/types/sdk/host/types.d.ts +1 -1
  156. package/dist/types/sdk/host/websocket-transport.d.ts +1 -1
  157. package/dist/types/sdk/index.d.ts +2 -1
  158. package/dist/types/sdk/lifecycle/broker-client.d.ts +8 -0
  159. package/dist/types/sdk/lifecycle/client.d.ts +59 -0
  160. package/dist/types/sdk/lifecycle/index.d.ts +3 -0
  161. package/dist/types/sdk/lifecycle/service.d.ts +232 -0
  162. package/dist/types/sdk/mcp/server.d.ts +9 -6
  163. package/dist/types/sdk/prompt-deadline-lease.d.ts +43 -0
  164. package/dist/types/sdk/prompt-deadline-manager.d.ts +28 -0
  165. package/dist/types/sdk/prompt-status.d.ts +10 -3
  166. package/dist/types/sdk/protocol/operation-registry.d.ts +5 -0
  167. package/dist/types/sdk/receipt-state.d.ts +17 -0
  168. package/dist/types/sdk/reconciliation-extensions.d.ts +2 -0
  169. package/dist/types/sdk/router/index.d.ts +1 -0
  170. package/dist/types/sdk/router/session-router.d.ts +152 -0
  171. package/dist/types/sdk/session-list.d.ts +22 -0
  172. package/dist/types/sdk/session-reconnect.d.ts +43 -1
  173. package/dist/types/sdk/session.d.ts +34 -1
  174. package/dist/types/sdk/transport/serve-cli.d.ts +1 -4
  175. package/dist/types/session/account-inventory.d.ts +86 -0
  176. package/dist/types/session/agent-session.d.ts +145 -6
  177. package/dist/types/session/agent-storage.d.ts +10 -0
  178. package/dist/types/session/coordinator-tool-label.d.ts +28 -0
  179. package/dist/types/session/internal/managed-session-storage.d.ts +23 -4
  180. package/dist/types/session/messages.d.ts +1 -1
  181. package/dist/types/session/secure-token-file.d.ts +3 -0
  182. package/dist/types/session/session-manager.d.ts +40 -1
  183. package/dist/types/session/startup-auth-config.d.ts +44 -0
  184. package/dist/types/session/terminal-abort.d.ts +378 -0
  185. package/dist/types/session/yield-queue.d.ts +7 -0
  186. package/dist/types/session-import/claude.d.ts +26 -0
  187. package/dist/types/session-import/codex.d.ts +88 -0
  188. package/dist/types/session-import/command.d.ts +35 -0
  189. package/dist/types/session-import/detect.d.ts +22 -0
  190. package/dist/types/session-import/index.d.ts +7 -0
  191. package/dist/types/session-import/provider-codex.d.ts +22 -0
  192. package/dist/types/session-import/provider-service.d.ts +38 -0
  193. package/dist/types/session-import/redact.d.ts +23 -0
  194. package/dist/types/session-import/service.d.ts +47 -0
  195. package/dist/types/session-import/types.d.ts +120 -0
  196. package/dist/types/setup/paseo/check.d.ts +16 -0
  197. package/dist/types/setup/paseo/install-saga.d.ts +115 -0
  198. package/dist/types/setup/paseo/json-publisher.d.ts +76 -0
  199. package/dist/types/setup/paseo/orchestration-preferences.d.ts +24 -0
  200. package/dist/types/setup/paseo/paseo-ownership.d.ts +87 -0
  201. package/dist/types/setup/paseo/paseo-setup.d.ts +29 -0
  202. package/dist/types/setup/paseo/provider-config.d.ts +35 -0
  203. package/dist/types/setup/paseo/remove.d.ts +14 -0
  204. package/dist/types/setup/paseo/result-types.d.ts +59 -0
  205. package/dist/types/setup/paseo/setup-deps.d.ts +73 -0
  206. package/dist/types/setup/paseo/skills-bridge.d.ts +44 -0
  207. package/dist/types/skill-state/active-state.d.ts +5 -1
  208. package/dist/types/slash-commands/acp-builtins.d.ts +5 -1
  209. package/dist/types/slash-commands/builtin-registry.d.ts +2 -0
  210. package/dist/types/slash-commands/helpers/credential-switch.d.ts +12 -0
  211. package/dist/types/slash-commands/helpers/usage-report.d.ts +5 -6
  212. package/dist/types/slash-commands/types.d.ts +7 -1
  213. package/dist/types/task/executor.d.ts +10 -0
  214. package/dist/types/task/index.d.ts +3 -1
  215. package/dist/types/tools/ask-answer-registry.d.ts +6 -3
  216. package/dist/types/tools/bash-sleep-advisory.d.ts +14 -0
  217. package/dist/types/tools/bash.d.ts +2 -6
  218. package/dist/types/tools/browser/attach.d.ts +11 -0
  219. package/dist/types/tools/browser/launch.d.ts +20 -0
  220. package/dist/types/tools/browser/profile-discovery.d.ts +6 -0
  221. package/dist/types/tools/browser/render.d.ts +1 -0
  222. package/dist/types/tools/browser.d.ts +3 -1
  223. package/dist/types/tools/image-gen.d.ts +9 -2
  224. package/dist/types/tools/index.d.ts +8 -0
  225. package/dist/types/tools/monitor.d.ts +1 -1
  226. package/dist/types/tools/skill-discovery.d.ts +8 -1
  227. package/dist/types/tools/subagent.d.ts +2 -0
  228. package/dist/types/tools/ultragoal-ask-guard.d.ts +2 -2
  229. package/dist/types/utils/herdr-pane.d.ts +76 -0
  230. package/package.json +19 -9
  231. package/scripts/build-binary.ts +2 -1
  232. package/scripts/build-sdk-package-smoke.ts +2 -13
  233. package/scripts/capture-sticky-viewport-showcase.ts +0 -1
  234. package/scripts/compile-args.ts +2 -0
  235. package/scripts/generate-sdk-adapter-parity-manifest.ts +31 -16
  236. package/scripts/generate-sdk-operation-inventory.ts +28 -0
  237. package/scripts/generate-telegram-baseline-manifest.ts +1 -0
  238. package/scripts/generate-tool-catalog.ts +6 -4
  239. package/scripts/verify-gjc-sdk-canonicalization.ts +5 -39
  240. package/src/async/job-manager.ts +352 -17
  241. package/src/async/support.ts +25 -0
  242. package/src/autoresearch/dashboard.ts +28 -13
  243. package/src/capability/index.ts +3 -1
  244. package/src/capability/mcp.ts +6 -0
  245. package/src/capability/types.ts +6 -0
  246. package/src/cli/accounts-cli.ts +395 -0
  247. package/src/cli/args.ts +23 -0
  248. package/src/cli/auth-broker-cli.ts +136 -66
  249. package/src/cli/auth-gateway-cli.ts +94 -70
  250. package/src/cli/config-cli.ts +27 -2
  251. package/src/cli/crash-cli.ts +78 -0
  252. package/src/cli/customize-doctor.ts +1605 -0
  253. package/src/cli/daemon-cli.ts +12 -3
  254. package/src/cli/fast-help.ts +1 -0
  255. package/src/cli/mcp-cli.ts +128 -60
  256. package/src/cli/notify-cli.ts +8 -14
  257. package/src/cli/root-flags.ts +8 -0
  258. package/src/cli/setup-cli.ts +145 -0
  259. package/src/cli/skills-cli.ts +38 -2
  260. package/src/cli/update-cli.ts +167 -12
  261. package/src/cli.ts +4 -0
  262. package/src/commands/accounts.ts +71 -0
  263. package/src/commands/crash.ts +35 -0
  264. package/src/commands/customize.ts +84 -0
  265. package/src/commands/daemon.ts +2 -2
  266. package/src/commands/deep-interview.ts +2 -0
  267. package/src/commands/launch.ts +1 -0
  268. package/src/commands/mcp.ts +12 -7
  269. package/src/commands/ralplan.ts +2 -0
  270. package/src/commands/sdk.ts +65 -32
  271. package/src/commands/setup.ts +5 -0
  272. package/src/commands/skills.ts +14 -4
  273. package/src/commands/ultragoal.ts +10 -1
  274. package/src/commands/update.ts +26 -15
  275. package/src/config/atomic-yaml-patch-worker.ts +57 -0
  276. package/src/config/atomic-yaml-patch.ts +769 -29
  277. package/src/config/config-schema-version.ts +10 -0
  278. package/src/config/file-lock.ts +70 -1
  279. package/src/config/mcp-schema.json +52 -14
  280. package/src/config/model-profile-activation.ts +20 -1
  281. package/src/config/model-profiles.ts +29 -19
  282. package/src/config/model-registry.ts +132 -21
  283. package/src/config/models-config-schema.ts +1 -1
  284. package/src/config/settings-schema.ts +76 -6
  285. package/src/config/settings.ts +4038 -199
  286. package/src/config/skill-settings-defaults.ts +41 -3
  287. package/src/config/task-delegation.ts +87 -0
  288. package/src/config/update-channel.ts +23 -0
  289. package/src/coordinator-mcp/server.ts +377 -308
  290. package/src/crash/index-store.ts +723 -0
  291. package/src/crash/nudge.ts +89 -0
  292. package/src/crash/record-loader.ts +149 -0
  293. package/src/crash/report.ts +551 -0
  294. package/src/crash/sanitize.ts +129 -0
  295. package/src/cursor.ts +36 -10
  296. package/src/customization/import.ts +946 -0
  297. package/src/customization/index.ts +4 -0
  298. package/src/customization/inventory.ts +335 -0
  299. package/src/customization/mutations.ts +141 -0
  300. package/src/customization/types.ts +223 -0
  301. package/src/daemon/control-types.ts +8 -1
  302. package/src/daemon/operator-contract.ts +6 -6
  303. package/src/defaults/gjc/skills/deep-interview/SKILL.md +11 -22
  304. package/src/defaults/gjc/skills/ralplan/SKILL.md +36 -23
  305. package/src/defaults/gjc/skills/ultragoal/SKILL.md +16 -0
  306. package/src/discovery/claude-hooks.ts +38 -0
  307. package/src/discovery/claude.ts +142 -209
  308. package/src/discovery/codex-hooks.ts +38 -0
  309. package/src/discovery/codex.ts +132 -198
  310. package/src/discovery/helpers.ts +8 -1
  311. package/src/discovery/index.ts +2 -0
  312. package/src/discovery/mcp-compat.ts +234 -0
  313. package/src/discovery/mcp-json.ts +6 -0
  314. package/src/edit/renderer.ts +40 -0
  315. package/src/edit/streaming.ts +4 -2
  316. package/src/exec/bash-executor.ts +24 -20
  317. package/src/export/html/index.ts +51 -2
  318. package/src/extensibility/extensions/loader.ts +9 -1
  319. package/src/extensibility/extensions/runner.ts +17 -4
  320. package/src/extensibility/extensions/types.ts +31 -6
  321. package/src/extensibility/gjc-plugins/constrained-hooks.ts +33 -0
  322. package/src/extensibility/gjc-plugins/installer.ts +9 -5
  323. package/src/extensibility/gjc-plugins/observability.ts +8 -2
  324. package/src/extensibility/gjc-plugins/registry.ts +13 -2
  325. package/src/extensibility/gjc-plugins/runtime-adapters.ts +3 -1
  326. package/src/extensibility/gjc-plugins/schema.ts +222 -39
  327. package/src/extensibility/hooks/loader.ts +154 -10
  328. package/src/extensibility/runtime-skill-discovery.ts +257 -60
  329. package/src/extensibility/skill-management.ts +368 -0
  330. package/src/extensibility/skills.ts +52 -16
  331. package/src/gjc-runtime/deep-interview-runtime.ts +45 -61
  332. package/src/gjc-runtime/launch-worktree.ts +56 -2
  333. package/src/gjc-runtime/linux-proc.ts +7 -1
  334. package/src/gjc-runtime/ralplan-runtime.ts +100 -164
  335. package/src/gjc-runtime/session-state-lock.ts +992 -0
  336. package/src/gjc-runtime/session-state-sidecar.ts +644 -142
  337. package/src/gjc-runtime/state-writer.ts +39 -14
  338. package/src/gjc-runtime/tmux-owner-isolation.ts +5 -4
  339. package/src/gjc-runtime/ultragoal-guard.ts +11 -4
  340. package/src/gjc-runtime/ultragoal-runtime.ts +44 -41
  341. package/src/gjc-runtime/workflow-settings.ts +596 -0
  342. package/src/goals/tools/goal-tool.ts +6 -1
  343. package/src/hooks/events.ts +291 -0
  344. package/src/hooks/native-skill-hook.ts +4 -0
  345. package/src/hooks/normalize.ts +369 -0
  346. package/src/hooks/skill-state.ts +165 -46
  347. package/src/internal-urls/docs-index.generated.ts +40 -32
  348. package/src/main.ts +32 -5
  349. package/src/migrate/mcp-mapper.ts +25 -2
  350. package/src/modes/acp/abort-scope.ts +29 -0
  351. package/src/modes/acp/acp-agent.ts +423 -98
  352. package/src/modes/components/assistant-message.ts +6 -1
  353. package/src/modes/components/customization/customization-dashboard.ts +443 -0
  354. package/src/modes/components/customization/import-wizard.ts +372 -0
  355. package/src/modes/components/customization/index.ts +1 -0
  356. package/src/modes/components/gajae-pet-widget.ts +539 -213
  357. package/src/modes/components/irc-sidebar.ts +11 -0
  358. package/src/modes/components/iterm-pet-transport.ts +585 -0
  359. package/src/modes/components/oauth-selector.ts +277 -86
  360. package/src/modes/components/pet-capability.ts +39 -40
  361. package/src/modes/components/settings-selector.ts +9 -0
  362. package/src/modes/components/status-line/gh.ts +45 -1
  363. package/src/modes/components/status-line/segments.ts +16 -1
  364. package/src/modes/components/tool-status-header.ts +13 -3
  365. package/src/modes/controllers/command-controller.ts +20 -8
  366. package/src/modes/controllers/extension-ui-controller.ts +56 -257
  367. package/src/modes/controllers/input-controller.ts +10 -0
  368. package/src/modes/controllers/runtime-mcp-command-controller.ts +16 -3
  369. package/src/modes/controllers/selector-controller.ts +177 -72
  370. package/src/modes/controllers/stt-controller.ts +3 -3
  371. package/src/modes/interactive-mode.ts +88 -16
  372. package/src/modes/print-mode.ts +35 -6
  373. package/src/modes/runtime-init.ts +21 -68
  374. package/src/modes/types.ts +1 -0
  375. package/src/modes/utils/injected-user-submission.ts +25 -0
  376. package/src/modes/utils/terminal-bell.ts +15 -1
  377. package/src/prompts/agent-fragments/ralplan-persistence.md +1 -1
  378. package/src/prompts/agent-fragments/restricted-bash.md +1 -1
  379. package/src/prompts/agents/critic.md +1 -1
  380. package/src/prompts/system/rlm-research.md +1 -1
  381. package/src/prompts/system/system-prompt.md +0 -2
  382. package/src/prompts/tools/bash.md +7 -1
  383. package/src/prompts/tools/browser.md +1 -1
  384. package/src/prompts/tools/cron.md +6 -6
  385. package/src/prompts/tools/read.md +0 -2
  386. package/src/prompts/tools/skill-discovery.md +4 -2
  387. package/src/runtime-mcp/client.ts +427 -23
  388. package/src/runtime-mcp/config.ts +37 -5
  389. package/src/runtime-mcp/manager-pool.test.ts +56 -0
  390. package/src/runtime-mcp/manager.ts +52 -2
  391. package/src/runtime-mcp/oauth-discovery.ts +13 -0
  392. package/src/runtime-mcp/oauth-flow.ts +46 -1
  393. package/src/runtime-mcp/pool-key.ts +5 -1
  394. package/src/runtime-mcp/pool.test.ts +9 -0
  395. package/src/runtime-mcp/protocol.test.ts +353 -0
  396. package/src/runtime-mcp/protocol.ts +624 -0
  397. package/src/runtime-mcp/scope-config.ts +78 -0
  398. package/src/runtime-mcp/tool-bridge.ts +44 -10
  399. package/src/runtime-mcp/transports/http.ts +84 -13
  400. package/src/runtime-mcp/types.ts +123 -12
  401. package/src/sdk/acp/adapter.ts +328 -97
  402. package/src/sdk/broker/broker.ts +160 -741
  403. package/src/sdk/broker/ensure.ts +9 -1
  404. package/src/sdk/broker/lifecycle.ts +510 -116
  405. package/src/sdk/broker/operation-receipt-ledger.ts +11 -2
  406. package/src/sdk/broker/process-incarnation.ts +19 -0
  407. package/src/sdk/broker/session-index.ts +516 -117
  408. package/src/sdk/broker/startup-failure.ts +28 -5
  409. package/src/sdk/broker/transport.ts +1 -13
  410. package/src/sdk/bus/chat-daemon-cli.ts +4 -2
  411. package/src/sdk/bus/chat-daemon-control.ts +67 -6
  412. package/src/sdk/bus/chat-daemon-runtime.ts +183 -859
  413. package/src/sdk/bus/config.ts +13 -0
  414. package/src/sdk/bus/conversation-store.ts +59 -171
  415. package/src/sdk/bus/daemon-paths.ts +20 -0
  416. package/src/sdk/bus/discord-conversation.ts +2 -0
  417. package/src/sdk/bus/discord-daemon.ts +524 -126
  418. package/src/sdk/bus/discord-live-provider.ts +206 -79
  419. package/src/sdk/bus/existing-thread-readiness.ts +4 -97
  420. package/src/sdk/bus/inbound-reaction-ordering.ts +122 -0
  421. package/src/sdk/bus/index.ts +2480 -456
  422. package/src/sdk/bus/kind-aware-reconciliation.ts +204 -34
  423. package/src/sdk/bus/lifecycle-commands.ts +2 -2
  424. package/src/sdk/bus/managed-daemon.ts +2 -145
  425. package/src/sdk/bus/notification-service.ts +196 -0
  426. package/src/sdk/bus/operator-runtime.ts +1 -1
  427. package/src/sdk/bus/prompt-reconciliation.ts +24 -15
  428. package/src/sdk/bus/reconciliation-store.ts +553 -24
  429. package/src/sdk/bus/session-control.ts +49 -3
  430. package/src/sdk/bus/slack-conversation.ts +4 -0
  431. package/src/sdk/bus/slack-daemon.ts +733 -134
  432. package/src/sdk/bus/slack-live-provider.ts +65 -15
  433. package/src/sdk/bus/slack-provider.ts +4 -0
  434. package/src/sdk/bus/slack-thread-binding.ts +9 -104
  435. package/src/sdk/bus/telegram-adoption-intent.ts +299 -209
  436. package/src/sdk/bus/telegram-daemon-cli.ts +194 -7
  437. package/src/sdk/bus/telegram-daemon-contract.ts +265 -19
  438. package/src/sdk/bus/telegram-daemon-control.ts +3 -13
  439. package/src/sdk/bus/telegram-daemon-orphan-reap.ts +498 -0
  440. package/src/sdk/bus/telegram-daemon-owner-registry.ts +176 -0
  441. package/src/sdk/bus/telegram-daemon.ts +2552 -2280
  442. package/src/sdk/bus/telegram-reference.ts +1 -226
  443. package/src/sdk/bus/telegram-setup.ts +2 -2
  444. package/src/sdk/bus/threaded-render.ts +1 -0
  445. package/src/sdk/bus/topic-registry.ts +279 -146
  446. package/src/sdk/cli/rows.ts +2 -35
  447. package/src/sdk/cli/session-cli.ts +776 -700
  448. package/src/sdk/client/client.ts +892 -1
  449. package/src/sdk/host/control/dispatch.ts +93 -58
  450. package/src/sdk/host/control/index.ts +1 -1
  451. package/src/sdk/host/control/operations.ts +18 -10
  452. package/src/sdk/host/control/runtime-gate.ts +40 -0
  453. package/src/sdk/host/host.ts +63 -12
  454. package/src/sdk/host/query/handlers.ts +66 -7
  455. package/src/sdk/host/reverse-leases.ts +2 -2
  456. package/src/sdk/host/sdk-surface-parity.test.ts +1 -1
  457. package/src/sdk/host/session-runtime.test.ts +2787 -34
  458. package/src/sdk/host/session-runtime.ts +1489 -101
  459. package/src/sdk/host/surface-policy.ts +12 -8
  460. package/src/sdk/host/types.ts +1 -1
  461. package/src/sdk/host/websocket-transport.lifecycle.test.ts +31 -59
  462. package/src/sdk/host/websocket-transport.ts +85 -50
  463. package/src/sdk/index.ts +2 -1
  464. package/src/sdk/lifecycle/broker-client.ts +53 -0
  465. package/src/sdk/lifecycle/client.ts +234 -0
  466. package/src/sdk/lifecycle/index.ts +3 -0
  467. package/src/sdk/{bus/recent-activity.ts → lifecycle/recent-sessions.ts} +5 -2
  468. package/src/sdk/lifecycle/service.ts +720 -0
  469. package/src/sdk/mcp/server.ts +226 -188
  470. package/src/sdk/prompt-deadline-lease.ts +55 -0
  471. package/src/sdk/prompt-deadline-manager.ts +148 -0
  472. package/src/sdk/prompt-status.ts +11 -3
  473. package/src/sdk/protocol/operation-inventory.generated.json +272 -129
  474. package/src/sdk/protocol/operation-registry.ts +71 -8
  475. package/src/sdk/receipt-state.ts +43 -0
  476. package/src/sdk/reconciliation-extensions.ts +14 -0
  477. package/src/sdk/router/index.ts +1 -0
  478. package/src/sdk/router/session-router.ts +1658 -0
  479. package/src/sdk/session-list.ts +93 -0
  480. package/src/sdk/session-reconnect.ts +45 -1
  481. package/src/sdk/session.ts +716 -120
  482. package/src/sdk/transport/serve-cli.ts +18 -34
  483. package/src/session/account-inventory.ts +603 -0
  484. package/src/session/agent-session.ts +2857 -582
  485. package/src/session/agent-storage.ts +35 -0
  486. package/src/session/blob-store.ts +32 -1
  487. package/src/session/coordinator-tool-label.ts +40 -0
  488. package/src/session/internal/managed-session-scope.ts +4 -0
  489. package/src/session/internal/managed-session-storage.ts +173 -12
  490. package/src/session/messages.ts +1 -1
  491. package/src/session/secure-token-file.ts +80 -0
  492. package/src/session/session-manager.ts +730 -58
  493. package/src/session/session-storage.ts +25 -16
  494. package/src/session/startup-auth-config.ts +297 -0
  495. package/src/session/terminal-abort.ts +1253 -0
  496. package/src/session/yield-queue.ts +57 -15
  497. package/src/session-import/claude.ts +334 -0
  498. package/src/session-import/codex.ts +1033 -0
  499. package/src/session-import/command.ts +77 -0
  500. package/src/session-import/detect.ts +133 -0
  501. package/src/session-import/index.ts +7 -0
  502. package/src/session-import/provider-codex.ts +327 -0
  503. package/src/session-import/provider-service.ts +457 -0
  504. package/src/session-import/redact.ts +128 -0
  505. package/src/session-import/service.ts +1264 -0
  506. package/src/session-import/types.ts +148 -0
  507. package/src/setup/paseo/check.ts +213 -0
  508. package/src/setup/paseo/install-saga.ts +293 -0
  509. package/src/setup/paseo/json-publisher.ts +263 -0
  510. package/src/setup/paseo/orchestration-preferences.ts +86 -0
  511. package/src/setup/paseo/paseo-ownership.ts +224 -0
  512. package/src/setup/paseo/paseo-setup.ts +253 -0
  513. package/src/setup/paseo/provider-config.ts +139 -0
  514. package/src/setup/paseo/remove.ts +183 -0
  515. package/src/setup/paseo/result-types.ts +79 -0
  516. package/src/setup/paseo/setup-deps.ts +175 -0
  517. package/src/setup/paseo/skills-bridge.ts +260 -0
  518. package/src/skill-state/active-state.ts +21 -11
  519. package/src/slash-commands/acp-builtins.ts +9 -1
  520. package/src/slash-commands/builtin-registry.ts +98 -16
  521. package/src/slash-commands/helpers/credential-switch.ts +143 -0
  522. package/src/slash-commands/helpers/mcp.ts +9 -3
  523. package/src/slash-commands/helpers/usage-report.ts +61 -56
  524. package/src/slash-commands/types.ts +7 -2
  525. package/src/task/executor.ts +30 -11
  526. package/src/task/index.ts +66 -7
  527. package/src/task/render.ts +23 -9
  528. package/src/tools/ask-answer-registry.ts +23 -7
  529. package/src/tools/ask.ts +8 -4
  530. package/src/tools/bash-sleep-advisory.ts +115 -0
  531. package/src/tools/bash.ts +98 -18
  532. package/src/tools/browser/attach.ts +71 -3
  533. package/src/tools/browser/launch.ts +86 -13
  534. package/src/tools/browser/profile-discovery.ts +57 -7
  535. package/src/tools/browser/render.ts +8 -1
  536. package/src/tools/browser.ts +130 -21
  537. package/src/tools/computer.enforcement.test.ts +1 -1
  538. package/src/tools/computer.ts +3 -2
  539. package/src/tools/cron.ts +12 -1
  540. package/src/tools/image-gen.ts +27 -16
  541. package/src/tools/index.ts +8 -0
  542. package/src/tools/job.ts +33 -6
  543. package/src/tools/monitor.ts +41 -2
  544. package/src/tools/skill-discovery.ts +21 -29
  545. package/src/tools/subagent.ts +142 -24
  546. package/src/tools/tool-catalog.generated.ts +6 -6
  547. package/src/tools/ultragoal-ask-guard.ts +10 -3
  548. package/src/utils/herdr-pane.ts +345 -0
  549. package/src/utils/title-generator.ts +2 -0
  550. package/src/web/scrapers/index.ts +6 -4
  551. package/dist/types/cli/malloc-env-guard.test.d.ts +0 -1
  552. package/dist/types/defaults/gjc-defaults.test.d.ts +0 -1
  553. package/dist/types/memory-backend/lazy-loading.test.d.ts +0 -1
  554. package/dist/types/runtime/boot-ordering.test.d.ts +0 -1
  555. package/dist/types/runtime/lazy-service.test.d.ts +0 -1
  556. package/dist/types/runtime/network-prewarm-service.test.d.ts +0 -1
  557. package/dist/types/runtime/workspace-tree-service.test.d.ts +0 -1
  558. package/dist/types/runtime-mcp/config.test.d.ts +0 -1
  559. package/dist/types/runtime-mcp/http.test.d.ts +0 -1
  560. package/dist/types/runtime-mcp/manager-pool.test.d.ts +0 -1
  561. package/dist/types/runtime-mcp/pool-key.test.d.ts +0 -1
  562. package/dist/types/runtime-mcp/pool.test.d.ts +0 -1
  563. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +0 -117
  564. package/dist/types/sdk/bus/lifecycle-orchestrator.d.ts +0 -141
  565. package/dist/types/sdk/bus/telegram-cli.d.ts +0 -31
  566. package/dist/types/sdk/elevation/allowlist.d.ts +0 -20
  567. package/dist/types/sdk/elevation/audit.d.ts +0 -93
  568. package/dist/types/sdk/elevation/capability.d.ts +0 -3
  569. package/dist/types/sdk/elevation/digest.d.ts +0 -72
  570. package/dist/types/sdk/elevation/dispatch-receipt.d.ts +0 -85
  571. package/dist/types/sdk/elevation/grant-ledger.d.ts +0 -163
  572. package/dist/types/sdk/elevation/owner.d.ts +0 -48
  573. package/dist/types/sdk/host/import-graph.test.d.ts +0 -1
  574. package/dist/types/sdk/host/sdk-surface-parity.test.d.ts +0 -1
  575. package/dist/types/sdk/host/session-runtime.test.d.ts +0 -1
  576. package/dist/types/sdk/host/websocket-transport.lifecycle.test.d.ts +0 -1
  577. package/dist/types/session/auth-broker-config.d.ts +0 -13
  578. package/dist/types/session/heap-eviction-artifacts.test.d.ts +0 -1
  579. package/dist/types/skills/skills.test.d.ts +0 -1
  580. package/dist/types/slash-commands/notify-command.test.d.ts +0 -1
  581. package/dist/types/task/token-log.test.d.ts +0 -1
  582. package/dist/types/tools/computer.enforcement.test.d.ts +0 -1
  583. package/dist/types/tools/descriptors.test.d.ts +0 -1
  584. package/scripts/g005-daemon-smoke.ts +0 -180
  585. package/scripts/g011-daemon-path-smoke.ts +0 -153
  586. package/src/sdk/bus/lifecycle-control-runtime.ts +0 -1733
  587. package/src/sdk/bus/lifecycle-orchestrator.ts +0 -489
  588. package/src/sdk/bus/telegram-cli.ts +0 -212
  589. package/src/sdk/elevation/allowlist.ts +0 -54
  590. package/src/sdk/elevation/audit.ts +0 -208
  591. package/src/sdk/elevation/capability.ts +0 -41
  592. package/src/sdk/elevation/digest.ts +0 -129
  593. package/src/sdk/elevation/dispatch-receipt.ts +0 -184
  594. package/src/sdk/elevation/grant-ledger.ts +0 -1142
  595. package/src/sdk/elevation/owner.ts +0 -109
  596. package/src/session/auth-broker-config.ts +0 -109
  597. /package/dist/types/{ai-core-import-gate.test.d.ts → discovery/claude-hooks.d.ts} +0 -0
  598. /package/dist/types/{cli/fixture-report.test.d.ts → discovery/codex-hooks.d.ts} +0 -0
  599. /package/dist/types/sdk/{bus/recent-activity.d.ts → lifecycle/recent-sessions.d.ts} +0 -0
@@ -1,3 +1,5 @@
1
+ import type { AuthCredentialSelector, CredentialInventoryRecord, CredentialRemovalTarget } from "@gajae-code/ai/core";
2
+ import { getEnvApiKey } from "@gajae-code/ai/core";
1
3
  import { getOAuthProviders } from "@gajae-code/ai/utils/oauth";
2
4
  import type { OAuthProviderInfo } from "@gajae-code/ai/utils/oauth/types";
3
5
  import { Container, fuzzyFilter, Input, matchesKey, Spacer, TruncatedText } from "@gajae-code/tui";
@@ -10,16 +12,44 @@ import type { ImportableCredential } from "../../setup/credential-import";
10
12
  import { DynamicBorder } from "./dynamic-border";
11
13
 
12
14
  const OAUTH_SELECTOR_MAX_VISIBLE = 10;
13
- /**
14
- * Component that renders an OAuth provider selector.
15
- */
15
+ const SAFE_LABEL_MAX_LENGTH = 160;
16
+
17
+ export interface OAuthSelectorAccountOptions {
18
+ accountProviderId?: string;
19
+ onAccountSelect?: (selector: AuthCredentialSelector) => void | Promise<void>;
20
+ onAutoSelect?: () => void | Promise<void>;
21
+ onAddAccount?: () => void | Promise<void>;
22
+ onAccountRemove?: (targets: readonly CredentialRemovalTarget[]) => void | Promise<void>;
23
+ }
24
+
25
+ type AccountEntry =
26
+ | { kind: "account"; row: CredentialInventoryRecord; target?: CredentialRemovalTarget; selectable: boolean }
27
+ | { kind: "auto"; selectable: true }
28
+ | { kind: "add"; selectable: boolean }
29
+ | { kind: "all"; selectable: boolean };
30
+
31
+ function safeLabel(value: unknown, fallback: string): string {
32
+ if (typeof value !== "string") return fallback;
33
+ const normalized = value
34
+ .replace(/bearer\s+[^\s,;]+/gi, "Bearer [redacted]")
35
+ .replace(/(api[_-]?key|token|secret|authorization)[=:]\s*[^\s,;]+/gi, "$1=[redacted]")
36
+ .replace(/[\u0000-\u001f\u007f]/g, " ")
37
+ .replace(/\s+/g, " ")
38
+ .trim()
39
+ .slice(0, SAFE_LABEL_MAX_LENGTH);
40
+ return normalized || fallback;
41
+ }
42
+
43
+ /** Component that renders an OAuth provider selector or a provider-scoped account selector. */
16
44
  export class OAuthSelectorComponent extends Container {
17
45
  #listContainer: Container;
18
46
  #allProviders: OAuthProviderInfo[] = [];
19
47
  #sortedProviders: OAuthProviderInfo[] = [];
20
48
  #filteredProviders: OAuthProviderInfo[] = [];
49
+ #accountEntries: AccountEntry[] = [];
50
+ #filteredAccountEntries: AccountEntry[] = [];
21
51
  #searchInput: Input;
22
- #selectedIndex: number = 0;
52
+ #selectedIndex = 0;
23
53
  #mode: "login" | "logout";
24
54
  #authStorage: AuthStorage;
25
55
  #onSelectCallback: (providerId: string) => void;
@@ -29,9 +59,16 @@ export class OAuthSelectorComponent extends Container {
29
59
  #requestRenderCallback?: () => void;
30
60
  #authState: Map<string, "checking" | "valid" | "invalid"> = new Map();
31
61
  #externalCredentialCandidates: ImportableCredential[] = [];
32
- #spinnerFrame: number = 0;
62
+ #spinnerFrame = 0;
33
63
  #spinnerInterval?: NodeJS.Timeout;
34
- #validationGeneration: number = 0;
64
+ #validationGeneration = 0;
65
+ #accountProviderId?: string;
66
+ #onAccountSelect?: (selector: AuthCredentialSelector) => void | Promise<void>;
67
+ #onAutoSelect?: () => void | Promise<void>;
68
+ #onAddAccount?: () => void | Promise<void>;
69
+ #onAccountRemove?: (targets: readonly CredentialRemovalTarget[]) => void | Promise<void>;
70
+ #pendingAccountRemovalTargets?: readonly CredentialRemovalTarget[];
71
+
35
72
  constructor(
36
73
  mode: "login" | "logout",
37
74
  authStorage: AuthStorage,
@@ -41,7 +78,7 @@ export class OAuthSelectorComponent extends Container {
41
78
  validateAuth?: (providerId: string) => Promise<boolean>;
42
79
  requestRender?: () => void;
43
80
  externalCredentialCandidates?: ImportableCredential[];
44
- },
81
+ } & Partial<OAuthSelectorAccountOptions>,
45
82
  ) {
46
83
  super();
47
84
  this.#mode = mode;
@@ -51,29 +88,32 @@ export class OAuthSelectorComponent extends Container {
51
88
  this.#validateAuthCallback = options?.validateAuth;
52
89
  this.#requestRenderCallback = options?.requestRender;
53
90
  this.#externalCredentialCandidates = options?.externalCredentialCandidates ?? [];
54
- // Load all OAuth providers
55
- this.#loadProviders();
91
+ this.#accountProviderId = options?.accountProviderId;
92
+ this.#onAccountSelect = options?.onAccountSelect;
93
+ this.#onAutoSelect = options?.onAutoSelect;
94
+ this.#onAddAccount = options?.onAddAccount;
95
+ this.#onAccountRemove = options?.onAccountRemove;
96
+
97
+ if (this.#accountProviderId) this.#loadAccountEntries();
98
+ else this.#loadProviders();
99
+
56
100
  this.addChild(new DynamicBorder());
57
101
  this.addChild(new Spacer(1));
58
- // Add title
59
- const title = mode === "login" ? "Select provider to login:" : "Select provider to logout:";
102
+ const title = this.#accountProviderId
103
+ ? `${mode === "login" ? "Select account to login for" : "Select account to logout from"} ${safeLabel(this.#accountProviderId, "provider")}:`
104
+ : mode === "login"
105
+ ? "Select provider to login:"
106
+ : "Select provider to logout:";
60
107
  this.addChild(new TruncatedText(theme.bold(title)));
61
108
  this.addChild(new Spacer(1));
62
- // Filter box: the provider list is long enough that arrow-key-only
63
- // navigation buries entries below the visible window.
64
109
  this.#searchInput = new Input();
65
- this.#searchInput.onSubmit = () => {
66
- this.#selectCurrentProvider();
67
- };
110
+ this.#searchInput.onSubmit = () => this.#selectCurrent();
68
111
  this.addChild(this.#searchInput);
69
112
  this.addChild(new Spacer(1));
70
- // Create list container
71
113
  this.#listContainer = new Container();
72
114
  this.addChild(this.#listContainer);
73
115
  this.addChild(new Spacer(1));
74
- // Add bottom border
75
116
  this.addChild(new DynamicBorder());
76
- // Initial render
77
117
  this.#updateList();
78
118
  this.#startValidation();
79
119
  }
@@ -87,12 +127,47 @@ export class OAuthSelectorComponent extends Container {
87
127
  this.stopValidation();
88
128
  super.dispose();
89
129
  }
130
+
90
131
  #loadProviders(): void {
91
132
  this.#allProviders = getOAuthProviders();
92
133
  }
93
134
 
135
+ #loadAccountEntries(): void {
136
+ const provider = this.#accountProviderId;
137
+ if (!provider) return;
138
+ const inventory = this.#authStorage
139
+ .listCredentialInventory(provider)
140
+ .filter(row => row.provider === provider)
141
+ .sort((left, right) => left.id - right.id);
142
+ const targets = new Map(
143
+ this.#authStorage.listCredentialRemovalTargets(provider).map(target => [target.id, target]),
144
+ );
145
+ const apiKeyOverrideActive =
146
+ this.#authStorage.hasRuntimeApiKey(provider) ||
147
+ this.#authStorage.hasConfigApiKey(provider) ||
148
+ Boolean(getEnvApiKey(provider));
149
+ const rows = inventory.filter(row => this.#mode === "login" || row.credentialKind === "oauth");
150
+ this.#accountEntries = rows.map(row => ({
151
+ kind: "account" as const,
152
+ row,
153
+ target: targets.get(row.id),
154
+ selectable:
155
+ row.credentialKind === "oauth" &&
156
+ !apiKeyOverrideActive &&
157
+ (this.#mode === "login" ? !row.disabled : targets.has(row.id)),
158
+ }));
159
+ if (this.#mode === "login") {
160
+ this.#accountEntries.push(
161
+ { kind: "auto", selectable: true },
162
+ { kind: "add", selectable: this.#onAddAccount !== undefined },
163
+ );
164
+ } else if (this.#accountEntries.some(entry => entry.kind === "account" && entry.selectable)) {
165
+ this.#accountEntries.push({ kind: "all", selectable: this.#onAccountRemove !== undefined });
166
+ }
167
+ }
168
+
94
169
  #startValidation(): void {
95
- if (!this.#validateAuthCallback) return;
170
+ if (this.#accountProviderId || !this.#validateAuthCallback) return;
96
171
  const generation = this.#validationGeneration + 1;
97
172
  this.#validationGeneration = generation;
98
173
 
@@ -122,17 +197,12 @@ export class OAuthSelectorComponent extends Container {
122
197
  } catch {
123
198
  isValid = false;
124
199
  }
125
-
126
200
  if (generation !== this.#validationGeneration) return;
127
201
  this.#authState.set(providerId, isValid ? "valid" : "invalid");
128
- // Only record the ordering hint when the credentials validated are still the
129
- // current ones; a result from a superseded generation must not describe them.
130
202
  if (authGeneration === this.#authStorage.getGeneration()) {
131
203
  recordProviderAuthHealth(this.#authStorage, providerId, isValid ? "valid" : "invalid");
132
204
  }
133
- if (![...this.#authState.values()].includes("checking")) {
134
- this.#stopSpinner();
135
- }
205
+ if (![...this.#authState.values()].includes("checking")) this.#stopSpinner();
136
206
  this.#updateList();
137
207
  this.#requestRenderCallback?.();
138
208
  }
@@ -141,9 +211,7 @@ export class OAuthSelectorComponent extends Container {
141
211
  if (this.#spinnerInterval) return;
142
212
  this.#spinnerInterval = setInterval(() => {
143
213
  const frameCount = theme.spinnerFrames.length;
144
- if (frameCount > 0) {
145
- this.#spinnerFrame = (this.#spinnerFrame + 1) % frameCount;
146
- }
214
+ if (frameCount > 0) this.#spinnerFrame = (this.#spinnerFrame + 1) % frameCount;
147
215
  this.#updateList();
148
216
  this.#requestRenderCallback?.();
149
217
  }, 80);
@@ -167,15 +235,70 @@ export class OAuthSelectorComponent extends Container {
167
235
  const spinner = frameCount > 0 ? theme.spinnerFrames[this.#spinnerFrame % frameCount] : theme.status.pending;
168
236
  return theme.fg("warning", ` ${spinner} checking`);
169
237
  }
170
- if (state === "invalid") {
171
- return theme.fg("error", ` ${theme.status.error} invalid`);
238
+ if (state === "invalid") return theme.fg("error", ` ${theme.status.error} invalid`);
239
+ if (state === "valid") return theme.fg("success", ` ${theme.status.success} logged in`);
240
+ return this.#authStorage.hasAuth(providerId) ? theme.fg("success", ` ${theme.status.success} logged in`) : "";
241
+ }
242
+
243
+ #accountLabel(entry: AccountEntry): string {
244
+ switch (entry.kind) {
245
+ case "auto":
246
+ return "AUTO (ranked)";
247
+ case "add":
248
+ return "Add new account";
249
+ case "all":
250
+ return "Remove all accounts";
251
+ case "account": {
252
+ const identity =
253
+ entry.row.credentialKind === "oauth"
254
+ ? safeLabel(entry.row.identityLabel, `OAuth account · row ${entry.row.id}`)
255
+ : `API key · row ${entry.row.id}`;
256
+ const status = entry.row.disabled
257
+ ? `disabled${entry.row.disabledCause ? `: ${safeLabel(entry.row.disabledCause, "disabled")}` : ""}`
258
+ : "active";
259
+ return `${identity} (${status})`;
260
+ }
172
261
  }
173
- if (state === "valid") {
174
- return theme.fg("success", ` ${theme.status.success} logged in`);
262
+ }
263
+
264
+ #clearPendingAccountRemoval(): boolean {
265
+ if (this.#pendingAccountRemovalTargets === undefined) return false;
266
+ this.#pendingAccountRemovalTargets = undefined;
267
+ this.#statusMessage = undefined;
268
+ return true;
269
+ }
270
+
271
+ #getAccountRemovalTargets(): CredentialRemovalTarget[] {
272
+ return this.#accountEntries
273
+ .filter(
274
+ (entry): entry is Extract<AccountEntry, { kind: "account" }> =>
275
+ entry.kind === "account" && entry.selectable && entry.target !== undefined,
276
+ )
277
+ .map(entry => entry.target as CredentialRemovalTarget);
278
+ }
279
+
280
+ #confirmOrArmAccountRemoval(targets: readonly CredentialRemovalTarget[], prompt: string): void {
281
+ if (this.#pendingAccountRemovalTargets !== undefined) {
282
+ const pending = this.#pendingAccountRemovalTargets;
283
+ this.#pendingAccountRemovalTargets = undefined;
284
+ this.#statusMessage = undefined;
285
+ void this.#onAccountRemove?.(pending);
286
+ return;
175
287
  }
176
- return this.#authStorage.hasAuth(providerId) ? theme.fg("success", ` ${theme.status.success} logged in`) : "";
288
+ if (targets.length === 0) {
289
+ this.#statusMessage = "No accounts available to remove.";
290
+ } else {
291
+ this.#pendingAccountRemovalTargets = targets;
292
+ this.#statusMessage = `${prompt} Enter to confirm, Esc to cancel`;
293
+ }
294
+ this.#updateList();
177
295
  }
178
296
  #updateList(): void {
297
+ if (this.#accountProviderId) {
298
+ this.#updateAccountList();
299
+ return;
300
+ }
301
+
179
302
  const selectedProviderId = this.#filteredProviders[this.#selectedIndex]?.id;
180
303
  const rankedProviders = this.#allProviders.map(provider => ({
181
304
  provider,
@@ -185,8 +308,6 @@ export class OAuthSelectorComponent extends Container {
185
308
  }));
186
309
  rankedProviders.sort(compareRankedProviders);
187
310
  this.#sortedProviders = rankedProviders.map(({ provider }) => provider);
188
- // Filter after ranking so an empty query preserves the curated order and a
189
- // query still surfaces matches in that same order of preference.
190
311
  this.#filteredProviders = fuzzyFilter(
191
312
  this.#sortedProviders,
192
313
  this.#searchInput.getValue(),
@@ -208,33 +329,25 @@ export class OAuthSelectorComponent extends Container {
208
329
  ? 0
209
330
  : Math.max(0, Math.min(this.#selectedIndex - Math.floor(maxVisible / 2), total - maxVisible));
210
331
  const endIndex = Math.min(startIndex + maxVisible, total);
211
-
212
332
  for (let i = startIndex; i < endIndex; i++) {
213
333
  const provider = this.#filteredProviders[i];
214
334
  if (!provider) continue;
215
335
  const isSelected = i === this.#selectedIndex;
216
336
  const isAvailable = provider.available;
217
337
  const statusIndicator = this.#getStatusIndicator(provider.id);
218
-
219
- let line = "";
220
- if (isSelected) {
221
- const prefix = theme.fg("accent", `${theme.nav.cursor} `);
222
- const text = isAvailable ? theme.fg("accent", provider.name) : theme.fg("dim", provider.name);
223
- line = prefix + text + statusIndicator;
224
- } else {
225
- const text = isAvailable ? ` ${provider.name}` : theme.fg("dim", ` ${provider.name}`);
226
- line = text + statusIndicator;
227
- }
228
- this.#listContainer.addChild(new TruncatedText(line, 0, 0));
338
+ const prefix = isSelected ? theme.fg("accent", `${theme.nav.cursor} `) : " ";
339
+ const text = isAvailable
340
+ ? isSelected
341
+ ? theme.fg("accent", provider.name)
342
+ : provider.name
343
+ : theme.fg("dim", provider.name);
344
+ this.#listContainer.addChild(new TruncatedText(prefix + text + statusIndicator, 0, 0));
229
345
  }
230
-
231
- // Scroll indicator when list is windowed
232
346
  if (startIndex > 0 || endIndex < total) {
233
- const scrollInfo = theme.fg("muted", ` (${this.#selectedIndex + 1}/${total})`);
234
- this.#listContainer.addChild(new TruncatedText(scrollInfo, 0, 0));
347
+ this.#listContainer.addChild(
348
+ new TruncatedText(theme.fg("muted", ` (${this.#selectedIndex + 1}/${total})`), 0, 0),
349
+ );
235
350
  }
236
-
237
- // Show "no providers" if empty
238
351
  if (total === 0) {
239
352
  const message = this.#searchInput.getValue().trim()
240
353
  ? "No providers match the filter"
@@ -245,7 +358,13 @@ export class OAuthSelectorComponent extends Container {
245
358
  }
246
359
  if (this.#statusMessage) {
247
360
  this.#listContainer.addChild(new Spacer(1));
248
- this.#listContainer.addChild(new TruncatedText(theme.fg("warning", ` ${this.#statusMessage}`), 0, 0));
361
+ this.#listContainer.addChild(
362
+ new TruncatedText(
363
+ theme.fg("warning", ` ${safeLabel(this.#statusMessage, "Unable to select provider")}`),
364
+ 0,
365
+ 0,
366
+ ),
367
+ );
249
368
  }
250
369
  if (this.#mode === "login" && this.#externalCredentialCandidates.length > 0) {
251
370
  this.#listContainer.addChild(new Spacer(1));
@@ -254,7 +373,7 @@ export class OAuthSelectorComponent extends Container {
254
373
  new TruncatedText(
255
374
  theme.fg(
256
375
  "success",
257
- ` ${theme.status.success} Imported ${credential.provider} from ${credential.source}`,
376
+ ` ${theme.status.success} Imported ${safeLabel(credential.provider, "provider")} from ${safeLabel(credential.source, "external source")}`,
258
377
  ),
259
378
  0,
260
379
  0,
@@ -263,7 +382,88 @@ export class OAuthSelectorComponent extends Container {
263
382
  }
264
383
  }
265
384
  }
266
- #selectCurrentProvider(): void {
385
+
386
+ #updateAccountList(): void {
387
+ const query = this.#searchInput.getValue().trim().toLowerCase();
388
+ this.#filteredAccountEntries = this.#accountEntries.filter(entry =>
389
+ this.#accountLabel(entry).toLowerCase().includes(query),
390
+ );
391
+ if (this.#selectedIndex >= this.#filteredAccountEntries.length) {
392
+ this.#selectedIndex = Math.max(0, this.#filteredAccountEntries.length - 1);
393
+ }
394
+ this.#listContainer.clear();
395
+ const total = this.#filteredAccountEntries.length;
396
+ const maxVisible = OAUTH_SELECTOR_MAX_VISIBLE;
397
+ const startIndex =
398
+ total <= maxVisible
399
+ ? 0
400
+ : Math.max(0, Math.min(this.#selectedIndex - Math.floor(maxVisible / 2), total - maxVisible));
401
+ const endIndex = Math.min(startIndex + maxVisible, total);
402
+ for (let i = startIndex; i < endIndex; i++) {
403
+ const entry = this.#filteredAccountEntries[i];
404
+ if (!entry) continue;
405
+ const selected = i === this.#selectedIndex;
406
+ const prefix = selected ? theme.fg("accent", `${theme.nav.cursor} `) : " ";
407
+ const label = this.#accountLabel(entry);
408
+ const disabled = !entry.selectable;
409
+ const text = disabled ? theme.fg("dim", label) : selected ? theme.fg("accent", label) : label;
410
+ this.#listContainer.addChild(new TruncatedText(prefix + text, 0, 0));
411
+ }
412
+ if (startIndex > 0 || endIndex < total) {
413
+ this.#listContainer.addChild(
414
+ new TruncatedText(theme.fg("muted", ` (${this.#selectedIndex + 1}/${total})`), 0, 0),
415
+ );
416
+ }
417
+ if (total === 0) {
418
+ this.#listContainer.addChild(new TruncatedText(theme.fg("muted", " No accounts available"), 0, 0));
419
+ }
420
+ if (this.#statusMessage) {
421
+ this.#listContainer.addChild(new Spacer(1));
422
+ this.#listContainer.addChild(
423
+ new TruncatedText(
424
+ theme.fg("warning", ` ${safeLabel(this.#statusMessage, "Unable to select account")}`),
425
+ 0,
426
+ 0,
427
+ ),
428
+ );
429
+ }
430
+ }
431
+
432
+ #selectCurrent(): void {
433
+ if (this.#accountProviderId) {
434
+ const selected = this.#filteredAccountEntries[this.#selectedIndex];
435
+ if (!selected) return;
436
+ if (!selected.selectable) {
437
+ this.#statusMessage = "This account is not selectable.";
438
+ this.#updateList();
439
+ return;
440
+ }
441
+ this.#statusMessage = undefined;
442
+ this.stopValidation();
443
+ if (selected.kind === "account") {
444
+ if (selected.row.credentialKind !== "oauth") return;
445
+ if (this.#mode === "login") {
446
+ void this.#onAccountSelect?.({ kind: "id", value: String(selected.row.id) });
447
+ } else if (selected.target) {
448
+ this.#confirmOrArmAccountRemoval([selected.target], "Remove this account?");
449
+ }
450
+ } else if (selected.kind === "auto") {
451
+ void this.#onAutoSelect?.();
452
+ } else if (selected.kind === "add") {
453
+ void this.#onAddAccount?.();
454
+ } else if (selected.kind === "all") {
455
+ if (this.#pendingAccountRemovalTargets !== undefined) {
456
+ const targets = this.#pendingAccountRemovalTargets;
457
+ this.#pendingAccountRemovalTargets = undefined;
458
+ void this.#onAccountRemove?.(targets);
459
+ } else {
460
+ const targets = this.#getAccountRemovalTargets();
461
+ this.#confirmOrArmAccountRemoval(targets, `Remove all ${targets.length} accounts?`);
462
+ }
463
+ }
464
+ return;
465
+ }
466
+
267
467
  const selectedProvider = this.#filteredProviders[this.#selectedIndex];
268
468
  if (selectedProvider?.available) {
269
469
  this.#statusMessage = undefined;
@@ -274,64 +474,55 @@ export class OAuthSelectorComponent extends Container {
274
474
  this.#updateList();
275
475
  }
276
476
  }
477
+
277
478
  handleInput(keyData: string): void {
278
- // Up arrow
479
+ const isConfirm = matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n";
480
+ const isCancel = matchesSelectCancel(keyData);
481
+ const pendingConfirmationCleared = !isConfirm && !isCancel ? this.#clearPendingAccountRemoval() : false;
482
+ const itemCount = this.#accountProviderId ? this.#filteredAccountEntries.length : this.#filteredProviders.length;
279
483
  if (matchesKey(keyData, "up")) {
280
- if (this.#filteredProviders.length > 0) {
281
- this.#selectedIndex =
282
- this.#selectedIndex === 0 ? this.#filteredProviders.length - 1 : this.#selectedIndex - 1;
283
- }
484
+ this.#clearPendingAccountRemoval();
485
+ if (itemCount > 0) this.#selectedIndex = this.#selectedIndex === 0 ? itemCount - 1 : this.#selectedIndex - 1;
284
486
  this.#statusMessage = undefined;
285
487
  this.#updateList();
286
488
  return;
287
489
  }
288
- // Down arrow
289
490
  if (matchesKey(keyData, "down")) {
290
- if (this.#filteredProviders.length > 0) {
291
- this.#selectedIndex =
292
- this.#selectedIndex === this.#filteredProviders.length - 1 ? 0 : this.#selectedIndex + 1;
293
- }
491
+ this.#clearPendingAccountRemoval();
492
+ if (itemCount > 0) this.#selectedIndex = this.#selectedIndex === itemCount - 1 ? 0 : this.#selectedIndex + 1;
294
493
  this.#statusMessage = undefined;
295
494
  this.#updateList();
296
495
  return;
297
496
  }
298
- // Page up - jump up by one visible page
299
497
  if (matchesKey(keyData, "pageUp")) {
300
- if (this.#filteredProviders.length > 0) {
301
- this.#selectedIndex = Math.max(0, this.#selectedIndex - OAUTH_SELECTOR_MAX_VISIBLE);
302
- }
498
+ this.#clearPendingAccountRemoval();
499
+ if (itemCount > 0) this.#selectedIndex = Math.max(0, this.#selectedIndex - OAUTH_SELECTOR_MAX_VISIBLE);
303
500
  this.#statusMessage = undefined;
304
501
  this.#updateList();
305
502
  return;
306
503
  }
307
- // Page down - jump down by one visible page
308
504
  if (matchesKey(keyData, "pageDown")) {
309
- if (this.#filteredProviders.length > 0) {
310
- this.#selectedIndex = Math.min(
311
- this.#filteredProviders.length - 1,
312
- this.#selectedIndex + OAUTH_SELECTOR_MAX_VISIBLE,
313
- );
314
- }
505
+ this.#clearPendingAccountRemoval();
506
+ if (itemCount > 0)
507
+ this.#selectedIndex = Math.min(itemCount - 1, this.#selectedIndex + OAUTH_SELECTOR_MAX_VISIBLE);
315
508
  this.#statusMessage = undefined;
316
509
  this.#updateList();
317
510
  return;
318
511
  }
319
- // Enter
320
- if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
321
- this.#selectCurrentProvider();
512
+ if (isConfirm) {
513
+ this.#selectCurrent();
322
514
  return;
323
515
  }
324
- // Escape or Ctrl+C
325
- if (matchesSelectCancel(keyData)) {
516
+ if (isCancel) {
517
+ const pending = this.#clearPendingAccountRemoval();
326
518
  this.stopValidation();
519
+ if (pending) this.#updateList();
327
520
  this.#onCancelCallback();
328
521
  return;
329
522
  }
330
- // Everything else edits the filter. A changed query invalidates the current
331
- // position, so selection restarts at the best match.
332
523
  const previousQuery = this.#searchInput.getValue();
333
524
  this.#searchInput.handleInput(keyData);
334
- if (this.#searchInput.getValue() !== previousQuery) {
525
+ if (pendingConfirmationCleared || this.#searchInput.getValue() !== previousQuery) {
335
526
  this.#selectedIndex = 0;
336
527
  this.#statusMessage = undefined;
337
528
  this.#updateList();
@@ -6,14 +6,15 @@ import {
6
6
  shouldProbeSixelCapability,
7
7
  TERMINAL,
8
8
  } from "@gajae-code/tui";
9
+ import type { PetTransportAvailability } from "./iterm-pet-transport";
9
10
 
10
- export type PetPixelProtocol = "sixel" | "kitty" | "iterm2";
11
+ export type PetPixelProtocol = "sixel" | "kitty" | "iterm";
11
12
 
12
- export const PET_UNAVAILABLE_DESCRIPTION = "Unavailable: requires compatible Kitty, iTerm2, or Sixel overlay rendering";
13
+ export const PET_UNAVAILABLE_DESCRIPTION = "Unavailable: requires compatible Kitty or Sixel overlay rendering";
13
14
  export const PET_SAVED_UNAVAILABLE_DESCRIPTION =
14
- "Saved, unavailable — requires compatible Kitty, iTerm2, or Sixel overlay rendering";
15
+ "Saved, unavailable — requires compatible Kitty or Sixel overlay rendering";
15
16
  export const PET_UNAVAILABLE_WARNING =
16
- "⚠ Pets aren’t available in this terminal. Its image support isn’t compatible with Gajae Pet’s overlay rendering yet. Try Kitty, Ghostty, WezTerm, iTerm2, or a terminal with compatible Sixel support.";
17
+ "⚠ Pets aren’t available in this terminal. Its image support isn’t compatible with Gajae Pet’s overlay rendering yet. Try Kitty, Ghostty, WezTerm, or a terminal with compatible Sixel support.";
17
18
  const PET_MULTIPLEXER_UNAVAILABLE_WARNING =
18
19
  "⚠ Gajae Pet graphics are unavailable inside tmux, screen, or zellij because image escapes are not forwarded end to end. Run gjc outside the multiplexer, or set PI_FORCE_IMAGE_PROTOCOL=sixel only when the full terminal chain supports Sixel.";
19
20
 
@@ -21,14 +22,34 @@ export function getPetUnavailableWarning(env: NodeJS.ProcessEnv = Bun.env): stri
21
22
  return isUnderTerminalMultiplexer(env) ? PET_MULTIPLEXER_UNAVAILABLE_WARNING : PET_UNAVAILABLE_WARNING;
22
23
  }
23
24
 
24
- export function getPetPixelProtocol(env: NodeJS.ProcessEnv = Bun.env): PetPixelProtocol | null {
25
- if (isUnderTerminalMultiplexer(env)) {
26
- const forced = env.PI_FORCE_IMAGE_PROTOCOL?.trim().toLowerCase();
27
- return TERMINAL.imageProtocol === ImageProtocol.Sixel && forced === "sixel" ? "sixel" : null;
28
- }
29
- if (TERMINAL.imageProtocol === ImageProtocol.Kitty) return "kitty";
25
+ let latestItermAvailability: PetTransportAvailability | undefined;
26
+ let verifiedItermAvailability: PetTransportAvailability | undefined;
27
+ const verifiedItermListeners = new Set<(availability: PetTransportAvailability | undefined) => void>();
28
+ export function subscribeVerifiedItermPetAvailability(
29
+ callback: (availability: PetTransportAvailability | undefined) => void,
30
+ ): () => void {
31
+ verifiedItermListeners.add(callback);
32
+ return () => verifiedItermListeners.delete(callback);
33
+ }
34
+ export function setVerifiedItermPetAvailability(availability: PetTransportAvailability | undefined): void {
35
+ latestItermAvailability = availability;
36
+ verifiedItermAvailability = availability?.available ? availability : undefined;
37
+ for (const listener of verifiedItermListeners) listener(verifiedItermAvailability);
38
+ }
39
+ export function getItermPetAvailability(): PetTransportAvailability | undefined {
40
+ return latestItermAvailability;
41
+ }
42
+ export function getVerifiedItermPetAvailability(): PetTransportAvailability | undefined {
43
+ return verifiedItermAvailability;
44
+ }
45
+ export function getItermPetUnavailableReason(): string | undefined {
46
+ return latestItermAvailability?.available ? undefined : latestItermAvailability?.reason;
47
+ }
48
+
49
+ export function getPetPixelProtocol(): PetPixelProtocol | null {
50
+ if (TERMINAL.imageProtocol === ImageProtocol.Kitty && !isUnderTerminalMultiplexer()) return "kitty";
30
51
  if (TERMINAL.imageProtocol === ImageProtocol.Sixel) return "sixel";
31
- if (TERMINAL.imageProtocol === ImageProtocol.Iterm2) return "iterm2";
52
+ if (verifiedItermAvailability?.available) return "iterm";
32
53
  return null;
33
54
  }
34
55
 
@@ -46,31 +67,13 @@ export function createPetSelectItems(
46
67
  const current = option.value === currentValue;
47
68
  const savedUnavailable = disabled && current;
48
69
  let description = `${option.description ?? ""}${current ? " (current)" : ""}`;
49
- if (disabled) {
50
- description = savedUnavailable ? PET_SAVED_UNAVAILABLE_DESCRIPTION : PET_UNAVAILABLE_DESCRIPTION;
51
- }
52
- return {
53
- ...option,
54
- label: savedUnavailable ? `${option.label} (saved)` : option.label,
55
- description,
56
- disabled,
57
- };
70
+ if (disabled) description = savedUnavailable ? PET_SAVED_UNAVAILABLE_DESCRIPTION : PET_UNAVAILABLE_DESCRIPTION;
71
+ return { ...option, label: savedUnavailable ? `${option.label} (saved)` : option.label, description, disabled };
58
72
  });
59
73
  }
60
74
 
61
- /**
62
- * Grace period before declaring the terminal pet-incapable at startup. The
63
- * asynchronous Sixel capability probe starts inside `TUI.start()` and answers
64
- * within its own 250 ms deadline; this margin covers probe scheduling so a
65
- * supported terminal is never told it is incompatible while the probe is
66
- * still in flight.
67
- */
68
75
  export const PET_CAPABILITY_SETTLE_MS = 1_000;
69
76
 
70
- /**
71
- * Whether the asynchronous startup Sixel capability probe may still enable
72
- * graphics for this session, meaning current unavailability is not final.
73
- */
74
77
  export function isPetCapabilityProbePending(
75
78
  env: NodeJS.ProcessEnv = Bun.env,
76
79
  platform: NodeJS.Platform = process.platform,
@@ -80,15 +83,6 @@ export function isPetCapabilityProbePending(
80
83
  return shouldProbeSixelCapability(env, platform);
81
84
  }
82
85
 
83
- /**
84
- * Deliver the pet-unavailable startup warning only once the capability
85
- * question is settled. With no probe pending, unavailability is final and
86
- * `onUnavailable` fires immediately. While a probe may still enable
87
- * graphics, the warning is deferred: a protocol-change event cancels it (the
88
- * saved pet re-applies through the existing subscription), and only the
89
- * settle deadline passing with the terminal still unavailable emits it.
90
- * Returns a disposer that cancels the pending decision.
91
- */
92
86
  export function warnWhenPetCapabilitySettled(options: {
93
87
  probePending: boolean;
94
88
  isAvailable?: () => boolean;
@@ -101,16 +95,21 @@ export function warnWhenPetCapabilitySettled(options: {
101
95
  }
102
96
  const isAvailable = options.isAvailable ?? isPetAvailable;
103
97
  let settled = false;
98
+ let unsubscribeIterm = () => {};
104
99
  const finish = () => {
105
100
  if (settled) return;
106
101
  settled = true;
107
102
  clearTimeout(timer);
108
103
  unsubscribe();
104
+ unsubscribeIterm();
109
105
  };
110
106
  const unsubscribe = onImageProtocolChanged(protocol => {
111
107
  if (!protocol) return;
112
108
  finish();
113
109
  });
110
+ unsubscribeIterm = subscribeVerifiedItermPetAvailability(availability => {
111
+ if (availability?.available) finish();
112
+ });
114
113
  const timer = setTimeout(() => {
115
114
  finish();
116
115
  if (!isAvailable()) options.onUnavailable();
@@ -1081,6 +1081,15 @@ export class SettingsSelectorComponent extends Container {
1081
1081
  return;
1082
1082
  }
1083
1083
  if (def.path === "theme.dark" || def.path === "theme.light") {
1084
+ // The theme commit persists through the controller, so it must honor
1085
+ // the same invalid-config guard as every other durable write: report
1086
+ // the repair error and leave the submenu open instead of no-oping.
1087
+ if (!settings.canWriteDurableConfig()) {
1088
+ this.callbacks.onError?.(
1089
+ "Cannot change settings while config.yml has invalid YAML syntax. Repair config.yml and reload settings.",
1090
+ );
1091
+ return;
1092
+ }
1084
1093
  if (!this.callbacks.onThemeCommit) return;
1085
1094
  void this.callbacks.onThemeCommit(def.path, value, currentValue).then(accepted => {
1086
1095
  if (accepted) done(value);