@gajae-code/coding-agent 0.17.2 → 0.17.4

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 (349) hide show
  1. package/CHANGELOG.md +194 -0
  2. package/dist/types/cli/daemon-cli.d.ts +3 -1
  3. package/dist/types/cli/github-release.d.ts +17 -0
  4. package/dist/types/cli/public-command-entry.d.ts +56 -0
  5. package/dist/types/cli/public-command-errors.d.ts +126 -0
  6. package/dist/types/cli/public-command-evidence.d.ts +99 -0
  7. package/dist/types/cli/public-command-help.d.ts +65 -0
  8. package/dist/types/cli/public-command-registry.d.ts +69 -0
  9. package/dist/types/cli/session-picker.d.ts +1 -1
  10. package/dist/types/cli/setup-cli.d.ts +1 -0
  11. package/dist/types/cli/skills-cli.d.ts +5 -0
  12. package/dist/types/cli.d.ts +3 -0
  13. package/dist/types/commands/daemon.d.ts +4 -50
  14. package/dist/types/commands/sdk.d.ts +20 -37
  15. package/dist/types/commands/setup.d.ts +3 -0
  16. package/dist/types/commands/skills.d.ts +9 -0
  17. package/dist/types/config/autorouting-tier-map.d.ts +18 -0
  18. package/dist/types/config/file-lock.d.ts +9 -1
  19. package/dist/types/config/model-profile-activation.d.ts +45 -1
  20. package/dist/types/config/model-registry.d.ts +11 -2
  21. package/dist/types/config/settings-schema.d.ts +11 -0
  22. package/dist/types/coordinator-mcp/question-state.d.ts +6 -1
  23. package/dist/types/daemon/operator-contract.d.ts +5 -1
  24. package/dist/types/eval/js/worker-protocol.d.ts +2 -0
  25. package/dist/types/extensibility/extensions/types.d.ts +12 -0
  26. package/dist/types/extensibility/gjc-plugins/registry.d.ts +4 -0
  27. package/dist/types/extensibility/runtime-skill-discovery.d.ts +18 -0
  28. package/dist/types/extensibility/shared-events.d.ts +3 -1
  29. package/dist/types/gjc-runtime/session-state-lock.d.ts +61 -0
  30. package/dist/types/internal-urls/embedded-protocol.d.ts +9 -0
  31. package/dist/types/internal-urls/index.d.ts +1 -0
  32. package/dist/types/internal-urls/parse.d.ts +9 -1
  33. package/dist/types/main.d.ts +22 -0
  34. package/dist/types/modes/acp/acp-agent.d.ts +55 -1
  35. package/dist/types/modes/acp/startup-provenance.d.ts +16 -0
  36. package/dist/types/modes/components/custom-provider-wizard.d.ts +19 -1
  37. package/dist/types/modes/components/session-selector.d.ts +5 -2
  38. package/dist/types/modes/components/user-message-selector.d.ts +5 -2
  39. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  40. package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
  41. package/dist/types/modes/interactive-mode.d.ts +1 -1
  42. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +11 -1
  43. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +14 -1
  44. package/dist/types/modes/types.d.ts +2 -2
  45. package/dist/types/runtime/process-lifecycle.d.ts +2 -0
  46. package/dist/types/runtime-mcp/manager.d.ts +56 -0
  47. package/dist/types/runtime-mcp/pool.d.ts +17 -0
  48. package/dist/types/runtime-mcp/redaction.d.ts +20 -0
  49. package/dist/types/runtime-mcp/startup-policy.d.ts +6 -0
  50. package/dist/types/runtime-mcp/tool-bridge.d.ts +9 -0
  51. package/dist/types/runtime-mcp/transports/http.d.ts +11 -0
  52. package/dist/types/sdk/broker/broker.d.ts +13 -2
  53. package/dist/types/sdk/broker/identity.d.ts +3 -1
  54. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +23 -3
  55. package/dist/types/sdk/broker/lifecycle.d.ts +15 -8
  56. package/dist/types/sdk/broker/runtime.d.ts +1 -1
  57. package/dist/types/sdk/broker/session-index.d.ts +22 -3
  58. package/dist/types/sdk/broker/spawn-authority.d.ts +4 -0
  59. package/dist/types/sdk/broker/stderr-drainer.d.ts +13 -0
  60. package/dist/types/sdk/bus/index.d.ts +19 -0
  61. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +3 -1
  62. package/dist/types/sdk/bus/telegram-daemon.d.ts +5 -0
  63. package/dist/types/sdk/cli/master-cli.d.ts +6 -10
  64. package/dist/types/sdk/cli/rows.d.ts +19 -1
  65. package/dist/types/sdk/cli/session-cli.d.ts +22 -5
  66. package/dist/types/sdk/client/client.d.ts +2 -0
  67. package/dist/types/sdk/guides/cli.d.ts +3 -4
  68. package/dist/types/sdk/guides/verify.d.ts +2 -0
  69. package/dist/types/sdk/host/host.d.ts +9 -0
  70. package/dist/types/sdk/host/session-runtime.d.ts +31 -0
  71. package/dist/types/sdk/host/types.d.ts +10 -0
  72. package/dist/types/sdk/index.d.ts +1 -0
  73. package/dist/types/sdk/prompt-deadline-flush.d.ts +83 -0
  74. package/dist/types/sdk/prompt-deadline-manager.d.ts +37 -0
  75. package/dist/types/sdk/prompt-tool-boundary.d.ts +73 -0
  76. package/dist/types/sdk/router/session-router.d.ts +2 -0
  77. package/dist/types/sdk/startup-capability.d.ts +2 -0
  78. package/dist/types/sdk/transport/relay.d.ts +1 -0
  79. package/dist/types/sdk/transport/serve-cli.d.ts +48 -4
  80. package/dist/types/sdk/transport/stdio.d.ts +5 -1
  81. package/dist/types/session/agent-session.d.ts +168 -21
  82. package/dist/types/session/fallback-chain-controller.d.ts +6 -6
  83. package/dist/types/session/fold-coordinator.d.ts +17 -3
  84. package/dist/types/session/internal/managed-session-storage.d.ts +7 -1
  85. package/dist/types/session/session-manager.d.ts +64 -1
  86. package/dist/types/session/session-work-lease.d.ts +14 -0
  87. package/dist/types/setup/model-onboarding-guidance.d.ts +1 -1
  88. package/dist/types/setup/provider-onboarding.d.ts +81 -1
  89. package/dist/types/tools/bash.d.ts +37 -11
  90. package/dist/types/tools/job.d.ts +1 -1
  91. package/dist/types/tools/steer-fold.d.ts +31 -0
  92. package/dist/types/utils/git.d.ts +28 -2
  93. package/dist/types/utils/mupdf-embedded.d.ts +2 -0
  94. package/dist/types/utils/mupdf.d.ts +4 -0
  95. package/dist/types/utils/session-title-generation.d.ts +6 -0
  96. package/dist/types/utils/title-generator.d.ts +11 -2
  97. package/dist/types/web/scrapers/types.d.ts +3 -0
  98. package/package.json +18 -10
  99. package/scripts/build-binary.ts +9 -3
  100. package/scripts/compile-args.ts +0 -9
  101. package/scripts/embed-mupdf.ts +52 -0
  102. package/scripts/generate-sdk-operation-inventory.ts +31 -1
  103. package/scripts/vendor-markit.ts +196 -0
  104. package/src/async/job-manager.ts +22 -1
  105. package/src/cli/daemon-cli.ts +53 -18
  106. package/src/cli/github-release.ts +87 -5
  107. package/src/cli/mcp-cli.ts +38 -4
  108. package/src/cli/public-command-entry.ts +450 -0
  109. package/src/cli/public-command-errors.ts +581 -0
  110. package/src/cli/public-command-evidence.ts +717 -0
  111. package/src/cli/public-command-help.ts +360 -0
  112. package/src/cli/public-command-registry.ts +535 -0
  113. package/src/cli/session-picker.ts +2 -1
  114. package/src/cli/setup-cli.ts +9 -1
  115. package/src/cli/skills-cli.ts +46 -2
  116. package/src/cli.ts +61 -1
  117. package/src/commands/daemon.ts +65 -69
  118. package/src/commands/sdk.ts +175 -117
  119. package/src/commands/setup.ts +2 -0
  120. package/src/commands/skills.ts +11 -0
  121. package/src/config/autorouting-tier-map.ts +14 -0
  122. package/src/config/file-lock.ts +147 -51
  123. package/src/config/model-profile-activation.ts +334 -43
  124. package/src/config/model-registry.ts +183 -25
  125. package/src/config/settings-schema.ts +26 -2
  126. package/src/coordinator-mcp/policy.ts +10 -5
  127. package/src/coordinator-mcp/question-gate-codec.ts +17 -0
  128. package/src/coordinator-mcp/question-state.ts +175 -8
  129. package/src/coordinator-mcp/server.ts +507 -81
  130. package/src/coordinator-mcp/session-reaper.ts +6 -4
  131. package/src/crash/record-loader.ts +6 -2
  132. package/src/daemon/operator-contract.ts +26 -12
  133. package/src/defaults/gjc/skills/ultragoal/SKILL.md +1 -1
  134. package/src/eval/js/context-manager.ts +8 -8
  135. package/src/eval/js/worker-core.ts +9 -4
  136. package/src/eval/js/worker-protocol.ts +2 -0
  137. package/src/extensibility/extensions/runner.ts +52 -4
  138. package/src/extensibility/extensions/types.ts +10 -0
  139. package/src/extensibility/gjc-plugins/registry.ts +196 -37
  140. package/src/extensibility/runtime-skill-discovery.ts +61 -3
  141. package/src/extensibility/shared-events.ts +3 -1
  142. package/src/gjc-runtime/session-state-lock.ts +165 -24
  143. package/src/gjc-runtime/ultragoal-succession.ts +31 -3
  144. package/src/internal-urls/docs-index.generated.ts +20 -19
  145. package/src/internal-urls/embedded-protocol.ts +83 -0
  146. package/src/internal-urls/index.ts +1 -0
  147. package/src/internal-urls/parse.ts +21 -1
  148. package/src/internal-urls/router.ts +4 -2
  149. package/src/main.ts +123 -55
  150. package/src/modes/acp/acp-agent.ts +408 -35
  151. package/src/modes/acp/startup-provenance.ts +59 -0
  152. package/src/modes/components/custom-provider-wizard.ts +308 -7
  153. package/src/modes/components/oauth-selector.ts +9 -0
  154. package/src/modes/components/session-selector.ts +47 -4
  155. package/src/modes/components/user-message-selector.ts +57 -15
  156. package/src/modes/controllers/command-controller.ts +58 -32
  157. package/src/modes/controllers/extension-ui-controller.ts +33 -7
  158. package/src/modes/controllers/input-controller.ts +4 -0
  159. package/src/modes/controllers/selector-controller.ts +161 -15
  160. package/src/modes/interactive-mode.ts +21 -7
  161. package/src/modes/runtime-init.ts +1 -0
  162. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +124 -7
  163. package/src/modes/shared/agent-wire/workflow-gate-types.ts +16 -2
  164. package/src/modes/types.ts +2 -2
  165. package/src/prompts/agent-fragments/browser-aside.md +1 -0
  166. package/src/prompts/system/title-system.md +1 -1
  167. package/src/prompts/tools/bash.md +1 -1
  168. package/src/prompts/tools/job.md +1 -1
  169. package/src/prompts/tools/read.md +2 -0
  170. package/src/runtime/network-prewarm-service.test.ts +36 -1
  171. package/src/runtime/network-prewarm-service.ts +28 -3
  172. package/src/runtime/process-lifecycle.ts +22 -11
  173. package/src/runtime-mcp/config.ts +16 -1
  174. package/src/runtime-mcp/manager.ts +689 -55
  175. package/src/runtime-mcp/pool.test.ts +31 -0
  176. package/src/runtime-mcp/pool.ts +304 -28
  177. package/src/runtime-mcp/redaction.ts +42 -0
  178. package/src/runtime-mcp/startup-policy.ts +7 -0
  179. package/src/runtime-mcp/tool-bridge.ts +71 -0
  180. package/src/runtime-mcp/transports/http.ts +108 -25
  181. package/src/sdk/acp/adapter.ts +27 -8
  182. package/src/sdk/broker/broker.ts +378 -52
  183. package/src/sdk/broker/identity.ts +9 -1
  184. package/src/sdk/broker/lifecycle-ledger.ts +95 -31
  185. package/src/sdk/broker/lifecycle.ts +690 -141
  186. package/src/sdk/broker/runtime.ts +1 -1
  187. package/src/sdk/broker/session-index.ts +97 -4
  188. package/src/sdk/broker/spawn-authority.ts +35 -7
  189. package/src/sdk/broker/stderr-drainer.ts +59 -0
  190. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  191. package/src/sdk/bus/chat-daemon-runtime.ts +1 -0
  192. package/src/sdk/bus/index.ts +690 -80
  193. package/src/sdk/bus/telegram-daemon-cli.ts +31 -2
  194. package/src/sdk/bus/telegram-daemon-contract.ts +3 -1
  195. package/src/sdk/bus/telegram-daemon.ts +10 -3
  196. package/src/sdk/cli/master-cli.ts +47 -36
  197. package/src/sdk/cli/rows.ts +30 -7
  198. package/src/sdk/cli/session-cli.ts +687 -184
  199. package/src/sdk/client/client.ts +18 -0
  200. package/src/sdk/guides/catalog.ts +2 -2
  201. package/src/sdk/guides/cli.ts +20 -62
  202. package/src/sdk/guides/verify.ts +14 -0
  203. package/src/sdk/host/host.ts +49 -5
  204. package/src/sdk/host/session-runtime.test.ts +138 -2
  205. package/src/sdk/host/session-runtime.ts +311 -97
  206. package/src/sdk/host/types.ts +12 -1
  207. package/src/sdk/host/websocket-transport.lifecycle.test.ts +84 -0
  208. package/src/sdk/host/websocket-transport.ts +40 -6
  209. package/src/sdk/index.ts +11 -0
  210. package/src/sdk/prompt-deadline-flush.ts +334 -0
  211. package/src/sdk/prompt-deadline-manager.ts +133 -1
  212. package/src/sdk/prompt-tool-boundary.ts +121 -0
  213. package/src/sdk/protocol/operation-inventory.generated.json +106 -0
  214. package/src/sdk/router/session-router.ts +9 -1
  215. package/src/sdk/session.ts +182 -7
  216. package/src/sdk/startup-capability.ts +1 -1
  217. package/src/sdk/transport/relay.ts +71 -6
  218. package/src/sdk/transport/serve-cli.ts +397 -42
  219. package/src/sdk/transport/socket.ts +12 -11
  220. package/src/sdk/transport/stdio.ts +7 -3
  221. package/src/session/agent-session.ts +2437 -400
  222. package/src/session/fallback-chain-controller.ts +6 -7
  223. package/src/session/fold-coordinator.ts +24 -5
  224. package/src/session/internal/managed-session-scope.ts +58 -18
  225. package/src/session/internal/managed-session-storage.ts +304 -11
  226. package/src/session/session-manager.ts +382 -44
  227. package/src/session/session-work-lease.ts +34 -0
  228. package/src/setup/model-onboarding-guidance.ts +1 -1
  229. package/src/setup/provider-onboarding.ts +397 -24
  230. package/src/slash-commands/builtin-registry.ts +258 -14
  231. package/src/task/index.ts +14 -2
  232. package/src/tools/bash.ts +98 -78
  233. package/src/tools/fetch.ts +119 -20
  234. package/src/tools/job.ts +151 -25
  235. package/src/tools/output-meta.ts +15 -2
  236. package/src/tools/path-utils.ts +27 -3
  237. package/src/tools/read.ts +3 -3
  238. package/src/tools/steer-fold.ts +107 -0
  239. package/src/tools/tool-catalog.generated.ts +29 -2
  240. package/src/tools/tool-errors.ts +4 -0
  241. package/src/utils/git.ts +53 -3
  242. package/src/utils/markit.ts +55 -41
  243. package/src/utils/mupdf-assets.d.ts +4 -0
  244. package/src/utils/mupdf-embedded.ts +4 -0
  245. package/src/utils/mupdf.ts +129 -0
  246. package/src/utils/session-title-generation.ts +29 -0
  247. package/src/utils/title-generator.ts +46 -2
  248. package/src/web/scrapers/types.ts +24 -3
  249. package/vendor/markit-ai/LICENSE +21 -0
  250. package/vendor/markit-ai/MANIFEST.json +412 -0
  251. package/vendor/markit-ai/dist/commands/config.d.ts +4 -0
  252. package/vendor/markit-ai/dist/commands/config.js +139 -0
  253. package/vendor/markit-ai/dist/commands/convert.d.ts +6 -0
  254. package/vendor/markit-ai/dist/commands/convert.js +113 -0
  255. package/vendor/markit-ai/dist/commands/formats.d.ts +2 -0
  256. package/vendor/markit-ai/dist/commands/formats.js +92 -0
  257. package/vendor/markit-ai/dist/commands/init.d.ts +2 -0
  258. package/vendor/markit-ai/dist/commands/init.js +29 -0
  259. package/vendor/markit-ai/dist/commands/onboard.d.ts +2 -0
  260. package/vendor/markit-ai/dist/commands/onboard.js +61 -0
  261. package/vendor/markit-ai/dist/commands/plugin.d.ts +4 -0
  262. package/vendor/markit-ai/dist/commands/plugin.js +58 -0
  263. package/vendor/markit-ai/dist/config.d.ts +26 -0
  264. package/vendor/markit-ai/dist/config.js +42 -0
  265. package/vendor/markit-ai/dist/converters/audio.d.ts +7 -0
  266. package/vendor/markit-ai/dist/converters/audio.js +104 -0
  267. package/vendor/markit-ai/dist/converters/csv.d.ts +7 -0
  268. package/vendor/markit-ai/dist/converters/csv.js +83 -0
  269. package/vendor/markit-ai/dist/converters/docx.d.ts +6 -0
  270. package/vendor/markit-ai/dist/converters/docx.js +57 -0
  271. package/vendor/markit-ai/dist/converters/epub.d.ts +8 -0
  272. package/vendor/markit-ai/dist/converters/epub.js +128 -0
  273. package/vendor/markit-ai/dist/converters/github.d.ts +18 -0
  274. package/vendor/markit-ai/dist/converters/github.js +148 -0
  275. package/vendor/markit-ai/dist/converters/html.d.ts +6 -0
  276. package/vendor/markit-ai/dist/converters/html.js +30 -0
  277. package/vendor/markit-ai/dist/converters/image.d.ts +6 -0
  278. package/vendor/markit-ai/dist/converters/image.js +124 -0
  279. package/vendor/markit-ai/dist/converters/ipynb.d.ts +6 -0
  280. package/vendor/markit-ai/dist/converters/ipynb.js +75 -0
  281. package/vendor/markit-ai/dist/converters/iwork.d.ts +20 -0
  282. package/vendor/markit-ai/dist/converters/iwork.js +391 -0
  283. package/vendor/markit-ai/dist/converters/json.d.ts +6 -0
  284. package/vendor/markit-ai/dist/converters/json.js +21 -0
  285. package/vendor/markit-ai/dist/converters/pdf/columns.d.ts +35 -0
  286. package/vendor/markit-ai/dist/converters/pdf/columns.js +93 -0
  287. package/vendor/markit-ai/dist/converters/pdf/extract.d.ts +19 -0
  288. package/vendor/markit-ai/dist/converters/pdf/extract.js +514 -0
  289. package/vendor/markit-ai/dist/converters/pdf/grid.d.ts +25 -0
  290. package/vendor/markit-ai/dist/converters/pdf/grid.js +795 -0
  291. package/vendor/markit-ai/dist/converters/pdf/headers.d.ts +24 -0
  292. package/vendor/markit-ai/dist/converters/pdf/headers.js +108 -0
  293. package/vendor/markit-ai/dist/converters/pdf/index.d.ts +19 -0
  294. package/vendor/markit-ai/dist/converters/pdf/index.js +136 -0
  295. package/vendor/markit-ai/dist/converters/pdf/mupdf-loader.js +9 -0
  296. package/vendor/markit-ai/dist/converters/pdf/render.d.ts +24 -0
  297. package/vendor/markit-ai/dist/converters/pdf/render.js +513 -0
  298. package/vendor/markit-ai/dist/converters/pdf/types.d.ts +75 -0
  299. package/vendor/markit-ai/dist/converters/pdf/types.js +1 -0
  300. package/vendor/markit-ai/dist/converters/plain-text.d.ts +6 -0
  301. package/vendor/markit-ai/dist/converters/plain-text.js +87 -0
  302. package/vendor/markit-ai/dist/converters/pptx.d.ts +8 -0
  303. package/vendor/markit-ai/dist/converters/pptx.js +289 -0
  304. package/vendor/markit-ai/dist/converters/rss.d.ts +11 -0
  305. package/vendor/markit-ai/dist/converters/rss.js +138 -0
  306. package/vendor/markit-ai/dist/converters/wikipedia.d.ts +6 -0
  307. package/vendor/markit-ai/dist/converters/wikipedia.js +33 -0
  308. package/vendor/markit-ai/dist/converters/xlsx.d.ts +8 -0
  309. package/vendor/markit-ai/dist/converters/xlsx.js +147 -0
  310. package/vendor/markit-ai/dist/converters/xml.d.ts +6 -0
  311. package/vendor/markit-ai/dist/converters/xml.js +18 -0
  312. package/vendor/markit-ai/dist/converters/yaml.d.ts +6 -0
  313. package/vendor/markit-ai/dist/converters/yaml.js +17 -0
  314. package/vendor/markit-ai/dist/converters/zip.d.ts +8 -0
  315. package/vendor/markit-ai/dist/converters/zip.js +57 -0
  316. package/vendor/markit-ai/dist/index.d.ts +30 -0
  317. package/vendor/markit-ai/dist/index.js +26 -0
  318. package/vendor/markit-ai/dist/main.d.ts +2 -0
  319. package/vendor/markit-ai/dist/main.js +191 -0
  320. package/vendor/markit-ai/dist/markit.d.ts +45 -0
  321. package/vendor/markit-ai/dist/markit.js +267 -0
  322. package/vendor/markit-ai/dist/plugins/api.d.ts +7 -0
  323. package/vendor/markit-ai/dist/plugins/api.js +42 -0
  324. package/vendor/markit-ai/dist/plugins/index.d.ts +4 -0
  325. package/vendor/markit-ai/dist/plugins/index.js +3 -0
  326. package/vendor/markit-ai/dist/plugins/installer.d.ts +25 -0
  327. package/vendor/markit-ai/dist/plugins/installer.js +176 -0
  328. package/vendor/markit-ai/dist/plugins/loader.d.ts +6 -0
  329. package/vendor/markit-ai/dist/plugins/loader.js +61 -0
  330. package/vendor/markit-ai/dist/plugins/types.d.ts +25 -0
  331. package/vendor/markit-ai/dist/plugins/types.js +1 -0
  332. package/vendor/markit-ai/dist/providers/anthropic.d.ts +2 -0
  333. package/vendor/markit-ai/dist/providers/anthropic.js +47 -0
  334. package/vendor/markit-ai/dist/providers/index.d.ts +21 -0
  335. package/vendor/markit-ai/dist/providers/index.js +56 -0
  336. package/vendor/markit-ai/dist/providers/openai.d.ts +2 -0
  337. package/vendor/markit-ai/dist/providers/openai.js +76 -0
  338. package/vendor/markit-ai/dist/providers/types.d.ts +26 -0
  339. package/vendor/markit-ai/dist/providers/types.js +1 -0
  340. package/vendor/markit-ai/dist/types.d.ts +36 -0
  341. package/vendor/markit-ai/dist/types.js +1 -0
  342. package/vendor/markit-ai/dist/utils/exit-codes.d.ts +4 -0
  343. package/vendor/markit-ai/dist/utils/exit-codes.js +4 -0
  344. package/vendor/markit-ai/dist/utils/output.d.ts +22 -0
  345. package/vendor/markit-ai/dist/utils/output.js +31 -0
  346. package/vendor/markit-ai/dist/utils/turndown.d.ts +8 -0
  347. package/vendor/markit-ai/dist/utils/turndown.js +70 -0
  348. package/vendor/markit-ai/package.json +74 -0
  349. package/vendor/markit-ai.patch +49 -0
