@farhanic017/octocode 3.5.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 (716) hide show
  1. package/.swarm_lessons.json +85 -0
  2. package/AGENTS.md +151 -0
  3. package/BUN_SHELL_MIGRATION_PLAN.md +136 -0
  4. package/Dockerfile +18 -0
  5. package/README.md +15 -0
  6. package/bin/octocode +31 -0
  7. package/bunfig.toml +7 -0
  8. package/git +0 -0
  9. package/package.json +151 -0
  10. package/parsers-config.ts +1 -0
  11. package/script/bench-search.ts +115 -0
  12. package/script/bench-test-suite.ts +52 -0
  13. package/script/build.ts +243 -0
  14. package/script/generate.ts +14 -0
  15. package/script/httpapi-exercise.ts +1 -0
  16. package/script/octo-shim.mjs +34 -0
  17. package/script/postinstall.mjs +198 -0
  18. package/script/profile-test-files.ts +42 -0
  19. package/script/publish.ts +211 -0
  20. package/script/run-workspace-server +106 -0
  21. package/script/schema.ts +77 -0
  22. package/script/time.ts +6 -0
  23. package/script/trace-imports.ts +154 -0
  24. package/specs/effect/error-boundaries-plan.md +235 -0
  25. package/specs/effect/errors.md +207 -0
  26. package/specs/effect/facades.md +218 -0
  27. package/specs/effect/guide.md +247 -0
  28. package/specs/effect/instance-context.md +13 -0
  29. package/specs/effect/loose-ends.md +30 -0
  30. package/specs/effect/migration.md +62 -0
  31. package/specs/effect/routes.md +61 -0
  32. package/specs/effect/schema.md +88 -0
  33. package/specs/effect/server-package.md +58 -0
  34. package/specs/effect/todo.md +241 -0
  35. package/specs/effect/tools.md +88 -0
  36. package/specs/openapi-translation-cleanup.md +204 -0
  37. package/specs/tui-plugins.md +544 -0
  38. package/specs/v2/api.ts +67 -0
  39. package/specs/v2/message-shape.md +136 -0
  40. package/specs/v2/notifications.md +13 -0
  41. package/specs/v2/tui-command-shim.md +67 -0
  42. package/src/account/account.ts +459 -0
  43. package/src/account/repo.ts +170 -0
  44. package/src/account/schema.ts +99 -0
  45. package/src/account/url.ts +8 -0
  46. package/src/acp/agent.ts +95 -0
  47. package/src/acp/config-option.ts +203 -0
  48. package/src/acp/content.ts +250 -0
  49. package/src/acp/directory.ts +210 -0
  50. package/src/acp/error.ts +90 -0
  51. package/src/acp/event.ts +345 -0
  52. package/src/acp/permission.ts +145 -0
  53. package/src/acp/profile.ts +42 -0
  54. package/src/acp/service.ts +1062 -0
  55. package/src/acp/tool.ts +321 -0
  56. package/src/acp/usage.ts +239 -0
  57. package/src/agent/agent.ts +451 -0
  58. package/src/agent/generate.txt +75 -0
  59. package/src/agent/prompt/compaction.txt +30 -0
  60. package/src/agent/prompt/explore.txt +18 -0
  61. package/src/agent/prompt/summary.txt +11 -0
  62. package/src/agent/prompt/title.txt +50 -0
  63. package/src/agent/subagent-permissions.ts +35 -0
  64. package/src/audio.d.ts +14 -0
  65. package/src/auth/index.ts +96 -0
  66. package/src/background/job.ts +36 -0
  67. package/src/bus/global.ts +22 -0
  68. package/src/cli/bootstrap.ts +11 -0
  69. package/src/cli/cmd/account.ts +264 -0
  70. package/src/cli/cmd/acp.ts +76 -0
  71. package/src/cli/cmd/agent.ts +259 -0
  72. package/src/cli/cmd/attach.ts +97 -0
  73. package/src/cli/cmd/cmd.ts +7 -0
  74. package/src/cli/cmd/db.ts +62 -0
  75. package/src/cli/cmd/debug/agent.handler.ts +188 -0
  76. package/src/cli/cmd/debug/agent.ts +27 -0
  77. package/src/cli/cmd/debug/config.ts +14 -0
  78. package/src/cli/cmd/debug/file.ts +87 -0
  79. package/src/cli/cmd/debug/index.ts +87 -0
  80. package/src/cli/cmd/debug/lsp.ts +51 -0
  81. package/src/cli/cmd/debug/ripgrep.ts +99 -0
  82. package/src/cli/cmd/debug/scrap.ts +18 -0
  83. package/src/cli/cmd/debug/skill.ts +15 -0
  84. package/src/cli/cmd/debug/snapshot.ts +50 -0
  85. package/src/cli/cmd/debug/startup.ts +11 -0
  86. package/src/cli/cmd/debug/v2.ts +46 -0
  87. package/src/cli/cmd/export.ts +292 -0
  88. package/src/cli/cmd/generate.ts +54 -0
  89. package/src/cli/cmd/github.handler.ts +1594 -0
  90. package/src/cli/cmd/github.shared.ts +30 -0
  91. package/src/cli/cmd/github.ts +42 -0
  92. package/src/cli/cmd/import.ts +224 -0
  93. package/src/cli/cmd/mcp.ts +859 -0
  94. package/src/cli/cmd/models.ts +66 -0
  95. package/src/cli/cmd/plug.ts +230 -0
  96. package/src/cli/cmd/pr.ts +115 -0
  97. package/src/cli/cmd/prompt-display.ts +1 -0
  98. package/src/cli/cmd/providers.ts +550 -0
  99. package/src/cli/cmd/run/demo.ts +1274 -0
  100. package/src/cli/cmd/run/entry.body.ts +205 -0
  101. package/src/cli/cmd/run/footer.command.tsx +1088 -0
  102. package/src/cli/cmd/run/footer.menu.tsx +353 -0
  103. package/src/cli/cmd/run/footer.permission.tsx +482 -0
  104. package/src/cli/cmd/run/footer.prompt.tsx +1351 -0
  105. package/src/cli/cmd/run/footer.question.tsx +580 -0
  106. package/src/cli/cmd/run/footer.subagent.tsx +180 -0
  107. package/src/cli/cmd/run/footer.ts +1153 -0
  108. package/src/cli/cmd/run/footer.view.tsx +992 -0
  109. package/src/cli/cmd/run/footer.width.ts +27 -0
  110. package/src/cli/cmd/run/otel.ts +117 -0
  111. package/src/cli/cmd/run/permission.shared.ts +256 -0
  112. package/src/cli/cmd/run/prompt.editor.ts +157 -0
  113. package/src/cli/cmd/run/prompt.shared.ts +153 -0
  114. package/src/cli/cmd/run/question.shared.ts +340 -0
  115. package/src/cli/cmd/run/runtime.boot.ts +202 -0
  116. package/src/cli/cmd/run/runtime.lifecycle.ts +431 -0
  117. package/src/cli/cmd/run/runtime.queue.ts +349 -0
  118. package/src/cli/cmd/run/runtime.shared.ts +17 -0
  119. package/src/cli/cmd/run/runtime.stdin.ts +37 -0
  120. package/src/cli/cmd/run/runtime.ts +905 -0
  121. package/src/cli/cmd/run/scrollback.shared.ts +92 -0
  122. package/src/cli/cmd/run/scrollback.surface.ts +448 -0
  123. package/src/cli/cmd/run/scrollback.writer.tsx +353 -0
  124. package/src/cli/cmd/run/splash.ts +284 -0
  125. package/src/cli/cmd/run/stream.transport.ts +1465 -0
  126. package/src/cli/cmd/run/stream.ts +175 -0
  127. package/src/cli/cmd/run/subagent-data.ts +876 -0
  128. package/src/cli/cmd/run/theme.ts +690 -0
  129. package/src/cli/cmd/run/tool.ts +1489 -0
  130. package/src/cli/cmd/run/trace.ts +94 -0
  131. package/src/cli/cmd/run/turn-summary.ts +47 -0
  132. package/src/cli/cmd/run/types.ts +350 -0
  133. package/src/cli/cmd/run/variant.shared.ts +215 -0
  134. package/src/cli/cmd/run.ts +897 -0
  135. package/src/cli/cmd/serve.ts +24 -0
  136. package/src/cli/cmd/stats.ts +393 -0
  137. package/src/cli/cmd/tui.ts +264 -0
  138. package/src/cli/cmd/uninstall.ts +353 -0
  139. package/src/cli/cmd/upgrade.ts +74 -0
  140. package/src/cli/cmd/web.ts +84 -0
  141. package/src/cli/effect/prompt.ts +37 -0
  142. package/src/cli/effect-cmd.ts +96 -0
  143. package/src/cli/error.ts +118 -0
  144. package/src/cli/heap.ts +66 -0
  145. package/src/cli/logo.ts +1 -0
  146. package/src/cli/network.ts +64 -0
  147. package/src/cli/tui/layer.ts +7 -0
  148. package/src/cli/tui/photon_rs_bg-bq08arze.wasm +0 -0
  149. package/src/cli/tui/tree-sitter-3jzf13jk.wasm +0 -0
  150. package/src/cli/tui/tree-sitter-bash-hq5s6fxb.wasm +0 -0
  151. package/src/cli/tui/tree-sitter-powershell-ryb2ffqs.wasm +0 -0
  152. package/src/cli/tui/worker.ts +99 -0
  153. package/src/cli/ui.ts +144 -0
  154. package/src/cli/upgrade.ts +53 -0
  155. package/src/command/index.ts +189 -0
  156. package/src/command/template/initialize.txt +66 -0
  157. package/src/command/template/review.txt +101 -0
  158. package/src/command/terminal-ai-commands.ts +567 -0
  159. package/src/config/agent.ts +68 -0
  160. package/src/config/command.ts +45 -0
  161. package/src/config/config.ts +686 -0
  162. package/src/config/entry-name.ts +19 -0
  163. package/src/config/managed.ts +77 -0
  164. package/src/config/markdown.ts +36 -0
  165. package/src/config/parse.ts +79 -0
  166. package/src/config/paths.ts +47 -0
  167. package/src/config/plugin.ts +79 -0
  168. package/src/config/reference.ts +48 -0
  169. package/src/config/tui-cwd.ts +5 -0
  170. package/src/config/tui-host-attention.ts +21 -0
  171. package/src/config/tui-migrate.ts +156 -0
  172. package/src/config/tui.ts +294 -0
  173. package/src/config/variable.ts +91 -0
  174. package/src/control-plane/adapters/index.ts +41 -0
  175. package/src/control-plane/adapters/worktree.ts +96 -0
  176. package/src/control-plane/dev/README.md +19 -0
  177. package/src/control-plane/dev/debug-workspace-plugin.ts +73 -0
  178. package/src/control-plane/types.ts +59 -0
  179. package/src/control-plane/util.ts +39 -0
  180. package/src/control-plane/workspace-adapter-runtime.ts +51 -0
  181. package/src/control-plane/workspace-context.ts +26 -0
  182. package/src/control-plane/workspace.ts +1075 -0
  183. package/src/desktop/agent.ts +147 -0
  184. package/src/desktop/index.ts +247 -0
  185. package/src/desktop/platform.ts +97 -0
  186. package/src/desktop/stream.ts +64 -0
  187. package/src/desktop/vision.ts +52 -0
  188. package/src/desktop/websocket.ts +120 -0
  189. package/src/effect/app-runtime.ts +133 -0
  190. package/src/effect/bootstrap-runtime.ts +23 -0
  191. package/src/effect/bridge.ts +84 -0
  192. package/src/effect/config-service.ts +67 -0
  193. package/src/effect/instance-ref.ts +11 -0
  194. package/src/effect/instance-registry.ts +12 -0
  195. package/src/effect/instance-state.ts +72 -0
  196. package/src/effect/promise.ts +17 -0
  197. package/src/effect/run-service.ts +47 -0
  198. package/src/effect/runner.ts +217 -0
  199. package/src/effect/runtime-flags.ts +76 -0
  200. package/src/env/index.ts +40 -0
  201. package/src/event-v2-bridge.ts +76 -0
  202. package/src/format/formatter.ts +404 -0
  203. package/src/format/index.ts +212 -0
  204. package/src/git/index.ts +347 -0
  205. package/src/id/id.ts +80 -0
  206. package/src/ide/index.ts +70 -0
  207. package/src/image/image.ts +177 -0
  208. package/src/index.ts +208 -0
  209. package/src/installation/index.ts +350 -0
  210. package/src/lsp/client.ts +686 -0
  211. package/src/lsp/diagnostic.ts +29 -0
  212. package/src/lsp/language.ts +121 -0
  213. package/src/lsp/launch.ts +21 -0
  214. package/src/lsp/lsp.ts +517 -0
  215. package/src/lsp/server.ts +2116 -0
  216. package/src/markdown.d.ts +4 -0
  217. package/src/mcp/auth.ts +171 -0
  218. package/src/mcp/builtin.ts +92 -0
  219. package/src/mcp/index.ts +1000 -0
  220. package/src/mcp/oauth-callback.ts +232 -0
  221. package/src/mcp/oauth-provider.ts +217 -0
  222. package/src/node.ts +5 -0
  223. package/src/patch/index.ts +689 -0
  224. package/src/permission/arity.ts +163 -0
  225. package/src/permission/evaluate.ts +1 -0
  226. package/src/permission/index.ts +230 -0
  227. package/src/plugin/azure.ts +26 -0
  228. package/src/plugin/cloudflare.ts +76 -0
  229. package/src/plugin/digitalocean.ts +391 -0
  230. package/src/plugin/github-copilot/copilot.ts +417 -0
  231. package/src/plugin/github-copilot/models.ts +246 -0
  232. package/src/plugin/index.ts +320 -0
  233. package/src/plugin/install.ts +439 -0
  234. package/src/plugin/loader.ts +237 -0
  235. package/src/plugin/meta.ts +188 -0
  236. package/src/plugin/openai/README.md +31 -0
  237. package/src/plugin/openai/codex.ts +647 -0
  238. package/src/plugin/openai/ws-pool.ts +290 -0
  239. package/src/plugin/openai/ws.ts +381 -0
  240. package/src/plugin/shared.ts +323 -0
  241. package/src/plugin/tui/internal.ts +12 -0
  242. package/src/plugin/tui/runtime.ts +1174 -0
  243. package/src/plugin/xai.ts +742 -0
  244. package/src/project/bootstrap-service.ts +9 -0
  245. package/src/project/bootstrap.ts +80 -0
  246. package/src/project/instance-context.ts +24 -0
  247. package/src/project/instance-layer.ts +11 -0
  248. package/src/project/instance-runtime.ts +16 -0
  249. package/src/project/instance-store.ts +207 -0
  250. package/src/project/project.ts +520 -0
  251. package/src/project/vcs.ts +435 -0
  252. package/src/provider/auth.ts +230 -0
  253. package/src/provider/error.ts +188 -0
  254. package/src/provider/model-status.ts +8 -0
  255. package/src/provider/provider.ts +2037 -0
  256. package/src/provider/transform.ts +1367 -0
  257. package/src/pty-preparation.ts +30 -0
  258. package/src/question/index.ts +229 -0
  259. package/src/question/schema.ts +10 -0
  260. package/src/reference/reference.ts +239 -0
  261. package/src/reference/repository-cache.ts +320 -0
  262. package/src/server/auth.ts +48 -0
  263. package/src/server/cors.ts +34 -0
  264. package/src/server/event.ts +13 -0
  265. package/src/server/global-lifecycle.ts +37 -0
  266. package/src/server/init-projectors.ts +3 -0
  267. package/src/server/mdns.ts +60 -0
  268. package/src/server/projectors.ts +1 -0
  269. package/src/server/proxy-util.ts +48 -0
  270. package/src/server/routes/instance/httpapi/AGENTS.md +39 -0
  271. package/src/server/routes/instance/httpapi/api.ts +80 -0
  272. package/src/server/routes/instance/httpapi/errors.ts +193 -0
  273. package/src/server/routes/instance/httpapi/groups/config.ts +65 -0
  274. package/src/server/routes/instance/httpapi/groups/control-plane.ts +35 -0
  275. package/src/server/routes/instance/httpapi/groups/control.ts +76 -0
  276. package/src/server/routes/instance/httpapi/groups/event.ts +29 -0
  277. package/src/server/routes/instance/httpapi/groups/experimental.ts +260 -0
  278. package/src/server/routes/instance/httpapi/groups/file.ts +172 -0
  279. package/src/server/routes/instance/httpapi/groups/global.ts +138 -0
  280. package/src/server/routes/instance/httpapi/groups/instance.ts +206 -0
  281. package/src/server/routes/instance/httpapi/groups/mcp.ts +156 -0
  282. package/src/server/routes/instance/httpapi/groups/metadata.ts +18 -0
  283. package/src/server/routes/instance/httpapi/groups/permission.ts +61 -0
  284. package/src/server/routes/instance/httpapi/groups/project-copy.ts +88 -0
  285. package/src/server/routes/instance/httpapi/groups/project.ts +93 -0
  286. package/src/server/routes/instance/httpapi/groups/provider.ts +101 -0
  287. package/src/server/routes/instance/httpapi/groups/pty.ts +172 -0
  288. package/src/server/routes/instance/httpapi/groups/query.ts +12 -0
  289. package/src/server/routes/instance/httpapi/groups/question.ts +74 -0
  290. package/src/server/routes/instance/httpapi/groups/reference.ts +60 -0
  291. package/src/server/routes/instance/httpapi/groups/sync.ts +113 -0
  292. package/src/server/routes/instance/httpapi/groups/tui.ts +208 -0
  293. package/src/server/routes/instance/httpapi/groups/workspace.ts +141 -0
  294. package/src/server/routes/instance/httpapi/handlers/config.ts +34 -0
  295. package/src/server/routes/instance/httpapi/handlers/control-plane.ts +37 -0
  296. package/src/server/routes/instance/httpapi/handlers/control.ts +37 -0
  297. package/src/server/routes/instance/httpapi/handlers/event.ts +102 -0
  298. package/src/server/routes/instance/httpapi/handlers/experimental.ts +187 -0
  299. package/src/server/routes/instance/httpapi/handlers/file.ts +128 -0
  300. package/src/server/routes/instance/httpapi/handlers/global.ts +157 -0
  301. package/src/server/routes/instance/httpapi/handlers/instance.ts +110 -0
  302. package/src/server/routes/instance/httpapi/handlers/mcp.ts +111 -0
  303. package/src/server/routes/instance/httpapi/handlers/permission.ts +41 -0
  304. package/src/server/routes/instance/httpapi/handlers/project-copy.ts +157 -0
  305. package/src/server/routes/instance/httpapi/handlers/project.ts +63 -0
  306. package/src/server/routes/instance/httpapi/handlers/provider.ts +113 -0
  307. package/src/server/routes/instance/httpapi/handlers/pty.ts +258 -0
  308. package/src/server/routes/instance/httpapi/handlers/question.ts +54 -0
  309. package/src/server/routes/instance/httpapi/handlers/reference.ts +27 -0
  310. package/src/server/routes/instance/httpapi/handlers/sync.ts +95 -0
  311. package/src/server/routes/instance/httpapi/handlers/tui.ts +131 -0
  312. package/src/server/routes/instance/httpapi/handlers/workspace.ts +102 -0
  313. package/src/server/routes/instance/httpapi/lifecycle.ts +57 -0
  314. package/src/server/routes/instance/httpapi/middleware/authorization.ts +150 -0
  315. package/src/server/routes/instance/httpapi/middleware/compression.ts +64 -0
  316. package/src/server/routes/instance/httpapi/middleware/cors-vary.ts +29 -0
  317. package/src/server/routes/instance/httpapi/middleware/error.ts +36 -0
  318. package/src/server/routes/instance/httpapi/middleware/fence.ts +25 -0
  319. package/src/server/routes/instance/httpapi/middleware/instance-context.ts +43 -0
  320. package/src/server/routes/instance/httpapi/middleware/proxy.ts +108 -0
  321. package/src/server/routes/instance/httpapi/middleware/schema-error.ts +42 -0
  322. package/src/server/routes/instance/httpapi/middleware/workspace-routing.ts +250 -0
  323. package/src/server/routes/instance/httpapi/public.ts +535 -0
  324. package/src/server/routes/instance/httpapi/server.ts +281 -0
  325. package/src/server/routes/instance/httpapi/websocket-tracker.ts +57 -0
  326. package/src/server/server.ts +218 -0
  327. package/src/server/shared/fence.ts +68 -0
  328. package/src/server/shared/pty-ticket.ts +15 -0
  329. package/src/server/shared/public-ui.ts +12 -0
  330. package/src/server/shared/tui-control.ts +28 -0
  331. package/src/server/shared/ui.ts +108 -0
  332. package/src/server/shared/workspace-routing.ts +38 -0
  333. package/src/server/tui-event.ts +53 -0
  334. package/src/share/share-next.ts +379 -0
  335. package/src/shell/shell.ts +215 -0
  336. package/src/skill/discovery.ts +115 -0
  337. package/src/skill/index.ts +382 -0
  338. package/src/snapshot/index.ts +762 -0
  339. package/src/sql.d.ts +4 -0
  340. package/src/storage/schema.ts +5 -0
  341. package/src/storage/storage.ts +329 -0
  342. package/src/swarm/evolution.ts +304 -0
  343. package/src/swarm/index.ts +7 -0
  344. package/src/swarm/learning.ts +736 -0
  345. package/src/swarm/vision.ts +131 -0
  346. package/src/sync/README.md +179 -0
  347. package/src/sync/schema.ts +11 -0
  348. package/src/temporary.ts +33 -0
  349. package/src/tool/apply_patch.ts +313 -0
  350. package/src/tool/apply_patch.txt +33 -0
  351. package/src/tool/browser_click.ts +51 -0
  352. package/src/tool/browser_drag.ts +76 -0
  353. package/src/tool/browser_evaluate.ts +51 -0
  354. package/src/tool/browser_hover.ts +51 -0
  355. package/src/tool/browser_navigate.ts +45 -0
  356. package/src/tool/browser_screenshot.ts +66 -0
  357. package/src/tool/browser_select.ts +52 -0
  358. package/src/tool/browser_type.ts +52 -0
  359. package/src/tool/browser_wait.ts +60 -0
  360. package/src/tool/desktop_clipboard.ts +82 -0
  361. package/src/tool/desktop_control.ts +160 -0
  362. package/src/tool/desktop_record.ts +93 -0
  363. package/src/tool/desktop_replay.ts +99 -0
  364. package/src/tool/desktop_screen_record.ts +143 -0
  365. package/src/tool/desktop_window.ts +142 -0
  366. package/src/tool/desktop_workflow.ts +81 -0
  367. package/src/tool/edit.ts +737 -0
  368. package/src/tool/edit.txt +10 -0
  369. package/src/tool/external-directory.ts +49 -0
  370. package/src/tool/glob.ts +84 -0
  371. package/src/tool/glob.txt +6 -0
  372. package/src/tool/grep.ts +140 -0
  373. package/src/tool/grep.txt +8 -0
  374. package/src/tool/invalid.ts +21 -0
  375. package/src/tool/json-schema.ts +164 -0
  376. package/src/tool/lsp.ts +113 -0
  377. package/src/tool/lsp.txt +24 -0
  378. package/src/tool/mcp-websearch.ts +96 -0
  379. package/src/tool/open_app.ts +55 -0
  380. package/src/tool/open_terminal.ts +69 -0
  381. package/src/tool/plan-enter.txt +14 -0
  382. package/src/tool/plan-exit.txt +13 -0
  383. package/src/tool/plan.ts +79 -0
  384. package/src/tool/question.ts +44 -0
  385. package/src/tool/question.txt +10 -0
  386. package/src/tool/read.ts +392 -0
  387. package/src/tool/read.txt +14 -0
  388. package/src/tool/registry.ts +527 -0
  389. package/src/tool/schema.ts +14 -0
  390. package/src/tool/screenshot.ts +72 -0
  391. package/src/tool/shell/id.ts +19 -0
  392. package/src/tool/shell/prompt.ts +307 -0
  393. package/src/tool/shell/shell.txt +21 -0
  394. package/src/tool/shell.ts +669 -0
  395. package/src/tool/skill.ts +72 -0
  396. package/src/tool/skill.txt +5 -0
  397. package/src/tool/task.ts +339 -0
  398. package/src/tool/task.txt +19 -0
  399. package/src/tool/todo.ts +57 -0
  400. package/src/tool/todowrite.txt +44 -0
  401. package/src/tool/tool.ts +180 -0
  402. package/src/tool/truncate.ts +160 -0
  403. package/src/tool/truncation-dir.ts +4 -0
  404. package/src/tool/visual_qa.ts +77 -0
  405. package/src/tool/webfetch.ts +192 -0
  406. package/src/tool/webfetch.txt +13 -0
  407. package/src/tool/websearch.ts +143 -0
  408. package/src/tool/websearch.txt +14 -0
  409. package/src/tool/write.ts +104 -0
  410. package/src/tool/write.txt +8 -0
  411. package/src/util/archive.ts +17 -0
  412. package/src/util/bom.ts +27 -0
  413. package/src/util/data-url.ts +9 -0
  414. package/src/util/defer.ts +10 -0
  415. package/src/util/effect-http-client.ts +11 -0
  416. package/src/util/error.ts +1 -0
  417. package/src/util/filesystem.ts +251 -0
  418. package/src/util/iife.ts +3 -0
  419. package/src/util/lazy.ts +20 -0
  420. package/src/util/local-context.ts +25 -0
  421. package/src/util/locale.ts +2 -0
  422. package/src/util/media.ts +26 -0
  423. package/src/util/process.ts +177 -0
  424. package/src/util/proxy-env.ts +72 -0
  425. package/src/util/queue.ts +32 -0
  426. package/src/util/record.ts +1 -0
  427. package/src/util/repository.ts +232 -0
  428. package/src/util/rpc.ts +66 -0
  429. package/src/util/signal.ts +12 -0
  430. package/src/util/sys-monitor.ts +223 -0
  431. package/src/util/timeout.ts +13 -0
  432. package/src/util/wildcard.ts +59 -0
  433. package/src/worktree/index.ts +645 -0
  434. package/sst-env.d.ts +10 -0
  435. package/test/AGENTS.md +204 -0
  436. package/test/EFFECT_TEST_MIGRATION.md +169 -0
  437. package/test/account/repo.test.ts +353 -0
  438. package/test/account/service.test.ts +453 -0
  439. package/test/acp/config-option.test.ts +229 -0
  440. package/test/acp/content.test.ts +201 -0
  441. package/test/acp/directory.test.ts +186 -0
  442. package/test/acp/error.test.ts +67 -0
  443. package/test/acp/event.test.ts +743 -0
  444. package/test/acp/permission.test.ts +273 -0
  445. package/test/acp/tool.test.ts +210 -0
  446. package/test/acp/usage.test.ts +315 -0
  447. package/test/agent/agent.test.ts +711 -0
  448. package/test/agent/plan-mode-subagent-bypass.test.ts +213 -0
  449. package/test/agent/plugin-agent-regression.test.ts +62 -0
  450. package/test/auth/auth.test.ts +77 -0
  451. package/test/background/job.test.ts +243 -0
  452. package/test/cli/account.test.ts +30 -0
  453. package/test/cli/acp/acp-test-client.ts +97 -0
  454. package/test/cli/acp/config-options.test.ts +103 -0
  455. package/test/cli/acp/helpers.ts +96 -0
  456. package/test/cli/acp/initialize-auth.test.ts +61 -0
  457. package/test/cli/acp/lifecycle.test.ts +118 -0
  458. package/test/cli/acp/prompt-content.test.ts +97 -0
  459. package/test/cli/acp/skills.test.ts +38 -0
  460. package/test/cli/cmd/tui/attention.test.ts +484 -0
  461. package/test/cli/effect-cmd-instance-als.test.ts +39 -0
  462. package/test/cli/error.test.ts +95 -0
  463. package/test/cli/github-action.test.ts +199 -0
  464. package/test/cli/github-remote.test.ts +90 -0
  465. package/test/cli/help/__snapshots__/help-snapshots.test.ts.snap +631 -0
  466. package/test/cli/help/help-snapshots.test.ts +137 -0
  467. package/test/cli/import.test.ts +54 -0
  468. package/test/cli/mcp-add.test.ts +74 -0
  469. package/test/cli/plugin-auth-picker.test.ts +120 -0
  470. package/test/cli/run/entry.body.test.ts +536 -0
  471. package/test/cli/run/footer.menu.test.ts +43 -0
  472. package/test/cli/run/footer.view.test.tsx +1336 -0
  473. package/test/cli/run/footer.width.test.ts +35 -0
  474. package/test/cli/run/permission.shared.test.ts +144 -0
  475. package/test/cli/run/prompt.editor.test.ts +101 -0
  476. package/test/cli/run/prompt.shared.test.ts +101 -0
  477. package/test/cli/run/question.shared.test.ts +115 -0
  478. package/test/cli/run/run-process.test.ts +84 -0
  479. package/test/cli/run/runtime.boot.test.ts +283 -0
  480. package/test/cli/run/runtime.queue.test.ts +481 -0
  481. package/test/cli/run/runtime.stdin.test.ts +71 -0
  482. package/test/cli/run/runtime.test.ts +238 -0
  483. package/test/cli/run/scrollback.surface.test.ts +1065 -0
  484. package/test/cli/run/stream.test.ts +56 -0
  485. package/test/cli/run/stream.transport.test.ts +2363 -0
  486. package/test/cli/run/subagent-data.test.ts +547 -0
  487. package/test/cli/run/theme.test.ts +177 -0
  488. package/test/cli/run/variant.shared.test.ts +217 -0
  489. package/test/cli/serve/serve-process.test.ts +61 -0
  490. package/test/cli/smokes/read-only.test.ts +115 -0
  491. package/test/cli/tui/attach.test.ts +11 -0
  492. package/test/cli/tui/editor-context-zed.test.ts +379 -0
  493. package/test/cli/tui/editor-context.test.tsx +297 -0
  494. package/test/cli/tui/plugin-add.test.ts +110 -0
  495. package/test/cli/tui/plugin-install.test.ts +87 -0
  496. package/test/cli/tui/plugin-lifecycle.test.ts +224 -0
  497. package/test/cli/tui/plugin-loader-entrypoint.test.ts +485 -0
  498. package/test/cli/tui/plugin-loader-pure.test.ts +72 -0
  499. package/test/cli/tui/plugin-loader.test.ts +1332 -0
  500. package/test/cli/tui/plugin-toggle.test.ts +264 -0
  501. package/test/cli/tui/thread.test.ts +36 -0
  502. package/test/cli.test.ts +7 -0
  503. package/test/command/acp-slash-detect.test.ts +202 -0
  504. package/test/command/demo-slash.test.ts +213 -0
  505. package/test/command/slash-head.test.ts +232 -0
  506. package/test/command/slash-parsing.test.ts +193 -0
  507. package/test/command/terminal-ai-commands.test.ts +536 -0
  508. package/test/config/agent-color.test.ts +47 -0
  509. package/test/config/config.test.ts +1994 -0
  510. package/test/config/entry-name.test.ts +57 -0
  511. package/test/config/fixtures/empty-frontmatter.md +4 -0
  512. package/test/config/fixtures/frontmatter.md +28 -0
  513. package/test/config/fixtures/markdown-header.md +11 -0
  514. package/test/config/fixtures/no-frontmatter.md +1 -0
  515. package/test/config/fixtures/weird-model-id.md +13 -0
  516. package/test/config/lsp.test.ts +69 -0
  517. package/test/config/markdown.test.ts +228 -0
  518. package/test/config/plugin.test.ts +0 -0
  519. package/test/config/tui.test.ts +886 -0
  520. package/test/control-plane/adapters.test.ts +71 -0
  521. package/test/control-plane/workspace.test.ts +1704 -0
  522. package/test/effect/app-runtime-logger.test.ts +105 -0
  523. package/test/effect/config-service.test.ts +65 -0
  524. package/test/effect/instance-state.test.ts +391 -0
  525. package/test/effect/run-service.test.ts +89 -0
  526. package/test/effect/runner.test.ts +514 -0
  527. package/test/effect/runtime-flags.test.ts +373 -0
  528. package/test/fake/account.ts +9 -0
  529. package/test/fake/auth.ts +8 -0
  530. package/test/fake/npm.ts +8 -0
  531. package/test/fake/provider.ts +82 -0
  532. package/test/fake/skill.ts +8 -0
  533. package/test/filesystem/filesystem.test.ts +319 -0
  534. package/test/fixture/agent-plugin.constants.ts +6 -0
  535. package/test/fixture/agent-plugin.ts +12 -0
  536. package/test/fixture/config.ts +23 -0
  537. package/test/fixture/db.ts +11 -0
  538. package/test/fixture/fixture.test.ts +26 -0
  539. package/test/fixture/fixture.ts +224 -0
  540. package/test/fixture/flag.ts +20 -0
  541. package/test/fixture/flock-worker.ts +72 -0
  542. package/test/fixture/lsp/fake-lsp-server.js +249 -0
  543. package/test/fixture/plug-worker.ts +96 -0
  544. package/test/fixture/plugin-meta-worker.ts +19 -0
  545. package/test/fixture/plugin.ts +10 -0
  546. package/test/fixture/skills/agents-sdk/SKILL.md +152 -0
  547. package/test/fixture/skills/agents-sdk/references/callable.md +92 -0
  548. package/test/fixture/skills/cloudflare/SKILL.md +211 -0
  549. package/test/fixture/skills/index.json +6 -0
  550. package/test/fixture/tui-environment.tsx +32 -0
  551. package/test/fixture/tui-plugin.ts +357 -0
  552. package/test/fixture/tui-runtime.ts +56 -0
  553. package/test/fixture/tui-sdk.ts +82 -0
  554. package/test/fixture/workspace.ts +30 -0
  555. package/test/format/format.test.ts +228 -0
  556. package/test/git/git.test.ts +178 -0
  557. package/test/ide/ide.test.ts +82 -0
  558. package/test/image/fixtures/picture-5mb-base64.png +0 -0
  559. package/test/image/image.test.ts +123 -0
  560. package/test/installation/installation.test.ts +231 -0
  561. package/test/lib/cli-process.ts +459 -0
  562. package/test/lib/effect.ts +177 -0
  563. package/test/lib/filesystem.ts +10 -0
  564. package/test/lib/llm-server.ts +771 -0
  565. package/test/lib/snapshot.ts +73 -0
  566. package/test/lib/test-provider.ts +37 -0
  567. package/test/lib/websocket.ts +46 -0
  568. package/test/lsp/client.test.ts +493 -0
  569. package/test/lsp/index.test.ts +232 -0
  570. package/test/lsp/jdtls-root.test.ts +459 -0
  571. package/test/lsp/launch.test.ts +22 -0
  572. package/test/lsp/lifecycle.test.ts +160 -0
  573. package/test/mcp/auth.test.ts +78 -0
  574. package/test/mcp/builtin.test.ts +95 -0
  575. package/test/mcp/headers.test.ts +126 -0
  576. package/test/mcp/lifecycle.test.ts +999 -0
  577. package/test/mcp/oauth-auto-connect.test.ts +274 -0
  578. package/test/mcp/oauth-browser.test.ts +232 -0
  579. package/test/mcp/oauth-callback.test.ts +34 -0
  580. package/test/mcp/oauth-provider.test.ts +61 -0
  581. package/test/patch/patch.test.ts +383 -0
  582. package/test/permission/arity.test.ts +33 -0
  583. package/test/permission/next.test.ts +1176 -0
  584. package/test/permission-task.test.ts +318 -0
  585. package/test/plugin/auth-override.test.ts +105 -0
  586. package/test/plugin/cloudflare.test.ts +68 -0
  587. package/test/plugin/codex.test.ts +247 -0
  588. package/test/plugin/github-copilot-models.test.ts +332 -0
  589. package/test/plugin/install-concurrency.test.ts +140 -0
  590. package/test/plugin/install.test.ts +573 -0
  591. package/test/plugin/loader-shared.test.ts +1303 -0
  592. package/test/plugin/meta.test.ts +137 -0
  593. package/test/plugin/openai-rollout.test.ts +17 -0
  594. package/test/plugin/openai-ws.test.ts +877 -0
  595. package/test/plugin/shared.test.ts +88 -0
  596. package/test/plugin/trigger.test.ts +120 -0
  597. package/test/plugin/workspace-adapter.test.ts +137 -0
  598. package/test/plugin/xai.test.ts +634 -0
  599. package/test/preload.ts +99 -0
  600. package/test/project/instance-bootstrap.test.ts +110 -0
  601. package/test/project/instance.test.ts +245 -0
  602. package/test/project/migrate-global.test.ts +170 -0
  603. package/test/project/project-directory.test.ts +169 -0
  604. package/test/project/project.test.ts +818 -0
  605. package/test/project/vcs.test.ts +336 -0
  606. package/test/project/worktree-remove.test.ts +126 -0
  607. package/test/project/worktree.test.ts +320 -0
  608. package/test/provider/amazon-bedrock.test.ts +360 -0
  609. package/test/provider/cf-ai-gateway-e2e.test.ts +132 -0
  610. package/test/provider/digitalocean.test.ts +123 -0
  611. package/test/provider/gitlab-duo.test.ts +412 -0
  612. package/test/provider/header-timeout.test.ts +233 -0
  613. package/test/provider/model-status.test.ts +61 -0
  614. package/test/provider/provider.test.ts +1795 -0
  615. package/test/provider/transform.test.ts +3937 -0
  616. package/test/pty/pty-shell.test.ts +102 -0
  617. package/test/question/question.test.ts +465 -0
  618. package/test/reference/reference.test.ts +311 -0
  619. package/test/server/AGENTS.md +15 -0
  620. package/test/server/auth.test.ts +59 -0
  621. package/test/server/global-bus.ts +31 -0
  622. package/test/server/httpapi-authorization.test.ts +174 -0
  623. package/test/server/httpapi-compression.test.ts +154 -0
  624. package/test/server/httpapi-config.test.ts +113 -0
  625. package/test/server/httpapi-control-plane.test.ts +63 -0
  626. package/test/server/httpapi-cors-vary.test.ts +66 -0
  627. package/test/server/httpapi-cors.test.ts +122 -0
  628. package/test/server/httpapi-error-middleware.test.ts +96 -0
  629. package/test/server/httpapi-event.test.ts +97 -0
  630. package/test/server/httpapi-exercise/assertions.ts +64 -0
  631. package/test/server/httpapi-exercise/backend.ts +144 -0
  632. package/test/server/httpapi-exercise/dsl.ts +210 -0
  633. package/test/server/httpapi-exercise/environment.ts +40 -0
  634. package/test/server/httpapi-exercise/index.ts +1538 -0
  635. package/test/server/httpapi-exercise/report.ts +66 -0
  636. package/test/server/httpapi-exercise/routing.ts +96 -0
  637. package/test/server/httpapi-exercise/runner.ts +267 -0
  638. package/test/server/httpapi-exercise/runtime.ts +52 -0
  639. package/test/server/httpapi-exercise/types.ts +123 -0
  640. package/test/server/httpapi-experimental.test.ts +300 -0
  641. package/test/server/httpapi-file.test.ts +76 -0
  642. package/test/server/httpapi-global.test.ts +66 -0
  643. package/test/server/httpapi-instance-context.test.ts +347 -0
  644. package/test/server/httpapi-instance-route-auth.test.ts +84 -0
  645. package/test/server/httpapi-instance.test.ts +265 -0
  646. package/test/server/httpapi-layer.ts +33 -0
  647. package/test/server/httpapi-listen.test.ts +415 -0
  648. package/test/server/httpapi-mcp-oauth.test.ts +73 -0
  649. package/test/server/httpapi-mcp.test.ts +234 -0
  650. package/test/server/httpapi-mdns.test.ts +82 -0
  651. package/test/server/httpapi-promptasync-context.test.ts +222 -0
  652. package/test/server/httpapi-provider.test.ts +403 -0
  653. package/test/server/httpapi-pty.test.ts +275 -0
  654. package/test/server/httpapi-public-openapi.test.ts +297 -0
  655. package/test/server/httpapi-query-schema-drift.test.ts +330 -0
  656. package/test/server/httpapi-reference.test.ts +56 -0
  657. package/test/server/httpapi-schema-error-body.test.ts +165 -0
  658. package/test/server/httpapi-sdk.test.ts +909 -0
  659. package/test/server/httpapi-sync.test.ts +154 -0
  660. package/test/server/httpapi-ui.test.ts +456 -0
  661. package/test/server/httpapi-v2-location.test.ts +85 -0
  662. package/test/server/httpapi-workspace-routing.test.ts +554 -0
  663. package/test/server/httpapi-workspace.test.ts +515 -0
  664. package/test/server/project-copy.test.ts +101 -0
  665. package/test/server/project-init-git.test.ts +117 -0
  666. package/test/server/proxy-util.test.ts +113 -0
  667. package/test/server/sdk-error-shape.test.ts +84 -0
  668. package/test/server/sdk-v1-smoke.test.ts +60 -0
  669. package/test/server/workspace-proxy.test.ts +181 -0
  670. package/test/server/workspace-routing.test.ts +94 -0
  671. package/test/server/worktree-endpoint-repro.test.ts +307 -0
  672. package/test/share/share-next.test.ts +344 -0
  673. package/test/shell/shell.test.ts +99 -0
  674. package/test/skill/discovery.test.ts +139 -0
  675. package/test/skill/skill.test.ts +571 -0
  676. package/test/snapshot/snapshot.test.ts +1121 -0
  677. package/test/storage/storage.test.ts +296 -0
  678. package/test/storage/workspace-time-migration.test.ts +50 -0
  679. package/test/tool/__snapshots__/parameters.test.ts.snap +484 -0
  680. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  681. package/test/tool/apply_patch.test.ts +533 -0
  682. package/test/tool/browser.integration.test.ts +141 -0
  683. package/test/tool/desktop.integration.test.ts +129 -0
  684. package/test/tool/desktop.test.ts +85 -0
  685. package/test/tool/edit.test.ts +578 -0
  686. package/test/tool/external-directory.test.ts +167 -0
  687. package/test/tool/fixtures/large-image.png +0 -0
  688. package/test/tool/fixtures/models-api.json +117299 -0
  689. package/test/tool/glob.test.ts +188 -0
  690. package/test/tool/grep.test.ts +266 -0
  691. package/test/tool/lsp.test.ts +181 -0
  692. package/test/tool/parameters.test.ts +293 -0
  693. package/test/tool/question.test.ts +138 -0
  694. package/test/tool/read.test.ts +659 -0
  695. package/test/tool/registry.test.ts +539 -0
  696. package/test/tool/shell.test.ts +1256 -0
  697. package/test/tool/skill.test.ts +135 -0
  698. package/test/tool/task.test.ts +901 -0
  699. package/test/tool/tool-define.test.ts +153 -0
  700. package/test/tool/truncation.test.ts +266 -0
  701. package/test/tool/webfetch.test.ts +113 -0
  702. package/test/tool/websearch.test.ts +99 -0
  703. package/test/tool/write.test.ts +276 -0
  704. package/test/util/data-url.test.ts +14 -0
  705. package/test/util/error.test.ts +16 -0
  706. package/test/util/filesystem.test.ts +656 -0
  707. package/test/util/glob.test.ts +164 -0
  708. package/test/util/iife.test.ts +36 -0
  709. package/test/util/lazy.test.ts +50 -0
  710. package/test/util/log.test.ts +77 -0
  711. package/test/util/module.test.ts +59 -0
  712. package/test/util/process.test.ts +128 -0
  713. package/test/util/repository.test.ts +93 -0
  714. package/test/util/timeout.test.ts +21 -0
  715. package/test/util/wildcard.test.ts +90 -0
  716. package/tsconfig.json +16 -0
