@kr78/pi-coding-agent 17.2.10

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 (1593) hide show
  1. package/CHANGELOG.md +14861 -0
  2. package/README.md +35 -0
  3. package/dist/CHANGELOG-chtdxh1z.md +14861 -0
  4. package/dist/cli.js +20483 -0
  5. package/dist/template-c2hyaytt.js +1653 -0
  6. package/dist/template-f8wx9vfn.css +1355 -0
  7. package/dist/template-qat058wr.html +55 -0
  8. package/dist/tool-views.generated-jdfmzwmn.js +35 -0
  9. package/examples/README.md +21 -0
  10. package/examples/custom-tools/README.md +104 -0
  11. package/examples/custom-tools/hello/index.ts +20 -0
  12. package/examples/extensions/README.md +142 -0
  13. package/examples/extensions/api-demo.ts +78 -0
  14. package/examples/extensions/chalk-logger.ts +25 -0
  15. package/examples/extensions/hello.ts +31 -0
  16. package/examples/extensions/pirate.ts +43 -0
  17. package/examples/extensions/plan-mode.ts +549 -0
  18. package/examples/extensions/reload-runtime.ts +38 -0
  19. package/examples/extensions/thinking-note.ts +13 -0
  20. package/examples/extensions/tools.ts +145 -0
  21. package/examples/extensions/with-deps/index.ts +36 -0
  22. package/examples/extensions/with-deps/package-lock.json +31 -0
  23. package/examples/extensions/with-deps/package.json +17 -0
  24. package/examples/hooks/README.md +56 -0
  25. package/examples/hooks/auto-commit-on-exit.ts +48 -0
  26. package/examples/hooks/confirm-destructive.ts +58 -0
  27. package/examples/hooks/custom-compaction.ts +115 -0
  28. package/examples/hooks/dirty-repo-guard.ts +51 -0
  29. package/examples/hooks/file-trigger.ts +40 -0
  30. package/examples/hooks/git-checkpoint.ts +52 -0
  31. package/examples/hooks/handoff.ts +149 -0
  32. package/examples/hooks/permission-gate.ts +33 -0
  33. package/examples/hooks/protected-paths.ts +29 -0
  34. package/examples/hooks/qna.ts +118 -0
  35. package/examples/hooks/status-line.ts +39 -0
  36. package/examples/sdk/01-minimal.ts +21 -0
  37. package/examples/sdk/02-custom-model.ts +49 -0
  38. package/examples/sdk/03-custom-prompt.ts +46 -0
  39. package/examples/sdk/04-skills.ts +43 -0
  40. package/examples/sdk/06-extensions.ts +82 -0
  41. package/examples/sdk/06-hooks.ts +61 -0
  42. package/examples/sdk/07-context-files.ts +35 -0
  43. package/examples/sdk/08-prompt-templates.ts +41 -0
  44. package/examples/sdk/08-slash-commands.ts +46 -0
  45. package/examples/sdk/09-api-keys-and-oauth.ts +54 -0
  46. package/examples/sdk/11-sessions.ts +47 -0
  47. package/examples/sdk/12-redis-sessions.ts +54 -0
  48. package/examples/sdk/13-sql-sessions.ts +61 -0
  49. package/examples/sdk/README.md +169 -0
  50. package/package.json +587 -0
  51. package/scripts/bench-guard.ts +71 -0
  52. package/scripts/bench-title-models.ts +332 -0
  53. package/scripts/build-binary.ts +117 -0
  54. package/scripts/bundle-dist.ts +123 -0
  55. package/scripts/compile-binary.ts +69 -0
  56. package/scripts/embed-mupdf-wasm.ts +67 -0
  57. package/scripts/format-prompts.ts +68 -0
  58. package/scripts/generate-aria-snapshot.ts +134 -0
  59. package/scripts/generate-docs-index.ts +58 -0
  60. package/scripts/generate-share-viewer.ts +34 -0
  61. package/scripts/legacy-pi-virtual-module.ts +205 -0
  62. package/scripts/measure-prompt-tokens.ts +63 -0
  63. package/scripts/omp +51 -0
  64. package/scripts/omp.ts +19 -0
  65. package/scripts/security-compare.ts +40 -0
  66. package/src/advisor/advise-tool.ts +234 -0
  67. package/src/advisor/config.ts +341 -0
  68. package/src/advisor/emission-guard.ts +172 -0
  69. package/src/advisor/index.ts +6 -0
  70. package/src/advisor/runtime.ts +1412 -0
  71. package/src/advisor/transcript-recorder.ts +215 -0
  72. package/src/advisor/watchdog.ts +135 -0
  73. package/src/async/index.ts +1 -0
  74. package/src/async/job-manager.ts +847 -0
  75. package/src/auto-thinking/classifier.ts +220 -0
  76. package/src/autolearn/controller.ts +152 -0
  77. package/src/autolearn/managed-skills.ts +255 -0
  78. package/src/autoresearch/command-resume.md +14 -0
  79. package/src/autoresearch/dashboard.ts +436 -0
  80. package/src/autoresearch/git.ts +331 -0
  81. package/src/autoresearch/helpers.ts +218 -0
  82. package/src/autoresearch/index.ts +541 -0
  83. package/src/autoresearch/prompt-setup.md +43 -0
  84. package/src/autoresearch/prompt.md +103 -0
  85. package/src/autoresearch/resume-message.md +10 -0
  86. package/src/autoresearch/state.ts +273 -0
  87. package/src/autoresearch/storage.ts +700 -0
  88. package/src/autoresearch/tools/init-experiment.ts +268 -0
  89. package/src/autoresearch/tools/log-experiment.ts +520 -0
  90. package/src/autoresearch/tools/run-experiment.ts +407 -0
  91. package/src/autoresearch/tools/update-notes.ts +109 -0
  92. package/src/autoresearch/types.ts +168 -0
  93. package/src/capability/context-file.ts +44 -0
  94. package/src/capability/extension-module.ts +34 -0
  95. package/src/capability/extension.ts +47 -0
  96. package/src/capability/fs.ts +117 -0
  97. package/src/capability/hook.ts +40 -0
  98. package/src/capability/index.ts +467 -0
  99. package/src/capability/instruction.ts +37 -0
  100. package/src/capability/mcp.ts +115 -0
  101. package/src/capability/prompt.ts +35 -0
  102. package/src/capability/rule-buckets.ts +66 -0
  103. package/src/capability/rule.ts +298 -0
  104. package/src/capability/settings.ts +34 -0
  105. package/src/capability/skill.ts +69 -0
  106. package/src/capability/slash-command.ts +40 -0
  107. package/src/capability/ssh.ts +41 -0
  108. package/src/capability/system-prompt.ts +34 -0
  109. package/src/capability/tool.ts +38 -0
  110. package/src/capability/types.ts +187 -0
  111. package/src/cleanse/agent.ts +226 -0
  112. package/src/cleanse/balance.ts +79 -0
  113. package/src/cleanse/checkers.ts +996 -0
  114. package/src/cleanse/index.ts +190 -0
  115. package/src/cleanse/loop.ts +51 -0
  116. package/src/cleanse/parsers.ts +726 -0
  117. package/src/cleanse/progress.ts +50 -0
  118. package/src/cleanse/prompts/assignment.md +47 -0
  119. package/src/cleanse/types.ts +72 -0
  120. package/src/cli/agents-cli.ts +138 -0
  121. package/src/cli/args.ts +368 -0
  122. package/src/cli/auth-broker-cli.ts +940 -0
  123. package/src/cli/auth-gateway-cli.ts +674 -0
  124. package/src/cli/bench-cli.ts +990 -0
  125. package/src/cli/browser-relay-cli.ts +119 -0
  126. package/src/cli/classify-install-target.ts +76 -0
  127. package/src/cli/claude-trace-cli.ts +795 -0
  128. package/src/cli/command-help.ts +107 -0
  129. package/src/cli/commands/init-xdg.ts +27 -0
  130. package/src/cli/completion-gen.ts +550 -0
  131. package/src/cli/config-cli.ts +459 -0
  132. package/src/cli/dry-balance-cli.ts +864 -0
  133. package/src/cli/extension-flags.ts +48 -0
  134. package/src/cli/file-processor.ts +132 -0
  135. package/src/cli/flag-tables.ts +367 -0
  136. package/src/cli/gallery-cli.ts +272 -0
  137. package/src/cli/gallery-fixtures/agentic.ts +420 -0
  138. package/src/cli/gallery-fixtures/codeintel.ts +187 -0
  139. package/src/cli/gallery-fixtures/edit.ts +254 -0
  140. package/src/cli/gallery-fixtures/fs.ts +245 -0
  141. package/src/cli/gallery-fixtures/index.ts +40 -0
  142. package/src/cli/gallery-fixtures/interaction.ts +46 -0
  143. package/src/cli/gallery-fixtures/memory.ts +81 -0
  144. package/src/cli/gallery-fixtures/misc.ts +177 -0
  145. package/src/cli/gallery-fixtures/search.ts +135 -0
  146. package/src/cli/gallery-fixtures/shell.ts +241 -0
  147. package/src/cli/gallery-fixtures/types.ts +57 -0
  148. package/src/cli/gallery-fixtures/web.ts +158 -0
  149. package/src/cli/gallery-screenshot.ts +279 -0
  150. package/src/cli/gc-cli.ts +1566 -0
  151. package/src/cli/grep-cli.ts +161 -0
  152. package/src/cli/grievances-cli.ts +256 -0
  153. package/src/cli/help-extra.ts +89 -0
  154. package/src/cli/initial-message.ts +58 -0
  155. package/src/cli/models-cli.ts +385 -0
  156. package/src/cli/plugin-cli.ts +996 -0
  157. package/src/cli/profile-alias.ts +369 -0
  158. package/src/cli/profile-bootstrap.ts +233 -0
  159. package/src/cli/read-cli.ts +99 -0
  160. package/src/cli/session-picker.ts +110 -0
  161. package/src/cli/setup-cli.ts +312 -0
  162. package/src/cli/setup-model-picker.ts +43 -0
  163. package/src/cli/shell-cli.ts +176 -0
  164. package/src/cli/ssh-cli.ts +179 -0
  165. package/src/cli/startup-cwd.ts +58 -0
  166. package/src/cli/stats-cli.ts +229 -0
  167. package/src/cli/thinking-levels.ts +7 -0
  168. package/src/cli/tiny-models-cli.ts +153 -0
  169. package/src/cli/ttsr-cli.ts +1013 -0
  170. package/src/cli/update-cli.ts +1184 -0
  171. package/src/cli/usage-cli.ts +1084 -0
  172. package/src/cli/usage-error.ts +7 -0
  173. package/src/cli/web-search-cli.ts +144 -0
  174. package/src/cli/worktree-cli.ts +311 -0
  175. package/src/cli-commands.ts +308 -0
  176. package/src/cli.ts +428 -0
  177. package/src/collab/crypto.ts +63 -0
  178. package/src/collab/display-name.ts +13 -0
  179. package/src/collab/guest.ts +764 -0
  180. package/src/collab/host.ts +692 -0
  181. package/src/collab/protocol.ts +296 -0
  182. package/src/collab/relay-client.ts +282 -0
  183. package/src/collab/replication-shrink.ts +111 -0
  184. package/src/commands/acp.ts +35 -0
  185. package/src/commands/agents.ts +58 -0
  186. package/src/commands/auth-broker.ts +100 -0
  187. package/src/commands/auth-gateway.ts +70 -0
  188. package/src/commands/bench.ts +64 -0
  189. package/src/commands/browser-relay.ts +53 -0
  190. package/src/commands/cleanse.ts +45 -0
  191. package/src/commands/commit.ts +47 -0
  192. package/src/commands/complete.ts +67 -0
  193. package/src/commands/completions.ts +61 -0
  194. package/src/commands/config.ts +52 -0
  195. package/src/commands/dry-balance.ts +43 -0
  196. package/src/commands/gallery.ts +61 -0
  197. package/src/commands/gc.ts +47 -0
  198. package/src/commands/grep.ts +49 -0
  199. package/src/commands/grievances.ts +52 -0
  200. package/src/commands/install.ts +107 -0
  201. package/src/commands/join.ts +40 -0
  202. package/src/commands/launch-help.ts +116 -0
  203. package/src/commands/launch.ts +34 -0
  204. package/src/commands/models.ts +61 -0
  205. package/src/commands/plugin.ts +79 -0
  206. package/src/commands/read.ts +39 -0
  207. package/src/commands/say.ts +146 -0
  208. package/src/commands/setup.ts +68 -0
  209. package/src/commands/share.ts +71 -0
  210. package/src/commands/shell.ts +30 -0
  211. package/src/commands/ssh.ts +61 -0
  212. package/src/commands/stats.ts +30 -0
  213. package/src/commands/tiny-models.ts +36 -0
  214. package/src/commands/token.ts +164 -0
  215. package/src/commands/ttsr.ts +125 -0
  216. package/src/commands/update.ts +34 -0
  217. package/src/commands/usage.ts +55 -0
  218. package/src/commands/web-search.ts +43 -0
  219. package/src/commands/worktree.ts +63 -0
  220. package/src/commit/agentic/agent.ts +322 -0
  221. package/src/commit/agentic/fallback.ts +96 -0
  222. package/src/commit/agentic/index.ts +383 -0
  223. package/src/commit/agentic/lock-files.ts +107 -0
  224. package/src/commit/agentic/prompts/analyze-file.md +22 -0
  225. package/src/commit/agentic/prompts/session-user.md +25 -0
  226. package/src/commit/agentic/prompts/split-confirm.md +1 -0
  227. package/src/commit/agentic/prompts/system.md +38 -0
  228. package/src/commit/agentic/state.ts +60 -0
  229. package/src/commit/agentic/tools/analyze-file.ts +148 -0
  230. package/src/commit/agentic/tools/git-file-diff.ts +191 -0
  231. package/src/commit/agentic/tools/git-hunk.ts +52 -0
  232. package/src/commit/agentic/tools/git-overview.ts +62 -0
  233. package/src/commit/agentic/tools/index.ts +54 -0
  234. package/src/commit/agentic/tools/propose-changelog.ts +147 -0
  235. package/src/commit/agentic/tools/propose-commit.ts +109 -0
  236. package/src/commit/agentic/tools/recent-commits.ts +81 -0
  237. package/src/commit/agentic/tools/schemas.ts +11 -0
  238. package/src/commit/agentic/tools/split-commit.ts +241 -0
  239. package/src/commit/agentic/topo-sort.ts +44 -0
  240. package/src/commit/agentic/trivial.ts +51 -0
  241. package/src/commit/agentic/validation.ts +183 -0
  242. package/src/commit/analysis/conventional.ts +64 -0
  243. package/src/commit/analysis/index.ts +4 -0
  244. package/src/commit/analysis/scope.ts +242 -0
  245. package/src/commit/analysis/summary.ts +107 -0
  246. package/src/commit/analysis/validation.ts +66 -0
  247. package/src/commit/changelog/detect.ts +40 -0
  248. package/src/commit/changelog/generate.ts +101 -0
  249. package/src/commit/changelog/index.ts +234 -0
  250. package/src/commit/changelog/parse.ts +44 -0
  251. package/src/commit/cli.ts +85 -0
  252. package/src/commit/git/diff.ts +148 -0
  253. package/src/commit/index.ts +5 -0
  254. package/src/commit/map-reduce/index.ts +69 -0
  255. package/src/commit/map-reduce/map-phase.ts +193 -0
  256. package/src/commit/map-reduce/reduce-phase.ts +49 -0
  257. package/src/commit/map-reduce/utils.ts +9 -0
  258. package/src/commit/message.ts +11 -0
  259. package/src/commit/model-selection.ts +95 -0
  260. package/src/commit/pipeline.ts +244 -0
  261. package/src/commit/prompts/analysis-system.md +148 -0
  262. package/src/commit/prompts/analysis-user.md +38 -0
  263. package/src/commit/prompts/changelog-system.md +50 -0
  264. package/src/commit/prompts/changelog-user.md +18 -0
  265. package/src/commit/prompts/file-observer-system.md +24 -0
  266. package/src/commit/prompts/file-observer-user.md +8 -0
  267. package/src/commit/prompts/reduce-system.md +50 -0
  268. package/src/commit/prompts/reduce-user.md +17 -0
  269. package/src/commit/prompts/summary-retry.md +3 -0
  270. package/src/commit/prompts/summary-system.md +38 -0
  271. package/src/commit/prompts/summary-user.md +13 -0
  272. package/src/commit/prompts/types-description.md +2 -0
  273. package/src/commit/shared-llm.ts +70 -0
  274. package/src/commit/types.ts +118 -0
  275. package/src/commit/utils/exclusions.ts +42 -0
  276. package/src/commit/utils.ts +58 -0
  277. package/src/config/api-key-resolver.ts +81 -0
  278. package/src/config/append-only-context-mode.ts +76 -0
  279. package/src/config/config-file.ts +347 -0
  280. package/src/config/inline-tool-descriptors-mode.ts +26 -0
  281. package/src/config/keybindings.ts +703 -0
  282. package/src/config/mcp-schema.json +252 -0
  283. package/src/config/model-discovery.ts +1077 -0
  284. package/src/config/model-registry.ts +2822 -0
  285. package/src/config/model-resolver.ts +2115 -0
  286. package/src/config/model-roles.ts +113 -0
  287. package/src/config/models-config-schema-bundle.ts +315 -0
  288. package/src/config/models-config-schema.ts +14 -0
  289. package/src/config/models-config.ts +130 -0
  290. package/src/config/prompt-templates.ts +205 -0
  291. package/src/config/provider-globals.ts +25 -0
  292. package/src/config/resolve-config-value.ts +95 -0
  293. package/src/config/service-tier.ts +146 -0
  294. package/src/config/settings-schema.ts +5887 -0
  295. package/src/config/settings.ts +2394 -0
  296. package/src/config.ts +242 -0
  297. package/src/cursor-bridge-tools.ts +81 -0
  298. package/src/cursor.ts +960 -0
  299. package/src/dap/client.ts +1043 -0
  300. package/src/dap/config.ts +480 -0
  301. package/src/dap/defaults.json +212 -0
  302. package/src/dap/index.ts +4 -0
  303. package/src/dap/session.ts +1841 -0
  304. package/src/dap/types.ts +611 -0
  305. package/src/debug/index.ts +584 -0
  306. package/src/debug/log-formatting.ts +58 -0
  307. package/src/debug/log-viewer.ts +966 -0
  308. package/src/debug/profiler.ts +168 -0
  309. package/src/debug/protocol-probe.ts +267 -0
  310. package/src/debug/raw-sse-buffer.ts +421 -0
  311. package/src/debug/raw-sse.ts +312 -0
  312. package/src/debug/remote-debugger.ts +151 -0
  313. package/src/debug/report-bundle.ts +411 -0
  314. package/src/debug/system-info.ts +111 -0
  315. package/src/debug/terminal-info.ts +124 -0
  316. package/src/discovery/agent-plugin-format.ts +551 -0
  317. package/src/discovery/agent-plugins.ts +341 -0
  318. package/src/discovery/agents-md.ts +67 -0
  319. package/src/discovery/agents.ts +300 -0
  320. package/src/discovery/at-imports.ts +273 -0
  321. package/src/discovery/builtin-defaults.ts +39 -0
  322. package/src/discovery/builtin-rules/go-add-cleanup.md +33 -0
  323. package/src/discovery/builtin-rules/go-bench-loop.md +36 -0
  324. package/src/discovery/builtin-rules/go-exp-promoted.md +40 -0
  325. package/src/discovery/builtin-rules/go-ioutil.md +37 -0
  326. package/src/discovery/builtin-rules/go-join-hostport.md +30 -0
  327. package/src/discovery/builtin-rules/go-new-expr.md +44 -0
  328. package/src/discovery/builtin-rules/go-rand-v2.md +41 -0
  329. package/src/discovery/builtin-rules/go-range-int.md +45 -0
  330. package/src/discovery/builtin-rules/index.ts +74 -0
  331. package/src/discovery/builtin-rules/rs-box-leak.md +49 -0
  332. package/src/discovery/builtin-rules/rs-future-prelude.md +24 -0
  333. package/src/discovery/builtin-rules/rs-lazylock.md +52 -0
  334. package/src/discovery/builtin-rules/rs-match-ergonomics.md +68 -0
  335. package/src/discovery/builtin-rules/rs-parking-lot.md +45 -0
  336. package/src/discovery/builtin-rules/rs-result-type.md +20 -0
  337. package/src/discovery/builtin-rules/ts-bare-catch.md +39 -0
  338. package/src/discovery/builtin-rules/ts-import-type.md +43 -0
  339. package/src/discovery/builtin-rules/ts-no-any.md +66 -0
  340. package/src/discovery/builtin-rules/ts-no-deprecated-leftovers.md +45 -0
  341. package/src/discovery/builtin-rules/ts-no-dynamic-import.md +40 -0
  342. package/src/discovery/builtin-rules/ts-no-inline-cast-access.md +56 -0
  343. package/src/discovery/builtin-rules/ts-no-local-is-record.md +48 -0
  344. package/src/discovery/builtin-rules/ts-no-return-type.md +45 -0
  345. package/src/discovery/builtin-rules/ts-no-test-timers.md +55 -0
  346. package/src/discovery/builtin-rules/ts-no-tiny-functions.md +51 -0
  347. package/src/discovery/builtin-rules/ts-promise-with-resolvers.md +66 -0
  348. package/src/discovery/builtin-rules/ts-redundant-clear-guard.md +75 -0
  349. package/src/discovery/builtin-rules/ts-set-map.md +28 -0
  350. package/src/discovery/builtin.ts +945 -0
  351. package/src/discovery/claude-plugins.ts +640 -0
  352. package/src/discovery/claude.ts +591 -0
  353. package/src/discovery/cline.ts +83 -0
  354. package/src/discovery/codex.ts +553 -0
  355. package/src/discovery/contained-path.ts +78 -0
  356. package/src/discovery/cursor.ts +223 -0
  357. package/src/discovery/gemini.ts +386 -0
  358. package/src/discovery/github.ts +337 -0
  359. package/src/discovery/helpers.ts +1183 -0
  360. package/src/discovery/index.ts +82 -0
  361. package/src/discovery/mcp-json.ts +182 -0
  362. package/src/discovery/omp-extension-roots.ts +272 -0
  363. package/src/discovery/omp-plugins.ts +409 -0
  364. package/src/discovery/opencode.ts +441 -0
  365. package/src/discovery/plugin-dir-roots.ts +28 -0
  366. package/src/discovery/ssh.ts +153 -0
  367. package/src/discovery/substitute-plugin-root.ts +77 -0
  368. package/src/discovery/vscode.ts +106 -0
  369. package/src/discovery/windsurf.ts +149 -0
  370. package/src/edit/apply-patch/index.ts +87 -0
  371. package/src/edit/apply-patch/parser.ts +174 -0
  372. package/src/edit/diff.ts +1008 -0
  373. package/src/edit/edit-clipboard.ts +23 -0
  374. package/src/edit/file-snapshot-store.ts +148 -0
  375. package/src/edit/hashline/block-resolver.ts +33 -0
  376. package/src/edit/hashline/diff.ts +402 -0
  377. package/src/edit/hashline/execute.ts +313 -0
  378. package/src/edit/hashline/filesystem.ts +247 -0
  379. package/src/edit/hashline/index.ts +5 -0
  380. package/src/edit/hashline/noop-loop-guard.ts +99 -0
  381. package/src/edit/hashline/params.ts +12 -0
  382. package/src/edit/index.ts +699 -0
  383. package/src/edit/modes/apply-patch.lark +19 -0
  384. package/src/edit/modes/apply-patch.ts +53 -0
  385. package/src/edit/modes/patch.ts +1958 -0
  386. package/src/edit/modes/replace.ts +1191 -0
  387. package/src/edit/normalize.ts +345 -0
  388. package/src/edit/notebook.ts +242 -0
  389. package/src/edit/read-file.ts +25 -0
  390. package/src/edit/renderer.ts +998 -0
  391. package/src/edit/snapshot-details.ts +77 -0
  392. package/src/edit/streaming.ts +710 -0
  393. package/src/eval/agent-bridge.ts +224 -0
  394. package/src/eval/backend-helpers.ts +48 -0
  395. package/src/eval/backend.ts +71 -0
  396. package/src/eval/bridge-timeout.ts +65 -0
  397. package/src/eval/budget-bridge.ts +48 -0
  398. package/src/eval/completion-bridge.ts +212 -0
  399. package/src/eval/concurrency-bridge.ts +34 -0
  400. package/src/eval/executor-base.ts +562 -0
  401. package/src/eval/idle-timeout.ts +91 -0
  402. package/src/eval/index.ts +6 -0
  403. package/src/eval/jl/executor.ts +551 -0
  404. package/src/eval/jl/index.ts +54 -0
  405. package/src/eval/jl/kernel.ts +236 -0
  406. package/src/eval/jl/prelude.jl +736 -0
  407. package/src/eval/jl/prelude.ts +3 -0
  408. package/src/eval/jl/runner.jl +666 -0
  409. package/src/eval/jl/runtime.ts +118 -0
  410. package/src/eval/js/context-manager.ts +872 -0
  411. package/src/eval/js/executor.ts +178 -0
  412. package/src/eval/js/index.ts +41 -0
  413. package/src/eval/js/process-entry.ts +31 -0
  414. package/src/eval/js/shared/helpers.ts +170 -0
  415. package/src/eval/js/shared/indirect-eval.ts +30 -0
  416. package/src/eval/js/shared/local-module-loader.ts +364 -0
  417. package/src/eval/js/shared/prelude.ts +2 -0
  418. package/src/eval/js/shared/prelude.txt +294 -0
  419. package/src/eval/js/shared/rewrite-imports.ts +550 -0
  420. package/src/eval/js/shared/runtime.ts +590 -0
  421. package/src/eval/js/shared/types.ts +18 -0
  422. package/src/eval/js/tool-bridge.ts +163 -0
  423. package/src/eval/js/worker-core.ts +380 -0
  424. package/src/eval/js/worker-entry.ts +37 -0
  425. package/src/eval/js/worker-protocol.ts +47 -0
  426. package/src/eval/kernel-base.ts +569 -0
  427. package/src/eval/py/display.ts +71 -0
  428. package/src/eval/py/executor.ts +643 -0
  429. package/src/eval/py/index.ts +57 -0
  430. package/src/eval/py/kernel.ts +235 -0
  431. package/src/eval/py/prelude.py +673 -0
  432. package/src/eval/py/prelude.ts +3 -0
  433. package/src/eval/py/runner.py +1425 -0
  434. package/src/eval/py/runtime.ts +276 -0
  435. package/src/eval/py/spawn-options.ts +134 -0
  436. package/src/eval/py/tool-bridge.ts +201 -0
  437. package/src/eval/rb/executor.ts +511 -0
  438. package/src/eval/rb/index.ts +54 -0
  439. package/src/eval/rb/kernel.ts +231 -0
  440. package/src/eval/rb/prelude.rb +551 -0
  441. package/src/eval/rb/prelude.ts +3 -0
  442. package/src/eval/rb/runner.rb +581 -0
  443. package/src/eval/rb/runtime.ts +132 -0
  444. package/src/eval/runtime-env.ts +104 -0
  445. package/src/eval/session-id.ts +8 -0
  446. package/src/eval/types.ts +48 -0
  447. package/src/exa/index.ts +2 -0
  448. package/src/exa/mcp-client.ts +370 -0
  449. package/src/exa/types.ts +69 -0
  450. package/src/exec/bash-executor.ts +627 -0
  451. package/src/exec/direnv.ts +145 -0
  452. package/src/exec/exec.ts +53 -0
  453. package/src/exec/non-interactive-env.ts +118 -0
  454. package/src/export/custom-share.ts +65 -0
  455. package/src/export/html/args.ts +20 -0
  456. package/src/export/html/index.ts +320 -0
  457. package/src/export/html/share-loader.js +102 -0
  458. package/src/export/html/template.css +1355 -0
  459. package/src/export/html/template.html +55 -0
  460. package/src/export/html/template.js +1653 -0
  461. package/src/export/html/tool-views.generated.js +35 -0
  462. package/src/export/html/vendor/highlight.min.js +1213 -0
  463. package/src/export/html/vendor/marked.min.js +6 -0
  464. package/src/export/html/web-palette.ts +126 -0
  465. package/src/export/share.ts +686 -0
  466. package/src/export/ttsr.ts +590 -0
  467. package/src/extensibility/custom-commands/bundled/ci-green/index.ts +54 -0
  468. package/src/extensibility/custom-commands/bundled/review/index.ts +698 -0
  469. package/src/extensibility/custom-commands/index.ts +2 -0
  470. package/src/extensibility/custom-commands/loader.ts +242 -0
  471. package/src/extensibility/custom-commands/types.ts +119 -0
  472. package/src/extensibility/custom-tools/index.ts +7 -0
  473. package/src/extensibility/custom-tools/loader.ts +301 -0
  474. package/src/extensibility/custom-tools/types.ts +287 -0
  475. package/src/extensibility/custom-tools/wrapper.ts +50 -0
  476. package/src/extensibility/extensions/compact-handler.ts +40 -0
  477. package/src/extensibility/extensions/get-commands-handler.ts +78 -0
  478. package/src/extensibility/extensions/index.ts +16 -0
  479. package/src/extensibility/extensions/load-errors.ts +13 -0
  480. package/src/extensibility/extensions/loader.ts +663 -0
  481. package/src/extensibility/extensions/managed-timers.ts +83 -0
  482. package/src/extensibility/extensions/model-api.ts +39 -0
  483. package/src/extensibility/extensions/runner.ts +1396 -0
  484. package/src/extensibility/extensions/types.ts +1603 -0
  485. package/src/extensibility/extensions/wrapper.ts +395 -0
  486. package/src/extensibility/hooks/index.ts +6 -0
  487. package/src/extensibility/hooks/loader.ts +243 -0
  488. package/src/extensibility/hooks/runner.ts +425 -0
  489. package/src/extensibility/hooks/tool-wrapper.ts +124 -0
  490. package/src/extensibility/hooks/types.ts +612 -0
  491. package/src/extensibility/legacy-pi-ai-shim.ts +161 -0
  492. package/src/extensibility/legacy-pi-coding-agent-shim.ts +1460 -0
  493. package/src/extensibility/legacy-pi-tui-shim.ts +43 -0
  494. package/src/extensibility/legacy-typebox.ts +101 -0
  495. package/src/extensibility/plugins/bun-git-cache.ts +91 -0
  496. package/src/extensibility/plugins/doctor.ts +65 -0
  497. package/src/extensibility/plugins/git-url.ts +367 -0
  498. package/src/extensibility/plugins/index.ts +9 -0
  499. package/src/extensibility/plugins/installer.ts +201 -0
  500. package/src/extensibility/plugins/legacy-pi-compat.ts +2643 -0
  501. package/src/extensibility/plugins/legacy-pi-virtual-modules.d.ts +4 -0
  502. package/src/extensibility/plugins/loader.ts +535 -0
  503. package/src/extensibility/plugins/manager.ts +1142 -0
  504. package/src/extensibility/plugins/marketplace/cache.ts +136 -0
  505. package/src/extensibility/plugins/marketplace/fetcher.ts +316 -0
  506. package/src/extensibility/plugins/marketplace/index.ts +6 -0
  507. package/src/extensibility/plugins/marketplace/manager.ts +927 -0
  508. package/src/extensibility/plugins/marketplace/registry.ts +192 -0
  509. package/src/extensibility/plugins/marketplace/source-resolver.ts +147 -0
  510. package/src/extensibility/plugins/marketplace/types.ts +192 -0
  511. package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
  512. package/src/extensibility/plugins/parser.ts +107 -0
  513. package/src/extensibility/plugins/runtime-config.ts +9 -0
  514. package/src/extensibility/plugins/types.ts +194 -0
  515. package/src/extensibility/session-handler-types.ts +21 -0
  516. package/src/extensibility/shared-events.ts +401 -0
  517. package/src/extensibility/skills.ts +531 -0
  518. package/src/extensibility/slash-commands.ts +131 -0
  519. package/src/extensibility/tool-event-input.ts +80 -0
  520. package/src/extensibility/tool-proxy.ts +28 -0
  521. package/src/extensibility/utils.ts +192 -0
  522. package/src/goals/index.ts +3 -0
  523. package/src/goals/runtime.ts +521 -0
  524. package/src/goals/state.ts +37 -0
  525. package/src/goals/tools/goal-tool.ts +251 -0
  526. package/src/hindsight/backend.ts +354 -0
  527. package/src/hindsight/bank.ts +156 -0
  528. package/src/hindsight/client.ts +680 -0
  529. package/src/hindsight/config.ts +193 -0
  530. package/src/hindsight/content.ts +266 -0
  531. package/src/hindsight/index.ts +8 -0
  532. package/src/hindsight/mental-models.ts +429 -0
  533. package/src/hindsight/seeds.json +32 -0
  534. package/src/hindsight/state.ts +530 -0
  535. package/src/hindsight/transcript.ts +71 -0
  536. package/src/index.ts +66 -0
  537. package/src/internal-urls/agent-protocol.ts +180 -0
  538. package/src/internal-urls/artifact-protocol.ts +151 -0
  539. package/src/internal-urls/docs-index.ts +102 -0
  540. package/src/internal-urls/filesystem-resource.ts +34 -0
  541. package/src/internal-urls/history-protocol.ts +198 -0
  542. package/src/internal-urls/index.ts +28 -0
  543. package/src/internal-urls/issue-pr-protocol.ts +594 -0
  544. package/src/internal-urls/json-query.ts +126 -0
  545. package/src/internal-urls/local-protocol.ts +471 -0
  546. package/src/internal-urls/mcp-protocol.ts +168 -0
  547. package/src/internal-urls/memory-protocol.ts +380 -0
  548. package/src/internal-urls/omp-protocol.ts +94 -0
  549. package/src/internal-urls/parse.ts +103 -0
  550. package/src/internal-urls/registry-helpers.ts +132 -0
  551. package/src/internal-urls/router.ts +153 -0
  552. package/src/internal-urls/rule-protocol.ts +45 -0
  553. package/src/internal-urls/security-protocol.ts +261 -0
  554. package/src/internal-urls/skill-protocol.ts +131 -0
  555. package/src/internal-urls/ssh-protocol.ts +368 -0
  556. package/src/internal-urls/types.ts +196 -0
  557. package/src/internal-urls/vault-protocol.ts +940 -0
  558. package/src/internal-urls/xd-protocol.ts +46 -0
  559. package/src/irc/bus.ts +380 -0
  560. package/src/jsonrpc/message-framing.ts +142 -0
  561. package/src/launch/broker.ts +1368 -0
  562. package/src/launch/client.ts +521 -0
  563. package/src/launch/ensure.ts +73 -0
  564. package/src/launch/paths.ts +14 -0
  565. package/src/launch/presence.ts +82 -0
  566. package/src/launch/protocol.ts +448 -0
  567. package/src/launch/spawn-options.ts +17 -0
  568. package/src/launch/terminal-output-worker-client.ts +53 -0
  569. package/src/launch/terminal-output-worker-protocol.ts +11 -0
  570. package/src/launch/terminal-output-worker.ts +23 -0
  571. package/src/launch/terminal-output.ts +46 -0
  572. package/src/lib/xai-http.ts +150 -0
  573. package/src/live/attestation.ts +91 -0
  574. package/src/live/controller.ts +517 -0
  575. package/src/live/prompts/agent-final-message.md +3 -0
  576. package/src/live/prompts/live-instructions.md +23 -0
  577. package/src/live/protocol.ts +233 -0
  578. package/src/live/transport.ts +422 -0
  579. package/src/live/visualizer.ts +221 -0
  580. package/src/live/voices.ts +18 -0
  581. package/src/lsp/client.ts +1465 -0
  582. package/src/lsp/clients/biome-client.ts +263 -0
  583. package/src/lsp/clients/index.ts +50 -0
  584. package/src/lsp/clients/lsp-linter-client.ts +85 -0
  585. package/src/lsp/clients/swiftlint-client.ts +120 -0
  586. package/src/lsp/config.ts +549 -0
  587. package/src/lsp/defaults.json +499 -0
  588. package/src/lsp/deferred-diagnostics.ts +66 -0
  589. package/src/lsp/diagnostics-ledger.ts +51 -0
  590. package/src/lsp/edits.ts +288 -0
  591. package/src/lsp/format-options.ts +119 -0
  592. package/src/lsp/index.ts +2821 -0
  593. package/src/lsp/lspmux.ts +233 -0
  594. package/src/lsp/mux/daemon.ts +348 -0
  595. package/src/lsp/mux/protocol.ts +96 -0
  596. package/src/lsp/mux/server.ts +797 -0
  597. package/src/lsp/render.ts +668 -0
  598. package/src/lsp/startup-events.ts +13 -0
  599. package/src/lsp/types.ts +479 -0
  600. package/src/lsp/utils.ts +747 -0
  601. package/src/main.ts +1799 -0
  602. package/src/markit/NOTICE +32 -0
  603. package/src/markit/converters/docx.ts +56 -0
  604. package/src/markit/converters/epub.ts +136 -0
  605. package/src/markit/converters/pdf/columns.ts +103 -0
  606. package/src/markit/converters/pdf/extract.ts +598 -0
  607. package/src/markit/converters/pdf/grid.ts +780 -0
  608. package/src/markit/converters/pdf/headers.ts +106 -0
  609. package/src/markit/converters/pdf/index.ts +146 -0
  610. package/src/markit/converters/pdf/render.ts +501 -0
  611. package/src/markit/converters/pdf/types.ts +84 -0
  612. package/src/markit/converters/pptx.ts +325 -0
  613. package/src/markit/converters/xlsx.ts +173 -0
  614. package/src/markit/index.ts +2 -0
  615. package/src/markit/registry.ts +59 -0
  616. package/src/markit/types.ts +35 -0
  617. package/src/mcp/client.ts +511 -0
  618. package/src/mcp/config-writer.ts +377 -0
  619. package/src/mcp/config.ts +385 -0
  620. package/src/mcp/index.ts +29 -0
  621. package/src/mcp/json-rpc.ts +122 -0
  622. package/src/mcp/loader.ts +125 -0
  623. package/src/mcp/manager.ts +1524 -0
  624. package/src/mcp/oauth-credentials.ts +104 -0
  625. package/src/mcp/oauth-discovery.ts +587 -0
  626. package/src/mcp/oauth-flow.ts +830 -0
  627. package/src/mcp/render.ts +214 -0
  628. package/src/mcp/request-id.ts +24 -0
  629. package/src/mcp/smithery-auth.ts +108 -0
  630. package/src/mcp/smithery-connect.ts +154 -0
  631. package/src/mcp/smithery-registry.ts +500 -0
  632. package/src/mcp/startup-events.ts +116 -0
  633. package/src/mcp/timeout.ts +59 -0
  634. package/src/mcp/tool-bridge.ts +691 -0
  635. package/src/mcp/tool-cache.ts +117 -0
  636. package/src/mcp/transports/header-policy.ts +95 -0
  637. package/src/mcp/transports/http.ts +508 -0
  638. package/src/mcp/transports/index.ts +7 -0
  639. package/src/mcp/transports/sse.ts +372 -0
  640. package/src/mcp/transports/stdio.ts +905 -0
  641. package/src/mcp/types.ts +467 -0
  642. package/src/memories/index.ts +1434 -0
  643. package/src/memories/storage.ts +578 -0
  644. package/src/memory-backend/index.ts +19 -0
  645. package/src/memory-backend/local-backend.ts +47 -0
  646. package/src/memory-backend/messages.ts +19 -0
  647. package/src/memory-backend/off-backend.ts +25 -0
  648. package/src/memory-backend/resolve.ts +25 -0
  649. package/src/memory-backend/runtime.ts +66 -0
  650. package/src/memory-backend/tool-names.ts +2 -0
  651. package/src/memory-backend/types.ts +166 -0
  652. package/src/mnemopi/backend.ts +629 -0
  653. package/src/mnemopi/config.ts +267 -0
  654. package/src/mnemopi/embed-client.ts +293 -0
  655. package/src/mnemopi/embed-protocol.ts +35 -0
  656. package/src/mnemopi/embed-worker.ts +114 -0
  657. package/src/mnemopi/index.ts +3 -0
  658. package/src/mnemopi/state.ts +940 -0
  659. package/src/modes/acp/acp-agent.ts +2595 -0
  660. package/src/modes/acp/acp-client-bridge.ts +154 -0
  661. package/src/modes/acp/acp-event-mapper.ts +1084 -0
  662. package/src/modes/acp/acp-mode.ts +48 -0
  663. package/src/modes/acp/index.ts +2 -0
  664. package/src/modes/acp/terminal-auth.ts +37 -0
  665. package/src/modes/components/advisor-config.ts +635 -0
  666. package/src/modes/components/advisor-message.ts +109 -0
  667. package/src/modes/components/agent-dashboard.ts +1252 -0
  668. package/src/modes/components/agent-hub-projection.ts +248 -0
  669. package/src/modes/components/agent-hub-renderer.ts +194 -0
  670. package/src/modes/components/agent-hub.ts +1126 -0
  671. package/src/modes/components/agent-transcript-viewer.ts +647 -0
  672. package/src/modes/components/ask-dialog.ts +1017 -0
  673. package/src/modes/components/assistant-message.ts +978 -0
  674. package/src/modes/components/background-tan-message.ts +36 -0
  675. package/src/modes/components/bash-execution.ts +233 -0
  676. package/src/modes/components/bordered-loader.ts +41 -0
  677. package/src/modes/components/btw-panel.ts +161 -0
  678. package/src/modes/components/cache-invalidation-marker.ts +110 -0
  679. package/src/modes/components/chat-block.ts +111 -0
  680. package/src/modes/components/chat-transcript-builder.ts +518 -0
  681. package/src/modes/components/codex-reset-fireworks.ts +369 -0
  682. package/src/modes/components/collab-prompt-message.ts +32 -0
  683. package/src/modes/components/compaction-summary-message.ts +221 -0
  684. package/src/modes/components/copy-selector.ts +218 -0
  685. package/src/modes/components/countdown-timer.ts +75 -0
  686. package/src/modes/components/custom-editor.ts +1036 -0
  687. package/src/modes/components/custom-message.ts +70 -0
  688. package/src/modes/components/diff.ts +254 -0
  689. package/src/modes/components/dynamic-border.ts +37 -0
  690. package/src/modes/components/error-banner.ts +33 -0
  691. package/src/modes/components/eval-execution.ts +169 -0
  692. package/src/modes/components/execution-shared.ts +101 -0
  693. package/src/modes/components/extensions/extension-dashboard.ts +492 -0
  694. package/src/modes/components/extensions/extension-list.ts +507 -0
  695. package/src/modes/components/extensions/index.ts +9 -0
  696. package/src/modes/components/extensions/inspector-panel.ts +326 -0
  697. package/src/modes/components/extensions/state-manager.ts +648 -0
  698. package/src/modes/components/extensions/types.ts +186 -0
  699. package/src/modes/components/footer.ts +274 -0
  700. package/src/modes/components/history-search.ts +268 -0
  701. package/src/modes/components/hook-editor.ts +213 -0
  702. package/src/modes/components/hook-input.ts +87 -0
  703. package/src/modes/components/hook-message.ts +67 -0
  704. package/src/modes/components/hook-selector.ts +691 -0
  705. package/src/modes/components/index.ts +42 -0
  706. package/src/modes/components/keybinding-hints.ts +56 -0
  707. package/src/modes/components/late-diagnostics-message.ts +60 -0
  708. package/src/modes/components/login-dialog.ts +197 -0
  709. package/src/modes/components/logout-account-selector.ts +130 -0
  710. package/src/modes/components/mcp-add-wizard.ts +1413 -0
  711. package/src/modes/components/message-frame.ts +98 -0
  712. package/src/modes/components/model-browser.ts +888 -0
  713. package/src/modes/components/model-hub.ts +2014 -0
  714. package/src/modes/components/model-picker.ts +237 -0
  715. package/src/modes/components/move-overlay.ts +293 -0
  716. package/src/modes/components/oauth-selector.ts +474 -0
  717. package/src/modes/components/omfg-panel.ts +141 -0
  718. package/src/modes/components/overlay-box.ts +109 -0
  719. package/src/modes/components/pause-screen.ts +208 -0
  720. package/src/modes/components/plan-review-overlay.ts +1226 -0
  721. package/src/modes/components/plan-toc.ts +138 -0
  722. package/src/modes/components/plugin-selector.ts +100 -0
  723. package/src/modes/components/plugin-settings.ts +745 -0
  724. package/src/modes/components/queue-mode-selector.ts +61 -0
  725. package/src/modes/components/read-tool-group.ts +856 -0
  726. package/src/modes/components/reset-usage-selector.ts +161 -0
  727. package/src/modes/components/segment-track.ts +89 -0
  728. package/src/modes/components/select-list-mouse-routing.ts +35 -0
  729. package/src/modes/components/selector-helpers.ts +129 -0
  730. package/src/modes/components/session-account-selector.ts +62 -0
  731. package/src/modes/components/session-selector.ts +1035 -0
  732. package/src/modes/components/settings-defs.ts +267 -0
  733. package/src/modes/components/settings-selector.ts +1445 -0
  734. package/src/modes/components/show-images-selector.ts +50 -0
  735. package/src/modes/components/skill-message.ts +110 -0
  736. package/src/modes/components/snapcompact-shape-preview-doc.md +14 -0
  737. package/src/modes/components/snapcompact-shape-preview.ts +192 -0
  738. package/src/modes/components/status-line/component.ts +1843 -0
  739. package/src/modes/components/status-line/context-thresholds.ts +86 -0
  740. package/src/modes/components/status-line/git-utils.ts +42 -0
  741. package/src/modes/components/status-line/index.ts +5 -0
  742. package/src/modes/components/status-line/presets.ts +106 -0
  743. package/src/modes/components/status-line/segments.ts +710 -0
  744. package/src/modes/components/status-line/separators.ts +55 -0
  745. package/src/modes/components/status-line/types.ts +159 -0
  746. package/src/modes/components/stripped-tool-calls-placeholder.ts +35 -0
  747. package/src/modes/components/theme-selector.ts +68 -0
  748. package/src/modes/components/thinking-selector.ts +57 -0
  749. package/src/modes/components/tiny-title-download-progress.ts +90 -0
  750. package/src/modes/components/tips.txt +26 -0
  751. package/src/modes/components/todo-reminder.ts +43 -0
  752. package/src/modes/components/tool-execution.ts +1467 -0
  753. package/src/modes/components/transcript-container.ts +527 -0
  754. package/src/modes/components/tree-selector.ts +1006 -0
  755. package/src/modes/components/ttsr-notification.ts +123 -0
  756. package/src/modes/components/usage-row.ts +52 -0
  757. package/src/modes/components/user-message-selector.ts +227 -0
  758. package/src/modes/components/user-message.ts +167 -0
  759. package/src/modes/components/visual-truncate.ts +63 -0
  760. package/src/modes/components/welcome.ts +578 -0
  761. package/src/modes/controllers/btw-controller.ts +246 -0
  762. package/src/modes/controllers/command-controller-shared.ts +109 -0
  763. package/src/modes/controllers/command-controller.ts +2024 -0
  764. package/src/modes/controllers/event-controller.ts +2173 -0
  765. package/src/modes/controllers/extension-ui-controller.ts +1243 -0
  766. package/src/modes/controllers/input-controller.ts +2037 -0
  767. package/src/modes/controllers/live-command-controller.ts +261 -0
  768. package/src/modes/controllers/mcp-command-controller.ts +2552 -0
  769. package/src/modes/controllers/omfg-controller.ts +287 -0
  770. package/src/modes/controllers/omfg-rule.ts +647 -0
  771. package/src/modes/controllers/selector-controller.ts +2058 -0
  772. package/src/modes/controllers/session-focus-controller.ts +117 -0
  773. package/src/modes/controllers/ssh-command-controller.ts +385 -0
  774. package/src/modes/controllers/streaming-reveal.ts +399 -0
  775. package/src/modes/controllers/tan-command-controller.ts +243 -0
  776. package/src/modes/controllers/todo-command-controller.ts +487 -0
  777. package/src/modes/controllers/tool-args-reveal.ts +591 -0
  778. package/src/modes/data/emojis.json +1 -0
  779. package/src/modes/emoji-autocomplete.ts +285 -0
  780. package/src/modes/github-ref-autocomplete.ts +75 -0
  781. package/src/modes/gradient-highlight.ts +99 -0
  782. package/src/modes/image-references.ts +137 -0
  783. package/src/modes/index.ts +10 -0
  784. package/src/modes/interactive-mode.ts +5055 -0
  785. package/src/modes/internal-url-autocomplete.ts +158 -0
  786. package/src/modes/loop-limit.ts +192 -0
  787. package/src/modes/magic-keyword-boundary.ts +23 -0
  788. package/src/modes/magic-keywords.ts +42 -0
  789. package/src/modes/markdown-prose.ts +247 -0
  790. package/src/modes/oauth-manual-input.ts +69 -0
  791. package/src/modes/orchestrate.ts +43 -0
  792. package/src/modes/print-mode.ts +282 -0
  793. package/src/modes/prompt-action-autocomplete.ts +322 -0
  794. package/src/modes/queue-input.ts +132 -0
  795. package/src/modes/rpc/host-tools.ts +204 -0
  796. package/src/modes/rpc/host-uris.ts +241 -0
  797. package/src/modes/rpc/rpc-client.ts +1213 -0
  798. package/src/modes/rpc/rpc-frame.ts +316 -0
  799. package/src/modes/rpc/rpc-input.ts +38 -0
  800. package/src/modes/rpc/rpc-messages.ts +127 -0
  801. package/src/modes/rpc/rpc-mode.ts +1519 -0
  802. package/src/modes/rpc/rpc-subagents.ts +265 -0
  803. package/src/modes/rpc/rpc-types.ts +544 -0
  804. package/src/modes/running-subagent-badge.ts +13 -0
  805. package/src/modes/runtime-init.ts +144 -0
  806. package/src/modes/session-observer-registry.ts +223 -0
  807. package/src/modes/session-teardown.ts +82 -0
  808. package/src/modes/setup-version.ts +11 -0
  809. package/src/modes/setup-wizard/index.ts +103 -0
  810. package/src/modes/setup-wizard/lazy.ts +16 -0
  811. package/src/modes/setup-wizard/scenes/glyph.ts +103 -0
  812. package/src/modes/setup-wizard/scenes/model.ts +132 -0
  813. package/src/modes/setup-wizard/scenes/outro.ts +35 -0
  814. package/src/modes/setup-wizard/scenes/providers.ts +105 -0
  815. package/src/modes/setup-wizard/scenes/sign-in.ts +312 -0
  816. package/src/modes/setup-wizard/scenes/splash.ts +201 -0
  817. package/src/modes/setup-wizard/scenes/theme.ts +330 -0
  818. package/src/modes/setup-wizard/scenes/types.ts +65 -0
  819. package/src/modes/setup-wizard/scenes/web-search.ts +153 -0
  820. package/src/modes/setup-wizard/startup-splash.ts +107 -0
  821. package/src/modes/setup-wizard/wizard-overlay.ts +335 -0
  822. package/src/modes/shared.ts +49 -0
  823. package/src/modes/skill-command.ts +91 -0
  824. package/src/modes/theme/dark.json +95 -0
  825. package/src/modes/theme/defaults/alabaster.json +93 -0
  826. package/src/modes/theme/defaults/amethyst.json +96 -0
  827. package/src/modes/theme/defaults/anthracite.json +93 -0
  828. package/src/modes/theme/defaults/basalt.json +91 -0
  829. package/src/modes/theme/defaults/birch.json +95 -0
  830. package/src/modes/theme/defaults/dark-abyss.json +91 -0
  831. package/src/modes/theme/defaults/dark-arctic.json +104 -0
  832. package/src/modes/theme/defaults/dark-aurora.json +95 -0
  833. package/src/modes/theme/defaults/dark-catppuccin.json +107 -0
  834. package/src/modes/theme/defaults/dark-cavern.json +91 -0
  835. package/src/modes/theme/defaults/dark-copper.json +95 -0
  836. package/src/modes/theme/defaults/dark-cosmos.json +90 -0
  837. package/src/modes/theme/defaults/dark-cyberpunk.json +102 -0
  838. package/src/modes/theme/defaults/dark-dracula.json +98 -0
  839. package/src/modes/theme/defaults/dark-eclipse.json +91 -0
  840. package/src/modes/theme/defaults/dark-ember.json +95 -0
  841. package/src/modes/theme/defaults/dark-equinox.json +90 -0
  842. package/src/modes/theme/defaults/dark-forest.json +96 -0
  843. package/src/modes/theme/defaults/dark-github.json +105 -0
  844. package/src/modes/theme/defaults/dark-gruvbox.json +112 -0
  845. package/src/modes/theme/defaults/dark-lavender.json +95 -0
  846. package/src/modes/theme/defaults/dark-lunar.json +89 -0
  847. package/src/modes/theme/defaults/dark-midnight.json +95 -0
  848. package/src/modes/theme/defaults/dark-monochrome.json +94 -0
  849. package/src/modes/theme/defaults/dark-monokai.json +98 -0
  850. package/src/modes/theme/defaults/dark-nebula.json +90 -0
  851. package/src/modes/theme/defaults/dark-nord.json +97 -0
  852. package/src/modes/theme/defaults/dark-ocean.json +101 -0
  853. package/src/modes/theme/defaults/dark-one.json +100 -0
  854. package/src/modes/theme/defaults/dark-poimandres.json +143 -0
  855. package/src/modes/theme/defaults/dark-rainforest.json +91 -0
  856. package/src/modes/theme/defaults/dark-reef.json +91 -0
  857. package/src/modes/theme/defaults/dark-retro.json +92 -0
  858. package/src/modes/theme/defaults/dark-rose-pine.json +96 -0
  859. package/src/modes/theme/defaults/dark-sakura.json +95 -0
  860. package/src/modes/theme/defaults/dark-slate.json +95 -0
  861. package/src/modes/theme/defaults/dark-solarized.json +97 -0
  862. package/src/modes/theme/defaults/dark-solstice.json +90 -0
  863. package/src/modes/theme/defaults/dark-starfall.json +91 -0
  864. package/src/modes/theme/defaults/dark-sunset.json +99 -0
  865. package/src/modes/theme/defaults/dark-swamp.json +90 -0
  866. package/src/modes/theme/defaults/dark-synthwave.json +103 -0
  867. package/src/modes/theme/defaults/dark-taiga.json +91 -0
  868. package/src/modes/theme/defaults/dark-terminal.json +95 -0
  869. package/src/modes/theme/defaults/dark-tokyo-night.json +101 -0
  870. package/src/modes/theme/defaults/dark-tundra.json +91 -0
  871. package/src/modes/theme/defaults/dark-twilight.json +91 -0
  872. package/src/modes/theme/defaults/dark-volcanic.json +91 -0
  873. package/src/modes/theme/defaults/graphite.json +92 -0
  874. package/src/modes/theme/defaults/index.ts +199 -0
  875. package/src/modes/theme/defaults/light-arctic.json +107 -0
  876. package/src/modes/theme/defaults/light-aurora-day.json +91 -0
  877. package/src/modes/theme/defaults/light-canyon.json +91 -0
  878. package/src/modes/theme/defaults/light-catppuccin.json +106 -0
  879. package/src/modes/theme/defaults/light-cirrus.json +90 -0
  880. package/src/modes/theme/defaults/light-coral.json +95 -0
  881. package/src/modes/theme/defaults/light-cyberpunk.json +96 -0
  882. package/src/modes/theme/defaults/light-dawn.json +90 -0
  883. package/src/modes/theme/defaults/light-dunes.json +91 -0
  884. package/src/modes/theme/defaults/light-eucalyptus.json +95 -0
  885. package/src/modes/theme/defaults/light-forest.json +100 -0
  886. package/src/modes/theme/defaults/light-frost.json +95 -0
  887. package/src/modes/theme/defaults/light-github.json +115 -0
  888. package/src/modes/theme/defaults/light-glacier.json +91 -0
  889. package/src/modes/theme/defaults/light-gruvbox.json +108 -0
  890. package/src/modes/theme/defaults/light-haze.json +90 -0
  891. package/src/modes/theme/defaults/light-honeycomb.json +95 -0
  892. package/src/modes/theme/defaults/light-lagoon.json +91 -0
  893. package/src/modes/theme/defaults/light-lavender.json +95 -0
  894. package/src/modes/theme/defaults/light-meadow.json +91 -0
  895. package/src/modes/theme/defaults/light-mint.json +95 -0
  896. package/src/modes/theme/defaults/light-monochrome.json +101 -0
  897. package/src/modes/theme/defaults/light-ocean.json +99 -0
  898. package/src/modes/theme/defaults/light-one.json +99 -0
  899. package/src/modes/theme/defaults/light-opal.json +91 -0
  900. package/src/modes/theme/defaults/light-orchard.json +91 -0
  901. package/src/modes/theme/defaults/light-paper.json +95 -0
  902. package/src/modes/theme/defaults/light-poimandres.json +143 -0
  903. package/src/modes/theme/defaults/light-prism.json +90 -0
  904. package/src/modes/theme/defaults/light-retro.json +98 -0
  905. package/src/modes/theme/defaults/light-sand.json +95 -0
  906. package/src/modes/theme/defaults/light-savanna.json +91 -0
  907. package/src/modes/theme/defaults/light-solarized.json +102 -0
  908. package/src/modes/theme/defaults/light-soleil.json +90 -0
  909. package/src/modes/theme/defaults/light-sunset.json +99 -0
  910. package/src/modes/theme/defaults/light-synthwave.json +98 -0
  911. package/src/modes/theme/defaults/light-tokyo-night.json +111 -0
  912. package/src/modes/theme/defaults/light-wetland.json +91 -0
  913. package/src/modes/theme/defaults/light-zenith.json +89 -0
  914. package/src/modes/theme/defaults/limestone.json +94 -0
  915. package/src/modes/theme/defaults/mahogany.json +97 -0
  916. package/src/modes/theme/defaults/marble.json +93 -0
  917. package/src/modes/theme/defaults/obsidian.json +91 -0
  918. package/src/modes/theme/defaults/onyx.json +91 -0
  919. package/src/modes/theme/defaults/pearl.json +93 -0
  920. package/src/modes/theme/defaults/porcelain.json +91 -0
  921. package/src/modes/theme/defaults/quartz.json +96 -0
  922. package/src/modes/theme/defaults/sandstone.json +95 -0
  923. package/src/modes/theme/defaults/titanium.json +90 -0
  924. package/src/modes/theme/light.json +93 -0
  925. package/src/modes/theme/mermaid-cache.ts +92 -0
  926. package/src/modes/theme/shimmer.ts +305 -0
  927. package/src/modes/theme/theme-schema.json +463 -0
  928. package/src/modes/theme/theme.ts +3171 -0
  929. package/src/modes/turn-budget.ts +31 -0
  930. package/src/modes/types.ts +494 -0
  931. package/src/modes/ultrathink.ts +42 -0
  932. package/src/modes/utils/context-usage.ts +518 -0
  933. package/src/modes/utils/copy-targets.ts +378 -0
  934. package/src/modes/utils/hotkeys-markdown.ts +65 -0
  935. package/src/modes/utils/interactive-context-helpers.ts +31 -0
  936. package/src/modes/utils/keybinding-matchers.ts +86 -0
  937. package/src/modes/utils/tools-markdown.ts +31 -0
  938. package/src/modes/utils/transcript-render-helpers.ts +253 -0
  939. package/src/modes/utils/ui-helpers.ts +976 -0
  940. package/src/modes/warp-events.ts +232 -0
  941. package/src/modes/workflow.ts +55 -0
  942. package/src/plan-mode/approved-plan.ts +194 -0
  943. package/src/plan-mode/model-transition.ts +51 -0
  944. package/src/plan-mode/plan-files.ts +40 -0
  945. package/src/plan-mode/plan-handoff.ts +37 -0
  946. package/src/plan-mode/plan-protection.ts +31 -0
  947. package/src/plan-mode/state.ts +6 -0
  948. package/src/priority.json +60 -0
  949. package/src/prompts/advisor/active-repo-watchdog.md +6 -0
  950. package/src/prompts/advisor/advise-tool.md +3 -0
  951. package/src/prompts/advisor/context-files.md +8 -0
  952. package/src/prompts/advisor/system.md +98 -0
  953. package/src/prompts/agents/designer.md +74 -0
  954. package/src/prompts/agents/frontmatter.md +12 -0
  955. package/src/prompts/agents/init.md +33 -0
  956. package/src/prompts/agents/librarian.md +119 -0
  957. package/src/prompts/agents/reviewer.md +139 -0
  958. package/src/prompts/agents/scout.md +58 -0
  959. package/src/prompts/agents/security-reviewer.md +75 -0
  960. package/src/prompts/agents/task.md +17 -0
  961. package/src/prompts/bench/cache-prefix-chunk.md +1 -0
  962. package/src/prompts/bench/cache-prefix.md +3 -0
  963. package/src/prompts/bench/cache-suffix.md +1 -0
  964. package/src/prompts/bench.md +6 -0
  965. package/src/prompts/ci-green-request.md +36 -0
  966. package/src/prompts/dry-balance-bench.md +8 -0
  967. package/src/prompts/goals/goal-budget-limit.md +16 -0
  968. package/src/prompts/goals/goal-continuation.md +28 -0
  969. package/src/prompts/goals/goal-mode-active.md +23 -0
  970. package/src/prompts/goals/goal-mode-context.md +4 -0
  971. package/src/prompts/goals/goal-todo-context.md +12 -0
  972. package/src/prompts/goals/guided-goal-interview.md +43 -0
  973. package/src/prompts/memories/consolidation.md +30 -0
  974. package/src/prompts/memories/consolidation_system.md +4 -0
  975. package/src/prompts/memories/read-path.md +17 -0
  976. package/src/prompts/memories/stage_one_input.md +6 -0
  977. package/src/prompts/memories/stage_one_system.md +21 -0
  978. package/src/prompts/review-custom-request.md +21 -0
  979. package/src/prompts/review-headless-request.md +16 -0
  980. package/src/prompts/review-request.md +68 -0
  981. package/src/prompts/security/scan-coordinator.md +7 -0
  982. package/src/prompts/security/scan-request.md +21 -0
  983. package/src/prompts/security/validate-request.md +8 -0
  984. package/src/prompts/session/launch-completion.md +1 -0
  985. package/src/prompts/skills/autoload.md +8 -0
  986. package/src/prompts/skills/user-invocation.md +11 -0
  987. package/src/prompts/steering/parent-irc.md +5 -0
  988. package/src/prompts/steering/user-interjection.md +6 -0
  989. package/src/prompts/system/active-repo-context.md +4 -0
  990. package/src/prompts/system/agent-creation-architect.md +50 -0
  991. package/src/prompts/system/agent-creation-user.md +6 -0
  992. package/src/prompts/system/auto-continue.md +1 -0
  993. package/src/prompts/system/auto-thinking-difficulty-local.md +14 -0
  994. package/src/prompts/system/auto-thinking-difficulty.md +14 -0
  995. package/src/prompts/system/autolearn-guidance-learn.md +1 -0
  996. package/src/prompts/system/autolearn-guidance.md +7 -0
  997. package/src/prompts/system/autolearn-nudge-autocontinue.md +5 -0
  998. package/src/prompts/system/background-tan-dispatch.md +8 -0
  999. package/src/prompts/system/btw-user.md +8 -0
  1000. package/src/prompts/system/commit-message-system.md +14 -0
  1001. package/src/prompts/system/computer-safety.md +14 -0
  1002. package/src/prompts/system/custom-system-prompt.md +64 -0
  1003. package/src/prompts/system/eager-task.md +7 -0
  1004. package/src/prompts/system/eager-todo.md +18 -0
  1005. package/src/prompts/system/empty-stop-retry.md +4 -0
  1006. package/src/prompts/system/gemini-tool-call-reminder.md +9 -0
  1007. package/src/prompts/system/interrupted-thinking.md +7 -0
  1008. package/src/prompts/system/irc-autoreply.md +6 -0
  1009. package/src/prompts/system/irc-incoming.md +9 -0
  1010. package/src/prompts/system/manual-continue.md +7 -0
  1011. package/src/prompts/system/mcp-xdev-guidance.md +11 -0
  1012. package/src/prompts/system/memory-consolidation-system.md +8 -0
  1013. package/src/prompts/system/memory-extraction-system.md +26 -0
  1014. package/src/prompts/system/mid-run-todo-nudge.md +3 -0
  1015. package/src/prompts/system/omfg-user.md +50 -0
  1016. package/src/prompts/system/orchestrate-notice.md +40 -0
  1017. package/src/prompts/system/personalities/default.md +18 -0
  1018. package/src/prompts/system/personalities/friendly.md +17 -0
  1019. package/src/prompts/system/personalities/pragmatic.md +15 -0
  1020. package/src/prompts/system/plan-mode-active.md +125 -0
  1021. package/src/prompts/system/plan-mode-approved.md +22 -0
  1022. package/src/prompts/system/plan-mode-compact-instructions.md +17 -0
  1023. package/src/prompts/system/plan-mode-reference.md +10 -0
  1024. package/src/prompts/system/plan-mode-subagent.md +33 -0
  1025. package/src/prompts/system/plan-mode-tool-decision-reminder.md +9 -0
  1026. package/src/prompts/system/plan-yolo-handoff.md +5 -0
  1027. package/src/prompts/system/prewalk-checklist.md +7 -0
  1028. package/src/prompts/system/prewalk-continue.md +1 -0
  1029. package/src/prompts/system/prewalk-plan.md +13 -0
  1030. package/src/prompts/system/project-prompt.md +61 -0
  1031. package/src/prompts/system/recap-user.md +9 -0
  1032. package/src/prompts/system/resolve-device-reminder.md +3 -0
  1033. package/src/prompts/system/rewind-report.md +6 -0
  1034. package/src/prompts/system/side-channel-no-tools.md +3 -0
  1035. package/src/prompts/system/snapcompact-context-frames-note.md +1 -0
  1036. package/src/prompts/system/snapcompact-context-stub.md +1 -0
  1037. package/src/prompts/system/snapcompact-system-frames-note.md +1 -0
  1038. package/src/prompts/system/snapcompact-system-stub.md +1 -0
  1039. package/src/prompts/system/snapcompact-toolresult-note.md +1 -0
  1040. package/src/prompts/system/speech-rewrite.md +15 -0
  1041. package/src/prompts/system/subagent-async-pending.md +6 -0
  1042. package/src/prompts/system/subagent-system-prompt.md +73 -0
  1043. package/src/prompts/system/subagent-user-prompt.md +3 -0
  1044. package/src/prompts/system/subagent-yield-reminder.md +23 -0
  1045. package/src/prompts/system/system-prompt.md +263 -0
  1046. package/src/prompts/system/tan-context-switch.md +17 -0
  1047. package/src/prompts/system/task-label.md +23 -0
  1048. package/src/prompts/system/thinking-loop-redirect.md +10 -0
  1049. package/src/prompts/system/title-marker-instruction.md +1 -0
  1050. package/src/prompts/system/title-system.md +16 -0
  1051. package/src/prompts/system/tool-call-loop-redirect.md +8 -0
  1052. package/src/prompts/system/ttsr-interrupt.md +7 -0
  1053. package/src/prompts/system/ttsr-tool-reminder.md +5 -0
  1054. package/src/prompts/system/ultrathink-notice.md +3 -0
  1055. package/src/prompts/system/unexpected-stop-classifier.md +17 -0
  1056. package/src/prompts/system/unexpected-stop-retry.md +4 -0
  1057. package/src/prompts/system/vibe-mode-active.md +26 -0
  1058. package/src/prompts/system/web-search.md +25 -0
  1059. package/src/prompts/system/workflow-notice.md +112 -0
  1060. package/src/prompts/system/xdev-mount-notice.md +20 -0
  1061. package/src/prompts/tools/apply-patch.md +65 -0
  1062. package/src/prompts/tools/ask.md +22 -0
  1063. package/src/prompts/tools/ast-edit.md +11 -0
  1064. package/src/prompts/tools/ast-grep.md +19 -0
  1065. package/src/prompts/tools/async-result.md +8 -0
  1066. package/src/prompts/tools/bash.md +23 -0
  1067. package/src/prompts/tools/browser.md +29 -0
  1068. package/src/prompts/tools/checkpoint.md +15 -0
  1069. package/src/prompts/tools/computer.md +26 -0
  1070. package/src/prompts/tools/debug.md +3 -0
  1071. package/src/prompts/tools/eval.md +45 -0
  1072. package/src/prompts/tools/github.md +22 -0
  1073. package/src/prompts/tools/glob.md +16 -0
  1074. package/src/prompts/tools/goal.md +11 -0
  1075. package/src/prompts/tools/grep.md +13 -0
  1076. package/src/prompts/tools/hub.md +34 -0
  1077. package/src/prompts/tools/image-attachment-describe-system.md +8 -0
  1078. package/src/prompts/tools/image-attachment-describe.md +10 -0
  1079. package/src/prompts/tools/image-gen.md +7 -0
  1080. package/src/prompts/tools/inspect-image-system.md +20 -0
  1081. package/src/prompts/tools/inspect-image.md +22 -0
  1082. package/src/prompts/tools/learn.md +7 -0
  1083. package/src/prompts/tools/lsp-late-diagnostic.md +8 -0
  1084. package/src/prompts/tools/lsp.md +19 -0
  1085. package/src/prompts/tools/manage-skill.md +9 -0
  1086. package/src/prompts/tools/memory-edit.md +12 -0
  1087. package/src/prompts/tools/patch.md +57 -0
  1088. package/src/prompts/tools/read.md +27 -0
  1089. package/src/prompts/tools/recall.md +7 -0
  1090. package/src/prompts/tools/reflect.md +5 -0
  1091. package/src/prompts/tools/replace.md +30 -0
  1092. package/src/prompts/tools/retain.md +6 -0
  1093. package/src/prompts/tools/rewind.md +14 -0
  1094. package/src/prompts/tools/security-publish.md +1 -0
  1095. package/src/prompts/tools/security-scan.md +1 -0
  1096. package/src/prompts/tools/task-async-contract.md +1 -0
  1097. package/src/prompts/tools/task-summary.md +20 -0
  1098. package/src/prompts/tools/task.md +83 -0
  1099. package/src/prompts/tools/todo.md +42 -0
  1100. package/src/prompts/tools/vibe-kill.md +3 -0
  1101. package/src/prompts/tools/vibe-list.md +3 -0
  1102. package/src/prompts/tools/vibe-send.md +9 -0
  1103. package/src/prompts/tools/vibe-spawn.md +10 -0
  1104. package/src/prompts/tools/vibe-turn-result.md +19 -0
  1105. package/src/prompts/tools/vibe-wait.md +8 -0
  1106. package/src/prompts/tools/web-search.md +8 -0
  1107. package/src/prompts/tools/write.md +14 -0
  1108. package/src/registry/agent-lifecycle.ts +505 -0
  1109. package/src/registry/agent-registry.ts +287 -0
  1110. package/src/registry/persisted-agents.ts +423 -0
  1111. package/src/sdk.ts +3820 -0
  1112. package/src/secrets/index.ts +375 -0
  1113. package/src/secrets/obfuscator.ts +2629 -0
  1114. package/src/secrets/regex.ts +21 -0
  1115. package/src/security/auth.ts +98 -0
  1116. package/src/security/cloud.ts +686 -0
  1117. package/src/security/comparison.ts +255 -0
  1118. package/src/security/contracts/ids.ts +111 -0
  1119. package/src/security/contracts/index.ts +4 -0
  1120. package/src/security/contracts/schemas.ts +219 -0
  1121. package/src/security/contracts/types.ts +254 -0
  1122. package/src/security/contracts/validation.ts +69 -0
  1123. package/src/security/coordinator.ts +708 -0
  1124. package/src/security/importers/codex-security.ts +387 -0
  1125. package/src/security/importers/index.ts +2 -0
  1126. package/src/security/importers/sarif.ts +357 -0
  1127. package/src/security/index.ts +13 -0
  1128. package/src/security/preflight.ts +405 -0
  1129. package/src/security/provenance.ts +106 -0
  1130. package/src/security/publication.ts +326 -0
  1131. package/src/security/remediation.ts +93 -0
  1132. package/src/security/resource-output.ts +50 -0
  1133. package/src/security/sarif.ts +78 -0
  1134. package/src/security/store.ts +430 -0
  1135. package/src/session/acp-permission-gate.ts +165 -0
  1136. package/src/session/agent-session-events.ts +67 -0
  1137. package/src/session/agent-session-types.ts +407 -0
  1138. package/src/session/agent-session.ts +9000 -0
  1139. package/src/session/agent-storage.ts +807 -0
  1140. package/src/session/artifacts.ts +154 -0
  1141. package/src/session/async-job-delivery.ts +82 -0
  1142. package/src/session/auth-broker-config.ts +92 -0
  1143. package/src/session/auth-storage.ts +25 -0
  1144. package/src/session/bash-runner.ts +326 -0
  1145. package/src/session/blob-store.ts +295 -0
  1146. package/src/session/checkpoint-entries.ts +81 -0
  1147. package/src/session/claude-session-store.ts +426 -0
  1148. package/src/session/client-bridge.ts +85 -0
  1149. package/src/session/codex-auto-reset.ts +673 -0
  1150. package/src/session/codex-session-store.ts +673 -0
  1151. package/src/session/compact-modes.ts +105 -0
  1152. package/src/session/credential-pin.ts +93 -0
  1153. package/src/session/eval-runner.ts +219 -0
  1154. package/src/session/exit-diagnostics.ts +310 -0
  1155. package/src/session/foreign-session-import.ts +52 -0
  1156. package/src/session/foreign-session-jsonl.ts +29 -0
  1157. package/src/session/foreign-session-store.ts +26 -0
  1158. package/src/session/history-storage.ts +329 -0
  1159. package/src/session/indexed-session-storage.ts +553 -0
  1160. package/src/session/irc-bridge.ts +203 -0
  1161. package/src/session/launch-completion.ts +37 -0
  1162. package/src/session/messages.ts +1304 -0
  1163. package/src/session/model-controls.ts +757 -0
  1164. package/src/session/prewalk.ts +279 -0
  1165. package/src/session/provider-image-budget.ts +86 -0
  1166. package/src/session/queued-messages.ts +99 -0
  1167. package/src/session/redis-session-storage.ts +257 -0
  1168. package/src/session/retry-fallback-chains.ts +455 -0
  1169. package/src/session/role-models.ts +85 -0
  1170. package/src/session/session-advisors.ts +1896 -0
  1171. package/src/session/session-context.ts +584 -0
  1172. package/src/session/session-dump-format.ts +216 -0
  1173. package/src/session/session-entries.ts +306 -0
  1174. package/src/session/session-handoff.ts +325 -0
  1175. package/src/session/session-history-format.ts +459 -0
  1176. package/src/session/session-listing.ts +715 -0
  1177. package/src/session/session-loader.ts +362 -0
  1178. package/src/session/session-maintenance.ts +3115 -0
  1179. package/src/session/session-manager.ts +2747 -0
  1180. package/src/session/session-memory.ts +222 -0
  1181. package/src/session/session-metadata.ts +53 -0
  1182. package/src/session/session-migrations.ts +78 -0
  1183. package/src/session/session-paths.ts +280 -0
  1184. package/src/session/session-persistence.ts +293 -0
  1185. package/src/session/session-provider-boundary.ts +306 -0
  1186. package/src/session/session-stats.ts +349 -0
  1187. package/src/session/session-storage.ts +774 -0
  1188. package/src/session/session-title-slot.ts +141 -0
  1189. package/src/session/session-tools.ts +1385 -0
  1190. package/src/session/session-workspace.ts +53 -0
  1191. package/src/session/settings-stream-fn.ts +79 -0
  1192. package/src/session/shake-types.ts +43 -0
  1193. package/src/session/snapcompact-inline.ts +545 -0
  1194. package/src/session/snapcompact-savings-journal.ts +113 -0
  1195. package/src/session/sql-session-storage.ts +374 -0
  1196. package/src/session/stream-guards.ts +417 -0
  1197. package/src/session/streaming-output.ts +1459 -0
  1198. package/src/session/todo-tracker.ts +380 -0
  1199. package/src/session/tool-choice-queue.ts +305 -0
  1200. package/src/session/ttsr-coordinator.ts +496 -0
  1201. package/src/session/turn-persistence.ts +142 -0
  1202. package/src/session/turn-recovery.ts +2005 -0
  1203. package/src/session/unexpected-stop-classifier.ts +141 -0
  1204. package/src/session/yield-queue.ts +288 -0
  1205. package/src/slash-commands/acp-builtins.ts +70 -0
  1206. package/src/slash-commands/available-commands.ts +105 -0
  1207. package/src/slash-commands/builtin-registry.ts +3149 -0
  1208. package/src/slash-commands/helpers/active-oauth-account.ts +80 -0
  1209. package/src/slash-commands/helpers/collab-qrcode.ts +28 -0
  1210. package/src/slash-commands/helpers/context-report.ts +66 -0
  1211. package/src/slash-commands/helpers/format.ts +46 -0
  1212. package/src/slash-commands/helpers/logout.ts +108 -0
  1213. package/src/slash-commands/helpers/marketplace-manager.ts +25 -0
  1214. package/src/slash-commands/helpers/mcp.ts +533 -0
  1215. package/src/slash-commands/helpers/parse.ts +85 -0
  1216. package/src/slash-commands/helpers/reset-usage.ts +68 -0
  1217. package/src/slash-commands/helpers/security.ts +451 -0
  1218. package/src/slash-commands/helpers/session-pin.ts +44 -0
  1219. package/src/slash-commands/helpers/ssh.ts +196 -0
  1220. package/src/slash-commands/helpers/stats-dashboard.ts +86 -0
  1221. package/src/slash-commands/helpers/todo.ts +285 -0
  1222. package/src/slash-commands/helpers/usage-report.ts +198 -0
  1223. package/src/slash-commands/marketplace-install-parser.ts +99 -0
  1224. package/src/slash-commands/types.ts +139 -0
  1225. package/src/ssh/config-writer.ts +183 -0
  1226. package/src/ssh/connection-manager.ts +667 -0
  1227. package/src/ssh/file-transfer.ts +214 -0
  1228. package/src/ssh/sshfs-mount.ts +163 -0
  1229. package/src/ssh/utils.ts +51 -0
  1230. package/src/startup-splash.ts +19 -0
  1231. package/src/stt/asr-client.ts +401 -0
  1232. package/src/stt/asr-protocol.ts +65 -0
  1233. package/src/stt/asr-worker.ts +603 -0
  1234. package/src/stt/downloader.ts +142 -0
  1235. package/src/stt/endpointer.ts +259 -0
  1236. package/src/stt/index.ts +6 -0
  1237. package/src/stt/models.ts +150 -0
  1238. package/src/stt/sherpa-runtime.ts +71 -0
  1239. package/src/stt/stt-controller.ts +320 -0
  1240. package/src/stt/submit-trigger.ts +74 -0
  1241. package/src/subprocess/worker-client.ts +463 -0
  1242. package/src/subprocess/worker-runtime.ts +494 -0
  1243. package/src/system-prompt.ts +921 -0
  1244. package/src/task/agents.ts +170 -0
  1245. package/src/task/commands.ts +132 -0
  1246. package/src/task/discovery.ts +145 -0
  1247. package/src/task/executor.ts +3431 -0
  1248. package/src/task/index.ts +1515 -0
  1249. package/src/task/isolation-ownership.ts +106 -0
  1250. package/src/task/isolation-runner.ts +444 -0
  1251. package/src/task/label.ts +40 -0
  1252. package/src/task/name-generator.ts +1577 -0
  1253. package/src/task/omp-command.ts +26 -0
  1254. package/src/task/output-manager.ts +115 -0
  1255. package/src/task/parallel.ts +221 -0
  1256. package/src/task/persisted-revive.ts +173 -0
  1257. package/src/task/prewalk.ts +6 -0
  1258. package/src/task/prompt-policy.ts +8 -0
  1259. package/src/task/provider-concurrency.ts +100 -0
  1260. package/src/task/read-only-policy.ts +27 -0
  1261. package/src/task/render.ts +1830 -0
  1262. package/src/task/renderer.ts +14 -0
  1263. package/src/task/repair-args.ts +118 -0
  1264. package/src/task/spawn-policy.ts +72 -0
  1265. package/src/task/structured-subagent.ts +676 -0
  1266. package/src/task/subprocess-tool-registry.ts +88 -0
  1267. package/src/task/types.ts +555 -0
  1268. package/src/task/worktree.ts +967 -0
  1269. package/src/task/yield-assembly.ts +198 -0
  1270. package/src/telemetry-export.ts +504 -0
  1271. package/src/thinking.ts +377 -0
  1272. package/src/tiny/device.ts +111 -0
  1273. package/src/tiny/dtype.ts +101 -0
  1274. package/src/tiny/message-preproc.ts +155 -0
  1275. package/src/tiny/models.ts +268 -0
  1276. package/src/tiny/text.ts +290 -0
  1277. package/src/tiny/title-client.ts +460 -0
  1278. package/src/tiny/title-protocol.ts +56 -0
  1279. package/src/tiny/worker.ts +354 -0
  1280. package/src/tools/acp-bridge.ts +125 -0
  1281. package/src/tools/approval.ts +245 -0
  1282. package/src/tools/ask.ts +1459 -0
  1283. package/src/tools/ast-edit.ts +718 -0
  1284. package/src/tools/ast-grep.ts +526 -0
  1285. package/src/tools/auto-generated-guard.ts +335 -0
  1286. package/src/tools/bash-interactive.ts +435 -0
  1287. package/src/tools/bash-interceptor.ts +148 -0
  1288. package/src/tools/bash-pty-selection.ts +14 -0
  1289. package/src/tools/bash-skill-urls.ts +350 -0
  1290. package/src/tools/bash.ts +1826 -0
  1291. package/src/tools/browser/aria/aria-snapshot.bundle.txt +7 -0
  1292. package/src/tools/browser/aria/aria-snapshot.ts +131 -0
  1293. package/src/tools/browser/attach.ts +219 -0
  1294. package/src/tools/browser/cmux/cmux-tab.ts +1531 -0
  1295. package/src/tools/browser/cmux/rpc.ts +200 -0
  1296. package/src/tools/browser/cmux/socket-client.ts +445 -0
  1297. package/src/tools/browser/launch.ts +939 -0
  1298. package/src/tools/browser/readable.ts +112 -0
  1299. package/src/tools/browser/registry.ts +447 -0
  1300. package/src/tools/browser/relay/bridge.ts +945 -0
  1301. package/src/tools/browser/relay/daemon.ts +117 -0
  1302. package/src/tools/browser/relay/extension-assets/background.js.txt +242 -0
  1303. package/src/tools/browser/relay/extension-assets/manifest.json.txt +14 -0
  1304. package/src/tools/browser/relay/extension-assets/options.html.txt +53 -0
  1305. package/src/tools/browser/relay/extension-assets/options.js.txt +23 -0
  1306. package/src/tools/browser/relay/kind.ts +41 -0
  1307. package/src/tools/browser/relay/protocol.ts +54 -0
  1308. package/src/tools/browser/relay/server.ts +141 -0
  1309. package/src/tools/browser/render.ts +229 -0
  1310. package/src/tools/browser/run-output.ts +76 -0
  1311. package/src/tools/browser/shared-daemon.ts +139 -0
  1312. package/src/tools/browser/tab-protocol.ts +123 -0
  1313. package/src/tools/browser/tab-supervisor.ts +1077 -0
  1314. package/src/tools/browser/tab-worker-entry.ts +29 -0
  1315. package/src/tools/browser/tab-worker.ts +1982 -0
  1316. package/src/tools/browser.ts +487 -0
  1317. package/src/tools/builtin-names.ts +67 -0
  1318. package/src/tools/checkpoint.ts +137 -0
  1319. package/src/tools/computer/exposure.ts +14 -0
  1320. package/src/tools/computer/protocol.ts +69 -0
  1321. package/src/tools/computer/supervisor.ts +409 -0
  1322. package/src/tools/computer/worker-entry.ts +39 -0
  1323. package/src/tools/computer/worker.ts +745 -0
  1324. package/src/tools/computer-renderer.ts +147 -0
  1325. package/src/tools/computer.ts +222 -0
  1326. package/src/tools/conflict-detect.ts +815 -0
  1327. package/src/tools/context.ts +49 -0
  1328. package/src/tools/debug.ts +1121 -0
  1329. package/src/tools/default-renderer.ts +154 -0
  1330. package/src/tools/essential-tools.ts +46 -0
  1331. package/src/tools/eval-backends.ts +34 -0
  1332. package/src/tools/eval-format/index.ts +24 -0
  1333. package/src/tools/eval-format/javascript.ts +952 -0
  1334. package/src/tools/eval-format/julia.ts +446 -0
  1335. package/src/tools/eval-format/python.ts +544 -0
  1336. package/src/tools/eval-format/ruby.ts +380 -0
  1337. package/src/tools/eval-render.ts +784 -0
  1338. package/src/tools/eval.ts +774 -0
  1339. package/src/tools/fetch.ts +1889 -0
  1340. package/src/tools/file-recorder.ts +35 -0
  1341. package/src/tools/fs-cache-invalidation.ts +28 -0
  1342. package/src/tools/gh-cache-invalidation.ts +175 -0
  1343. package/src/tools/gh-format.ts +12 -0
  1344. package/src/tools/gh-renderer.ts +484 -0
  1345. package/src/tools/gh.ts +3958 -0
  1346. package/src/tools/github-cache.ts +663 -0
  1347. package/src/tools/glob.ts +691 -0
  1348. package/src/tools/grep.ts +1920 -0
  1349. package/src/tools/grouped-file-output.ts +210 -0
  1350. package/src/tools/hub/index.ts +579 -0
  1351. package/src/tools/hub/jobs.ts +714 -0
  1352. package/src/tools/hub/launch.ts +690 -0
  1353. package/src/tools/hub/messaging.ts +735 -0
  1354. package/src/tools/hub/types.ts +117 -0
  1355. package/src/tools/image-gen.ts +1689 -0
  1356. package/src/tools/image-providers.ts +50 -0
  1357. package/src/tools/index.ts +736 -0
  1358. package/src/tools/inspect-image-renderer.ts +133 -0
  1359. package/src/tools/inspect-image.ts +316 -0
  1360. package/src/tools/json-tree.ts +260 -0
  1361. package/src/tools/jtd-to-json-schema.ts +219 -0
  1362. package/src/tools/jtd-to-typescript.ts +136 -0
  1363. package/src/tools/jtd-utils.ts +102 -0
  1364. package/src/tools/learn.ts +141 -0
  1365. package/src/tools/list-limit.ts +40 -0
  1366. package/src/tools/manage-skill.ts +102 -0
  1367. package/src/tools/match-line-format.ts +20 -0
  1368. package/src/tools/memory-edit.ts +61 -0
  1369. package/src/tools/memory-recall.ts +102 -0
  1370. package/src/tools/memory-reflect.ts +88 -0
  1371. package/src/tools/memory-render.ts +211 -0
  1372. package/src/tools/memory-retain.ts +89 -0
  1373. package/src/tools/output-meta.ts +860 -0
  1374. package/src/tools/output-schema-validator.ts +307 -0
  1375. package/src/tools/path-utils.ts +1489 -0
  1376. package/src/tools/plan-mode-guard.ts +155 -0
  1377. package/src/tools/puppeteer/00_stealth_tampering.txt +44 -0
  1378. package/src/tools/puppeteer/01_stealth_activity.txt +80 -0
  1379. package/src/tools/puppeteer/02_stealth_hairline.txt +57 -0
  1380. package/src/tools/puppeteer/03_stealth_botd.txt +380 -0
  1381. package/src/tools/puppeteer/04_stealth_iframe.txt +174 -0
  1382. package/src/tools/puppeteer/05_stealth_webgl.txt +233 -0
  1383. package/src/tools/puppeteer/06_stealth_screen.txt +260 -0
  1384. package/src/tools/puppeteer/07_stealth_fonts.txt +99 -0
  1385. package/src/tools/puppeteer/08_stealth_audio.txt +63 -0
  1386. package/src/tools/puppeteer/09_stealth_locale.txt +51 -0
  1387. package/src/tools/puppeteer/10_stealth_plugins.txt +212 -0
  1388. package/src/tools/puppeteer/11_stealth_hardware.txt +59 -0
  1389. package/src/tools/puppeteer/12_stealth_codecs.txt +42 -0
  1390. package/src/tools/puppeteer/13_stealth_worker.txt +235 -0
  1391. package/src/tools/read.ts +3762 -0
  1392. package/src/tools/render-utils.ts +926 -0
  1393. package/src/tools/renderers.ts +133 -0
  1394. package/src/tools/report-tool-issue.ts +568 -0
  1395. package/src/tools/resolve.ts +423 -0
  1396. package/src/tools/review.ts +103 -0
  1397. package/src/tools/run-scope.ts +417 -0
  1398. package/src/tools/security-scan.ts +287 -0
  1399. package/src/tools/shell-tokenize.ts +213 -0
  1400. package/src/tools/sqlite-reader.ts +884 -0
  1401. package/src/tools/terminal-output.ts +141 -0
  1402. package/src/tools/todo.ts +1226 -0
  1403. package/src/tools/tool-errors.ts +62 -0
  1404. package/src/tools/tool-result.ts +102 -0
  1405. package/src/tools/tool-timeouts.ts +38 -0
  1406. package/src/tools/tts.ts +266 -0
  1407. package/src/tools/vibe.ts +608 -0
  1408. package/src/tools/write.ts +1646 -0
  1409. package/src/tools/xdev.ts +560 -0
  1410. package/src/tools/yield.ts +486 -0
  1411. package/src/tts/downloader.ts +64 -0
  1412. package/src/tts/index.ts +10 -0
  1413. package/src/tts/models.ts +137 -0
  1414. package/src/tts/runtime.ts +21 -0
  1415. package/src/tts/speakable.ts +392 -0
  1416. package/src/tts/speech-enhancer.ts +206 -0
  1417. package/src/tts/streaming-player.ts +120 -0
  1418. package/src/tts/tts-client.ts +475 -0
  1419. package/src/tts/tts-protocol.ts +69 -0
  1420. package/src/tts/tts-worker.ts +434 -0
  1421. package/src/tts/vocalizer.ts +419 -0
  1422. package/src/tts/wav.ts +58 -0
  1423. package/src/tui/code-cell.ts +268 -0
  1424. package/src/tui/file-list.ts +55 -0
  1425. package/src/tui/hyperlink.ts +178 -0
  1426. package/src/tui/index.ts +13 -0
  1427. package/src/tui/output-block.ts +268 -0
  1428. package/src/tui/status-line.ts +54 -0
  1429. package/src/tui/tree-list.ts +172 -0
  1430. package/src/tui/types.ts +15 -0
  1431. package/src/tui/utils.ts +103 -0
  1432. package/src/tui/width-aware-text.ts +58 -0
  1433. package/src/utils/active-repo-context.ts +143 -0
  1434. package/src/utils/block-context.ts +312 -0
  1435. package/src/utils/changelog.ts +373 -0
  1436. package/src/utils/clipboard.ts +361 -0
  1437. package/src/utils/command-args.ts +74 -0
  1438. package/src/utils/commit-message-generator.ts +148 -0
  1439. package/src/utils/cpuprofile.ts +235 -0
  1440. package/src/utils/edit-mode.ts +61 -0
  1441. package/src/utils/enhanced-paste.ts +230 -0
  1442. package/src/utils/event-bus.ts +33 -0
  1443. package/src/utils/external-editor.ts +80 -0
  1444. package/src/utils/fetch-timeout.ts +10 -0
  1445. package/src/utils/file-display-mode.ts +44 -0
  1446. package/src/utils/file-mentions.ts +293 -0
  1447. package/src/utils/git.ts +2472 -0
  1448. package/src/utils/image-loading.ts +236 -0
  1449. package/src/utils/image-resize.ts +420 -0
  1450. package/src/utils/image-vision-fallback.ts +196 -0
  1451. package/src/utils/inspect-image-mode.ts +39 -0
  1452. package/src/utils/ipc.ts +38 -0
  1453. package/src/utils/jj.ts +422 -0
  1454. package/src/utils/lang-from-path.ts +251 -0
  1455. package/src/utils/late-cleanup.ts +17 -0
  1456. package/src/utils/local-date.ts +7 -0
  1457. package/src/utils/mac-file-urls.applescript +37 -0
  1458. package/src/utils/markit-cache.ts +166 -0
  1459. package/src/utils/markit.ts +223 -0
  1460. package/src/utils/mupdf-wasm-embed.ts +12 -0
  1461. package/src/utils/open.ts +126 -0
  1462. package/src/utils/profile-tree.ts +111 -0
  1463. package/src/utils/prompt-path.ts +3 -0
  1464. package/src/utils/qrcode.ts +535 -0
  1465. package/src/utils/sample-profile.ts +437 -0
  1466. package/src/utils/session-color.ts +142 -0
  1467. package/src/utils/shell-snapshot-fn-env.sh +63 -0
  1468. package/src/utils/shell-snapshot.ts +326 -0
  1469. package/src/utils/sixel.ts +69 -0
  1470. package/src/utils/thinking-display.ts +163 -0
  1471. package/src/utils/title-generator.ts +597 -0
  1472. package/src/utils/token-rate.ts +72 -0
  1473. package/src/utils/tool-choice.ts +50 -0
  1474. package/src/utils/tools-manager.ts +411 -0
  1475. package/src/utils/turndown.ts +82 -0
  1476. package/src/utils/zip.ts +1106 -0
  1477. package/src/vibe/runtime.ts +1684 -0
  1478. package/src/vibe/state.ts +4 -0
  1479. package/src/web/kagi.ts +305 -0
  1480. package/src/web/parallel.ts +354 -0
  1481. package/src/web/scrapers/artifacthub.ts +207 -0
  1482. package/src/web/scrapers/arxiv.ts +83 -0
  1483. package/src/web/scrapers/aur.ts +162 -0
  1484. package/src/web/scrapers/biorxiv.ts +133 -0
  1485. package/src/web/scrapers/bluesky.ts +262 -0
  1486. package/src/web/scrapers/brew.ts +172 -0
  1487. package/src/web/scrapers/cheatsh.ts +68 -0
  1488. package/src/web/scrapers/chocolatey.ts +196 -0
  1489. package/src/web/scrapers/choosealicense.ts +95 -0
  1490. package/src/web/scrapers/cisa-kev.ts +87 -0
  1491. package/src/web/scrapers/clojars.ts +154 -0
  1492. package/src/web/scrapers/coingecko.ts +177 -0
  1493. package/src/web/scrapers/crates-io.ts +97 -0
  1494. package/src/web/scrapers/crossref.ts +136 -0
  1495. package/src/web/scrapers/devto.ts +147 -0
  1496. package/src/web/scrapers/discogs.ts +306 -0
  1497. package/src/web/scrapers/discourse.ts +197 -0
  1498. package/src/web/scrapers/dockerhub.ts +138 -0
  1499. package/src/web/scrapers/docs-rs.ts +663 -0
  1500. package/src/web/scrapers/fdroid.ts +134 -0
  1501. package/src/web/scrapers/firefox-addons.ts +191 -0
  1502. package/src/web/scrapers/flathub.ts +223 -0
  1503. package/src/web/scrapers/github-gist.ts +58 -0
  1504. package/src/web/scrapers/github.ts +800 -0
  1505. package/src/web/scrapers/gitlab.ts +401 -0
  1506. package/src/web/scrapers/go-pkg.ts +266 -0
  1507. package/src/web/scrapers/hackage.ts +129 -0
  1508. package/src/web/scrapers/hackernews.ts +189 -0
  1509. package/src/web/scrapers/hex.ts +105 -0
  1510. package/src/web/scrapers/huggingface.ts +321 -0
  1511. package/src/web/scrapers/iacr.ts +89 -0
  1512. package/src/web/scrapers/index.ts +252 -0
  1513. package/src/web/scrapers/jetbrains-marketplace.ts +159 -0
  1514. package/src/web/scrapers/lemmy.ts +203 -0
  1515. package/src/web/scrapers/lobsters.ts +175 -0
  1516. package/src/web/scrapers/mastodon.ts +292 -0
  1517. package/src/web/scrapers/maven.ts +138 -0
  1518. package/src/web/scrapers/mdn.ts +173 -0
  1519. package/src/web/scrapers/metacpan.ts +222 -0
  1520. package/src/web/scrapers/musicbrainz.ts +250 -0
  1521. package/src/web/scrapers/npm.ts +98 -0
  1522. package/src/web/scrapers/nuget.ts +183 -0
  1523. package/src/web/scrapers/nvd.ts +222 -0
  1524. package/src/web/scrapers/ollama.ts +239 -0
  1525. package/src/web/scrapers/open-vsx.ts +106 -0
  1526. package/src/web/scrapers/opencorporates.ts +292 -0
  1527. package/src/web/scrapers/openlibrary.ts +336 -0
  1528. package/src/web/scrapers/orcid.ts +286 -0
  1529. package/src/web/scrapers/osv.ts +176 -0
  1530. package/src/web/scrapers/packagist.ts +160 -0
  1531. package/src/web/scrapers/pub-dev.ts +143 -0
  1532. package/src/web/scrapers/pubmed.ts +211 -0
  1533. package/src/web/scrapers/pypi.ts +112 -0
  1534. package/src/web/scrapers/rawg.ts +110 -0
  1535. package/src/web/scrapers/readthedocs.ts +121 -0
  1536. package/src/web/scrapers/reddit.ts +95 -0
  1537. package/src/web/scrapers/repology.ts +251 -0
  1538. package/src/web/scrapers/rfc.ts +201 -0
  1539. package/src/web/scrapers/rubygems.ts +103 -0
  1540. package/src/web/scrapers/searchcode.ts +189 -0
  1541. package/src/web/scrapers/sec-edgar.ts +261 -0
  1542. package/src/web/scrapers/semantic-scholar.ts +171 -0
  1543. package/src/web/scrapers/snapcraft.ts +187 -0
  1544. package/src/web/scrapers/sourcegraph.ts +336 -0
  1545. package/src/web/scrapers/spdx.ts +108 -0
  1546. package/src/web/scrapers/spotify.ts +198 -0
  1547. package/src/web/scrapers/stackoverflow.ts +120 -0
  1548. package/src/web/scrapers/terraform.ts +277 -0
  1549. package/src/web/scrapers/tldr.ts +47 -0
  1550. package/src/web/scrapers/twitter.ts +94 -0
  1551. package/src/web/scrapers/types.ts +354 -0
  1552. package/src/web/scrapers/utils.ts +109 -0
  1553. package/src/web/scrapers/vimeo.ts +133 -0
  1554. package/src/web/scrapers/vscode-marketplace.ts +187 -0
  1555. package/src/web/scrapers/w3c.ts +156 -0
  1556. package/src/web/scrapers/wikidata.ts +344 -0
  1557. package/src/web/scrapers/wikipedia.ts +84 -0
  1558. package/src/web/scrapers/youtube.ts +325 -0
  1559. package/src/web/search/index.ts +393 -0
  1560. package/src/web/search/provider.ts +272 -0
  1561. package/src/web/search/providers/anthropic.ts +406 -0
  1562. package/src/web/search/providers/base.ts +112 -0
  1563. package/src/web/search/providers/brave.ts +181 -0
  1564. package/src/web/search/providers/browser-headers.ts +82 -0
  1565. package/src/web/search/providers/browser-page.ts +125 -0
  1566. package/src/web/search/providers/codex.ts +788 -0
  1567. package/src/web/search/providers/duckduckgo.ts +382 -0
  1568. package/src/web/search/providers/ecosia.ts +183 -0
  1569. package/src/web/search/providers/exa.ts +513 -0
  1570. package/src/web/search/providers/firecrawl.ts +213 -0
  1571. package/src/web/search/providers/gemini.ts +634 -0
  1572. package/src/web/search/providers/google.ts +195 -0
  1573. package/src/web/search/providers/jina.ts +144 -0
  1574. package/src/web/search/providers/kagi.ts +98 -0
  1575. package/src/web/search/providers/kimi.ts +221 -0
  1576. package/src/web/search/providers/mojeek.ts +220 -0
  1577. package/src/web/search/providers/parallel.ts +186 -0
  1578. package/src/web/search/providers/perplexity-auth.ts +142 -0
  1579. package/src/web/search/providers/perplexity.ts +997 -0
  1580. package/src/web/search/providers/public.ts +199 -0
  1581. package/src/web/search/providers/searxng.ts +467 -0
  1582. package/src/web/search/providers/startpage.ts +225 -0
  1583. package/src/web/search/providers/synthetic.ts +126 -0
  1584. package/src/web/search/providers/tavily.ts +244 -0
  1585. package/src/web/search/providers/tinyfish.ts +166 -0
  1586. package/src/web/search/providers/utils.ts +130 -0
  1587. package/src/web/search/providers/xai.ts +372 -0
  1588. package/src/web/search/providers/zai.ts +452 -0
  1589. package/src/web/search/query.ts +850 -0
  1590. package/src/web/search/render.ts +262 -0
  1591. package/src/web/search/types.ts +518 -0
  1592. package/src/web/search/utils.ts +17 -0
  1593. package/src/workspace-tree.ts +326 -0