@@ -0,0 +1,59 @@
1
+ import { AcpSdkAdapterError } from "../../sdk/acp";
2
+
3
+ type JsonObject = Record<string, unknown>;
4
+
5
+ export type PrimaryControlSurface = "cli" | "sdk";
6
+
7
+ /**
8
+ * Startup-control provenance is the authority record ACP needs before it may take
9
+ * permission, provider, and lifecycle ownership of a session. Hosts serving that
10
+ * record report capabilities carrying `promptTerminalOutcomeVersion`
11
+ * and `primaryControlSurface`; a host that predates the record reports neither, and a
12
+ * host that cannot answer at all reports nothing. Those are three different operator
13
+ * problems, so they get three different messages instead of one "restart the session".
14
+ */
15
+ export function resolveStartupProvenance(input: {
16
+ sessionId: string;
17
+ capabilities: JsonObject | undefined;
18
+ queryFailure: unknown;
19
+ }): PrimaryControlSurface {
20
+ const { sessionId, capabilities, queryFailure } = input;
21
+ if (!capabilities)
22
+ throw new AcpSdkAdapterError(
23
+ "unavailable",
24
+ `ACP session ${sessionId} did not answer runtime.capabilities, so its startup control provenance is unknown${describeFailure(queryFailure)}.`,
25
+ );
26
+ const surface =
27
+ capabilities.primaryControlSurface === "sdk"
28
+ ? "sdk"
29
+ : capabilities.primaryControlSurface === "cli"
30
+ ? "cli"
31
+ : undefined;
32
+ if (capabilities.promptTerminalOutcomeVersion !== 1 || surface === undefined)
33
+ throw new AcpSdkAdapterError(
34
+ "unavailable",
35
+ `ACP session ${sessionId} is served by a live GJC host that predates startup control provenance. Upgrading the client does not upgrade that already-running host: stop it, then reopen the session so the current build resumes it from its stored record.`,
36
+ );
37
+ return surface;
38
+ }
39
+
40
+ function describeFailure(failure: unknown): string {
41
+ if (failure === undefined) return "";
42
+ let code: unknown;
43
+ try {
44
+ if ((typeof failure !== "object" || failure === null) && typeof failure !== "function") return " (query failed)";
45
+ code = (failure as { code?: unknown }).code;
46
+ } catch {
47
+ return " (query failed)";
48
+ }
49
+ switch (code) {
50
+ case "connection_closed":
51
+ return " (connection closed)";
52
+ case "timeout":
53
+ return " (query timed out)";
54
+ case "uncertain_after_send":
55
+ return " (query outcome uncertain)";
56
+ default:
57
+ return " (query failed)";
58
+ }
59
+ }
@@ -1,3 +1,4 @@
1
+ import * as crypto from "node:crypto";
1
2
  import { Container, Input, matchesKey, SecretInput, Spacer, Text, TruncatedText } from "@gajae-code/tui";