@@ -0,0 +1,1153 @@
1
+ // RunFooter -- the mutable control surface for direct interactive mode.
2
+ //
3
+ // In the split-footer architecture, scrollback is immutable (append-only)
4
+ // and the footer is the only region that can repaint. RunFooter owns both
5
+ // sides of that boundary:
6
+ //
7
+ // Scrollback: append() queues StreamCommit entries and flush() drains them
8
+ // through retained scrollback surfaces. Commits coalesce in a microtask
9
+ // queue so direct-mode transcript updates still preserve ordering without
10
+ // rebuilding the session model.
11
+ //
12
+ // Footer: event() updates the SolidJS signal-backed FooterState, which
13
+ // drives the reactive footer view (prompt, status, permission, question).
14
+ // present() swaps the active footer view and resizes the footer region.
15
+ //
16
+ // Lifecycle:
17
+ // - close() flushes pending commits and notifies listeners (the prompt
18
+ // queue uses this to know when to stop).
19
+ // - destroy() does the same plus tears down event listeners and clears
20
+ // internal state.
21
+ // - The renderer's DESTROY event triggers destroy() so the footer
22
+ // doesn't outlive the renderer.
23
+ //
24
+ // Ctrl-c clears a live prompt draft first; otherwise interrupt and exit use a
25
+ // two-press pattern where the first press shows a hint and the second press
26
+ // within 5 seconds actually fires the action.
27
+ import { CliRenderEvents, type CliRenderer, type KeyEvent, type Renderable, type TreeSitterClient } from "@opentui/core"
28
+ import type { Keymap } from "@opentui/keymap"
29
+ import { render } from "@opentui/solid"
30
+ import { createComponent, createSignal, type Accessor, type Setter } from "solid-js"
31
+ import { createStore, reconcile } from "solid-js/store"
32
+ import { OctocodeKeymapProvider } from "@octocode-ai/tui/keymap"
33
+ import { withRunSpan } from "./otel"
34
+ import { RUN_COMMAND_PANEL_ROWS, RUN_SUBAGENT_PANEL_ROWS } from "./footer.command"
35
+ import { SUBAGENT_INSPECTOR_ROWS } from "./footer.subagent"
36
+ import { PROMPT_MAX_ROWS, TEXTAREA_MIN_ROWS } from "./footer.prompt"
37
+ import { RunFooterView } from "./footer.view"
38
+ import { RunScrollbackStream } from "./scrollback.surface"
39
+ import { RUN_THEME_FALLBACK, resolveRunTheme, type RunTheme } from "./theme"
40
+ import { modelInfo } from "./variant.shared"
41
+ import type {
42
+ FooterApi,
43
+ FooterEvent,
44
+ FooterPatch,
45
+ FooterPromptRoute,
46
+ FooterQueuedPrompt,
47
+ FooterState,
48
+ FooterSubagentState,
49
+ FooterView,
50
+ PermissionReply,
51
+ QuestionReject,
52
+ QuestionReply,
53
+ RunAgent,
54
+ RunCommand,
55
+ RunDiffStyle,
56
+ RunInput,
57
+ RunPrompt,
58
+ RunProvider,
59
+ RunResource,
60
+ RunTuiConfig,
61
+ StreamCommit,
62
+ } from "./types"
63
+
64
+ type CycleResult = {
65
+ modelLabel?: string
66
+ status?: string
67
+ variant?: string | undefined
68
+ variants?: string[]
69
+ }
70
+
71
+ type RunFooterOptions = {
72
+ directory: string
73
+ findFiles: (query: string) => Promise<string[]>
74
+ agents: RunAgent[]
75
+ resources: RunResource[]
76
+ commands?: RunCommand[]
77
+ wrote?: boolean
78
+ sessionID: () => string | undefined
79
+ agentLabel: string
80
+ modelLabel: string
81
+ model: RunInput["model"]
82
+ variant: string | undefined
83
+ first: boolean
84
+ history?: RunPrompt[]
85
+ theme: RunTheme
86
+ keymap: Keymap<Renderable, KeyEvent>
87
+ tuiConfig: RunTuiConfig
88
+ backgroundSubagents: boolean
89
+ diffStyle: RunDiffStyle
90
+ onPermissionReply: (input: PermissionReply) => void | Promise<void>
91
+ onQuestionReply: (input: QuestionReply) => void | Promise<void>
92
+ onQuestionReject: (input: QuestionReject) => void | Promise<void>
93
+ onCycleVariant?: () => CycleResult | void
94
+ onModelSelect?: (model: NonNullable<RunInput["model"]>) => CycleResult | void | Promise<CycleResult | void>
95
+ onVariantSelect?: (variant: string | undefined) => CycleResult | void | Promise<CycleResult | void>
96
+ onInterrupt?: () => void
97
+ onBackground?: () => void
98
+ onEditorOpen: (input: { value: string }) => Promise<string | undefined>
99
+ onExit?: () => void
100
+ onSubagentSelect?: (sessionID: string | undefined) => void
101
+ treeSitterClient?: TreeSitterClient
102
+ }
103
+
104
+ const PERMISSION_ROWS = 12
105
+ const QUESTION_ROWS = 14
106
+ const COMMAND_ROWS = RUN_COMMAND_PANEL_ROWS
107
+ const SKILL_ROWS = RUN_COMMAND_PANEL_ROWS
108
+ const SUBAGENT_ROWS = RUN_SUBAGENT_PANEL_ROWS
109
+ const MODEL_ROWS = RUN_COMMAND_PANEL_ROWS
110
+ const VARIANT_ROWS = RUN_COMMAND_PANEL_ROWS
111
+ const NOTICE_DURATION = 3000
112
+ const THEME_REFRESH_DELAYS = [1000, 1000] as const
113
+
114
+ function createEmptySubagentState(): FooterSubagentState {
115
+ return {
116
+ tabs: [],
117
+ details: {},
118
+ permissions: [],
119
+ questions: [],
120
+ }
121
+ }
122
+
123
+ function eventPatch(next: FooterEvent): FooterPatch | undefined {
124
+ if (next.type === "queue") {
125
+ return { queue: next.queue }
126
+ }
127
+
128
+ if (next.type === "first") {
129
+ return { first: next.first }
130
+ }
131
+
132
+ if (next.type === "model") {
133
+ return { model: next.model }
134
+ }
135
+
136
+ if (next.type === "turn.send") {
137
+ return {
138
+ phase: "running",
139
+ status: "sending prompt",
140
+ queue: next.queue,
141
+ interrupt: 0,
142
+ exit: 0,
143
+ }
144
+ }
145
+
146
+ if (next.type === "turn.wait") {
147
+ return {
148
+ phase: "running",
149
+ status: "waiting for assistant",
150
+ }
151
+ }
152
+
153
+ if (next.type === "turn.idle") {
154
+ return {
155
+ phase: "idle",
156
+ status: "",
157
+ queue: next.queue,
158
+ }
159
+ }
160
+
161
+ if (next.type === "stream.patch") {
162
+ return next.patch
163
+ }
164
+
165
+ return undefined
166
+ }
167
+
168
+ export class RunFooter implements FooterApi {
169
+ private closed = false
170
+ private destroyed = false
171
+ private prompts = new Set<(input: RunPrompt) => void>()
172
+ private queuedRemoves = new Set<(messageID: string) => boolean | Promise<boolean>>()
173
+ private closes = new Set<() => void>()
174
+ // Microtask-coalesced commit queue. Flushed on next microtask or on close/destroy.
175
+ private queue: StreamCommit[] = []
176
+ private pending = false
177
+ private flushing: Promise<void> = Promise.resolve()
178
+ private flushError: unknown
179
+ // Fixed portion of footer height above the textarea.
180
+ private base: number
181
+ private rows = TEXTAREA_MIN_ROWS
182
+ private agents: Accessor<RunAgent[]>
183
+ private setAgents: Setter<RunAgent[]>
184
+ private resources: Accessor<RunResource[]>
185
+ private setResources: Setter<RunResource[]>
186
+ private commands: Accessor<RunCommand[] | undefined>
187
+ private setCommands: Setter<RunCommand[] | undefined>
188
+ private providers: Accessor<RunProvider[] | undefined>
189
+ private setProviders: Setter<RunProvider[] | undefined>
190
+ private currentModel: Accessor<RunInput["model"]>
191
+ private setCurrentModel: Setter<RunInput["model"]>
192
+ private variants: Accessor<string[]>
193
+ private setVariants: Setter<string[]>
194
+ private currentVariant: Accessor<string | undefined>
195
+ private setCurrentVariant: Setter<string | undefined>
196
+ private theme: Accessor<RunTheme>
197
+ private setTheme: Setter<RunTheme>
198
+ private state: Accessor<FooterState>
199
+ private setState: Setter<FooterState>
200
+ private view: Accessor<FooterView>
201
+ private setView: Setter<FooterView>
202
+ private subagent: Accessor<FooterSubagentState>
203
+ private setSubagent: (next: FooterSubagentState) => void
204
+ private queuedPrompts: Accessor<FooterQueuedPrompt[]>
205
+ private setQueuedPrompts: Setter<FooterQueuedPrompt[]>
206
+ private promptRoute: FooterPromptRoute = { type: "composer" }
207
+ private subagentMenuRows = SUBAGENT_ROWS
208
+ private autocomplete = false
209
+ private interruptTimeout: NodeJS.Timeout | undefined
210
+ private exitTimeout: NodeJS.Timeout | undefined
211
+ private noticeTimeout: NodeJS.Timeout | undefined
212
+ private noticeRestoreStatus = ""
213
+ private statusVersion = 0
214
+ private requestExitHandler: (() => boolean) | undefined
215
+ private scrollback: RunScrollbackStream
216
+ private themes: RunTheme[]
217
+ private paletteRefreshRunning = false
218
+ private paletteRefreshQueued = false
219
+ private themeRefreshTimeouts: NodeJS.Timeout[] = []
220
+
221
+ private createScrollback(wrote: boolean): RunScrollbackStream {
222
+ return new RunScrollbackStream(this.renderer, this.theme(), {
223
+ diffStyle: this.options.diffStyle,
224
+ wrote,
225
+ sessionID: this.options.sessionID,
226
+ treeSitterClient: this.options.treeSitterClient,
227
+ onThemeRelease: (theme) => {
228
+ void this.renderer
229
+ .idle()
230
+ .catch(() => {})
231
+ .finally(() => this.destroyTheme(theme))
232
+ },
233
+ })
234
+ }
235
+
236
+ constructor(
237
+ private renderer: CliRenderer,
238
+ private options: RunFooterOptions,
239
+ ) {
240
+ const [state, setState] = createSignal<FooterState>({
241
+ phase: "idle",
242
+ status: "",
243
+ queue: 0,
244
+ model: options.modelLabel,
245
+ duration: "",
246
+ usage: "",
247
+ first: options.first,
248
+ interrupt: 0,
249
+ exit: 0,
250
+ })
251
+ this.state = state
252
+ this.setState = setState
253
+ const [view, setView] = createSignal<FooterView>({ type: "prompt" })
254
+ this.view = view
255
+ this.setView = setView
256
+ const [agents, setAgents] = createSignal(options.agents)
257
+ this.agents = agents
258
+ this.setAgents = setAgents
259
+ const [resources, setResources] = createSignal(options.resources)
260
+ this.resources = resources
261
+ this.setResources = setResources
262
+ const [commands, setCommands] = createSignal<RunCommand[] | undefined>(options.commands)
263
+ this.commands = commands
264
+ this.setCommands = setCommands
265
+ const [providers, setProviders] = createSignal<RunProvider[] | undefined>()
266
+ this.providers = providers
267
+ this.setProviders = setProviders
268
+ const [currentModel, setCurrentModel] = createSignal<RunInput["model"]>(options.model)
269
+ this.currentModel = currentModel
270
+ this.setCurrentModel = setCurrentModel
271
+ const [variants, setVariants] = createSignal<string[]>([])
272
+ this.variants = variants
273
+ this.setVariants = setVariants
274
+ const [currentVariant, setCurrentVariant] = createSignal(options.variant)
275
+ this.currentVariant = currentVariant
276
+ this.setCurrentVariant = setCurrentVariant
277
+ const [theme, setTheme] = createSignal(options.theme)
278
+ this.theme = theme
279
+ this.setTheme = setTheme
280
+ this.themes = [options.theme]
281
+ const [subagent, setSubagent] = createStore<FooterSubagentState>(createEmptySubagentState())
282
+ this.subagent = () => subagent
283
+ this.setSubagent = (next) => {
284
+ setSubagent("tabs", reconcile(next.tabs, { key: "sessionID" }))
285
+ setSubagent("details", reconcile(next.details))
286
+ setSubagent("permissions", reconcile(next.permissions, { key: "id" }))
287
+ setSubagent("questions", reconcile(next.questions, { key: "id" }))
288
+ }
289
+ const [queuedPrompts, setQueuedPrompts] = createSignal<FooterQueuedPrompt[]>([])
290
+ this.queuedPrompts = queuedPrompts
291
+ this.setQueuedPrompts = setQueuedPrompts
292
+ this.base = Math.max(1, renderer.footerHeight - TEXTAREA_MIN_ROWS)
293
+ this.scrollback = this.createScrollback(options.wrote ?? false)
294
+
295
+ this.renderer.on(CliRenderEvents.DESTROY, this.handleDestroy)
296
+ this.renderer.on(CliRenderEvents.PALETTE, this.handlePalette)
297
+ this.renderer.on(CliRenderEvents.THEME_MODE, this.handleThemeRefresh)
298
+ this.renderer.prependInputHandler(this.handleThemeNotification)
299
+ process.on("SIGUSR2", this.handleThemeSignal)
300
+
301
+ const footer = this
302
+ void render(
303
+ () =>
304
+ createComponent(OctocodeKeymapProvider, {
305
+ keymap: options.keymap,
306
+ get children() {
307
+ return createComponent(RunFooterView, {
308
+ directory: options.directory,
309
+ state: footer.state,
310
+ view: footer.view,
311
+ subagent: footer.subagent,
312
+ queuedPrompts: footer.queuedPrompts,
313
+ findFiles: options.findFiles,
314
+ agents: footer.agents,
315
+ resources: footer.resources,
316
+ commands: footer.commands,
317
+ providers: footer.providers,
318
+ currentModel: footer.currentModel,
319
+ variants: footer.variants,
320
+ currentVariant: footer.currentVariant,
321
+ theme: footer.theme,
322
+ diffStyle: options.diffStyle,
323
+ tuiConfig: options.tuiConfig,
324
+ backgroundSubagents: options.backgroundSubagents,
325
+ history: options.history,
326
+ agent: options.agentLabel,
327
+ onSubmit: footer.handlePrompt,
328
+ onPermissionReply: footer.handlePermissionReply,
329
+ onQuestionReply: footer.handleQuestionReply,
330
+ onQuestionReject: footer.handleQuestionReject,
331
+ onCycle: footer.handleCycle,
332
+ onInterrupt: footer.handleInterrupt,
333
+ onBackground: options.onBackground,
334
+ onEditorOpen: options.onEditorOpen,
335
+ onInputClear: footer.handleInputClear,
336
+ onExitRequest: footer.handleExit,
337
+ onRequestExit: footer.setRequestExitHandler,
338
+ onExit: () => footer.close(),
339
+ onModelSelect: footer.handleModelSelect,
340
+ onVariantSelect: footer.handleVariantSelect,
341
+ onRows: footer.syncRows,
342
+ onLayout: footer.syncLayout,
343
+ onStatus: footer.setStatus,
344
+ onSubagentSelect: options.onSubagentSelect,
345
+ onQueuedRemove: footer.handleQueuedRemove,
346
+ })
347
+ },
348
+ }),
349
+ this.renderer,
350
+ ).catch(() => {
351
+ if (!this.isGone) {
352
+ this.close()
353
+ }
354
+ })
355
+ }
356
+
357
+ public get isClosed(): boolean {
358
+ return this.closed || this.isGone
359
+ }
360
+
361
+ private get isGone(): boolean {
362
+ return this.destroyed || this.renderer.isDestroyed
363
+ }
364
+
365
+ public onPrompt(fn: (input: RunPrompt) => void): () => void {
366
+ this.prompts.add(fn)
367
+ return () => {
368
+ this.prompts.delete(fn)
369
+ }
370
+ }
371
+
372
+ public onQueuedRemove(fn: (messageID: string) => boolean | Promise<boolean>): () => void {
373
+ this.queuedRemoves.add(fn)
374
+ return () => {
375
+ this.queuedRemoves.delete(fn)
376
+ }
377
+ }
378
+
379
+ public onClose(fn: () => void): () => void {
380
+ if (this.isClosed) {
381
+ fn()
382
+ return () => {}
383
+ }
384
+
385
+ this.closes.add(fn)
386
+ return () => {
387
+ this.closes.delete(fn)
388
+ }
389
+ }
390
+
391
+ public event(next: FooterEvent): void {
392
+ if (next.type === "turn.duration") {
393
+ const current = this.currentModel()
394
+ this.flush()
395
+ this.flushing = this.flushing
396
+ .then(() =>
397
+ this.scrollback.writeTurnSummary({
398
+ agent: this.options.agentLabel,
399
+ model: current ? modelInfo(this.providers(), current).model : this.state().model,
400
+ duration: next.duration,
401
+ }),
402
+ )
403
+ .catch((error) => {
404
+ this.flushError = error
405
+ })
406
+ return
407
+ }
408
+
409
+ if (next.type === "catalog") {
410
+ if (this.isGone) {
411
+ return
412
+ }
413
+
414
+ this.setAgents(next.agents)
415
+ this.setResources(next.resources)
416
+ if (next.commands !== undefined) {
417
+ this.setCommands(next.commands)
418
+ }
419
+ return
420
+ }
421
+
422
+ if (next.type === "models") {
423
+ if (this.isGone) {
424
+ return
425
+ }
426
+
427
+ this.setProviders(next.providers)
428
+ return
429
+ }
430
+
431
+ if (next.type === "variants") {
432
+ if (this.isGone) {
433
+ return
434
+ }
435
+
436
+ this.setVariants(next.variants)
437
+ this.setCurrentVariant(next.current)
438
+ return
439
+ }
440
+
441
+ if (next.type === "queued.prompts") {
442
+ if (this.isGone) {
443
+ return
444
+ }
445
+
446
+ this.setQueuedPrompts(next.prompts)
447
+ return
448
+ }
449
+
450
+ const patch = eventPatch(next)
451
+ if (patch) {
452
+ if (typeof patch.status === "string") {
453
+ this.clearNoticeTimer()
454
+ }
455
+ if (next.type === "turn.send") {
456
+ this.clearInterruptTimer()
457
+ this.clearExitTimer()
458
+ }
459
+ this.patch(patch)
460
+ return
461
+ }
462
+
463
+ if (next.type === "stream.subagent") {
464
+ if (this.isGone) {
465
+ return
466
+ }
467
+
468
+ this.setSubagent(next.state)
469
+ this.applyHeight()
470
+ return
471
+ }
472
+
473
+ if (next.type === "stream.view") {
474
+ this.present(next.view)
475
+ }
476
+ }
477
+
478
+ private patch(next: FooterPatch): void {
479
+ if (this.isGone) {
480
+ return
481
+ }
482
+
483
+ const prev = this.state()
484
+ if (typeof next.status === "string") {
485
+ this.statusVersion++
486
+ }
487
+ const state = {
488
+ phase: next.phase ?? prev.phase,
489
+ status: typeof next.status === "string" ? next.status : prev.status,
490
+ queue: typeof next.queue === "number" ? Math.max(0, next.queue) : prev.queue,
491
+ model: typeof next.model === "string" ? next.model : prev.model,
492
+ duration: typeof next.duration === "string" ? next.duration : prev.duration,
493
+ usage: typeof next.usage === "string" ? next.usage : prev.usage,
494
+ first: typeof next.first === "boolean" ? next.first : prev.first,
495
+ interrupt:
496
+ typeof next.interrupt === "number" && Number.isFinite(next.interrupt)
497
+ ? Math.max(0, Math.floor(next.interrupt))
498
+ : prev.interrupt,
499
+ exit:
500
+ typeof next.exit === "number" && Number.isFinite(next.exit) ? Math.max(0, Math.floor(next.exit)) : prev.exit,
501
+ }
502
+
503
+ if (state.phase === "idle") {
504
+ state.interrupt = 0
505
+ }
506
+
507
+ this.setState(state)
508
+
509
+ if (prev.phase === "running" && state.phase === "idle") {
510
+ this.flush()
511
+ this.completeScrollback()
512
+ }
513
+ }
514
+
515
+ private completeScrollback(): void {
516
+ const phase = this.state().phase
517
+ this.flushing = this.flushing
518
+ .then(() =>
519
+ withRunSpan(
520
+ "RunFooter.completeScrollback",
521
+ {
522
+ "octocode.footer.phase": phase,
523
+ "session.id": this.options.sessionID() || undefined,
524
+ },
525
+ async () => {
526
+ await this.scrollback.complete()
527
+ },
528
+ ),
529
+ )
530
+ .catch((error) => {
531
+ this.flushError = error
532
+ })
533
+ }
534
+
535
+ private present(view: FooterView): void {
536
+ if (this.isGone) {
537
+ return
538
+ }
539
+
540
+ this.setView(view)
541
+ this.applyHeight()
542
+ }
543
+
544
+ // Queues a scrollback commit. Consecutive progress chunks for the same
545
+ // part coalesce by appending text, reducing the number of retained-surface
546
+ // updates. Actual flush happens on the next microtask, so a burst of events
547
+ // from one reducer pass becomes a single ordered drain.
548
+ public append(commit: StreamCommit): void {
549
+ if (this.isGone) {
550
+ return
551
+ }
552
+
553
+ const last = this.queue.at(-1)
554
+ if (
555
+ last &&
556
+ last.phase === "progress" &&
557
+ commit.phase === "progress" &&
558
+ last.kind === commit.kind &&
559
+ last.source === commit.source &&
560
+ last.partID === commit.partID &&
561
+ last.tool === commit.tool
562
+ ) {
563
+ last.text += commit.text
564
+ } else {
565
+ this.queue.push(commit)
566
+ }
567
+
568
+ if (this.pending) {
569
+ return
570
+ }
571
+
572
+ this.pending = true
573
+ queueMicrotask(() => {
574
+ this.pending = false
575
+ this.flush()
576
+ })
577
+ }
578
+
579
+ public idle(): Promise<void> {
580
+ if (this.isGone) {
581
+ return Promise.resolve()
582
+ }
583
+
584
+ this.flush()
585
+ if (this.state().phase === "idle") {
586
+ this.completeScrollback()
587
+ }
588
+
589
+ return this.flushing.then(async () => {
590
+ if (this.flushError !== undefined) {
591
+ const error = this.flushError
592
+ this.flushError = undefined
593
+ throw error
594
+ }
595
+
596
+ if (this.isGone) {
597
+ return
598
+ }
599
+
600
+ if (this.queue.length > 0) {
601
+ return this.idle()
602
+ }
603
+
604
+ await this.renderer.idle().catch(() => {})
605
+ })
606
+ }
607
+
608
+ public resetForReplay(wrote: boolean): void {
609
+ if (this.isGone) {
610
+ return
611
+ }
612
+
613
+ this.scrollback.destroy()
614
+ this.scrollback = this.createScrollback(wrote)
615
+ }
616
+
617
+ public currentTheme(): RunTheme {
618
+ return this.theme()
619
+ }
620
+
621
+ private destroyTheme(theme: RunTheme): void {
622
+ const index = this.themes.indexOf(theme)
623
+ if (index === -1) {
624
+ return
625
+ }
626
+
627
+ this.themes.splice(index, 1)
628
+ theme.block.syntax?.destroy()
629
+ theme.block.subtleSyntax?.destroy()
630
+ }
631
+
632
+ public close(): void {
633
+ if (this.closed) {
634
+ return
635
+ }
636
+
637
+ this.flush()
638
+ this.notifyClose()
639
+ }
640
+
641
+ public requestExit(): boolean {
642
+ return this.requestExitHandler?.() ?? this.handleExit()
643
+ }
644
+
645
+ public destroy(): void {
646
+ this.handleDestroy()
647
+ }
648
+
649
+ private notifyClose(): void {
650
+ if (this.closed) {
651
+ return
652
+ }
653
+
654
+ this.closed = true
655
+ for (const fn of [...this.closes]) {
656
+ fn()
657
+ }
658
+ }
659
+
660
+ private setStatus = (status: string): void => {
661
+ this.setNotice(status)
662
+ }
663
+
664
+ private setNotice(status: string): void {
665
+ const restore = this.noticeTimeout ? this.noticeRestoreStatus : this.state().status
666
+ this.clearNoticeTimer(false)
667
+ this.patch({ status })
668
+ if (!status) {
669
+ this.noticeRestoreStatus = ""
670
+ return
671
+ }
672
+
673
+ this.noticeRestoreStatus = restore
674
+ const version = this.statusVersion
675
+ this.noticeTimeout = setTimeout(() => {
676
+ this.noticeTimeout = undefined
677
+ if (this.isGone || version !== this.statusVersion) {
678
+ this.noticeRestoreStatus = ""
679
+ return
680
+ }
681
+
682
+ const next = this.noticeRestoreStatus
683
+ this.noticeRestoreStatus = ""
684
+ this.patch({ status: next })
685
+ }, NOTICE_DURATION)
686
+ }
687
+
688
+ private setRequestExitHandler = (fn?: () => boolean): void => {
689
+ this.requestExitHandler = fn
690
+ }
691
+
692
+ private handleQueuedRemove = async (messageID: string): Promise<boolean> => {
693
+ const fn = [...this.queuedRemoves][0]
694
+ return fn ? await fn(messageID) : false
695
+ }
696
+
697
+ private handleInputClear = (): void => {
698
+ this.clearInterruptTimer()
699
+ this.clearExitTimer()
700
+ if (this.state().interrupt === 0 && this.state().exit === 0) {
701
+ return
702
+ }
703
+
704
+ this.patch({ interrupt: 0, exit: 0 })
705
+ }
706
+
707
+ // Resizes the footer to fit the current view. Permission and question views
708
+ // get fixed extra rows; the prompt view scales with textarea line count.
709
+ private applyHeight(): void {
710
+ const type = this.view().type
711
+ const height =
712
+ type === "permission"
713
+ ? this.base + PERMISSION_ROWS
714
+ : type === "question"
715
+ ? this.base + QUESTION_ROWS
716
+ : this.promptRoute.type === "command"
717
+ ? 1 + COMMAND_ROWS
718
+ : this.promptRoute.type === "skill"
719
+ ? 1 + SKILL_ROWS
720
+ : this.promptRoute.type === "model"
721
+ ? 1 + MODEL_ROWS
722
+ : this.promptRoute.type === "variant"
723
+ ? 1 + VARIANT_ROWS
724
+ : this.promptRoute.type === "queued-menu"
725
+ ? 1 + this.subagentMenuRows
726
+ : this.promptRoute.type === "subagent-menu"
727
+ ? 1 + this.subagentMenuRows
728
+ : this.promptRoute.type === "subagent"
729
+ ? this.base + SUBAGENT_INSPECTOR_ROWS
730
+ : this.base + Math.max(TEXTAREA_MIN_ROWS, Math.min(PROMPT_MAX_ROWS, this.rows))
731
+
732
+ if (height !== this.renderer.footerHeight) {
733
+ this.renderer.footerHeight = height
734
+ }
735
+ }
736
+
737
+ private syncRows = (value: number): void => {
738
+ if (this.isGone) {
739
+ return
740
+ }
741
+
742
+ const rows = Math.max(TEXTAREA_MIN_ROWS, Math.min(PROMPT_MAX_ROWS, value))
743
+ if (rows === this.rows) {
744
+ return
745
+ }
746
+
747
+ this.rows = rows
748
+ if (this.view().type === "prompt") {
749
+ this.applyHeight()
750
+ }
751
+ }
752
+
753
+ private syncLayout = (next: { route: FooterPromptRoute; autocomplete: boolean; subagentRows: number }): void => {
754
+ this.promptRoute = next.route
755
+ this.autocomplete = next.autocomplete
756
+ this.subagentMenuRows = next.subagentRows
757
+ if (this.view().type === "prompt") {
758
+ this.applyHeight()
759
+ }
760
+ }
761
+
762
+ private handlePrompt = (input: RunPrompt): boolean => {
763
+ if (this.isClosed) {
764
+ return false
765
+ }
766
+
767
+ if (this.state().first) {
768
+ this.patch({ first: false })
769
+ }
770
+
771
+ if (this.prompts.size === 0) {
772
+ this.setNotice("input queue unavailable")
773
+ return false
774
+ }
775
+
776
+ for (const fn of [...this.prompts]) {
777
+ fn(input)
778
+ }
779
+
780
+ return true
781
+ }
782
+
783
+ private handlePermissionReply = async (input: PermissionReply): Promise<void> => {
784
+ if (this.isClosed) {
785
+ return
786
+ }
787
+
788
+ await this.options.onPermissionReply(input)
789
+ }
790
+
791
+ private handleQuestionReply = async (input: QuestionReply): Promise<void> => {
792
+ if (this.isClosed) {
793
+ return
794
+ }
795
+
796
+ await this.options.onQuestionReply(input)
797
+ }
798
+
799
+ private handleQuestionReject = async (input: QuestionReject): Promise<void> => {
800
+ if (this.isClosed) {
801
+ return
802
+ }
803
+
804
+ await this.options.onQuestionReject(input)
805
+ }
806
+
807
+ private handleCycle = (): void => {
808
+ const result = this.options.onCycleVariant?.()
809
+ if (!result) {
810
+ this.setNotice("no variants available")
811
+ return
812
+ }
813
+
814
+ const patch: FooterPatch = {}
815
+
816
+ if ("variants" in result) {
817
+ this.setVariants(result.variants ?? [])
818
+ }
819
+
820
+ if ("variant" in result) {
821
+ this.setCurrentVariant(result.variant)
822
+ }
823
+
824
+ if (result.modelLabel) {
825
+ patch.model = result.modelLabel
826
+ }
827
+
828
+ this.patch(patch)
829
+ this.setNotice(result.status ?? "variant updated")
830
+ }
831
+
832
+ private handleModelSelect = (model: NonNullable<RunInput["model"]>): void => {
833
+ if (this.isClosed) {
834
+ return
835
+ }
836
+
837
+ const previous = this.currentModel()
838
+ this.setCurrentModel(model)
839
+ if (!previous || previous.providerID !== model.providerID || previous.modelID !== model.modelID) {
840
+ this.setCurrentVariant(undefined)
841
+ }
842
+ void Promise.resolve()
843
+ .then(() => this.options.onModelSelect?.(model))
844
+ .then((result) => {
845
+ const current = this.currentModel()
846
+ if (
847
+ !result ||
848
+ this.isClosed ||
849
+ !current ||
850
+ current.providerID !== model.providerID ||
851
+ current.modelID !== model.modelID
852
+ ) {
853
+ return
854
+ }
855
+
856
+ if ("variants" in result) {
857
+ this.setVariants(result.variants ?? [])
858
+ }
859
+
860
+ if ("variant" in result) {
861
+ this.setCurrentVariant(result.variant)
862
+ }
863
+
864
+ const patch: FooterPatch = {}
865
+ if (result.modelLabel) {
866
+ patch.model = result.modelLabel
867
+ }
868
+
869
+ if (patch.model) {
870
+ this.patch(patch)
871
+ }
872
+ if (result.status) {
873
+ this.setNotice(result.status)
874
+ }
875
+ })
876
+ .catch(() => {})
877
+ }
878
+
879
+ private handleVariantSelect = (variant: string | undefined): void => {
880
+ if (this.isClosed) {
881
+ return
882
+ }
883
+
884
+ const model = this.currentModel()
885
+ void Promise.resolve()
886
+ .then(() => this.options.onVariantSelect?.(variant))
887
+ .then((result) => {
888
+ const current = this.currentModel()
889
+ if (
890
+ !result ||
891
+ this.isClosed ||
892
+ (model && (!current || current.providerID !== model.providerID || current.modelID !== model.modelID))
893
+ ) {
894
+ return
895
+ }
896
+
897
+ if ("variants" in result) {
898
+ this.setVariants(result.variants ?? [])
899
+ }
900
+
901
+ if ("variant" in result) {
902
+ this.setCurrentVariant(result.variant)
903
+ }
904
+
905
+ const patch: FooterPatch = {}
906
+ if (result.modelLabel) {
907
+ patch.model = result.modelLabel
908
+ }
909
+
910
+ if (patch.model) {
911
+ this.patch(patch)
912
+ }
913
+ if (result.status) {
914
+ this.setNotice(result.status)
915
+ }
916
+ })
917
+ .catch(() => {})
918
+ }
919
+
920
+ private clearInterruptTimer(): void {
921
+ if (!this.interruptTimeout) {
922
+ return
923
+ }
924
+
925
+ clearTimeout(this.interruptTimeout)
926
+ this.interruptTimeout = undefined
927
+ }
928
+
929
+ private clearNoticeTimer(reset = true): void {
930
+ if (!this.noticeTimeout) {
931
+ if (reset) {
932
+ this.noticeRestoreStatus = ""
933
+ }
934
+ return
935
+ }
936
+
937
+ clearTimeout(this.noticeTimeout)
938
+ this.noticeTimeout = undefined
939
+ if (reset) {
940
+ this.noticeRestoreStatus = ""
941
+ }
942
+ }
943
+
944
+ private armInterruptTimer(): void {
945
+ this.clearInterruptTimer()
946
+ this.interruptTimeout = setTimeout(() => {
947
+ this.interruptTimeout = undefined
948
+ if (this.isGone || this.state().phase !== "running") {
949
+ return
950
+ }
951
+
952
+ this.patch({ interrupt: 0 })
953
+ }, 5000)
954
+ }
955
+
956
+ private clearExitTimer(): void {
957
+ if (!this.exitTimeout) {
958
+ return
959
+ }
960
+
961
+ clearTimeout(this.exitTimeout)
962
+ this.exitTimeout = undefined
963
+ }
964
+
965
+ private armExitTimer(): void {
966
+ this.clearExitTimer()
967
+ this.exitTimeout = setTimeout(() => {
968
+ this.exitTimeout = undefined
969
+ if (this.isGone || this.isClosed) {
970
+ return
971
+ }
972
+
973
+ this.patch({ exit: 0 })
974
+ }, 5000)
975
+ }
976
+
977
+ // Two-press interrupt: first press shows a hint ("esc again to interrupt"),
978
+ // second press within 5 seconds fires onInterrupt. The timer resets the
979
+ // counter if the user doesn't follow through.
980
+ private handleInterrupt = (): boolean => {
981
+ if (this.isClosed || this.state().phase !== "running") {
982
+ return false
983
+ }
984
+
985
+ const next = this.state().interrupt + 1
986
+ this.patch({ interrupt: next })
987
+
988
+ if (next < 2) {
989
+ this.armInterruptTimer()
990
+ return true
991
+ }
992
+
993
+ this.clearInterruptTimer()
994
+ this.patch({ interrupt: 0 })
995
+ this.setNotice("interrupting")
996
+ this.options.onInterrupt?.()
997
+ return true
998
+ }
999
+
1000
+ private handleExit = (): boolean => {
1001
+ if (this.isClosed) {
1002
+ return true
1003
+ }
1004
+
1005
+ this.clearInterruptTimer()
1006
+ const next = this.state().exit + 1
1007
+ this.patch({ exit: next, interrupt: 0 })
1008
+
1009
+ if (next < 2) {
1010
+ this.armExitTimer()
1011
+ return true
1012
+ }
1013
+
1014
+ this.clearExitTimer()
1015
+ this.patch({ exit: 0, status: "exiting" })
1016
+ this.close()
1017
+ this.options.onExit?.()
1018
+ return true
1019
+ }
1020
+
1021
+ private handlePalette = (): void => {
1022
+ void resolveRunTheme(this.renderer).then((theme) => {
1023
+ if (this.isGone) {
1024
+ theme.block.syntax?.destroy()
1025
+ theme.block.subtleSyntax?.destroy()
1026
+ return
1027
+ }
1028
+
1029
+ // Keep the last known good theme when a runtime OSC probe times out.
1030
+ if (theme === RUN_THEME_FALLBACK) {
1031
+ return
1032
+ }
1033
+
1034
+ this.themes.push(theme)
1035
+ this.setTheme(theme)
1036
+ this.renderer.setBackgroundColor(theme.background)
1037
+ this.flushing = this.flushing
1038
+ .then(() => this.scrollback.setTheme(theme))
1039
+ .catch((error) => {
1040
+ this.flushError = error
1041
+ })
1042
+ })
1043
+ }
1044
+
1045
+ private handleThemeNotification = (sequence: string): boolean => {
1046
+ if (sequence !== "\x1b[?997;1n" && sequence !== "\x1b[?997;2n") {
1047
+ return false
1048
+ }
1049
+
1050
+ // OpenTUI clears its palette cache only when dark/light mode changes.
1051
+ // Refresh for same-mode terminal theme swaps too.
1052
+ queueMicrotask(this.handleThemeRefresh)
1053
+ return false
1054
+ }
1055
+
1056
+ private handleThemeRefresh = (): void => {
1057
+ if (this.isGone) {
1058
+ return
1059
+ }
1060
+
1061
+ if (this.paletteRefreshRunning) {
1062
+ this.paletteRefreshQueued = true
1063
+ return
1064
+ }
1065
+
1066
+ this.paletteRefreshRunning = true
1067
+ const retry = this.renderer.paletteDetectionStatus === "detecting"
1068
+ this.renderer.clearPaletteCache()
1069
+ void this.renderer
1070
+ .getPalette({ size: 256 })
1071
+ .catch(() => {})
1072
+ .finally(() => {
1073
+ this.paletteRefreshRunning = false
1074
+ if (!retry && !this.paletteRefreshQueued) {
1075
+ return
1076
+ }
1077
+
1078
+ this.paletteRefreshQueued = false
1079
+ this.handleThemeRefresh()
1080
+ })
1081
+ }
1082
+
1083
+ public refreshTheme(): void {
1084
+ this.handleThemeRefresh()
1085
+ }
1086
+
1087
+ private handleThemeSignal = (): void => {
1088
+ // Omarchy signals immediately after requesting a terminal config reload.
1089
+ for (const timeout of this.themeRefreshTimeouts) clearTimeout(timeout)
1090
+ this.themeRefreshTimeouts = THEME_REFRESH_DELAYS.map((delay) =>
1091
+ setTimeout(() => {
1092
+ this.handleThemeRefresh()
1093
+ }, delay),
1094
+ )
1095
+ }
1096
+
1097
+ private handleDestroy = (): void => {
1098
+ if (this.destroyed) {
1099
+ return
1100
+ }
1101
+
1102
+ this.flush()
1103
+ this.destroyed = true
1104
+ this.notifyClose()
1105
+ this.clearInterruptTimer()
1106
+ this.clearExitTimer()
1107
+ this.clearNoticeTimer()
1108
+ this.renderer.off(CliRenderEvents.DESTROY, this.handleDestroy)
1109
+ this.renderer.off(CliRenderEvents.PALETTE, this.handlePalette)
1110
+ this.renderer.off(CliRenderEvents.THEME_MODE, this.handleThemeRefresh)
1111
+ this.renderer.removeInputHandler(this.handleThemeNotification)
1112
+ process.off("SIGUSR2", this.handleThemeSignal)
1113
+ for (const timeout of this.themeRefreshTimeouts) clearTimeout(timeout)
1114
+ this.themeRefreshTimeouts.length = 0
1115
+ this.prompts.clear()
1116
+ this.queuedRemoves.clear()
1117
+ this.closes.clear()
1118
+ this.scrollback.destroy()
1119
+ for (const theme of [...this.themes]) this.destroyTheme(theme)
1120
+ }
1121
+
1122
+ // Drains the commit queue to scrollback. The surface manager owns grouping,
1123
+ // spacing, and progressive markdown/code settling so direct mode can append
1124
+ // immutable transcript rows without rewriting history.
1125
+ private flush(): void {
1126
+ if (this.isGone || this.queue.length === 0) {
1127
+ this.queue.length = 0
1128
+ return
1129
+ }
1130
+
1131
+ const batch = this.queue.splice(0)
1132
+ const phase = this.state().phase
1133
+ this.flushing = this.flushing
1134
+ .then(() =>
1135
+ withRunSpan(
1136
+ "RunFooter.flush",
1137
+ {
1138
+ "octocode.batch.commits": batch.length,
1139
+ "octocode.footer.phase": phase,
1140
+ "session.id": this.options.sessionID() || undefined,
1141
+ },
1142
+ async () => {
1143
+ for (const item of batch) {
1144
+ await this.scrollback.append(item)
1145
+ }
1146
+ },
1147
+ ),
1148
+ )
1149
+ .catch((error) => {
1150
+ this.flushError = error
1151
+ })
1152
+ }
1153
+ }