@@ -0,0 +1,2037 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
4
+ import type { ImageContent } from "@oh-my-pi/pi-ai";
5
+ import { type AutocompleteProvider, matchesKey, type SlashCommand } from "@oh-my-pi/pi-tui";
6
+ import { isEnoent, logger, sanitizeText } from "@oh-my-pi/pi-utils";
7
+ import { isSettingsInitialized, settings } from "../../config/settings";
8
+ import { resolveLocalRoot } from "../../internal-urls";
9
+ import { AssistantMessageComponent } from "../../modes/components/assistant-message";
10
+ import { extractImagePathFromText } from "../../modes/components/custom-editor";
11
+ import { ReadToolGroupComponent } from "../../modes/components/read-tool-group";
12
+ import { renderSegmentTrack } from "../../modes/components/segment-track";
13
+ import { StrippedToolCallsPlaceholder } from "../../modes/components/stripped-tool-calls-placeholder";
14
+ import { TinyTitleDownloadProgressComponent } from "../../modes/components/tiny-title-download-progress";
15
+ import { ToolExecutionComponent } from "../../modes/components/tool-execution";
16
+ import { expandEmoticons } from "../../modes/emoji-autocomplete";
17
+ import { materializeImageReferenceLinks, shiftImageMarkers } from "../../modes/image-references";
18
+ import { createPromptActionAutocompleteProvider } from "../../modes/prompt-action-autocomplete";
19
+ import { parseQueueShorthand, splitQueuedMessages } from "../../modes/queue-input";
20
+ import { invokeSkillCommandFromText, isKnownSkillCommand } from "../../modes/skill-command";
21
+ import type { InteractiveModeContext } from "../../modes/types";
22
+ import manualContinuePrompt from "../../prompts/system/manual-continue.md" with { type: "text" };
23
+ import { USER_INTERRUPT_LABEL } from "../../session/messages";
24
+ import { executeBuiltinSlashCommand } from "../../slash-commands/builtin-registry";
25
+ import { isTinyTitleLocalModelKey } from "../../tiny/models";
26
+ import { tinyTitleClient } from "../../tiny/title-client";
27
+ import type { TinyTitleProgressEvent } from "../../tiny/title-protocol";
28
+ import { shortenPath, TRUNCATE_LENGTHS, truncateToWidth } from "../../tools/render-utils";
29
+ import { vocalizer } from "../../tts/vocalizer";
30
+ import {
31
+ copyToClipboard,
32
+ readImageFromClipboard,
33
+ readMacFileUrlsFromClipboard,
34
+ readTextFromClipboard,
35
+ } from "../../utils/clipboard";
36
+ import { EnhancedPasteController } from "../../utils/enhanced-paste";
37
+ import { getEditorCommand, openInEditor } from "../../utils/external-editor";
38
+ import { ensureSupportedImageInput, ImageInputTooLargeError, loadImageInput } from "../../utils/image-loading";
39
+ import { resizeImage } from "../../utils/image-resize";
40
+
41
+ /**
42
+ * Slash commands that may carry secrets in their arguments should never be
43
+ * persisted to history.
44
+ *
45
+ * - /login accepts three callback forms (redirect URL, query string, raw auth
46
+ * code) — all can contain OAuth code=/state= params.
47
+ * - /join <link> carries a 32-byte room key and optional write token.
48
+ * - /mcp add --token <token> carries a bearer token.
49
+ *
50
+ * The command name is extracted the same way as parseSlashCommand() — splitting
51
+ * on the earliest whitespace or colon — so /login:?code=... is correctly matched.
52
+ */
53
+ export function shouldSkipHistory(slashText: string): boolean {
54
+ if (!slashText.startsWith("/")) return false;
55
+ const body = slashText.slice(1);
56
+ // Match parseSlashCommand: split on earliest whitespace or colon.
57
+ const firstWs = body.search(/\s/);
58
+ const firstColon = body.indexOf(":");
59
+ const sep = firstWs === -1 ? firstColon : firstColon === -1 ? firstWs : Math.min(firstWs, firstColon);
60
+ const name = sep === -1 ? body : body.slice(0, sep);
61
+ const hasArgs = sep !== -1;
62
+ // /login <anything> — parseCallbackInput() accepts redirect URLs, query
63
+ // strings (?code=...), and raw auth codes, all of which carry secrets.
64
+ if (name === "login" && hasArgs) return true;
65
+ // /join <link> — the link carries the 32-byte room key and write token.
66
+ if (name === "join" && hasArgs) return true;
67
+ if (name === "mcp") {
68
+ const args = body.slice(sep + 1).trim();
69
+ return args.startsWith("add") && /--token\s/.test(args);
70
+ }
71
+ return false;
72
+ }
73
+
74
+ interface Expandable {
75
+ setExpanded(expanded: boolean): void;
76
+ }
77
+
78
+ function isExpandable(obj: unknown): obj is Expandable {
79
+ return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
80
+ }
81
+
82
+ /** Minimal contract for any component that can receive a paste payload directly. */
83
+ interface PasteTarget {
84
+ pasteText(text: string): void;
85
+ }
86
+
87
+ function hasPasteText(value: unknown): value is PasteTarget {
88
+ return typeof value === "object" && value !== null && typeof (value as PasteTarget).pasteText === "function";
89
+ }
90
+
91
+ const SHELL_PROMPT_COMMAND_RE =
92
+ /^(?:\.{0,2}\/|~\/|cd(?:\s|$)|sudo(?:\s|$)|git(?:\s|$)|bun(?:\s|$)|npm(?:\s|$)|pnpm(?:\s|$)|yarn(?:\s|$)|node(?:\s|$)|python\d*(?:\s|$)|cargo(?:\s|$)|go(?:\s|$)|make(?:\s|$)|docker(?:\s|$)|kubectl(?:\s|$))/;
93
+ const SHELL_PROMPT_OPERATOR_RE = /(?:^|\s)(?:&&|\|\||\||2>&1|[<>]{1,2})(?:\s|$)/;
94
+ const OMP_STATUS_LINE_RE = /^\s*in:\s+\d+\s+out:\s+\d+(?:\s+cache\s+\S+)?\s+t:\s+\S+\s+tok\/s:\s+\S+/m;
95
+
96
+ function looksLikePastedShellPrompt(code: string): boolean {
97
+ const firstLine = code.split("\n", 1)[0]?.trimStart() ?? "";
98
+ return (
99
+ SHELL_PROMPT_COMMAND_RE.test(firstLine) ||
100
+ SHELL_PROMPT_OPERATOR_RE.test(firstLine) ||
101
+ OMP_STATUS_LINE_RE.test(code)
102
+ );
103
+ }
104
+
105
+ function pythonCommandPrefixLength(trimmedText: string): 0 | 1 | 2 {
106
+ if (trimmedText.charCodeAt(0) !== 36 /* $ */) return 0;
107
+ if (trimmedText.charCodeAt(1) === 123 /* { */) return 0;
108
+
109
+ const prefixLength = trimmedText.charCodeAt(1) === 36 /* $ */ ? 2 : 1;
110
+ const next = trimmedText.charCodeAt(prefixLength);
111
+ if (Number.isNaN(next)) return prefixLength;
112
+ return next === 32 || next === 9 || next === 10 || next === 13 ? prefixLength : 0;
113
+ }
114
+
115
+ function parsePythonCommandInput(text: string): { code: string; isExcluded: boolean } | undefined {
116
+ const trimmed = text.trimStart();
117
+ const prefixLength = pythonCommandPrefixLength(trimmed);
118
+ if (prefixLength === 0) return undefined;
119
+ const code = trimmed.slice(prefixLength).trim();
120
+ if (prefixLength === 1 && looksLikePastedShellPrompt(code)) return undefined;
121
+ return {
122
+ code,
123
+ isExcluded: prefixLength === 2,
124
+ };
125
+ }
126
+
127
+ /** Wrap pasted text in `<attachment>` tags so the model treats it as one quoted block. */
128
+ function wrapPasteInAttachmentBlock(content: string): string {
129
+ return `<attachment>\n${content}\n</attachment>`;
130
+ }
131
+
132
+ /** Run a teardown abort that must never throw (Esc / Ctrl+C path). A thrown
133
+ * error is logged at debug instead of silently swallowed, so a failing abort
134
+ * stays diagnosable without disturbing teardown ordering. */
135
+ function safeAbort(label: string, fn: () => void): void {
136
+ try {
137
+ fn();
138
+ } catch (err) {
139
+ logger.debug(`Failed to abort ${label}`, { error: err instanceof Error ? err.message : String(err) });
140
+ }
141
+ }
142
+
143
+ const TINY_TITLE_PROGRESS_DONE_TTL_MS = 3_000;
144
+ // A cached model fires its file-load events in a short burst and then goes silent
145
+ // while onnxruntime builds the session; a genuine download keeps streaming progress
146
+ // events for seconds. Only reveal the bar once a still-incomplete event arrives after
147
+ // this grace window, so an already-downloaded model never flashes the bar.
148
+ const TINY_TITLE_PROGRESS_REVEAL_DELAY_MS = 1_000;
149
+ // Double-tap ← on an empty editor opens the Agent Hub (and, in a focused
150
+ // subagent view, ←← returns to the main session). The second tap must land
151
+ // inside this window. The lower bound rejects terminal-synthesized arrow-key
152
+ // bursts: "click to move cursor" / pointer features in iTerm2, WezTerm, kitty,
153
+ // and tmux emit several arrow keys in a single stdin read (sub-millisecond
154
+ // apart) on a stray click, which used to pop the hub with no key ever pressed.
155
+ // Three or more rapid taps are likewise treated as a burst, not a gesture. A
156
+ // deliberate human double-tap is always tens of milliseconds apart.
157
+ const LEFT_DOUBLE_TAP_MIN_GAP_MS = 40;
158
+ const LEFT_DOUBLE_TAP_MAX_GAP_MS = 500;
159
+
160
+ export class InputController {
161
+ constructor(
162
+ private ctx: InteractiveModeContext,
163
+ /** Injectable clipboard reads so tests can drive paste flows without a real clipboard. */
164
+ private clipboard: {
165
+ readImage: typeof readImageFromClipboard;
166
+ readText: typeof readTextFromClipboard;
167
+ readMacFileUrls?: typeof readMacFileUrlsFromClipboard;
168
+ } = {
169
+ readImage: readImageFromClipboard,
170
+ readText: readTextFromClipboard,
171
+ readMacFileUrls: readMacFileUrlsFromClipboard,
172
+ },
173
+ ) {}
174
+
175
+ #enhancedPaste?: EnhancedPasteController;
176
+ #focusedLeftTapListenerInstalled = false;
177
+ #focusedPasteListenerInstalled = false;
178
+ #btwBranchListenerInstalled = false;
179
+ #btwCopyListenerInstalled = false;
180
+ // Tap counter for the double-← gesture; reset whenever a quiet gap
181
+ // (>= LEFT_DOUBLE_TAP_MAX_GAP_MS) starts a fresh sequence. See
182
+ // #detectLeftDoubleTap.
183
+ #leftTapCount = 0;
184
+ // Sequential index for `local://paste-N.md` references created by the large-paste
185
+ // flow. Seeded from 0 and bumped past existing paste files.
186
+ #pasteCounter = 0;
187
+
188
+ #showTinyTitleDownloadProgress(modelKey: string): void {
189
+ if (!isTinyTitleLocalModelKey(modelKey)) return;
190
+ const component = new TinyTitleDownloadProgressComponent(modelKey);
191
+ let added = false;
192
+ let disposed = false;
193
+ let removeTimer: NodeJS.Timeout | undefined;
194
+ const remove = (): void => {
195
+ if (disposed) return;
196
+ disposed = true;
197
+ unsubscribe();
198
+ if (removeTimer) {
199
+ clearTimeout(removeTimer);
200
+ removeTimer = undefined;
201
+ }
202
+ if (added) {
203
+ this.ctx.chatContainer.removeChild(component);
204
+ this.ctx.ui.requestRender();
205
+ }
206
+ };
207
+ const scheduleRemove = (): void => {
208
+ if (removeTimer) clearTimeout(removeTimer);
209
+ removeTimer = setTimeout(remove, TINY_TITLE_PROGRESS_DONE_TTL_MS);
210
+ removeTimer.unref?.();
211
+ };
212
+ let revealAt = 0;
213
+ const update = (event: TinyTitleProgressEvent): void => {
214
+ if (disposed || event.modelKey !== modelKey) return;
215
+ component.update(event);
216
+ if (revealAt === 0) revealAt = performance.now() + TINY_TITLE_PROGRESS_REVEAL_DELAY_MS;
217
+ const complete = component.isComplete();
218
+ // Reveal only for a download still in flight past the grace window. Cache hits
219
+ // either complete or fall silent (onnx init emits no events) before this fires.
220
+ if (!added && !complete && performance.now() >= revealAt) {
221
+ this.ctx.chatContainer.addChild(component);
222
+ added = true;
223
+ }
224
+ if (added) this.ctx.ui.requestRender();
225
+ if (complete) {
226
+ if (added) scheduleRemove();
227
+ else remove();
228
+ }
229
+ };
230
+ const unsubscribe = tinyTitleClient.onProgress(update);
231
+ }
232
+
233
+ #abortStreamingTurn(): void {
234
+ void this.ctx.session.abort({ reason: USER_INTERRUPT_LABEL });
235
+ }
236
+
237
+ setupKeyHandlers(): void {
238
+ this.ctx.editor.setActionKeys("app.interrupt", this.ctx.keybindings.getKeys("app.interrupt"));
239
+ if (!this.#focusedLeftTapListenerInstalled) {
240
+ this.#focusedLeftTapListenerInstalled = true;
241
+ this.ctx.ui.addInputListener(data => {
242
+ if (!this.ctx.focusedAgentId) return undefined;
243
+ if (!matchesKey(data, "left")) return undefined;
244
+ if (this.ctx.editor.getText().trim()) return undefined;
245
+ this.#handleFocusedLeftTap();
246
+ return { consume: true };
247
+ });
248
+ }
249
+ if (!this.#btwBranchListenerInstalled) {
250
+ this.#btwBranchListenerInstalled = true;
251
+ this.ctx.ui.addInputListener(data => {
252
+ if (!matchesKey(data, "b")) return undefined;
253
+ if (!this.ctx.handlesBtwBranchKey()) return undefined;
254
+ if (this.ctx.ui.getFocused() !== this.ctx.editor) return undefined;
255
+ if (this.ctx.editor.getText().trim()) return undefined;
256
+ void this.ctx.handleBtwBranchKey();
257
+ return { consume: true };
258
+ });
259
+ }
260
+ if (!this.#btwCopyListenerInstalled) {
261
+ this.#btwCopyListenerInstalled = true;
262
+ this.ctx.ui.addInputListener(data => {
263
+ if (!matchesKey(data, "c")) return undefined;
264
+ if (!this.ctx.canCopyBtw()) return undefined;
265
+ if (this.ctx.ui.getFocused() !== this.ctx.editor) return undefined;
266
+ if (this.ctx.editor.getText().trim()) return undefined;
267
+ void this.ctx.handleBtwCopyKey();
268
+ return { consume: true };
269
+ });
270
+ }
271
+ if (!this.#focusedPasteListenerInstalled) {
272
+ this.#focusedPasteListenerInstalled = true;
273
+ this.ctx.ui.addInputListener(data => {
274
+ const focused = this.ctx.ui.getFocused();
275
+ if (!focused || focused === this.ctx.editor || !hasPasteText(focused)) return undefined;
276
+ if (!this.ctx.keybindings.matches(data, "app.clipboard.pasteImage")) return undefined;
277
+ void this.handleImagePaste();
278
+ return { consume: true };
279
+ });
280
+ }
281
+ this.ctx.editor.onEscape = () => {
282
+ // Side-channel panels are the topmost view. Esc dismisses them before
283
+ // touching loop mode, maintenance, or the underlying main turn.
284
+ // Active context maintenance owns Esc: auto/manual compaction,
285
+ // handoff generation, and auto-retry backoff all advertise
286
+ // "(esc to cancel)". Dispatch on live session state instead of
287
+ // swapping onEscape handlers — interleaved start/end events used
288
+ // to clobber the single saved-handler slot (auto-compaction start
289
+ // → /compact → auto end → manual finally), leaving Esc wired to a
290
+ // stale no-op closure until restart.
291
+ //
292
+ // While a subagent is focused, Esc honors the advertised view action
293
+ // ("Esc returns to main") instead of cancelling maintenance —
294
+ // accidentally killing a focused subagent's compaction on the way out
295
+ // was #2819. The auto-maintenance loaders relabel their hint to match
296
+ // (see EventController). Main-session maintenance still owns Esc and
297
+ // stays cancellable from the main view (focused submit gates /compact
298
+ // and handoff, so manual maintenance is main-only anyway).
299
+ if (this.ctx.hasActiveBtw() && this.ctx.handleBtwEscape()) {
300
+ return;
301
+ }
302
+ if (this.ctx.hasActiveOmfg() && this.ctx.handleOmfgEscape()) {
303
+ return;
304
+ }
305
+
306
+ if (!this.ctx.focusedAgentId) {
307
+ const viewSession = this.ctx.viewSession;
308
+ let aborted = false;
309
+ if (viewSession.isCompacting) {
310
+ safeAbort("compaction", () => viewSession.abortCompaction());
311
+ aborted = true;
312
+ }
313
+ if (viewSession.isGeneratingHandoff) {
314
+ safeAbort("handoff", () => viewSession.abortHandoff());
315
+ aborted = true;
316
+ }
317
+ if (viewSession.isRetrying) {
318
+ safeAbort("retry", () => viewSession.abortRetry());
319
+ aborted = true;
320
+ }
321
+ if (aborted) return;
322
+ }
323
+
324
+ if (vocalizer.isSpeaking()) {
325
+ // Playback from the completed response can overlap the next agent
326
+ // turn. Silence it before interrupting any ongoing main-turn work.
327
+ vocalizer.clear();
328
+ this.ctx.lastEscapeTime = 0;
329
+ return;
330
+ }
331
+
332
+ if (this.ctx.loopModeEnabled) {
333
+ if (this.ctx.session.isStreaming) {
334
+ this.#abortStreamingTurn();
335
+ } else {
336
+ this.ctx.pauseLoop();
337
+ this.ctx.cancelPendingSubmission();
338
+ }
339
+ return;
340
+ }
341
+ if (this.ctx.focusedAgentId) {
342
+ // Esc never interrupts the focused agent's turn: clear typed text,
343
+ // else return the view to the main session. Interrupt via empty
344
+ // steer-flush submit if needed.
345
+ if (this.ctx.editor.getText().trim()) {
346
+ this.ctx.editor.setText("");
347
+ this.ctx.ui.requestRender();
348
+ } else {
349
+ void this.ctx.unfocusSession();
350
+ }
351
+ return; // double-escape backtrack (/tree, /branch) stays main-only
352
+ }
353
+ if (this.ctx.collabGuest) {
354
+ // Guest Esc: ask the host to interrupt its agent; the local replica
355
+ // session is never streaming, so the native abort path below would
356
+ // no-op.
357
+ if (this.ctx.collabGuest.state?.isStreaming || this.ctx.loadingAnimation) {
358
+ this.ctx.collabGuest.sendAbort();
359
+ }
360
+ return;
361
+ }
362
+ if (this.ctx.loadingAnimation) {
363
+ if (this.ctx.cancelPendingSubmission()) {
364
+ return;
365
+ }
366
+ this.restoreQueuedMessagesToEditor({ abort: true });
367
+ } else if (this.ctx.session.isBashRunning) {
368
+ this.ctx.session.abortBash();
369
+ } else if (this.ctx.isBashMode) {
370
+ this.ctx.editor.setText("");
371
+ this.ctx.isBashMode = false;
372
+ this.ctx.updateEditorBorderColor();
373
+ } else if (this.ctx.session.isEvalRunning) {
374
+ this.ctx.session.abortEval();
375
+ } else if (this.ctx.isPythonMode) {
376
+ this.ctx.editor.setText("");
377
+ this.ctx.isPythonMode = false;
378
+ this.ctx.updateEditorBorderColor();
379
+ } else if (this.ctx.session.isStreaming) {
380
+ this.#abortStreamingTurn();
381
+ } else if (this.ctx.editor.getText().trim()) {
382
+ // Esc must not destroy an in-progress draft.
383
+ this.ctx.lastEscapeTime = 0;
384
+ } else {
385
+ // Double-interrupt with empty editor triggers /tree, /branch, or nothing based on setting
386
+ const action = settings.get("doubleEscapeAction");
387
+ if (action !== "none") {
388
+ const now = Date.now();
389
+ if (now - this.ctx.lastEscapeTime < 500) {
390
+ if (action === "tree") {
391
+ this.ctx.showTreeSelector();
392
+ } else {
393
+ this.ctx.showUserMessageSelector();
394
+ }
395
+ this.ctx.ui.resetDisplay();
396
+ this.ctx.lastEscapeTime = 0;
397
+ } else {
398
+ this.ctx.lastEscapeTime = now;
399
+ }
400
+ }
401
+ }
402
+ };
403
+
404
+ this.ctx.editor.setActionKeys("app.clear", this.ctx.keybindings.getKeys("app.clear"));
405
+ this.ctx.editor.onClear = () => this.handleCtrlC();
406
+ this.ctx.editor.setActionKeys("app.exit", this.ctx.keybindings.getKeys("app.exit"));
407
+ this.ctx.editor.setActionKeys("app.display.reset", this.ctx.keybindings.getKeys("app.display.reset"));
408
+ this.ctx.editor.onDisplayReset = () => {
409
+ // Explicit user gesture (display reset, Alt+L by default): re-query the
410
+ // terminal background once so a mid-session light/dark switch is picked
411
+ // up even on terminals without an end-to-end Mode 2031 notification
412
+ // path (#5352). The appearance callback re-evaluates the auto theme; the
413
+ // repaint below then renders the resolved palette. Bounded to one OSC 11
414
+ // probe per gesture — no timers, no periodic polling.
415
+ this.ctx.resetDisplayAfterAppearanceRefresh();
416
+ };
417
+ this.ctx.editor.onExit = () => this.handleCtrlD();
418
+ this.ctx.editor.setActionKeys("app.suspend", this.ctx.keybindings.getKeys("app.suspend"));
419
+ this.ctx.editor.onSuspend = () => this.handleCtrlZ();
420
+ this.ctx.editor.setActionKeys("app.thinking.cycle", this.ctx.keybindings.getKeys("app.thinking.cycle"));
421
+ this.ctx.editor.onCycleThinkingLevel = () => this.cycleThinkingLevel();
422
+ this.ctx.editor.setActionKeys("app.model.cycleForward", this.ctx.keybindings.getKeys("app.model.cycleForward"));
423
+ this.ctx.editor.onCycleModelForward = () => this.cycleRoleModel("forward");
424
+ this.ctx.editor.setActionKeys("app.model.cycleBackward", this.ctx.keybindings.getKeys("app.model.cycleBackward"));
425
+ this.ctx.editor.onCycleModelBackward = () => this.cycleRoleModel("backward");
426
+ this.ctx.editor.setActionKeys(
427
+ "app.model.selectTemporary",
428
+ this.ctx.keybindings.getKeys("app.model.selectTemporary"),
429
+ );
430
+ this.ctx.editor.onSelectModelTemporary = () => this.ctx.showModelSelector({ temporaryOnly: true });
431
+
432
+ // Global debug handler on TUI (works regardless of focus)
433
+ this.ctx.ui.onDebug = () => this.ctx.showDebugSelector();
434
+ this.ctx.editor.setActionKeys("app.model.select", this.ctx.keybindings.getKeys("app.model.select"));
435
+ this.ctx.editor.onSelectModel = () => this.ctx.showModelSelector();
436
+ this.ctx.editor.setActionKeys("app.history.search", this.ctx.keybindings.getKeys("app.history.search"));
437
+ this.ctx.editor.onHistorySearch = () => this.ctx.showHistorySearch();
438
+ this.ctx.editor.setActionKeys("app.thinking.toggle", this.ctx.keybindings.getKeys("app.thinking.toggle"));
439
+ this.ctx.editor.onToggleThinking = () => this.ctx.toggleThinkingBlockVisibility();
440
+ this.ctx.editor.setActionKeys("app.editor.external", this.ctx.keybindings.getKeys("app.editor.external"));
441
+ this.ctx.editor.onExternalEditor = () => void this.openExternalEditor();
442
+ this.ctx.editor.setActionKeys(
443
+ "app.clipboard.pasteImage",
444
+ this.ctx.keybindings.getKeys("app.clipboard.pasteImage"),
445
+ );
446
+ this.ctx.editor.onPasteImage = () => this.handleImagePaste();
447
+ this.ctx.editor.onPasteImagePath = path => this.handleImagePathPaste(path);
448
+ this.ctx.editor.setActionKeys(
449
+ "app.clipboard.pasteTextRaw",
450
+ this.ctx.keybindings.getKeys("app.clipboard.pasteTextRaw"),
451
+ );
452
+ this.ctx.editor.onPasteTextRaw = () => void this.handleClipboardTextRawPaste();
453
+ this.ctx.editor.onLargePaste = (text, lineCount) => this.handleLargePaste(text, lineCount);
454
+ this.ctx.editor.setActionKeys(
455
+ "app.clipboard.copyPrompt",
456
+ this.ctx.keybindings.getKeys("app.clipboard.copyPrompt"),
457
+ );
458
+ this.ctx.editor.onCopyPrompt = () => this.handleCopyPrompt();
459
+ this.ctx.editor.setActionKeys("app.tools.expand", this.ctx.keybindings.getKeys("app.tools.expand"));
460
+ this.ctx.editor.onExpandTools = () => this.toggleToolOutputExpansion();
461
+ this.ctx.editor.setActionKeys(
462
+ "app.tools.toggleVisibility",
463
+ this.ctx.keybindings.getKeys("app.tools.toggleVisibility"),
464
+ );
465
+ this.ctx.editor.onToggleToolActivity = () => this.toggleToolActivityVisibility();
466
+ this.ctx.editor.setActionKeys("app.message.dequeue", this.ctx.keybindings.getKeys("app.message.dequeue"));
467
+ this.ctx.editor.onDequeue = () => this.handleDequeue();
468
+ this.ctx.editor.setActionKeys("app.retry", this.ctx.keybindings.getKeys("app.retry"));
469
+ this.ctx.editor.onRetry = () => void this.handleRetry();
470
+ this.ctx.editor.clearCustomKeyHandlers();
471
+ // Wire up extension shortcuts
472
+ this.registerExtensionShortcuts();
473
+ const planModeKeys = this.ctx.keybindings.getKeys("app.plan.toggle");
474
+ for (const key of planModeKeys) {
475
+ this.ctx.editor.setCustomKeyHandler(key, () => void this.ctx.handlePlanModeCommand());
476
+ }
477
+
478
+ for (const key of this.ctx.keybindings.getKeys("app.session.new")) {
479
+ this.ctx.editor.setCustomKeyHandler(key, () => this.ctx.handleClearCommand());
480
+ }
481
+ for (const key of this.ctx.keybindings.getKeys("app.session.tree")) {
482
+ this.ctx.editor.setCustomKeyHandler(key, () => this.ctx.showTreeSelector());
483
+ }
484
+ for (const key of this.ctx.keybindings.getKeys("app.session.fork")) {
485
+ this.ctx.editor.setCustomKeyHandler(key, () => this.ctx.showUserMessageSelector());
486
+ }
487
+ for (const key of this.ctx.keybindings.getKeys("app.session.resume")) {
488
+ this.ctx.editor.setCustomKeyHandler(key, () => this.ctx.showSessionSelector());
489
+ }
490
+ for (const key of this.ctx.keybindings.getKeys("app.message.followUp")) {
491
+ this.ctx.editor.setCustomKeyHandler(key, () => void this.handleFollowUp());
492
+ }
493
+ for (const key of this.ctx.keybindings.getKeys("app.stt.toggle")) {
494
+ this.ctx.editor.setCustomKeyHandler(key, () => void this.ctx.handleSTTToggle());
495
+ }
496
+ for (const key of this.ctx.keybindings.getKeys("app.live.toggle")) {
497
+ this.ctx.editor.setCustomKeyHandler(key, () => void this.ctx.handleLiveCommand());
498
+ }
499
+ // Hold the space bar to push-to-talk: the editor recognizes the auto-repeat burst, tracks
500
+ // the spam back out, and toggles STT on hold start / release. Gated on `stt.enabled` so a
501
+ // disabled STT leaves the space bar typing normally.
502
+ this.ctx.editor.sttHoldEnabled = () => settings.get("stt.enabled");
503
+ this.ctx.editor.onSpaceHoldStart = () => void this.ctx.handleSTTToggle();
504
+ this.ctx.editor.onSpaceHoldEnd = () => void this.ctx.handleSTTToggle();
505
+ for (const key of this.ctx.keybindings.getKeys("app.clipboard.copyLine")) {
506
+ this.ctx.editor.setCustomKeyHandler(key, () => this.handleCopyCurrentLine());
507
+ }
508
+ const hubKeys = new Set([
509
+ ...this.ctx.keybindings.getKeys("app.agents.hub"),
510
+ ...this.ctx.keybindings.getKeys("app.session.observe"),
511
+ ]);
512
+ for (const key of hubKeys) {
513
+ this.ctx.editor.setCustomKeyHandler(key, () => this.ctx.showAgentHub());
514
+ }
515
+
516
+ // Double-tap left arrow on an empty editor: opens the agent hub from the
517
+ // main session, or returns the focused subagent view to the main session.
518
+ // Focused ←← intentionally matches Esc. From the main session the gesture
519
+ // stays inert when there are no subagents (requireContent); the explicit
520
+ // hub key still opens the empty roster. `armCloseTap` hands this gesture's
521
+ // tap state to the hub so the same ←← that opened it also arms its close —
522
+ // otherwise the hub's fresh detector demands a second ←← (issue #4780).
523
+ this.ctx.editor.onLeftAtStart = () => {
524
+ if (this.ctx.focusedAgentId) {
525
+ this.#handleFocusedLeftTap();
526
+ return;
527
+ }
528
+ if (this.#detectLeftDoubleTap()) {
529
+ this.ctx.showAgentHub({ requireContent: true, armCloseTap: true });
530
+ }
531
+ };
532
+
533
+ this.#setupEnhancedPaste();
534
+
535
+ this.ctx.editor.onChange = (text: string) => {
536
+ const wasBashMode = this.ctx.isBashMode;
537
+ const wasPythonMode = this.ctx.isPythonMode;
538
+ const trimmed = text.trimStart();
539
+ this.ctx.isBashMode = trimmed.startsWith("!");
540
+ this.ctx.isPythonMode = parsePythonCommandInput(trimmed) !== undefined;
541
+ if (wasBashMode !== this.ctx.isBashMode || wasPythonMode !== this.ctx.isPythonMode) {
542
+ this.ctx.updateEditorBorderColor();
543
+ }
544
+ };
545
+ }
546
+
547
+ #handleFocusedLeftTap(): void {
548
+ if (this.#detectLeftDoubleTap()) {
549
+ void this.ctx.unfocusSession();
550
+ }
551
+ }
552
+
553
+ /**
554
+ * Detect a deliberate double-← gesture, rejecting terminal-synthesized arrow
555
+ * bursts. Returns true only on the *second* tap of a fresh sequence when it
556
+ * lands a human-plausible interval after the first
557
+ * (`[LEFT_DOUBLE_TAP_MIN_GAP_MS, LEFT_DOUBLE_TAP_MAX_GAP_MS)`). Taps closer
558
+ * than the lower bound, or any third-and-later tap before a quiet gap, are a
559
+ * burst and never fire — so a stray click that makes the terminal emit a run
560
+ * of ← keys can no longer pop the Agent Hub.
561
+ */
562
+ #detectLeftDoubleTap(): boolean {
563
+ const now = Date.now();
564
+ const sinceLast = now - this.ctx.lastLeftTapTime;
565
+ this.ctx.lastLeftTapTime = now;
566
+ if (sinceLast >= LEFT_DOUBLE_TAP_MAX_GAP_MS) {
567
+ // Quiet gap: this tap starts a fresh sequence.
568
+ this.#leftTapCount = 1;
569
+ return false;
570
+ }
571
+ this.#leftTapCount += 1;
572
+ if (this.#leftTapCount === 2 && sinceLast >= LEFT_DOUBLE_TAP_MIN_GAP_MS) {
573
+ // Exactly two taps, the second a human-plausible interval after the first.
574
+ this.#leftTapCount = 0;
575
+ this.ctx.lastLeftTapTime = 0;
576
+ return true;
577
+ }
578
+ return false;
579
+ }
580
+
581
+ #setupEnhancedPaste(): void {
582
+ if (this.#enhancedPaste) return;
583
+
584
+ this.#enhancedPaste = new EnhancedPasteController({
585
+ write: data => this.ctx.ui.terminal.write(data),
586
+ pasteText: text => {
587
+ // Route enhanced-paste text to the currently focused component when it
588
+ // exposes a `pasteText` hook (modal Input prompts: OAuth API-key entry,
589
+ // Perplexity OTP, GitHub Enterprise URL, manual redirect URL). Falling
590
+ // back to the main editor would have buried the text in the detached
591
+ // editor while the modal Input had focus (#2127).
592
+ const focused = this.ctx.ui.getFocused();
593
+ const target = focused && focused !== this.ctx.editor && hasPasteText(focused) ? focused : this.ctx.editor;
594
+ target.pasteText(text);
595
+ this.ctx.ui.requestRender();
596
+ },
597
+ pasteImage: async image => {
598
+ // Images can only land in the main editor — when a modal Input is
599
+ // focused, refuse rather than dump the binary blob in a hidden buffer.
600
+ const focused = this.ctx.ui.getFocused();
601
+ if (focused && focused !== this.ctx.editor && hasPasteText(focused)) {
602
+ this.ctx.showStatus("Image paste is not supported in this prompt");
603
+ return;
604
+ }
605
+ await this.#normalizeAndInsertPastedImage(image, `Unsupported pasted image format: ${image.mimeType}`);
606
+ },
607
+ showStatus: message => this.ctx.showStatus(message),
608
+ });
609
+ this.ctx.ui.addInputListener(data => (this.#enhancedPaste?.handleInput(data) ? { consume: true } : undefined));
610
+ this.ctx.ui.addStartListener(() => this.#enhancedPaste?.enable());
611
+ }
612
+
613
+ setupEditorSubmitHandler(): void {
614
+ this.ctx.editor.onSubmit = async (text: string) => {
615
+ text = text.trim();
616
+ const hasPendingImages = this.ctx.editor.pendingImages.length > 0;
617
+ if ((!isSettingsInitialized() || settings.get("emojiAutocomplete")) && text) text = expandEmoticons(text);
618
+
619
+ // Focused subagent session: the editor is a plain chat box for it.
620
+ // Everything below (continue shortcuts, slash/bash/python, loop,
621
+ // compaction queueing) is main-session-only.
622
+ if (this.ctx.focusedAgentId) {
623
+ await this.#submitToFocusedSession(text, "steer");
624
+ return;
625
+ }
626
+
627
+ // Empty submit while streaming with queued messages: abort the active
628
+ // turn and let the post-unwind drain deliver the agent-core queue.
629
+ if (!text && !hasPendingImages && this.ctx.session.isStreaming) {
630
+ if (this.ctx.session.queuedMessageCount > 0) {
631
+ const aborting = this.ctx.session.abort({ reason: USER_INTERRUPT_LABEL });
632
+ await aborting;
633
+ this.ctx.updatePendingMessagesDisplay();
634
+ this.ctx.ui.requestRender();
635
+ }
636
+ return;
637
+ }
638
+
639
+ if (!text && !hasPendingImages) return;
640
+
641
+ // Continue shortcuts: "." or "c" resume the agent with a hidden agent-authored
642
+ // developer directive (no visible user message) instead of an empty turn, so the
643
+ // model continues the prior intent rather than second-guessing the interrupt.
644
+ if (text === "." || text === "c") {
645
+ if (this.ctx.onInputCallback) {
646
+ this.ctx.editor.clearDraft();
647
+ this.ctx.onInputCallback({
648
+ text: manualContinuePrompt,
649
+ cancelled: false,
650
+ started: true,
651
+ synthetic: true,
652
+ userInitiated: true,
653
+ });
654
+ }
655
+ return;
656
+ }
657
+
658
+ const runner = this.ctx.session.extensionRunner;
659
+ let inputImages = this.ctx.editor.pendingImages.length > 0 ? [...this.ctx.editor.pendingImages] : undefined;
660
+ let inputImageLinks =
661
+ this.ctx.editor.pendingImageLinks.length > 0 ? [...this.ctx.editor.pendingImageLinks] : undefined;
662
+ let hasInputImages = (inputImages?.length ?? 0) > 0;
663
+
664
+ if (runner?.hasHandlers("input")) {
665
+ const result = await runner.emitInput(text, inputImages, "interactive");
666
+ if (result?.handled) {
667
+ this.ctx.editor.clearDraft();
668
+ return;
669
+ }
670
+ if (result?.text !== undefined) {
671
+ text = result.text.trim();
672
+ }
673
+ if (result?.images !== undefined) {
674
+ inputImages = result.images;
675
+ inputImageLinks = await materializeImageReferenceLinks(
676
+ inputImages,
677
+ this.ctx.sessionManager.putBlob.bind(this.ctx.sessionManager),
678
+ );
679
+ }
680
+ hasInputImages = (inputImages?.length ?? 0) > 0;
681
+ }
682
+
683
+ if (!text && !hasInputImages) return;
684
+
685
+ const queueBody = parseQueueShorthand(text);
686
+ if (queueBody !== undefined) {
687
+ await this.#queueForYield(queueBody, {
688
+ historyText: text,
689
+ images: inputImages,
690
+ imageLinks: inputImageLinks,
691
+ });
692
+ return;
693
+ }
694
+
695
+ // Handle built-in slash commands
696
+ if (text) {
697
+ const slashResult = await executeBuiltinSlashCommand(text, {
698
+ ctx: this.ctx,
699
+ });
700
+ if (slashResult === true) {
701
+ if (!shouldSkipHistory(text)) this.ctx.editor.addToHistory(text);
702
+ return;
703
+ }
704
+ if (typeof slashResult === "string") {
705
+ // Command handled but returned remaining text to use as prompt.
706
+ // Record the original slash command text so Up Arrow recalls
707
+ // "/loop 10 fix bug" rather than just "fix bug".
708
+ if (!shouldSkipHistory(text)) this.ctx.editor.addToHistory(text);
709
+ text = slashResult;
710
+ }
711
+ }
712
+
713
+ // Collab guest: prompts execute on the host; local slash/skill/bash/
714
+ // python execution is host-only (builtins are gated inside
715
+ // executeBuiltinSlashCommand, which already consumed allowed ones).
716
+ if (this.ctx.collabGuest) {
717
+ if (text.startsWith("/")) {
718
+ this.ctx.showStatus(`${text.split(/\s+/, 1)[0]} is host-only during a collab session`);
719
+ this.ctx.editor.setText("");
720
+ return;
721
+ }
722
+ if (text.startsWith("!") || parsePythonCommandInput(text)) {
723
+ this.ctx.showStatus("Local execution is host-only during a collab session");
724
+ this.ctx.editor.setText("");
725
+ return;
726
+ }
727
+ if (this.ctx.collabGuest.readOnly) {
728
+ // Keep the typed text: the prompt was not consumed.
729
+ this.ctx.showStatus("This collab link is read-only — prompting is disabled");
730
+ return;
731
+ }
732
+ const images = inputImages && inputImages.length > 0 ? [...inputImages] : undefined;
733
+ this.ctx.editor.clearDraft(text);
734
+ // No local render: the prompt comes back from the host as a
735
+ // collab-prompt event/entry and renders with the author badge.
736
+ this.ctx.collabGuest.sendPrompt(text, images);
737
+ return;
738
+ }
739
+
740
+ // Handle skill commands (/skill:name [args]). Enter ⇒ steer (matches the
741
+ // free-text Enter semantics below); Ctrl+Enter routes through `handleFollowUp`.
742
+ // During compaction, queue immediately so bash/python/loop-mode branches do
743
+ // not consume the skill before the compaction-resume path re-parses it.
744
+ if (text && isKnownSkillCommand(this.ctx, text)) {
745
+ if (this.ctx.session.isCompacting) {
746
+ const images = inputImages && inputImages.length > 0 ? [...inputImages] : undefined;
747
+ this.ctx.queueCompactionMessage(text, "steer", images);
748
+ return;
749
+ }
750
+ if (await this.#invokeSkillCommand(text, "steer", inputImages, inputImageLinks)) {
751
+ return;
752
+ }
753
+ }
754
+
755
+ // Handle bash command (! for normal, !! for excluded from context)
756
+ if (text.startsWith("!")) {
757
+ const isExcluded = text.startsWith("!!");
758
+ const command = isExcluded ? text.slice(2).trim() : text.slice(1).trim();
759
+ if (command) {
760
+ if (this.ctx.session.isBashRunning) {
761
+ this.ctx.showWarning("A bash command is already running. Press Esc to cancel it first.");
762
+ this.ctx.editor.setText(text);
763
+ return;
764
+ }
765
+ this.ctx.editor.addToHistory(text);
766
+ await this.ctx.handleBashCommand(command, isExcluded);
767
+ this.ctx.isBashMode = false;
768
+ this.ctx.updateEditorBorderColor();
769
+ return;
770
+ }
771
+ }
772
+
773
+ // Handle python command (`$ <code>` for normal, `$$ <code>` for excluded from context).
774
+ // Shell-style variables such as `$HOME` are normal prose unless a space follows the sigil.
775
+ const pythonCommand = parsePythonCommandInput(text);
776
+ if (pythonCommand) {
777
+ const { code, isExcluded } = pythonCommand;
778
+ if (code) {
779
+ if (this.ctx.session.isEvalRunning) {
780
+ this.ctx.showWarning("A Python execution is already running. Press Esc to cancel it first.");
781
+ this.ctx.editor.setText(text);
782
+ return;
783
+ }
784
+ this.ctx.editor.addToHistory(text);
785
+ await this.ctx.handlePythonCommand(code, isExcluded);
786
+ this.ctx.isPythonMode = false;
787
+ this.ctx.updateEditorBorderColor();
788
+ return;
789
+ }
790
+ }
791
+
792
+ // While loop mode is on, every user-typed prompt becomes the new loop
793
+ // prompt that auto-resubmits after each yield.
794
+ if (this.ctx.loopModeEnabled) {
795
+ this.ctx.setLoopPrompt(text);
796
+ }
797
+
798
+ // Queue input during compaction
799
+ if (this.ctx.session.isCompacting) {
800
+ const images = inputImages && inputImages.length > 0 ? [...inputImages] : undefined;
801
+ this.ctx.queueCompactionMessage(text, "steer", images);
802
+ return;
803
+ }
804
+ // Extension commands are local actions. Execute them before the normal
805
+ // submission path creates an optimistic user message; otherwise a
806
+ // consumed command remains rendered like a prompt sent to the model.
807
+ if (this.#isLocalExtensionCommand(text)) {
808
+ this.ctx.editor.clearDraft(text);
809
+ try {
810
+ await this.ctx.session.prompt(text, { images: inputImages });
811
+ } catch (error) {
812
+ this.ctx.editor.setText(text);
813
+ this.ctx.showError(error instanceof Error ? error.message : String(error));
814
+ }
815
+ return;
816
+ }
817
+
818
+ // If streaming, use prompt() with steer behavior
819
+ // This handles extension commands (execute immediately), prompt template expansion, and queueing
820
+ if (this.ctx.session.isStreaming) {
821
+ this.ctx.editor.addToHistory(text);
822
+ this.ctx.editor.setText("");
823
+ this.ctx.editor.imageLinks = undefined;
824
+ const images = inputImages && inputImages.length > 0 ? [...inputImages] : undefined;
825
+ this.ctx.editor.pendingImages = [];
826
+ this.ctx.editor.pendingImageLinks = [];
827
+ // Record the signature so the queued message's eventual delivery
828
+ // (a user-role `message_start` event) leaves any draft the user has
829
+ // typed since queuing intact. Same protection as #783, applied to
830
+ // the streaming/queue path.
831
+ try {
832
+ await this.ctx.withLocalSubmission(
833
+ text,
834
+ () => this.ctx.session.prompt(text, { streamingBehavior: "steer", images }),
835
+ { imageCount: images?.length ?? 0 },
836
+ );
837
+ } catch (error) {
838
+ // Don't lose the queued steer draft: restore text and images so
839
+ // the user can retry after dispatch validation/queue failures.
840
+ this.ctx.editor.setText(text);
841
+ if (images && images.length > 0) {
842
+ this.ctx.editor.pendingImages = [...images];
843
+ this.ctx.editor.pendingImageLinks = inputImageLinks
844
+ ? [...inputImageLinks]
845
+ : images.map(() => undefined);
846
+ this.ctx.editor.imageLinks = this.ctx.editor.pendingImageLinks;
847
+ }
848
+ this.ctx.showError(error instanceof Error ? error.message : String(error));
849
+ }
850
+ this.ctx.updatePendingMessagesDisplay();
851
+ this.ctx.ui.requestRender();
852
+ return;
853
+ }
854
+
855
+ // Normal message submission
856
+ // First, move any pending bash components to chat
857
+ this.ctx.flushPendingBashComponents();
858
+
859
+ if (this.ctx.onInputCallback) {
860
+ // Include any pending images from clipboard paste
861
+ this.ctx.editor.imageLinks = undefined;
862
+ const images = inputImages && inputImages.length > 0 ? [...inputImages] : undefined;
863
+ this.ctx.editor.pendingImages = [];
864
+ this.ctx.editor.pendingImageLinks = [];
865
+
866
+ // Render user message immediately, then let session events catch up.
867
+ // Tag the submission as "steer": this is a normal Enter the controller
868
+ // believed was idle, but a background turn can start in the gap before
869
+ // `submitInteractiveInput` dispatches it. Steering matches the
870
+ // streaming-branch Enter (above) and keeps the message from throwing
871
+ // AgentBusyError on that race.
872
+ const submission = this.ctx.startPendingSubmission({
873
+ text,
874
+ images,
875
+ imageLinks: inputImageLinks,
876
+ streamingBehavior: "steer",
877
+ });
878
+ // Start titling only after the optimistic row painted, so the local
879
+ // tiny-title worker's subprocess spawn never blocks the first frame.
880
+ this.#maybeStartTitleGeneration(text);
881
+
882
+ this.ctx.onInputCallback(submission);
883
+ } else {
884
+ // No input waiter: the main loop is between turns (post-turn
885
+ // epilogue, retry backoff, or a scheduled continue) with the agent
886
+ // momentarily idle. The editor already cleared itself on Enter, so
887
+ // falling through here would silently swallow the message. Submit a
888
+ // real prompt directly; if a background turn starts in the gap,
889
+ // `streamingBehavior: "steer"` preserves the typed-message queueing
890
+ // semantics instead of throwing AgentBusyError.
891
+ this.ctx.editor.imageLinks = undefined;
892
+ const images = inputImages && inputImages.length > 0 ? [...inputImages] : undefined;
893
+ this.ctx.editor.pendingImages = [];
894
+ this.ctx.editor.pendingImageLinks = [];
895
+ this.#maybeStartTitleGeneration(text);
896
+ try {
897
+ await this.ctx.withLocalSubmission(
898
+ text,
899
+ () => this.ctx.session.prompt(text, { streamingBehavior: "steer", images }),
900
+ {
901
+ imageCount: images?.length ?? 0,
902
+ },
903
+ );
904
+ } catch (error) {
905
+ // Don't lose the message: hand the text and images back to the
906
+ // editor so the user can retry (e.g. prompt dispatch rejecting an
907
+ // extension command).
908
+ this.ctx.editor.setText(text);
909
+ if (images && images.length > 0) {
910
+ this.ctx.editor.pendingImages = [...images];
911
+ this.ctx.editor.pendingImageLinks = inputImageLinks
912
+ ? [...inputImageLinks]
913
+ : images.map(() => undefined);
914
+ this.ctx.editor.imageLinks = this.ctx.editor.pendingImageLinks;
915
+ }
916
+ this.ctx.showError(error instanceof Error ? error.message : String(error));
917
+ }
918
+ this.ctx.updatePendingMessagesDisplay();
919
+ this.ctx.ui.requestRender();
920
+ }
921
+ this.ctx.editor.addToHistory(text);
922
+ };
923
+ }
924
+
925
+ /**
926
+ * Kick off session-title generation after the optimistic user row paints.
927
+ * Local extension commands are consumed before reaching the shared session
928
+ * title gate and must not name the conversation.
929
+ */
930
+ #isLocalExtensionCommand(text: string): boolean {
931
+ const extensionCommandSpace = text.indexOf(" ");
932
+ return (
933
+ text.startsWith("/") &&
934
+ this.ctx.session.extensionRunner?.getCommand(
935
+ extensionCommandSpace === -1 ? text.slice(1) : text.slice(1, extensionCommandSpace),
936
+ ) !== undefined
937
+ );
938
+ }
939
+
940
+ #maybeStartTitleGeneration(text: string): void {
941
+ if (this.#isLocalExtensionCommand(text)) {
942
+ return;
943
+ }
944
+ this.ctx.session.maybeStartTitleGeneration(text, () => {
945
+ this.#showTinyTitleDownloadProgress(this.ctx.settings.get("providers.tinyModel"));
946
+ });
947
+ }
948
+
949
+ /** Submit editor text to the focused subagent session (chat-only focus policy). */
950
+ async #submitToFocusedSession(text: string, streamingBehavior: "steer" | "followUp"): Promise<void> {
951
+ const target = this.ctx.viewSession;
952
+ const images = this.ctx.editor.pendingImages.length > 0 ? [...this.ctx.editor.pendingImages] : undefined;
953
+ const imageLinks =
954
+ images && this.ctx.editor.pendingImageLinks.length > 0 ? [...this.ctx.editor.pendingImageLinks] : undefined;
955
+ if (!text && !images) {
956
+ if (target.isStreaming && target.queuedMessageCount > 0) {
957
+ const aborting = target.abort({ reason: USER_INTERRUPT_LABEL });
958
+ await aborting;
959
+ this.ctx.updatePendingMessagesDisplay();
960
+ this.ctx.ui.requestRender();
961
+ }
962
+ return;
963
+ }
964
+ if (text && (text.startsWith("/") || text.startsWith("!") || parsePythonCommandInput(text))) {
965
+ this.ctx.showStatus("Commands run in the main session — press ←← to return first");
966
+ return; // editor text not cleared: Editor does not auto-clear on submit
967
+ }
968
+ this.ctx.editor.clearDraft(text);
969
+ try {
970
+ // prompt() handles idle (new turn) and streaming (queues per streamingBehavior).
971
+ await this.ctx.withLocalSubmission(text, () => target.prompt(text, { streamingBehavior, images }), {
972
+ imageCount: images?.length ?? 0,
973
+ });
974
+ } catch (error) {
975
+ // Hand the message back, mirroring the main submit error path: restore
976
+ // pasted images so the user can retry an image-only or text+image draft.
977
+ this.ctx.editor.setText(text);
978
+ if (images && images.length > 0) {
979
+ this.ctx.editor.pendingImages = [...images];
980
+ this.ctx.editor.pendingImageLinks = imageLinks ? [...imageLinks] : images.map(() => undefined);
981
+ this.ctx.editor.imageLinks = this.ctx.editor.pendingImageLinks;
982
+ }
983
+ this.ctx.showError(error instanceof Error ? error.message : String(error));
984
+ }
985
+ this.ctx.updatePendingMessagesDisplay();
986
+ this.ctx.ui.requestRender();
987
+ }
988
+
989
+ handleCtrlC(): void {
990
+ // Sync-flush the session JSONL so in-flight writes survive a hard exit.
991
+ // The TUI consumes Ctrl+C as a key event in raw mode, so postmortem's
992
+ // process-level SIGINT handler never fires. shutdown() awaits its own
993
+ // async flush — this sync pass is a superset that also covers the
994
+ // first-press case and the hard-abort path below.
995
+ try {
996
+ this.ctx.sessionManager.flushSync();
997
+ } catch (err) {
998
+ logger.warn("session-manager sync flush on Ctrl+C failed", {
999
+ error: err instanceof Error ? err.message : String(err),
1000
+ });
1001
+ }
1002
+
1003
+ // Hard-abort: a Ctrl+C arriving while shutdown() is already running
1004
+ // means the user has waited long enough for whatever teardown step is
1005
+ // stuck (typically an extension's session_shutdown handler hanging on
1006
+ // IPC). The 2s session_shutdown cap (see runner.ts) already bounds the
1007
+ // common case; this is the defense-in-depth ladder for everything
1008
+ // else. See issue #2600.
1009
+ if (this.ctx.isShuttingDown) {
1010
+ process.exit(130); // 128 + SIGINT
1011
+ }
1012
+
1013
+ const now = Date.now();
1014
+ if (now - this.ctx.lastSigintTime < 500) {
1015
+ void this.ctx.shutdown();
1016
+ } else {
1017
+ this.ctx.clearEditor();
1018
+ this.ctx.lastSigintTime = now;
1019
+ }
1020
+ }
1021
+
1022
+ handleCtrlD(): void {
1023
+ // Editor text (if any) is snapshotted at the start of shutdown() and
1024
+ // persisted as a draft for the next resume. Empty text is also fine —
1025
+ // shutdown clears any stale sidecar in that case.
1026
+ void this.ctx.shutdown();
1027
+ }
1028
+
1029
+ handleCtrlZ(): void {
1030
+ // Job-control suspend is POSIX-only: on Windows `process.kill(_, "SIGSTOP")`
1031
+ // throws `TypeError: Unknown signal: SIGSTOP` and takes the whole agent down
1032
+ // via an uncaught exception (issue #2036, originally for SIGTSTP — same
1033
+ // shape for SIGSTOP). No-op on platforms that cannot suspend.
1034
+ if (process.platform === "win32") {
1035
+ this.ctx.showStatus("Suspend (Ctrl+Z) is not supported on this platform");
1036
+ return;
1037
+ }
1038
+
1039
+ // Capture the listener so we can detach it if the signal never fires;
1040
+ // otherwise a failed suspend would leave a stale SIGCONT handler that
1041
+ // fires on the next unrelated continue and tries to re-`start()` an
1042
+ // already-running TUI.
1043
+ const onResume = (): void => {
1044
+ this.ctx.ui.start();
1045
+ this.ctx.ui.requestRender(true);
1046
+ };
1047
+ process.once("SIGCONT", onResume);
1048
+
1049
+ // Stop the TUI (restore terminal to normal mode) before sending the
1050
+ // signal so the parent shell sees a sane terminal state.
1051
+ this.ctx.ui.stop();
1052
+
1053
+ try {
1054
+ // SIGSTOP — not SIGTSTP — to the foreground process group (pid=0).
1055
+ //
1056
+ // SIGTSTP: brush-core (the embedded shell behind every bash tool call)
1057
+ // installs a tokio SIGTSTP listener on `Process::wait` to detect when
1058
+ // its children have been stopped (`crates/vendor/brush-core/src/sys/
1059
+ // unix/signal.rs::tstp_signal_listener` → `tokio::signal::unix::
1060
+ // signal(SIGTSTP)`). Per tokio's documented contract, the first call
1061
+ // for a given SignalKind permanently replaces the kernel-default
1062
+ // handler for the lifetime of the process. So once the user has
1063
+ // issued even one bash command — e.g. `/usr/bin/true` — SIGTSTP no
1064
+ // longer stops omp: tokio swallows it and the TUI ends up torn down
1065
+ // while the process keeps running with no live terminal (issue
1066
+ // [#3461]). SIGSTOP cannot be caught, blocked, or ignored, so the
1067
+ // kernel stops the process regardless of installed handlers.
1068
+ //
1069
+ // pid=0 (foreground process group, not just our PID): omp is not
1070
+ // always the shell's direct child. Package-manager launchers (`npx`,
1071
+ // `pnpm exec`, `bunx`, …) wait on the real CLI from a parent shim
1072
+ // that shares omp's process group, and a `omp … | tee log` style
1073
+ // pipeline puts a sibling foreground job member in the same group
1074
+ // too. The shell sees the job as stopped only when its direct
1075
+ // child / pipeline leader is stopped, so suspending only our PID
1076
+ // leaves wrappers and pipeline peers running and the terminal
1077
+ // hung — exactly the failure shape we're fixing. Stopping the whole
1078
+ // group keeps the shell's job-control view consistent. Long-lived
1079
+ // children that must survive the suspend (Linux/other POSIX MCP stdio
1080
+ // servers via the platform-specific `detached: true` spawn in
1081
+ // `mcp/transports/stdio.ts`, every brush external command via brush's
1082
+ // per-child `setsid` in `crates/vendor/brush-core/src/commands.rs`) are
1083
+ // their own sessions, so pgid=0 does not reach them.
1084
+ process.kill(0, "SIGSTOP");
1085
+ } catch (err) {
1086
+ // The runtime refused the signal (e.g. seccomp filter blocks SIGSTOP
1087
+ // delivery to the process group). Tear the resume hook down and
1088
+ // bring the TUI back so the user is not stranded on a frozen prompt.
1089
+ process.removeListener("SIGCONT", onResume);
1090
+ this.ctx.ui.start();
1091
+ this.ctx.ui.requestRender(true);
1092
+ const reason = err instanceof Error ? err.message : String(err);
1093
+ this.ctx.showError(`Failed to suspend: ${reason}`);
1094
+ }
1095
+ }
1096
+
1097
+ handleDequeue(): void {
1098
+ const restored = this.restoreQueuedMessagesToEditor();
1099
+ if (restored === 0) {
1100
+ this.ctx.showStatus("No queued messages to restore");
1101
+ } else {
1102
+ this.ctx.showStatus(`Restored ${restored} queued message${restored > 1 ? "s" : ""} to editor`);
1103
+ }
1104
+ }
1105
+
1106
+ /**
1107
+ * Dispatch a `/skill:<name> [args]` invocation through `promptCustomMessage`
1108
+ * using the supplied `streamingBehavior`. Returns false when the text is not
1109
+ * a registered skill command and leaves the editor state untouched. Registered
1110
+ * skills consume the full composer draft (text plus pending images) before
1111
+ * dispatch; if dispatch rejects, the draft is restored so the user can retry.
1112
+ */
1113
+ async #invokeSkillCommand(
1114
+ text: string,
1115
+ streamingBehavior: "steer" | "followUp",
1116
+ images?: ImageContent[],
1117
+ imageLinks?: (string | undefined)[],
1118
+ ): Promise<boolean> {
1119
+ if (!isKnownSkillCommand(this.ctx, text)) return false;
1120
+ const draftImages = images && images.length > 0 ? [...images] : undefined;
1121
+ const draftImageLinks = draftImages && imageLinks && imageLinks.length > 0 ? [...imageLinks] : undefined;
1122
+ const restoreDraft = () => {
1123
+ this.ctx.editor.setText(text);
1124
+ if (draftImages && draftImages.length > 0) {
1125
+ this.ctx.editor.pendingImages = [...draftImages];
1126
+ this.ctx.editor.pendingImageLinks = draftImageLinks
1127
+ ? [...draftImageLinks]
1128
+ : draftImages.map(() => undefined);
1129
+ this.ctx.editor.imageLinks = this.ctx.editor.pendingImageLinks;
1130
+ }
1131
+ };
1132
+
1133
+ this.ctx.editor.clearDraft(text);
1134
+ try {
1135
+ const handled = await invokeSkillCommandFromText(this.ctx, text, streamingBehavior, {
1136
+ images: draftImages,
1137
+ propagateErrors: true,
1138
+ });
1139
+ if (!handled) {
1140
+ restoreDraft();
1141
+ return false;
1142
+ }
1143
+ return true;
1144
+ } catch (error) {
1145
+ restoreDraft();
1146
+ this.ctx.showError(error instanceof Error ? error.message : String(error));
1147
+ return true;
1148
+ } finally {
1149
+ if (this.ctx.session.isStreaming) {
1150
+ this.ctx.updatePendingMessagesDisplay();
1151
+ this.ctx.ui.requestRender();
1152
+ }
1153
+ }
1154
+ }
1155
+
1156
+ async handleRetry(): Promise<void> {
1157
+ if (this.ctx.collabGuest) {
1158
+ this.ctx.showStatus("/retry is host-only during a collab session");
1159
+ return;
1160
+ }
1161
+ const didRetry = await this.ctx.viewSession.retry();
1162
+ if (didRetry) {
1163
+ this.ctx.editor.clearDraft();
1164
+ } else {
1165
+ this.ctx.showStatus("Nothing to retry");
1166
+ }
1167
+ }
1168
+
1169
+ /** Queue `/queue` input behind an active turn, or start it immediately when idle. */
1170
+ async handleQueueCommand(text: string): Promise<void> {
1171
+ const images = this.ctx.editor.pendingImages.length > 0 ? [...this.ctx.editor.pendingImages] : undefined;
1172
+ const imageLinks =
1173
+ images && this.ctx.editor.pendingImageLinks.length > 0 ? [...this.ctx.editor.pendingImageLinks] : undefined;
1174
+ await this.#queueForYield(text, { images, imageLinks });
1175
+ }
1176
+
1177
+ async #queueForYield(
1178
+ text: string,
1179
+ options: {
1180
+ historyText?: string;
1181
+ images?: ImageContent[];
1182
+ imageLinks?: (string | undefined)[];
1183
+ },
1184
+ ): Promise<void> {
1185
+ const splitMessages = splitQueuedMessages(text);
1186
+ if (splitMessages.length === 0 && !options.images?.length) {
1187
+ this.ctx.editor.clearDraft();
1188
+ this.ctx.showWarning("Usage: /queue <message> (or start a prompt with -> / =>)");
1189
+ return;
1190
+ }
1191
+
1192
+ const messages = splitMessages.length > 0 ? splitMessages : [""];
1193
+ const originalDraft = this.ctx.editor.getText();
1194
+ const images = options.images?.length ? [...options.images] : undefined;
1195
+ const imageLinks = options.imageLinks
1196
+ ? [...options.imageLinks]
1197
+ : images
1198
+ ? images.map(() => undefined)
1199
+ : undefined;
1200
+ this.ctx.editor.clearDraft(options.historyText);
1201
+
1202
+ if (this.ctx.session.isCompacting) {
1203
+ for (let index = 0; index < messages.length; index++) {
1204
+ this.ctx.compactionQueuedMessages.push({
1205
+ text: messages[index] ?? "",
1206
+ mode: "followUp",
1207
+ images: index === 0 ? images : undefined,
1208
+ });
1209
+ }
1210
+ this.ctx.updatePendingMessagesDisplay();
1211
+ this.ctx.showStatus(
1212
+ messages.length === 1
1213
+ ? "Queued message for after compaction"
1214
+ : `Queued ${messages.length} messages for after compaction`,
1215
+ );
1216
+ this.ctx.ui.requestRender();
1217
+ return;
1218
+ }
1219
+
1220
+ const startImmediately = !this.ctx.session.isStreaming && this.ctx.session.queuedMessageCount === 0;
1221
+ let queuedCount = 0;
1222
+ try {
1223
+ if (startImmediately && this.ctx.onInputCallback) {
1224
+ const first = messages[0] ?? "";
1225
+ const submission = this.ctx.startPendingSubmission({
1226
+ text: first,
1227
+ images,
1228
+ imageLinks,
1229
+ streamingBehavior: "followUp",
1230
+ });
1231
+ this.ctx.onInputCallback(submission);
1232
+ queuedCount = 1;
1233
+ }
1234
+ while (queuedCount < messages.length) {
1235
+ const message = messages[queuedCount] ?? "";
1236
+ const queuedImages = queuedCount === 0 ? images : undefined;
1237
+ await this.ctx.withLocalSubmission(
1238
+ message,
1239
+ async () => {
1240
+ if (startImmediately && queuedCount === 0) {
1241
+ await this.ctx.session.prompt(message, {
1242
+ images: queuedImages,
1243
+ streamingBehavior: "followUp",
1244
+ });
1245
+ } else {
1246
+ await this.ctx.session.followUp(message, queuedImages);
1247
+ }
1248
+ },
1249
+ { imageCount: queuedImages?.length ?? 0 },
1250
+ );
1251
+ queuedCount++;
1252
+ }
1253
+ } catch (error) {
1254
+ if (queuedCount === 0) {
1255
+ this.ctx.editor.setText(originalDraft);
1256
+ if (images) {
1257
+ this.ctx.editor.pendingImages = images;
1258
+ this.ctx.editor.pendingImageLinks = imageLinks ?? images.map(() => undefined);
1259
+ this.ctx.editor.imageLinks = this.ctx.editor.pendingImageLinks;
1260
+ }
1261
+ } else {
1262
+ const remaining = messages.slice(queuedCount);
1263
+ const restored =
1264
+ remaining.length === 1
1265
+ ? `=> ${remaining[0]}`
1266
+ : `=>\n${remaining
1267
+ .map((message, index) => `${index + 1}. ${message.replaceAll("\n", "\n ")}`)
1268
+ .join("\n")}`;
1269
+ this.ctx.editor.setText(restored);
1270
+ }
1271
+ this.ctx.showError(error instanceof Error ? error.message : String(error));
1272
+ }
1273
+
1274
+ this.ctx.updatePendingMessagesDisplay();
1275
+ if (queuedCount === messages.length) {
1276
+ this.ctx.showStatus(
1277
+ startImmediately
1278
+ ? queuedCount === 1
1279
+ ? "Sent queued message"
1280
+ : `Sent first message; queued ${queuedCount - 1} for later yields`
1281
+ : queuedCount === 1
1282
+ ? "Queued message for when the agent yields"
1283
+ : `Queued ${queuedCount} messages for when the agent yields`,
1284
+ );
1285
+ }
1286
+ this.ctx.ui.requestRender();
1287
+ }
1288
+
1289
+ /** Send editor text as a follow-up message (queued behind current stream). */
1290
+ async handleFollowUp(): Promise<void> {
1291
+ let text = this.ctx.editor.getExpandedText().trim();
1292
+ const images = this.ctx.editor.pendingImages.length > 0 ? [...this.ctx.editor.pendingImages] : undefined;
1293
+ const imageLinks =
1294
+ images && this.ctx.editor.pendingImageLinks.length > 0 ? [...this.ctx.editor.pendingImageLinks] : undefined;
1295
+ if (!text && !images) return;
1296
+
1297
+ // Focused subagent session: follow-ups go to it; non-chat input is gated.
1298
+ if (this.ctx.focusedAgentId) {
1299
+ await this.#submitToFocusedSession(text, "followUp");
1300
+ return;
1301
+ }
1302
+
1303
+ // Compaction first: while compacting, free text gets queued via
1304
+ // `queueCompactionMessage`, and `/skill:*` rides the same queue so a
1305
+ // skill typed during compaction is not lost or short-circuited through
1306
+ // `promptCustomMessage`. The compaction-resume path re-parses the
1307
+ // queued text into a user-attributed skill invocation before delivery.
1308
+ if (this.ctx.session.isCompacting) {
1309
+ const images = this.ctx.editor.pendingImages.length > 0 ? [...this.ctx.editor.pendingImages] : undefined;
1310
+ this.ctx.queueCompactionMessage(text, "followUp", images);
1311
+ return;
1312
+ }
1313
+
1314
+ if (text) {
1315
+ const slashResult = await executeBuiltinSlashCommand(text, {
1316
+ ctx: this.ctx,
1317
+ });
1318
+ if (slashResult === true) {
1319
+ if (!shouldSkipHistory(text)) this.ctx.editor.addToHistory(text);
1320
+ return;
1321
+ }
1322
+ if (typeof slashResult === "string") {
1323
+ // Command handled but returned remaining text to use as prompt.
1324
+ // Record the original slash command text so Up Arrow recalls it.
1325
+ if (!shouldSkipHistory(text)) this.ctx.editor.addToHistory(text);
1326
+ text = slashResult;
1327
+ }
1328
+ }
1329
+
1330
+ // Skill commands invoke through the custom-message path regardless of
1331
+ // which keybinding submitted them. Enter routes them as `steer`;
1332
+ // Ctrl+Enter (this handler) routes them as `followUp`.
1333
+ if (text && (await this.#invokeSkillCommand(text, "followUp", images, imageLinks))) {
1334
+ return;
1335
+ }
1336
+
1337
+ // Hand the message back on dispatch failure (model/API-key validation,
1338
+ // queue rejection): restore both text AND pending images so an image-only
1339
+ // or text+image draft can be retried, mirroring the main submit error path.
1340
+ const restoreOnError = (error: unknown) => {
1341
+ this.ctx.editor.setText(text);
1342
+ if (images && images.length > 0) {
1343
+ this.ctx.editor.pendingImages = [...images];
1344
+ this.ctx.editor.pendingImageLinks = imageLinks ? [...imageLinks] : images.map(() => undefined);
1345
+ this.ctx.editor.imageLinks = this.ctx.editor.pendingImageLinks;
1346
+ }
1347
+ this.ctx.showError(error instanceof Error ? error.message : String(error));
1348
+ };
1349
+
1350
+ if (this.ctx.session.isStreaming) {
1351
+ this.ctx.editor.clearDraft(text);
1352
+ try {
1353
+ await this.ctx.withLocalSubmission(
1354
+ text,
1355
+ () => this.ctx.session.prompt(text, { streamingBehavior: "followUp", images }),
1356
+ { imageCount: images?.length ?? 0 },
1357
+ );
1358
+ } catch (error) {
1359
+ restoreOnError(error);
1360
+ }
1361
+ this.ctx.updatePendingMessagesDisplay();
1362
+ this.ctx.ui.requestRender();
1363
+ return;
1364
+ }
1365
+
1366
+ // Not streaming — just submit normally
1367
+ this.ctx.editor.clearDraft(text);
1368
+ try {
1369
+ await this.ctx.withLocalSubmission(text, () => this.ctx.session.prompt(text, { images }), {
1370
+ imageCount: images?.length ?? 0,
1371
+ });
1372
+ } catch (error) {
1373
+ restoreOnError(error);
1374
+ }
1375
+ }
1376
+
1377
+ restoreQueuedMessagesToEditor(options?: { abort?: boolean; currentText?: string }): number {
1378
+ this.ctx.locallySubmittedUserSignatures.clear();
1379
+ // On Esc (abort) drop non-user internal steers so the post-abort drain can't
1380
+ // auto-resume; plain Alt+Up dequeue preserves them for the continuing stream.
1381
+ const { steering, followUp } = this.ctx.session.clearQueue({ forInterrupt: options?.abort });
1382
+ // Messages typed while compacting live in `compactionQueuedMessages`, not the
1383
+ // agent queue `clearQueue()` drains — but the pending bar shows the same
1384
+ // "Alt+Up to edit" hint for them (ui-helpers `updatePendingMessagesDisplay`).
1385
+ // Drain them here too so the dequeue restores every message the hint
1386
+ // advertises; otherwise a skill/text queued during compaction is stranded and
1387
+ // Alt+Up reports "No queued messages to restore".
1388
+ const compactionQueued = this.ctx.compactionQueuedMessages;
1389
+ this.ctx.compactionQueuedMessages = [];
1390
+ const allQueued = [
1391
+ ...steering,
1392
+ ...compactionQueued.filter(e => e.mode === "steer").map(e => ({ text: e.text, images: e.images })),
1393
+ ...followUp,
1394
+ ...compactionQueued.filter(e => e.mode === "followUp").map(e => ({ text: e.text, images: e.images })),
1395
+ ];
1396
+ if (allQueued.length === 0) {
1397
+ this.ctx.updatePendingMessagesDisplay();
1398
+ if (options?.abort) {
1399
+ void this.ctx.session.abort({ reason: USER_INTERRUPT_LABEL });
1400
+ }
1401
+ return 0;
1402
+ }
1403
+ // Image markers are positional: `[Image #N]` ↔ `pendingImages[N-1]`. Each
1404
+ // queued message numbered its markers against its own local image list
1405
+ // (1..K). Because we prepend the queued text but append the queued images
1406
+ // to `pendingImages`, any existing draft images (M of them) — plus images
1407
+ // already pulled in by earlier queued messages — shift the slot index that
1408
+ // every marker must point to. Bumping each message's markers by the
1409
+ // running offset keeps the merged text aligned with the merged
1410
+ // `pendingImages` order; draft markers stay valid because draft images
1411
+ // keep their original positions.
1412
+ const queuedImages = allQueued.flatMap(e => e.images ?? []);
1413
+ let queuedText: string;
1414
+ if (queuedImages.length > 0) {
1415
+ const parts: string[] = [];
1416
+ let imageOffset = this.ctx.editor.pendingImages.length;
1417
+ for (const entry of allQueued) {
1418
+ parts.push(shiftImageMarkers(entry.text, imageOffset));
1419
+ if (entry.images && entry.images.length > 0) imageOffset += entry.images.length;
1420
+ }
1421
+ queuedText = parts.join("\n\n");
1422
+ } else {
1423
+ queuedText = allQueued.map(e => e.text).join("\n\n");
1424
+ }
1425
+ const currentText = options?.currentText ?? this.ctx.editor.getText();
1426
+ const combinedText = [queuedText, currentText].filter(t => t.trim()).join("\n\n");
1427
+ this.ctx.editor.setText(combinedText);
1428
+ // Hand queued images back to the pending-image buffer (links are
1429
+ // re-materialized lazily; the restored text already carries the
1430
+ // renumbered `[Image #N, WxH]` markers).
1431
+ if (queuedImages.length > 0) {
1432
+ this.ctx.editor.pendingImages.push(...queuedImages);
1433
+ this.ctx.editor.pendingImageLinks.push(...queuedImages.map(() => undefined));
1434
+ this.ctx.editor.imageLinks = this.ctx.editor.pendingImageLinks;
1435
+ }
1436
+ this.ctx.updatePendingMessagesDisplay();
1437
+ if (options?.abort) {
1438
+ void this.ctx.session.abort({ reason: USER_INTERRUPT_LABEL });
1439
+ }
1440
+ return allQueued.length;
1441
+ }
1442
+
1443
+ async #insertPendingImage(imageData: ImageContent): Promise<void> {
1444
+ const imageLink = (
1445
+ await materializeImageReferenceLinks(
1446
+ [
1447
+ {
1448
+ type: "image",
1449
+ data: imageData.data,
1450
+ mimeType: imageData.mimeType,
1451
+ },
1452
+ ],
1453
+ this.ctx.sessionManager.putBlob.bind(this.ctx.sessionManager),
1454
+ )
1455
+ )?.[0];
1456
+ this.ctx.editor.pendingImages.push({
1457
+ type: "image",
1458
+ data: imageData.data,
1459
+ mimeType: imageData.mimeType,
1460
+ });
1461
+ this.ctx.editor.pendingImageLinks.push(imageLink);
1462
+ this.ctx.editor.imageLinks = this.ctx.editor.pendingImageLinks;
1463
+ const imageNum = this.ctx.editor.pendingImages.length;
1464
+ const dims = await this.#imageDimensions(imageData);
1465
+ const label = dims ? `[Image #${imageNum}, ${dims.width}x${dims.height}]` : `[Image #${imageNum}]`;
1466
+ this.ctx.editor.insertText(`${label} `);
1467
+ this.ctx.ui.requestRender();
1468
+ }
1469
+
1470
+ /** Probe pixel dimensions for the marker label (`[Image #N, WxH]`). Returns undefined when the
1471
+ * header can't be decoded, so the caller falls back to a bare `[Image #N]`. */
1472
+ async #imageDimensions(image: ImageContent): Promise<{ width: number; height: number } | undefined> {
1473
+ try {
1474
+ const { width, height } = await new Bun.Image(Buffer.from(image.data, "base64")).metadata();
1475
+ if (width && height) return { width, height };
1476
+ } catch {
1477
+ // Unknown/corrupt header — fall back to a bare label.
1478
+ }
1479
+ return undefined;
1480
+ }
1481
+
1482
+ async #normalizeAndInsertPastedImage(image: ImageContent, unsupportedMessage: string): Promise<boolean> {
1483
+ let imageData = await ensureSupportedImageInput(image);
1484
+ if (!imageData) {
1485
+ this.ctx.showStatus(unsupportedMessage);
1486
+ return false;
1487
+ }
1488
+ if (settings.get("images.autoResize")) {
1489
+ try {
1490
+ const resized = await resizeImage({
1491
+ type: "image",
1492
+ data: imageData.data,
1493
+ mimeType: imageData.mimeType,
1494
+ });
1495
+ imageData = { type: "image", data: resized.data, mimeType: resized.mimeType };
1496
+ } catch {
1497
+ // Keep the normalized image when resize fails.
1498
+ }
1499
+ }
1500
+ await this.#insertPendingImage(imageData);
1501
+ return true;
1502
+ }
1503
+
1504
+ /**
1505
+ * Win+Shift+S on Windows 11 leaves the screenshot bitmap on the clipboard
1506
+ * while the terminal pastes a transient packaged-app TempState path
1507
+ * (…\MicrosoftWindows.Client.Core_*\TempState\…) that is already gone — or
1508
+ * never materialized — by the time we read it. Whenever a pasted image path
1509
+ * can't be turned into an image locally, those clipboard bytes are the real
1510
+ * payload, so prefer them before degrading to a text paste.
1511
+ *
1512
+ * Skipped over SSH: the clipboard read would hit the remote host, not the
1513
+ * terminal that holds the screenshot. Returns true when the clipboard owned
1514
+ * the outcome (image attached, or an unsupported-format status surfaced), so
1515
+ * the caller stops without emitting its own degraded diagnostic.
1516
+ */
1517
+ async #tryPasteClipboardImage(): Promise<boolean> {
1518
+ const env = process.env;
1519
+ if (env.SSH_CONNECTION || env.SSH_TTY || env.SSH_CLIENT) return false;
1520
+ try {
1521
+ const image = await this.clipboard.readImage();
1522
+ if (!image) return false;
1523
+ await this.#normalizeAndInsertPastedImage(
1524
+ { type: "image", data: image.data.toBase64(), mimeType: image.mimeType },
1525
+ `Unsupported clipboard image format: ${image.mimeType}`,
1526
+ );
1527
+ return true;
1528
+ } catch {
1529
+ return false;
1530
+ }
1531
+ }
1532
+
1533
+ async handleImagePathPaste(path: string): Promise<void> {
1534
+ try {
1535
+ const image = await loadImageInput({
1536
+ path,
1537
+ cwd: this.ctx.sessionManager.getCwd(),
1538
+ autoResize: false,
1539
+ });
1540
+ if (!image) {
1541
+ // Path resolved but is not a readable image (e.g. a zero-byte or
1542
+ // locked transient screenshot file). Prefer the clipboard bytes.
1543
+ if (await this.#tryPasteClipboardImage()) return;
1544
+ this.ctx.editor.pasteText(path);
1545
+ this.ctx.ui.requestRender();
1546
+ this.ctx.showStatus("Pasted path is not a supported image");
1547
+ return;
1548
+ }
1549
+ await this.#normalizeAndInsertPastedImage(
1550
+ { type: "image", data: image.data, mimeType: image.mimeType },
1551
+ `Unsupported pasted image format: ${image.mimeType}`,
1552
+ );
1553
+ } catch (error) {
1554
+ if (error instanceof ImageInputTooLargeError) {
1555
+ this.ctx.editor.pasteText(path);
1556
+ this.ctx.ui.requestRender();
1557
+ this.ctx.showStatus(error.message);
1558
+ return;
1559
+ }
1560
+ if (isEnoent(error)) {
1561
+ // #2375: the bracketed paste forwarded by a local terminal carries a
1562
+ // path on the *local* filesystem. The bytes may still be on the
1563
+ // clipboard (Win+Shift+S), so try those before giving up.
1564
+ if (await this.#tryPasteClipboardImage()) return;
1565
+ // Over SSH the clipboard lives on the remote host, so the path is
1566
+ // genuinely unreachable; pasting it as text would look like the
1567
+ // image was attached when nothing was sent. Surface an SSH-aware
1568
+ // diagnostic instead. The pasted path is untrusted terminal input —
1569
+ // strip control/ANSI/newlines, collapse home to `~`, and bound the
1570
+ // displayed length before splicing it into the status string.
1571
+ const env = process.env;
1572
+ const overSsh = Boolean(env.SSH_CONNECTION || env.SSH_TTY || env.SSH_CLIENT);
1573
+ const displayPath = truncateToWidth(
1574
+ shortenPath(
1575
+ sanitizeText(path)
1576
+ .replace(/[\r\n\t]+/g, " ")
1577
+ .trim(),
1578
+ ),
1579
+ TRUNCATE_LENGTHS.CONTENT,
1580
+ );
1581
+ this.ctx.showStatus(
1582
+ overSsh
1583
+ ? `Image not found at ${displayPath}. Over SSH this path is local to your terminal — paste the image directly (clipboard image-paste shortcut) to send its bytes.`
1584
+ : `Image not found at ${displayPath}`,
1585
+ );
1586
+ return;
1587
+ }
1588
+ if (await this.#tryPasteClipboardImage()) return;
1589
+ this.ctx.editor.pasteText(path);
1590
+ this.ctx.ui.requestRender();
1591
+ this.ctx.showStatus("Failed to read pasted image path");
1592
+ }
1593
+ }
1594
+
1595
+ async handleImagePaste(): Promise<boolean> {
1596
+ try {
1597
+ // When a modal paste-capable prompt (login/API-key Input) owns focus,
1598
+ // only clipboard text may land there. Image payloads must not mutate
1599
+ // the hidden main editor — mirror the enhanced-paste `pasteImage`
1600
+ // behavior and surface the unsupported-status instead (#6057).
1601
+ const focusedNow = this.ctx.ui.getFocused();
1602
+ const promptTarget =
1603
+ focusedNow && focusedNow !== this.ctx.editor && hasPasteText(focusedNow) ? focusedNow : null;
1604
+ const image = await this.clipboard.readImage();
1605
+ if (image) {
1606
+ if (promptTarget) {
1607
+ this.ctx.showStatus("Image paste is not supported in this prompt");
1608
+ return false;
1609
+ }
1610
+ return await this.#normalizeAndInsertPastedImage(
1611
+ {
1612
+ type: "image",
1613
+ data: image.data.toBase64(),
1614
+ mimeType: image.mimeType,
1615
+ },
1616
+ `Unsupported clipboard image format: ${image.mimeType}`,
1617
+ );
1618
+ }
1619
+ // #3506: macOS Finder `Cmd+C` puts only a `public.file-url`
1620
+ // representation on the pasteboard. `pbpaste` (the backing call
1621
+ // for `readText` on Darwin) only surfaces plain text / RTF / EPS,
1622
+ // so it returns empty for file-url-only pasteboards — the smart
1623
+ // text fallback below would dead-end with "Clipboard is empty".
1624
+ // Reach the file URL directly via AppleScript and route every
1625
+ // image-shaped path through {@link handleImagePathPaste}, matching
1626
+ // the bracketed-paste handler in `CustomEditor.handleInput` which
1627
+ // iterates every extracted image path. Multi-image Finder
1628
+ // selections must not silently drop after the first attach.
1629
+ // `readMacFileUrls` returns an empty list off Darwin, so the
1630
+ // check is free on every other platform.
1631
+ const fileUrls = promptTarget ? [] : ((await this.clipboard.readMacFileUrls?.()) ?? []);
1632
+ let attachedFromFileUrls = false;
1633
+ for (const url of fileUrls) {
1634
+ const candidate = extractImagePathFromText(url);
1635
+ if (!candidate) continue;
1636
+ await this.handleImagePathPaste(candidate);
1637
+ attachedFromFileUrls = true;
1638
+ }
1639
+ if (attachedFromFileUrls) return true;
1640
+ // Smart paste (#1628): no image on the clipboard — fall back to
1641
+ // pasting its text so the same chord covers both payload kinds.
1642
+ // Hosts that pre-empt the terminal's own paste (VS Code's
1643
+ // integrated terminal, Win+V clipboard history) deliver only
1644
+ // this keypress, so a miss here must not dead-end.
1645
+ const text = await this.clipboard.readText();
1646
+ if (!text) {
1647
+ this.ctx.showStatus("Clipboard is empty");
1648
+ return false;
1649
+ }
1650
+ // #3506: when the clipboard text is an explicit image file path,
1651
+ // route through {@link handleImagePathPaste} so the image is
1652
+ // loaded and attached instead of pasting the path as literal
1653
+ // text. Covers terminals that paste the Finder file path as
1654
+ // plain text rather than as a `public.file-url` (most macOS
1655
+ // terminals do this for image clipboards).
1656
+ const imagePath = promptTarget ? null : extractImagePathFromText(text);
1657
+ if (imagePath) {
1658
+ await this.handleImagePathPaste(imagePath);
1659
+ return true;
1660
+ }
1661
+ // Route to the focused component when it accepts pastes (modal
1662
+ // Input prompts), matching the enhanced-paste text path (#2127).
1663
+ const target = promptTarget ?? this.ctx.editor;
1664
+ target.pasteText(text);
1665
+ this.ctx.ui.requestRender();
1666
+ return true;
1667
+ } catch {
1668
+ this.ctx.showStatus("Failed to read clipboard");
1669
+ return false;
1670
+ }
1671
+ }
1672
+
1673
+ async handleClipboardTextRawPaste(): Promise<void> {
1674
+ try {
1675
+ const text = await this.clipboard.readText();
1676
+ if (text) {
1677
+ this.ctx.editor.insertText(text);
1678
+ this.ctx.ui.requestRender();
1679
+ } else {
1680
+ this.ctx.showStatus("No text in clipboard to paste raw");
1681
+ }
1682
+ } catch {
1683
+ this.ctx.showStatus("Failed to paste raw text from clipboard");
1684
+ }
1685
+ }
1686
+
1687
+ /**
1688
+ * Editor `onLargePaste` hook: gate a marker-sized paste behind the large-paste menu. Returns
1689
+ * `true` to intercept (the editor skips its default `[Paste]` marker) once the paste reaches the
1690
+ * configured `paste.largeMenuThreshold` line count; otherwise `false` for default collapse-to-marker
1691
+ * behavior. The async menu is fired and forgotten — the editor only needs the synchronous verdict.
1692
+ */
1693
+ handleLargePaste(text: string, lineCount: number): boolean {
1694
+ const threshold = this.ctx.settings.get("paste.largeMenuThreshold");
1695
+ if (!(threshold > 0) || lineCount < threshold) return false;
1696
+ void this.presentLargePasteMenu(text, lineCount);
1697
+ return true;
1698
+ }
1699
+
1700
+ /**
1701
+ * Present the large-paste menu and apply the chosen action: wrap in `<attachment>` tags (collapsed
1702
+ * to a `[Paste]` marker that expands on submit), save the text to a file and reference its path so
1703
+ * the agent can `read` it on demand, or paste inline. Cancelling (Esc) falls back to the default
1704
+ * inline paste marker, so the pasted content is never lost.
1705
+ */
1706
+ async presentLargePasteMenu(text: string, lineCount: number): Promise<void> {
1707
+ const WRAPPED_BLOCK = "Attach as a wrapped block";
1708
+ const LOCAL_FILE = "Attach as local file";
1709
+ const INLINE = "Paste inline";
1710
+
1711
+ let choice: string | undefined;
1712
+ try {
1713
+ choice = await this.ctx.showHookSelector(
1714
+ `Pasted ${lineCount} lines`,
1715
+ [
1716
+ { label: WRAPPED_BLOCK, description: "Wrap the text in <attachment> tags, collapsed to a marker" },
1717
+ { label: LOCAL_FILE, description: "Save the text to a local://paste file" },
1718
+ { label: INLINE, description: "Collapse the text to an inline paste marker" },
1719
+ ],
1720
+ { helpText: "Esc to paste inline" },
1721
+ );
1722
+ } catch (error) {
1723
+ logger.warn("large-paste menu failed", { error: error instanceof Error ? error.message : String(error) });
1724
+ choice = undefined;
1725
+ }
1726
+
1727
+ switch (choice) {
1728
+ case WRAPPED_BLOCK:
1729
+ this.ctx.editor.insertPaste(wrapPasteInAttachmentBlock(text));
1730
+ break;
1731
+ case LOCAL_FILE:
1732
+ await this.#attachPasteAsFile(text, lineCount);
1733
+ break;
1734
+ case INLINE:
1735
+ this.ctx.editor.insertPaste(text);
1736
+ break;
1737
+ default:
1738
+ // Esc / cancel: keep the original behavior — collapse to an inline paste marker.
1739
+ this.ctx.editor.insertPaste(text);
1740
+ break;
1741
+ }
1742
+ this.ctx.ui.requestRender();
1743
+ }
1744
+
1745
+ /**
1746
+ * Save a large paste to the session's `local://` store and insert a clean `local://paste-N.md`
1747
+ * reference into the editor so the agent can `read` it on demand — instead of inlining the text or
1748
+ * leaking a raw temp path. Falls back to an inline paste marker when the write fails, so the
1749
+ * content is never lost.
1750
+ */
1751
+ async #attachPasteAsFile(text: string, lineCount: number): Promise<void> {
1752
+ try {
1753
+ // Mirror the exact mapping the read tool's local:// resolver uses so a later
1754
+ // `read local://paste-N.md` lands on the file written here.
1755
+ const localRoot = resolveLocalRoot({
1756
+ getArtifactsDir: () => this.ctx.sessionManager.getArtifactsDir(),
1757
+ getSessionId: () => this.ctx.sessionManager.getSessionId(),
1758
+ });
1759
+ let name: string;
1760
+ let filePath: string;
1761
+ do {
1762
+ this.#pasteCounter++;
1763
+ name = `paste-${this.#pasteCounter}.md`;
1764
+ filePath = path.join(localRoot, name);
1765
+ } while (await Bun.file(filePath).exists());
1766
+ await Bun.write(filePath, text);
1767
+ this.ctx.editor.insertText(`local://${name} `);
1768
+ this.ctx.showStatus(`Saved ${lineCount} pasted lines to local://${name}`);
1769
+ } catch (error) {
1770
+ logger.warn("failed to save large paste to file", {
1771
+ error: error instanceof Error ? error.message : String(error),
1772
+ });
1773
+ this.ctx.editor.insertPaste(text);
1774
+ this.ctx.showError("Failed to save paste to a file — pasted inline instead");
1775
+ }
1776
+ }
1777
+
1778
+ createAutocompleteProvider(commands: SlashCommand[], basePath: string): AutocompleteProvider {
1779
+ return createPromptActionAutocompleteProvider({
1780
+ commands,
1781
+ basePath,
1782
+ keybindings: this.ctx.keybindings,
1783
+ copyCurrentLine: () => this.handleCopyCurrentLine(),
1784
+ copyPrompt: () => this.handleCopyPrompt(),
1785
+ undo: prefix => this.ctx.editor.undoPastTransientText(prefix),
1786
+ moveCursorToMessageEnd: () => this.ctx.editor.moveToMessageEnd(),
1787
+ moveCursorToMessageStart: () => this.ctx.editor.moveToMessageStart(),
1788
+ moveCursorToLineStart: () => this.ctx.editor.moveToLineStart(),
1789
+ moveCursorToLineEnd: () => this.ctx.editor.moveToLineEnd(),
1790
+ });
1791
+ }
1792
+
1793
+ /** Copy the current editor line to the system clipboard. */
1794
+ handleCopyCurrentLine(): void {
1795
+ const { line } = this.ctx.editor.getCursor();
1796
+ const text = this.ctx.editor.getLines()[line] || "";
1797
+ if (!text) {
1798
+ this.ctx.showStatus("Nothing to copy");
1799
+ return;
1800
+ }
1801
+ try {
1802
+ copyToClipboard(text);
1803
+ const sanitized = sanitizeText(text);
1804
+ const preview = sanitized.length > 30 ? `${sanitized.slice(0, 30)}...` : sanitized;
1805
+ this.ctx.showStatus(`Copied line: ${preview}`);
1806
+ } catch {
1807
+ this.ctx.showWarning("Failed to copy to clipboard");
1808
+ }
1809
+ }
1810
+
1811
+ /** Copy current prompt text to system clipboard. */
1812
+ handleCopyPrompt(): void {
1813
+ const text = this.ctx.editor.getText();
1814
+ if (!text) {
1815
+ this.ctx.showStatus("Nothing to copy");
1816
+ return;
1817
+ }
1818
+ try {
1819
+ copyToClipboard(text);
1820
+ const sanitized = sanitizeText(text);
1821
+ const preview = sanitized.length > 30 ? `${sanitized.slice(0, 30)}...` : sanitized;
1822
+ this.ctx.showStatus(`Copied: ${preview}`);
1823
+ } catch {
1824
+ this.ctx.showWarning("Failed to copy to clipboard");
1825
+ }
1826
+ }
1827
+
1828
+ cycleThinkingLevel(): void {
1829
+ if (this.ctx.focusedAgentId) {
1830
+ this.ctx.showStatus("Model/thinking apply to the main session — press ←← to return first");
1831
+ return;
1832
+ }
1833
+ const newLevel = this.ctx.session.cycleThinkingLevel();
1834
+ if (newLevel === undefined) {
1835
+ this.ctx.showStatus("Current model does not support thinking");
1836
+ } else {
1837
+ this.ctx.statusLine.invalidate();
1838
+ this.ctx.updateEditorBorderColor();
1839
+ }
1840
+ }
1841
+
1842
+ async cycleRoleModel(direction: "forward" | "backward" = "forward"): Promise<void> {
1843
+ if (this.ctx.focusedAgentId) {
1844
+ this.ctx.showStatus("Model/thinking apply to the main session — press ←← to return first");
1845
+ return;
1846
+ }
1847
+ try {
1848
+ const cycleOrder = settings.get("cycleOrder");
1849
+ const result = await this.ctx.session.cycleRoleModels(cycleOrder, direction);
1850
+ if (!result) {
1851
+ this.ctx.showStatus("Only one role model available");
1852
+ return;
1853
+ }
1854
+
1855
+ this.ctx.statusLine.invalidate();
1856
+ this.ctx.updateEditorBorderColor();
1857
+ // The status line already reports the resolved model + thinking level, so
1858
+ // the cycle status is just a status-line-style chip track (active role
1859
+ // filled), matching the plan-approval model slider. It renders into its
1860
+ // own anchored container above the editor (cleared+rebuilt each cycle),
1861
+ // so it updates in place instead of stacking duplicates in the scrollback.
1862
+ const track = renderSegmentTrack(
1863
+ cycleOrder.map(role => ({ label: role })),
1864
+ cycleOrder.indexOf(result.role),
1865
+ );
1866
+ this.ctx.showModelCycleTrack(track);
1867
+ } catch (error) {
1868
+ this.ctx.showError(error instanceof Error ? error.message : String(error));
1869
+ }
1870
+ }
1871
+
1872
+ toggleToolOutputExpansion(): void {
1873
+ if (this.ctx.hideToolActivity) {
1874
+ const visibilityKey = this.ctx.keybindings.getDisplayString("app.tools.toggleVisibility");
1875
+ const visibilityHint = visibilityKey ? `${visibilityKey} or /settings` : "/settings";
1876
+ this.ctx.showStatus(`Tool activity is hidden — show it with ${visibilityHint} before expanding`);
1877
+ return;
1878
+ }
1879
+ this.setToolsExpanded(!this.ctx.toolOutputExpanded);
1880
+ }
1881
+
1882
+ toggleToolActivityVisibility(): void {
1883
+ this.ctx.hideToolActivity = !this.ctx.hideToolActivity;
1884
+ this.ctx.settings.set("display.hideToolActivity", this.ctx.hideToolActivity);
1885
+
1886
+ if (!this.ctx.hideToolActivity) {
1887
+ this.ctx.toolOutputExpanded = false;
1888
+ }
1889
+
1890
+ for (const child of this.ctx.chatContainer.children) {
1891
+ if (child instanceof ToolExecutionComponent || child instanceof ReadToolGroupComponent) {
1892
+ if (!this.ctx.hideToolActivity) child.setExpanded(false);
1893
+ child.setToolActivityVisible(!this.ctx.hideToolActivity);
1894
+ } else if (child instanceof AssistantMessageComponent) {
1895
+ child.setToolResultImagesVisible(!this.ctx.hideToolActivity);
1896
+ } else if (child instanceof StrippedToolCallsPlaceholder) {
1897
+ child.setToolActivityVisible(!this.ctx.hideToolActivity);
1898
+ }
1899
+ }
1900
+
1901
+ if (this.ctx.hideToolActivity) this.ctx.ui.clearInlineImages();
1902
+ this.ctx.ui.resetDisplay();
1903
+ this.ctx.showStatus(`Tool activity: ${this.ctx.hideToolActivity ? "hidden" : "visible"}`);
1904
+ }
1905
+
1906
+ setToolsExpanded(expanded: boolean): void {
1907
+ this.ctx.toolOutputExpanded = expanded;
1908
+ for (const child of this.ctx.chatContainer.children) {
1909
+ if (isExpandable(child)) {
1910
+ child.setExpanded(expanded);
1911
+ }
1912
+ }
1913
+ // Toggling expansion mutates every block, but on ED3-risk terminals the
1914
+ // transcript freezes a snapshot of each block once it scrolls past the live
1915
+ // region (committed native scrollback is immutable there). A plain repaint
1916
+ // replays those stale snapshots, so the toggle appears to do nothing above
1917
+ // the live block. resetDisplay() invalidates the snapshots and forces a
1918
+ // full clear + replay — the keyboard-accessible resize-reset equivalent —
1919
+ // which is the only path that re-emits the whole transcript at its new
1920
+ // heights.
1921
+ this.ctx.ui.resetDisplay();
1922
+ }
1923
+
1924
+ toggleThinkingBlockVisibility(): void {
1925
+ // When thinking is "off" and the session has not produced reasoning
1926
+ // content, thinking blocks stay auto-hidden; the toggle would only corrupt
1927
+ // the persisted preference. OpenAI-compatible servers can stream reasoning
1928
+ // without advertising model support, so observed thinking content unlocks
1929
+ // the display toggle.
1930
+ const thinkingOff =
1931
+ ((this.ctx.viewSession ?? this.ctx.session)?.thinkingLevel ?? ThinkingLevel.Off) === ThinkingLevel.Off;
1932
+ if (thinkingOff && !this.ctx.hasDisplayableThinkingContent) {
1933
+ this.ctx.showStatus("Thinking is off — enable thinking to show blocks");
1934
+ return;
1935
+ }
1936
+ this.ctx.hideThinkingBlock = !this.ctx.hideThinkingBlock;
1937
+ this.ctx.settings.set("hideThinkingBlock", this.ctx.hideThinkingBlock);
1938
+
1939
+ for (const child of this.ctx.chatContainer.children) {
1940
+ if (child instanceof AssistantMessageComponent) {
1941
+ child.setHideThinkingBlock(this.ctx.hideThinkingBlock);
1942
+ }
1943
+ }
1944
+
1945
+ if (this.ctx.streamingComponent && this.ctx.streamingMessage) {
1946
+ this.ctx.streamingComponent.setHideThinkingBlock(this.ctx.hideThinkingBlock);
1947
+ this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage);
1948
+ }
1949
+
1950
+ // Every block now carries the new flag, but on ED3-risk terminals the
1951
+ // blocks that scrolled past the live region are frozen snapshots in
1952
+ // committed scrollback — a plain repaint replays them stale, so scrolling
1953
+ // up still shows the old thinking expanded. resetDisplay() retires those
1954
+ // snapshots (it invalidates every block) and forces a full clear + replay
1955
+ // of the whole transcript, matching setToolsExpanded()'s redraw.
1956
+ this.ctx.ui.resetDisplay();
1957
+
1958
+ this.ctx.showStatus(`Thinking blocks: ${this.ctx.hideThinkingBlock ? "hidden" : "visible"}`);
1959
+ }
1960
+
1961
+ #getEditorTerminalPath(): string | null {
1962
+ if (process.platform === "win32") {
1963
+ return null;
1964
+ }
1965
+ return "/dev/tty";
1966
+ }
1967
+
1968
+ async #openEditorTerminalHandle(): Promise<fs.FileHandle | null> {
1969
+ const terminalPath = this.#getEditorTerminalPath();
1970
+ if (!terminalPath) {
1971
+ return null;
1972
+ }
1973
+ try {
1974
+ return await fs.open(terminalPath, "r+");
1975
+ } catch {
1976
+ return null;
1977
+ }
1978
+ }
1979
+
1980
+ async openExternalEditor(): Promise<void> {
1981
+ const editorCmd = getEditorCommand();
1982
+ if (!editorCmd) {
1983
+ this.ctx.showWarning("No editor configured. Set $VISUAL or $EDITOR environment variable.");
1984
+ return;
1985
+ }
1986
+
1987
+ const currentText = this.ctx.editor.getExpandedText?.() ?? this.ctx.editor.getText();
1988
+
1989
+ let ttyHandle: fs.FileHandle | null = null;
1990
+ try {
1991
+ ttyHandle = await this.#openEditorTerminalHandle();
1992
+ this.ctx.ui.stop();
1993
+
1994
+ const stdio: [number | "inherit", number | "inherit", number | "inherit"] = ttyHandle
1995
+ ? [ttyHandle.fd, ttyHandle.fd, ttyHandle.fd]
1996
+ : ["inherit", "inherit", "inherit"];
1997
+
1998
+ const result = await openInEditor(editorCmd, currentText, { extension: ".omp.md", stdio });
1999
+ if (result !== null) {
2000
+ this.ctx.editor.setText(result);
2001
+ }
2002
+ } catch (error) {
2003
+ this.ctx.showWarning(
2004
+ `Failed to open external editor: ${error instanceof Error ? error.message : String(error)}`,
2005
+ );
2006
+ } finally {
2007
+ if (ttyHandle) {
2008
+ await ttyHandle.close();
2009
+ }
2010
+
2011
+ this.ctx.ui.start();
2012
+ this.ctx.ui.requestRender();
2013
+ }
2014
+ }
2015
+
2016
+ registerExtensionShortcuts(): void {
2017
+ const runner = this.ctx.session.extensionRunner;
2018
+ if (!runner) return;
2019
+
2020
+ const shortcuts = runner.getShortcuts();
2021
+ for (const [keyId, shortcut] of shortcuts) {
2022
+ this.ctx.editor.setCustomKeyHandler(keyId, () => {
2023
+ const ctx = runner.createCommandContext();
2024
+ try {
2025
+ shortcut.handler(ctx);
2026
+ } catch (err) {
2027
+ runner.emitError({
2028
+ extensionPath: shortcut.extensionPath,
2029
+ event: "shortcut",
2030
+ error: err instanceof Error ? err.message : String(err),
2031
+ stack: err instanceof Error ? err.stack : undefined,
2032
+ });
2033
+ }
2034
+ });
2035
+ }
2036
+ }
2037
+ }