2
3
  import type { ProviderCompatibility, ProviderSetupInput } from "../../setup/provider-onboarding";
3
4
  import { theme } from "../theme/theme";
@@ -12,6 +13,7 @@ type WizardStep =
12
13
  | "base-url"
13
14
  | "credential-source"
14
15
  | "credential"
16
+ | "discover"
15
17
  | "models"
16
18
  | "confirm"
17
19
  | "force-confirm";
@@ -22,11 +24,33 @@ interface WizardState {
22
24
  baseUrl: string;
23
25
  credentialSource: CustomProviderCredentialSource;
24
26
  credential: string;
27
+ discoverModels: boolean;
28
+ discoveredModels: string[];
29
+ /** Inputs that produced discoveredModels; empty when results are stale/unprobed. */
30
+ discoveredForBaseUrl: string;
31
+ /** SHA-256 of the credential that produced the results — never the secret itself. */
32
+ discoveredForCredentialHash: string;
33
+ discoveredForCredentialSource: CustomProviderCredentialSource;
34
+ discoveryError: string | null;
25
35
  models: string;
26
36
  }
27
37
 
28
38
  export type CustomProviderWizardSubmit = ProviderSetupInput;
29
39
 
40
+ export interface CustomProviderWizardDiscoveryDeps {
41
+ discoverModels?: (input: {
42
+ baseUrl: string;
43
+ apiKeyEnv?: string;
44
+ apiKey?: string;
45
+ signal?: AbortSignal;
46
+ }) => Promise<{ models: string[] }>;
47
+ }
48
+
49
+ /** Non-secret fingerprint binding probe results to the credential that produced them. */
50
+ function fingerprintCredential(credential: string): string {
51
+ return `sha256:${crypto.createHash("sha256").update(credential, "utf8").digest("hex")}`;
52
+ }
53
+
30
54
  export class CustomProviderWizardComponent extends Container {
31
55
  #contentContainer: Container;
32
56
  #input: Input | SecretInput | null = null;
@@ -39,22 +63,37 @@ export class CustomProviderWizardComponent extends Container {
39
63
  baseUrl: "",
40
64
  credentialSource: "env",
41
65
  credential: "",
66
+ discoverModels: false,
67
+ discoveredModels: [],
68
+ discoveredForBaseUrl: "",
69
+ discoveredForCredentialHash: "",
70
+ discoveredForCredentialSource: "env",
71
+ discoveryError: null,
42
72
  models: "",
43
73
  };
44
74
  #submitInFlight = false;
75
+ #discoveryInFlight = false;
76
+ #discoveryGeneration = 0;
77
+ #submitGeneration = 0;
78
+ #discoveryAbort: AbortController | null = null;
79
+ #submitAbort: AbortController | null = null;
80
+ #dismissed = false;
45
81
  #onSubmit: (input: CustomProviderWizardSubmit) => void;
46
82
  #onCancel: () => void;
47
83
  #onRender: () => void;
84
+ #discoveryDeps: CustomProviderWizardDiscoveryDeps = {};
48
85
 
49
86
  constructor(
50
87
  onSubmit: (input: CustomProviderWizardSubmit) => void,
51
88
  onCancel: () => void,
52
89
  onRender: () => void = () => {},
90
+ discoveryDeps: CustomProviderWizardDiscoveryDeps = {},
53
91
  ) {
54
92
  super();
55
93
  this.#onSubmit = onSubmit;
56
94
  this.#onCancel = onCancel;
57
95
  this.#onRender = onRender;
96
+ this.#discoveryDeps = discoveryDeps;
58
97
 
59
98
  this.addChild(new DynamicBorder());
60
99
  this.addChild(new Spacer(1));
@@ -83,6 +122,8 @@ export class CustomProviderWizardComponent extends Container {
83
122
  handleInput(keyData: string): void {
84
123
  if (matchesAppInterrupt(keyData)) {
85
124
  if (this.#step === "compatibility") {
125
+ this.#dismissed = true;
126
+ this.#cancelDiscovery();
86
127
  this.#clearLiteralCredential();
87
128
  this.#onCancel();
88
129
  return;
@@ -155,6 +196,9 @@ export class CustomProviderWizardComponent extends Container {
155
196
  this.#renderSecretInputStep();
156
197
  }
157
198
  break;
199
+ case "discover":
200
+ this.#renderDiscoverStep();
201
+ break;
158
202
  case "models":
159
203
  this.#renderInputStep(
160
204
  "Step 6: Model id(s)",
@@ -204,6 +248,49 @@ export class CustomProviderWizardComponent extends Container {
204
248
  this.#addHelp("[Enter to continue, Esc to go back]");
205
249
  }
206
250
 
251
+ #renderDiscoverStep(): void {
252
+ this.#contentContainer.addChild(new Text(theme.fg("accent", "Step 6: Model discovery")));
253
+ this.#contentContainer.addChild(new Spacer(1));
254
+ if (this.#state.compatibility !== "openai") {
255
+ this.#contentContainer.addChild(
256
+ new Text("Model discovery needs an OpenAI-compatible endpoint. Continue with manual model ids.", 0, 0),
257
+ );
258
+ this.#contentContainer.addChild(new Spacer(1));
259
+ this.#addOption(0, "Enter model ids manually");
260
+ this.#addHelp("[Enter to continue, Esc to go back]");
261
+ return;
262
+ }
263
+ if (this.#discoveryInFlight) {
264
+ this.#contentContainer.addChild(new Text("Discovering models from the provider /v1/models endpoint…", 0, 0));
265
+ this.#contentContainer.addChild(new Spacer(1));
266
+ this.#addHelp("The API key is sent only to your configured base URL.");
267
+ return;
268
+ }
269
+ if (this.#state.discoveryError) {
270
+ this.#contentContainer.addChild(
271
+ new Text(theme.fg("warning", `Discovery failed: ${this.#state.discoveryError}`), 0, 0),
272
+ );
273
+ this.#contentContainer.addChild(new Spacer(1));
274
+ }
275
+ if (this.#state.discoveredModels.length > 0) {
276
+ const preview = this.#state.discoveredModels.slice(0, 10).join(", ");
277
+ const extra =
278
+ this.#state.discoveredModels.length > 10 ? ` (+${this.#state.discoveredModels.length - 10} more)` : "";
279
+ this.#contentContainer.addChild(
280
+ new Text(`Discovered ${this.#state.discoveredModels.length} model(s): ${preview}${extra}`, 0, 0),
281
+ );
282
+ this.#contentContainer.addChild(new Spacer(1));
283
+ } else if (!this.#state.discoveryError) {
284
+ this.#contentContainer.addChild(
285
+ new Text("Probe the provider /v1/models endpoint, or enter model ids manually.", 0, 0),
286
+ );
287
+ this.#contentContainer.addChild(new Spacer(1));
288
+ }
289
+ this.#addOption(0, this.#state.discoveredModels.length > 0 ? "Use discovered models" : "Discover models now");
290
+ this.#addOption(1, "Enter model ids manually");
291
+ this.#addHelp("[↑↓ to navigate, Enter to select, Esc to go back]");
292
+ }
293
+
207
294
  #renderSecretInputStep(): void {
208
295
  this.#contentContainer.addChild(new Text(theme.fg("accent", "Step 5: Credential")));
209
296
  this.#contentContainer.addChild(new Spacer(1));
@@ -214,9 +301,7 @@ export class CustomProviderWizardComponent extends Container {
214
301
  const credential = secret.consume().trim();
215
302
  if (!credential) return;
216
303
  this.#state.credential = credential;
217
- this.#step = "models";
218
- this.#renderStep();
219
- this.#onRender();
304
+ this.#enterDiscovery();
220
305
  };
221
306
  this.#input = input;
222
307
  this.#contentContainer.addChild(input);
@@ -267,16 +352,25 @@ export class CustomProviderWizardComponent extends Container {
267
352
  if (!value) return;
268
353
  if (this.#step === "provider-id") {
269
354
  this.#state.providerId = value;
355
+ // A provider-ID revision invalidates pending submits exactly
356
+ // like URL/credential revisions do.
357
+ this.#clearStaleDiscovery();
270
358
  this.#step = "base-url";
271
359
  } else if (this.#step === "base-url") {
272
360
  this.#state.baseUrl = value;
361
+ this.#clearStaleDiscovery();
273
362
  this.#step = "credential-source";
274
363
  this.#selectedIndex = 0;
275
364
  } else if (this.#step === "credential") {
276
365
  this.#state.credential = value;
277
- this.#step = "models";
366
+ this.#enterDiscovery();
367
+ return;
278
368
  } else if (this.#step === "models") {
279
369
  this.#state.models = value;
370
+ // Manual entry supersedes any accepted discovery (clearStale
371
+ // also resets discoverModels): submitting both would silently
372
+ // persist live discovery the manual screen never disclosed.
373
+ this.#clearStaleDiscovery();
280
374
  this.#step = "confirm";
281
375
  this.#selectedIndex = 0;
282
376
  this.#lastSubmitError = null;
@@ -285,6 +379,18 @@ export class CustomProviderWizardComponent extends Container {
285
379
  this.#onRender();
286
380
  }
287
381
 
382
+ #enterDiscovery(): void {
383
+ this.#clearStaleDiscovery();
384
+ this.#step = "discover";
385
+ this.#selectedIndex = 0;
386
+ if (this.#state.compatibility === "openai" && this.#discoveryDeps.discoverModels) {
387
+ this.#runDiscovery();
388
+ } else {
389
+ this.#renderStep();
390
+ this.#onRender();
391
+ }
392
+ }
393
+
288
394
  #selectCurrentOption(): void {
289
395
  if (this.#step === "compatibility") {
290
396
  this.#state.compatibility = this.#selectedIndex === 0 ? "openai" : "anthropic";
@@ -293,37 +399,189 @@ export class CustomProviderWizardComponent extends Container {
293
399
  this.#state.credentialSource = this.#selectedIndex === 0 ? "env" : "literal";
294
400
  this.#state.credential = "";
295
401
  this.#step = "credential";
402
+ } else if (this.#step === "discover") {
403
+ if (this.#state.compatibility !== "openai") {
404
+ this.#step = "models";
405
+ } else if (this.#selectedIndex === 0) {
406
+ if (this.#state.discoveredModels.length > 0 && this.#discoveryResultsAreFresh()) {
407
+ this.#state.discoverModels = true;
408
+ this.#state.models = "";
409
+ this.#step = "confirm";
410
+ this.#selectedIndex = 0;
411
+ this.#lastSubmitError = null;
412
+ } else {
413
+ this.#runDiscovery();
414
+ return;
415
+ }
416
+ } else {
417
+ this.#state.discoverModels = false;
418
+ this.#cancelDiscovery();
419
+ this.#step = "models";
420
+ }
296
421
  } else if (this.#step === "confirm" || this.#step === "force-confirm") {
297
422
  if (this.#selectedIndex === 0) {
298
423
  this.#submit();
299
424
  return;
300
425
  }
301
- this.#step = "models";
426
+ this.#goBack();
427
+ return;
302
428
  }
303
429
  this.#renderStep();
304
430
  this.#onRender();
305
431
  }
306
432
 
433
+ #clearStaleDiscovery(): void {
434
+ this.#discoveryGeneration += 1;
435
+ this.#submitGeneration += 1;
436
+ this.#discoveryAbort?.abort();
437
+ this.#discoveryAbort = null;
438
+ // A revision also invalidates an in-flight submit: without this,
439
+ // a submit-time probe started before the edit could still persist
440
+ // the old URL/key and close the wizard as successful.
441
+ this.#submitAbort?.abort();
442
+ this.#submitAbort = null;
443
+ this.#discoveryInFlight = false;
444
+ this.#state.discoveredModels = [];
445
+ this.#state.discoveredForBaseUrl = "";
446
+ this.#state.discoveredForCredentialHash = "";
447
+ this.#state.discoverModels = false;
448
+ this.#state.discoveryError = null;
449
+ }
450
+
451
+ #discoveryResultsAreFresh(): boolean {
452
+ return (
453
+ this.#state.discoveredForBaseUrl !== "" &&
454
+ this.#state.discoveredForBaseUrl === this.#state.baseUrl.trim() &&
455
+ this.#state.discoveredForCredentialHash !== "" &&
456
+ this.#state.discoveredForCredentialHash === fingerprintCredential(this.#state.credential.trim()) &&
457
+ this.#state.discoveredForCredentialSource === this.#state.credentialSource
458
+ );
459
+ }
460
+
461
+ #runDiscovery(): void {
462
+ const discover = this.#discoveryDeps.discoverModels;
463
+ if (!discover || this.#discoveryInFlight) return;
464
+ this.#discoveryAbort?.abort();
465
+ const controller = new AbortController();
466
+ this.#discoveryAbort = controller;
467
+ this.#discoveryInFlight = true;
468
+ this.#discoveryGeneration += 1;
469
+ const generation = this.#discoveryGeneration;
470
+ this.#state.discoveryError = null;
471
+ this.#renderStep();
472
+ this.#onRender();
473
+ // Immutable fingerprint of the inputs being probed: completions are
474
+ // accepted only when both the generation and the live inputs still
475
+ // match, so an edit made while the request was in flight cannot
476
+ // inherit a catalog fetched for different inputs.
477
+ const credential = this.#state.credential.trim();
478
+ const fingerprint = {
479
+ baseUrl: this.#state.baseUrl.trim(),
480
+ credential,
481
+ credentialSource: this.#state.credentialSource,
482
+ };
483
+ const request = {
484
+ baseUrl: fingerprint.baseUrl,
485
+ apiKeyEnv: fingerprint.credentialSource === "env" ? fingerprint.credential : undefined,
486
+ apiKey: fingerprint.credentialSource === "literal" ? fingerprint.credential : undefined,
487
+ signal: controller.signal,
488
+ };
489
+ void Promise.resolve()
490
+ .then(() => discover(request))
491
+ .then(
492
+ result => {
493
+ if (!this.#isCurrentDiscoveryGeneration(generation)) return;
494
+ this.#discoveryInFlight = false;
495
+ if (!this.#discoveryFingerprintMatches(fingerprint)) {
496
+ if (this.#step === "discover") {
497
+ this.#renderStep();
498
+ this.#onRender();
499
+ }
500
+ return;
501
+ }
502
+ const models = [...new Set(result.models.map(model => model.trim()).filter(Boolean))].sort((a, b) =>
503
+ a.localeCompare(b),
504
+ );
505
+ this.#state.discoveredModels = models;
506
+ this.#state.discoveredForBaseUrl = fingerprint.baseUrl;
507
+ this.#state.discoveredForCredentialHash = fingerprintCredential(fingerprint.credential);
508
+ this.#state.discoveredForCredentialSource = fingerprint.credentialSource;
509
+ this.#state.discoveryError = models.length === 0 ? "The endpoint returned no models." : null;
510
+ this.#selectedIndex = 0;
511
+ if (this.#step === "discover") {
512
+ this.#renderStep();
513
+ this.#onRender();
514
+ }
515
+ },
516
+ error => {
517
+ if (!this.#isCurrentDiscoveryGeneration(generation)) return;
518
+ this.#discoveryInFlight = false;
519
+ if (!this.#discoveryFingerprintMatches(fingerprint)) {
520
+ if (this.#step === "discover") {
521
+ this.#renderStep();
522
+ this.#onRender();
523
+ }
524
+ return;
525
+ }
526
+ this.#state.discoveredModels = [];
527
+ this.#state.discoveredForBaseUrl = "";
528
+ this.#state.discoveredForCredentialHash = "";
529
+ this.#state.discoveryError = error instanceof Error ? error.message : String(error);
530
+ this.#selectedIndex = 1;
531
+ if (this.#step === "discover") {
532
+ this.#renderStep();
533
+ this.#onRender();
534
+ }
535
+ },
536
+ );
537
+ }
538
+
539
+ #isCurrentDiscoveryGeneration(generation: number): boolean {
540
+ return generation === this.#discoveryGeneration;
541
+ }
542
+
543
+ #discoveryFingerprintMatches(fingerprint: {
544
+ baseUrl: string;
545
+ credential: string;
546
+ credentialSource: CustomProviderCredentialSource;
547
+ }): boolean {
548
+ return (
549
+ fingerprint.baseUrl === this.#state.baseUrl.trim() &&
550
+ fingerprint.credential === this.#state.credential.trim() &&
551
+ fingerprint.credentialSource === this.#state.credentialSource
552
+ );
553
+ }
554
+
307
555
  #submit(): void {
308
556
  if (this.#submitInFlight) return;
309
557
  this.#submitInFlight = true;
558
+ // Own the submit-time pre-write probe lifecycle: Esc-cancel during a
559
+ // stalled submit aborts the probe via #cancelDiscovery so the
560
+ // provider cannot be written after dismissal.
561
+ this.#submitAbort?.abort();
562
+ this.#submitAbort = new AbortController();
563
+ this.#submitGeneration += 1;
310
564
  let submission: unknown;
311
565
  try {
312
566
  submission = this.#onSubmit(this.#buildInput(this.#step === "force-confirm"));
313
567
  } catch (error) {
314
568
  this.#submitInFlight = false;
569
+ this.#endSubmitDiscovery();
315
570
  throw error;
316
571
  }
317
572
  if (!(submission instanceof Promise)) {
318
573
  this.#submitInFlight = false;
574
+ this.#endSubmitDiscovery();
319
575
  return;
320
576
  }
321
577
  void submission.then(
322
578
  () => {
323
579
  this.#submitInFlight = false;
580
+ this.#endSubmitDiscovery();
324
581
  },
325
582
  () => {
326
583
  this.#submitInFlight = false;
584
+ this.#endSubmitDiscovery();
327
585
  },
328
586
  );
329
587
  }
@@ -339,6 +597,8 @@ export class CustomProviderWizardComponent extends Container {
339
597
  .split(",")
340
598
  .map(model => model.trim())
341
599
  .filter(Boolean),
600
+ discover: this.#state.compatibility === "openai" ? this.#state.discoverModels : false,
601
+ discoverySignal: this.#submitAbort?.signal,
342
602
  force,
343
603
  };
344
604
 
@@ -346,9 +606,44 @@ export class CustomProviderWizardComponent extends Container {
346
606
  }
347
607
 
348
608
  complete(): void {
609
+ this.#cancelDiscovery();
349
610
  this.#clearLiteralCredential();
350
611
  }
351
612
 
613
+ /**
614
+ * Abort any in-flight probe and invalidate its generation so a late
615
+ * completion cannot render into (or retain credentials from) a
616
+ * dismissed wizard. Used by both submit and cancel paths.
617
+ */
618
+ #cancelDiscovery(): void {
619
+ this.#discoveryGeneration += 1;
620
+ this.#submitGeneration += 1;
621
+ this.#discoveryAbort?.abort();
622
+ this.#discoveryAbort = null;
623
+ this.#submitAbort?.abort();
624
+ this.#submitAbort = null;
625
+ this.#discoveryInFlight = false;
626
+ }
627
+
628
+ /** Clear the settled submit controller without aborting (submit finished). */
629
+ #endSubmitDiscovery(): void {
630
+ this.#submitAbort = null;
631
+ }
632
+
633
+ /** Generation token for the latest submit attempt. */
634
+ currentSubmitGeneration(): number {
635
+ return this.#submitGeneration;
636
+ }
637
+
638
+ /**
639
+ * Whether a submit attempt is still current: same generation and not
640
+ * dismissed. Revision or cancel invalidates pending completions so a
641
+ * stale refresh cannot report success or rerender over new inputs.
642
+ */
643
+ isSubmitCurrent(generation: number): boolean {
644
+ return generation === this.#submitGeneration && !this.#dismissed;
645
+ }
646
+
352
647
  #clearLiteralCredential(): void {
353
648
  if (this.#state.credentialSource === "literal") this.#state.credential = "";
354
649
  }
@@ -358,7 +653,8 @@ export class CustomProviderWizardComponent extends Container {
358
653
  this.#step === "confirm" ||
359
654
  this.#step === "force-confirm" ||
360
655
  this.#step === "compatibility" ||
361
- this.#step === "credential-source"
656
+ this.#step === "credential-source" ||
657
+ this.#step === "discover"
362
658
  ? 1
363
659
  : 0;
364
660
  this.#selectedIndex = (this.#selectedIndex + delta + maxIndex + 1) % (maxIndex + 1);
@@ -367,11 +663,16 @@ export class CustomProviderWizardComponent extends Container {
367
663
  }
368
664
 
369
665
  #goBack(): void {
666
+ if (this.#step === "discover" || this.#step === "confirm" || this.#step === "force-confirm") {
667
+ // Revision starts on entry to the editor, not when its new value is saved.
668
+ this.#cancelDiscovery();
669
+ }
370
670
  if (this.#step === "provider-id") this.#step = "compatibility";
371
671
  else if (this.#step === "base-url") this.#step = "provider-id";
372
672
  else if (this.#step === "credential-source") this.#step = "base-url";
373
673
  else if (this.#step === "credential") this.#step = "credential-source";
374
- else if (this.#step === "models") this.#step = "credential";
674
+ else if (this.#step === "discover") this.#step = "credential";
675
+ else if (this.#step === "models") this.#step = "discover";
375
676
  else if (this.#step === "confirm" || this.#step === "force-confirm") this.#step = "models";
376
677
  this.#selectedIndex = 0;
377
678
  this.#renderStep();
@@ -164,6 +164,15 @@ export class OAuthSelectorComponent extends Container {
164
164
  { kind: "auto", selectable: true },
165
165
  { kind: "add", selectable: this.#onAddAccount !== undefined },
166
166
  );
167
+ const firstAccount = this.#accountEntries.find(entry => entry.kind === "account");
168
+ if (firstAccount && !firstAccount.selectable) {
169
+ const addAccountIndex =
170
+ firstAccount.row.disabled && this.#onAddAccount !== undefined
171
+ ? this.#accountEntries.findIndex(entry => entry.kind === "add" && entry.selectable)
172
+ : -1;
173
+ this.#selectedIndex =
174
+ addAccountIndex >= 0 ? addAccountIndex : this.#accountEntries.findIndex(entry => entry.selectable);
175
+ }
167
176
  } else if (this.#accountEntries.some(entry => entry.kind === "account" && entry.selectable)) {
168
177
  this.#accountEntries.push({ kind: "all", selectable: this.#onAccountRemove !== undefined });
169
178
  }
@@ -39,6 +39,7 @@ export type SessionInspector = (sessionPath: string) => Promise<ResumeTailInspec
39
39
 
40
40
  type SelectorState =
41
41
  | { kind: "browsing" }
42
+ | { kind: "starring"; token: number }
42
43
  | { kind: "checking"; session: SessionInfo; token: number }
43
44
  | { kind: "confirming"; session: SessionInfo; token: number; identity: ResumeSessionIdentity }
44
45
  | { kind: "settled" };
@@ -52,12 +53,13 @@ class SessionList implements Component {
52
53
  onCancel?: () => void;
53
54
  onExit: () => void = () => {};
54
55
  onDeleteRequest?: (session: SessionInfo) => void;
56
+ onToggleStarRequest?: (session: SessionInfo) => void;
55
57
 
56
58
  constructor(
57
59
  private readonly allSessions: SessionInfo[],
58
60
  private readonly showCwd = false,
59
61
  ) {
60
- this.#filteredSessions = allSessions;
62
+ this.#filteredSessions = prioritizeStarredSessions(allSessions);
61
63
  this.#searchInput.onSubmit = () => {
62
64
  const selected = this.#filteredSessions[this.#selectedIndex];
63
65
  if (selected) this.onSelect?.(selected);
@@ -100,6 +102,17 @@ class SessionList implements Component {
100
102
  this.#filterSessions(this.#searchInput.getValue());
101
103
  }
102
104
 
105
+ setSessionStarred(sessionPath: string, starred: boolean): void {
106
+ const session = this.allSessions.find(session => session.path === sessionPath);
107
+ if (!session) return;
108
+ session.starred = starred;
109
+ this.#filterSessions(this.#searchInput.getValue());
110
+ this.#selectedIndex = Math.max(
111
+ 0,
112
+ this.#filteredSessions.findIndex(session => session.path === sessionPath),
113
+ );
114
+ }
115
+
103
116
  invalidate(): void {}
104
117
 
105
118
  render(width: number): string[] {
@@ -155,6 +168,8 @@ class SessionList implements Component {
155
168
  }
156
169
  if (start > 0 || end < this.#filteredSessions.length)
157
170
  lines.push(theme.fg("muted", ` (${this.#selectedIndex + 1}/${this.#filteredSessions.length})`));
171
+ if (this.onToggleStarRequest)
172
+ lines.push(theme.fg("muted", truncateToWidth(" [Ctrl+S to star/unstar selected session]", width)));
158
173
  lines.push(
159
174
  "",
160
175
  theme.fg("muted", " [Del to delete selected transcript/artifacts, Enter to select, Esc to cancel]"),
@@ -177,7 +192,10 @@ class SessionList implements Component {
177
192
  this.#selectedIndex += delta;
178
193
  this.#clampSelectedIndex();
179
194
  };
180
- if (matchesKey(keyData, "delete")) {
195
+ if (matchesKey(keyData, "ctrl+s")) {
196
+ const selected = this.#filteredSessions[this.#selectedIndex];
197
+ if (selected) this.onToggleStarRequest?.(selected);
198
+ } else if (matchesKey(keyData, "delete")) {
181
199
  const selected = this.#filteredSessions[this.#selectedIndex];
182
200
  if (selected) this.onDeleteRequest?.(selected);
183
201
  } else if (matchesKey(keyData, "up")) move(-1);
@@ -194,7 +212,7 @@ class SessionList implements Component {
194
212
  }
195
213
  }
196
214
 
197
- /** A one-shot resume consent selector. It never opens or mutates sessions. */
215
+ /** A one-shot resume consent selector with host-owned deletion and star persistence. */
198
216
  export class SessionSelectorComponent extends Container {
199
217
  #sessionList: SessionList;
200
218
  #confirmationDialog: HookSelectorComponent | null = null;
@@ -211,6 +229,7 @@ export class SessionSelectorComponent extends Container {
211
229
  private readonly onDelete?: (session: SessionInfo) => Promise<boolean>,
212
230
  private readonly inspector?: SessionInspector,
213
231
  private readonly onSelection?: (selection: SessionSelectionResult) => void,
232
+ private readonly onSetStarred?: (session: SessionInfo, starred: boolean) => Promise<void>,
214
233
  ) {
215
234
  super();
216
235
  this.addChild(new Spacer(1));
@@ -219,7 +238,7 @@ export class SessionSelectorComponent extends Container {
219
238
  this.addChild(new DynamicBorder());
220
239
  this.addChild(new Spacer(1));
221
240
  this.addChild(this.#messageContainer);
222
- this.#sessionList = new SessionList(prioritizeStarredSessions(sessions));
241
+ this.#sessionList = new SessionList([...sessions]);
223
242
  this.#sessionList.onSelect = session => {
224
243
  if (this.inspector) void this.#inspect(session);
225
244
  else this.#dispatchSelect(session.path);
@@ -227,6 +246,7 @@ export class SessionSelectorComponent extends Container {
227
246
  this.#sessionList.onCancel = () => this.#cancel();
228
247
  this.#sessionList.onExit = () => this.#exit();
229
248
  this.#sessionList.onDeleteRequest = session => this.#showDeleteConfirmation(session);
249
+ if (this.onSetStarred) this.#sessionList.onToggleStarRequest = session => void this.#toggleStar(session);
230
250
  this.addChild(this.#sessionList);
231
251
  this.addChild(new Spacer(1));
232
252
  this.addChild(new DynamicBorder());
@@ -294,6 +314,29 @@ export class SessionSelectorComponent extends Container {
294
314
  this.#sessionList.setInputFrozen(true);
295
315
  this.onExit();
296
316
  }
317
+ async #toggleStar(session: SessionInfo): Promise<void> {
318
+ if (!this.onSetStarred || this.#state.kind !== "browsing" || this.#confirmationDialog) return;
319
+ const token = ++this.#nextToken;
320
+ const starred = !session.starred;
321
+ this.#state = { kind: "starring", token };
322
+ this.#sessionList.setInputFrozen(true);
323
+ this.#clearError();
324
+ this.#requestRender();
325
+ try {
326
+ await this.onSetStarred(session, starred);
327
+ if (this.#state.kind !== "starring" || this.#state.token !== token) return;
328
+ this.#sessionList.setSessionStarred(session.path, starred);
329
+ } catch (error) {
330
+ if (this.#state.kind !== "starring" || this.#state.token !== token) return;
331
+ this.#showError(error instanceof Error ? error.message : String(error));
332
+ } finally {
333
+ if (this.#state.kind === "starring" && this.#state.token === token) {
334
+ this.#state = { kind: "browsing" };
335
+ this.#sessionList.setInputFrozen(false);
336
+ this.#requestRender();
337
+ }
338
+ }
339
+ }
297
340
  async #inspect(session: SessionInfo): Promise<void> {
298
341
  const inspector = this.inspector;
299
342
  if (!inspector || this.#state.kind !== "browsing") return;