@iowarp/clio-coder 0.3.0 → 0.3.2
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.
- package/CHANGELOG.md +123 -2
- package/CONTRIBUTING.md +23 -23
- package/README.md +295 -627
- package/dist/acp-BIYHVZIM.js +128 -0
- package/dist/{agents-Q4MYPMUW.js → agents-YT6SSRIT.js} +52 -27
- package/dist/assets/codewiki.json +1 -0
- package/dist/assets/grammars/notices/@vscode__tree-sitter-wasm/LICENSE +21 -0
- package/dist/assets/grammars/notices/@vscode__tree-sitter-wasm/cgmanifest.json +16 -0
- package/dist/assets/grammars/notices/tree-sitter-wasms/LICENSE +24 -0
- package/dist/assets/grammars/tree-sitter-c-sharp.wasm +0 -0
- package/dist/assets/grammars/tree-sitter-c.wasm +0 -0
- package/dist/assets/grammars/tree-sitter-cpp.wasm +0 -0
- package/dist/assets/grammars/tree-sitter-go.wasm +0 -0
- package/dist/assets/grammars/tree-sitter-java.wasm +0 -0
- package/dist/assets/grammars/tree-sitter-javascript.wasm +0 -0
- package/dist/assets/grammars/tree-sitter-python.wasm +0 -0
- package/dist/assets/grammars/tree-sitter-ruby.wasm +0 -0
- package/dist/assets/grammars/tree-sitter-rust.wasm +0 -0
- package/dist/assets/grammars/tree-sitter-tsx.wasm +0 -0
- package/dist/assets/grammars/tree-sitter-typescript.wasm +0 -0
- package/dist/assets/grammars/tree-sitter.wasm +0 -0
- package/dist/{auth-O6HYIJ6J.js → auth-5TWEIYDN.js} +30 -15
- package/dist/chunk-2EHAIA3X.js +481 -0
- package/dist/{chunk-ME6DNWIU.js → chunk-2IR2NMPA.js} +12 -4
- package/dist/chunk-2SFS6XQE.js +122 -0
- package/dist/{chunk-KKKPTZLM.js → chunk-2STGEDFV.js} +8 -1
- package/dist/chunk-2VTFPG5O.js +48 -0
- package/dist/chunk-3I7MS7N2.js +40 -0
- package/dist/chunk-3R73A4XB.js +69 -0
- package/dist/{chunk-JPOWPFCU.js → chunk-3ZXDFGR5.js} +52 -23
- package/dist/chunk-4BJ5BYCE.js +61 -0
- package/dist/{chunk-26BZQOAD.js → chunk-4BPJXDWC.js} +161 -260
- package/dist/chunk-4KLWL3UC.js +18 -0
- package/dist/{chunk-5LDRNKX2.js → chunk-4VP4KH3K.js} +161 -262
- package/dist/chunk-4ZG3XFUR.js +77 -0
- package/dist/chunk-5B2AEOW5.js +5407 -0
- package/dist/chunk-5TSRNF4G.js +510 -0
- package/dist/{chunk-WD6AJM35.js → chunk-5UUP6MWO.js} +48 -69
- package/dist/{chunk-X3BR7HWV.js → chunk-5WIGXA4T.js} +7 -1
- package/dist/chunk-65DEGPJ6.js +52 -0
- package/dist/chunk-6EJMN2Y3.js +17 -0
- package/dist/chunk-6EJV5X2W.js +16405 -0
- package/dist/chunk-6N5PTWMY.js +136 -0
- package/dist/chunk-6XLNIQDB.js +27 -0
- package/dist/{chunk-XNISANGE.js → chunk-77VKQEHF.js} +171 -74
- package/dist/chunk-7CR24IG7.js +242 -0
- package/dist/{chunk-UWIJNAOB.js → chunk-7EYHLWU7.js} +907 -638
- package/dist/chunk-7MNJORFF.js +22 -0
- package/dist/{chunk-JWHCJDCI.js → chunk-A3CYT5EX.js} +257 -79
- package/dist/chunk-AGYYIBLL.js +1069 -0
- package/dist/chunk-AO4RKG4M.js +277 -0
- package/dist/chunk-APJ265NV.js +7410 -0
- package/dist/chunk-ARBGF5F7.js +174 -0
- package/dist/{chunk-TFSSEXL6.js → chunk-BMEMKKIT.js} +14 -4
- package/dist/{chunk-X3NE4WVW.js → chunk-BNAZZHFG.js} +7 -1
- package/dist/chunk-CBCAPZAA.js +229 -0
- package/dist/{chunk-QV47YRF4.js → chunk-CGKSTWHD.js} +7 -1
- package/dist/chunk-CMZWFGD2.js +352 -0
- package/dist/chunk-ECH6PKUQ.js +39 -0
- package/dist/{chunk-67MTHP2E.js → chunk-ED4KHGC3.js} +13 -5
- package/dist/chunk-EKMEHE4H.js +340 -0
- package/dist/{chunk-O4EJEDHO.js → chunk-EPVUXGXG.js} +50 -35
- package/dist/chunk-FCSXB6T2.js +338 -0
- package/dist/chunk-FJ3H4MN5.js +48 -0
- package/dist/chunk-FQ4SKYE4.js +29 -0
- package/dist/chunk-G2DE3C7R.js +644 -0
- package/dist/chunk-G4BMMOKF.js +182 -0
- package/dist/{chunk-7BHLZB3A.js → chunk-GGXXDWE4.js} +190 -1218
- package/dist/chunk-HC4CLZ2Y.js +68 -0
- package/dist/{chunk-E3NYWENM.js → chunk-HFSBBKSQ.js} +55 -61
- package/dist/chunk-HKIYEGME.js +30 -0
- package/dist/chunk-I4HZDVNP.js +73 -0
- package/dist/chunk-IGLFWIYI.js +161 -0
- package/dist/chunk-IHXBNWMM.js +137 -0
- package/dist/chunk-IKCO5N3L.js +162 -0
- package/dist/chunk-IR4CFBFN.js +56 -0
- package/dist/{chunk-DXQNI4PC.js → chunk-IWHMRKLL.js} +8 -1
- package/dist/{chunk-VOO7NYPP.js → chunk-J5HN4RYU.js} +27 -9
- package/dist/{chunk-4UQA7NCT.js → chunk-J5Q24KAG.js} +7 -2
- package/dist/{chunk-KBR4MZZR.js → chunk-JVCV3ICN.js} +7 -1
- package/dist/chunk-KJ5LWLOE.js +1077 -0
- package/dist/chunk-LBMZMYH2.js +285 -0
- package/dist/{chunk-3DANZDGR.js → chunk-LBNRH5WM.js} +100 -161
- package/dist/{chunk-TKHQ4DGZ.js → chunk-LZSJBIVT.js} +7358 -7414
- package/dist/{chunk-UGYAX5YI.js → chunk-M6SHUN7Q.js} +13 -8
- package/dist/chunk-MAW544W2.js +1882 -0
- package/dist/chunk-MBS4V7ZP.js +217 -0
- package/dist/{chunk-2J63S4SF.js → chunk-MFFY33HR.js} +167 -140
- package/dist/chunk-MNA4JGU4.js +255 -0
- package/dist/chunk-MQSRRFWA.js +3428 -0
- package/dist/{chunk-RQDWMVRB.js → chunk-MVVUPGPW.js} +138 -137
- package/dist/chunk-OAO4GE4M.js +619 -0
- package/dist/chunk-OHHN2SO4.js +5135 -0
- package/dist/chunk-OKGUZO2U.js +34 -0
- package/dist/{chunk-BBUH4VAA.js → chunk-OOJYHWRB.js} +60 -361
- package/dist/{chunk-D5YD55AR.js → chunk-OQ33BKR3.js} +12 -1
- package/dist/chunk-OQE5J4C6.js +73 -0
- package/dist/{chunk-AMFR5YA3.js → chunk-ORBHGJC5.js} +26 -12
- package/dist/chunk-POHLU5DW.js +1186 -0
- package/dist/chunk-QKMUKYO7.js +4961 -0
- package/dist/{chunk-7RBKDI66.js → chunk-QTYWRVRA.js} +104 -12
- package/dist/{chunk-5N2FG33Q.js → chunk-R346GLFC.js} +7 -1
- package/dist/chunk-SRF2PJNW.js +184 -0
- package/dist/chunk-SST6Z5JA.js +80 -0
- package/dist/{chunk-XV4ZJ6ZM.js → chunk-STBPMHSX.js} +473 -1194
- package/dist/{chunk-HBU5MTAM.js → chunk-SVNXH4L2.js} +7 -1
- package/dist/chunk-T6YILFSB.js +80 -0
- package/dist/chunk-TXOTCRLG.js +15 -0
- package/dist/chunk-TZTZS7QK.js +227 -0
- package/dist/chunk-UOV2BYIW.js +107 -0
- package/dist/{chunk-527KG6XR.js → chunk-V4RXGQ5Q.js} +72 -185
- package/dist/chunk-VAKQQHWR.js +434 -0
- package/dist/chunk-VG7TBQIY.js +128 -0
- package/dist/chunk-VJWL6YS5.js +244 -0
- package/dist/chunk-WEH5XRJQ.js +32 -0
- package/dist/{chunk-VPAWTYLY.js → chunk-WEPFGWHJ.js} +7 -1
- package/dist/chunk-WMSVI4G2.js +2095 -0
- package/dist/chunk-WVO7V2QY.js +797 -0
- package/dist/chunk-X4RCMKVQ.js +641 -0
- package/dist/chunk-X75S7HFS.js +374 -0
- package/dist/chunk-XN3L4EYL.js +46 -0
- package/dist/{chunk-HMYNFFY4.js → chunk-XPLRXC72.js} +7 -2
- package/dist/{chunk-PIDUD6M2.js → chunk-YCWGATWI.js} +12 -5
- package/dist/chunk-YHZX5GEU.js +193 -0
- package/dist/chunk-YXLYO42X.js +91 -0
- package/dist/{chunk-UHTSULZS.js → chunk-ZDOOVTXZ.js} +38 -73
- package/dist/chunk-ZI647VB5.js +37 -0
- package/dist/{chunk-CLJ5HLUD.js → chunk-ZWLZP4ZT.js} +25 -6
- package/dist/cli/index.js +70 -56
- package/dist/clio-4LY5K2AC.js +25 -0
- package/dist/code-nav-7AX6FYE6.js +600 -0
- package/dist/codewiki/build-worker.js +66 -0
- package/dist/compile-cache-CVJMMODC.js +18 -0
- package/dist/{components-JVHMUBEB.js → components-KELWS457.js} +31 -9
- package/dist/{config-ZFCDBMDC.js → config-GTLUW2PR.js} +104 -56
- package/dist/configure-R6A64DHX.js +42 -0
- package/dist/context-5VKGUVJJ.js +866 -0
- package/dist/{context-CDXTP2MP.js → context-JFZEJ7W5.js} +21 -13
- package/dist/{context-E3KIFVXI.js → context-RW5HC47S.js} +77 -36
- package/dist/{context-clear-3F4PLXOS.js → context-clear-6ZHBAZZT.js} +64 -30
- package/dist/{context-index-Q7YSYTR3.js → context-index-BZ4UYMTC.js} +36 -24
- package/dist/dispatch-runner-VKBRCWQC.js +1997 -0
- package/dist/{docs-YIETIWZI.js → docs-2C2LTVT2.js} +32 -13
- package/dist/doctor-KI767GSN.js +87 -0
- package/dist/{eval-B3KZZESM.js → eval-XSSNATB4.js} +93 -27
- package/dist/{evidence-V67CHM35.js → evidence-UA6AWDQQ.js} +55 -27
- package/dist/{evolve-YDZSUQYA.js → evolve-QNTFGV6Z.js} +59 -26
- package/dist/{extensions-SRG7XCAH.js → extensions-QVDOHDGJ.js} +23 -8
- package/dist/{fleet-CA2CRTVG.js → fleet-Q7UOMUSG.js} +173 -56
- package/dist/fleet-preflight-DDN536IT.js +26 -0
- package/dist/{init-2OZDJE2D.js → init-WBB65ZHQ.js} +79 -34
- package/dist/{memory-3PIQQAKX.js → memory-MD3O64RI.js} +57 -28
- package/dist/{models-DY35XI7Y.js → models-BZU34YWD.js} +49 -24
- package/dist/monitor-MEQA5C3I.js +661 -0
- package/dist/{chunk-PS4PFJQP.js → orchestrator-CGFKEP27.js} +22837 -18564
- package/dist/{paths-5OMXW7Z4.js → paths-UXLN5YYZ.js} +17 -6
- package/dist/preload-P6DGH2PZ.js +13 -0
- package/dist/{reset-PIFYNOS3.js → reset-L2FQEE3E.js} +25 -13
- package/dist/{run-3VSPP24F.js → run-IV4Q6RLN.js} +115 -63
- package/dist/{share-D36RQCXM.js → share-S5BZQC5I.js} +23 -10
- package/dist/{skills-F2MRLELY.js → skills-LQEKRDTN.js} +42 -15
- package/dist/{skills-eval-E2ZTW4PL.js → skills-eval-3DC4HEWS.js} +90 -45
- package/dist/steer-GGWFUJUD.js +77 -0
- package/dist/{targets-DZMEZAH4.js → targets-C4SSGQOB.js} +78 -60
- package/dist/terminal-lease-IT5JW2NR.js +395 -0
- package/dist/{trace-7NYCUI2J.js → trace-PNCASAXC.js} +48 -17
- package/dist/tree-sitter-HGKH6LG4.js +4673 -0
- package/dist/{uninstall-AD3JWHBB.js → uninstall-FZCQCDKC.js} +19 -8
- package/dist/{upgrade-WYYBKGDY.js → upgrade-7TT7SQ3G.js} +66 -24
- package/dist/{usage-ULIDAGFF.js → usage-GV4PKT3M.js} +71 -32
- package/dist/verify-G6V4D2G7.js +716 -0
- package/dist/version-KGEG7EPO.js +21 -0
- package/dist/web-fetch-2YHJ3KTG.js +638 -0
- package/dist/{wiki-generate-PKFIX6OB.js → wiki-generate-DQF6Z66B.js} +98 -48
- package/dist/worker/entry.js +297 -54
- package/dist/workspace-G4ZWUIPR.js +22 -0
- package/docs/README.md +29 -21
- package/docs/acp.md +191 -16
- package/docs/alcf-provider.md +1 -1
- package/docs/architecture.md +157 -7
- package/docs/artifact-placement.md +101 -0
- package/docs/artifact-versions.md +1 -1
- package/docs/built-in-agents.md +4 -2
- package/docs/capacity-and-scheduling.md +1 -1
- package/docs/commands-and-modes.md +224 -97
- package/docs/config-knobs-audit.md +1 -3
- package/docs/configuration-and-targets.md +236 -38
- package/docs/context-engine.md +34 -7
- package/docs/development-pipeline.md +102 -0
- package/docs/dispatch-architecture-rationale.md +1 -1
- package/docs/documentation-coverage.md +4 -4
- package/docs/documentation-guide.md +3 -3
- package/docs/environment-variables.md +15 -6
- package/docs/eval-runner.md +3 -3
- package/docs/evals-internal.md +2 -2
- package/docs/evidence-and-memory.md +10 -2
- package/docs/evolution.md +2 -2
- package/docs/exit-codes-and-output.md +15 -9
- package/docs/extensions-and-sharing.md +25 -18
- package/docs/fleet-demo-runbook.md +22 -23
- package/docs/fleet-dispatch.md +117 -16
- package/docs/git-commit-provenance.md +120 -0
- package/docs/glossary.md +107 -15
- package/docs/installation-and-lifecycle.md +75 -17
- package/docs/middleware-and-components.md +1 -1
- package/docs/model-catalog.md +46 -15
- package/docs/observability.md +8 -5
- package/docs/performance-methodology.md +491 -0
- package/docs/pi-boundary.md +72 -0
- package/docs/proactive-memory.md +14 -14
- package/docs/prompt-envelope-and-tools.md +25 -4
- package/docs/provider-adapter-cookbook.md +57 -4
- package/docs/release-cut-checklist.md +129 -111
- package/docs/safety-model.md +28 -8
- package/docs/scientific-validation.md +3 -3
- package/docs/session-lifecycle.md +55 -12
- package/docs/skills-marketplace.md +12 -8
- package/docs/time-conventions.md +101 -0
- package/docs/tool-usage.md +6 -5
- package/docs/trace-store.md +1 -1
- package/docs/troubleshooting.md +10 -7
- package/docs/tui-design.md +121 -28
- package/docs/worker-dispatch-mechanics.md +3 -3
- package/package.json +33 -36
- package/skills/README.md +5 -6
- package/skills/coding/ast-grep/SKILL.md +136 -0
- package/skills/coding/ast-grep/evals.md +56 -0
- package/skills/coding/ast-grep/references/rule_reference.md +297 -0
- package/skills/coding/coding-standards/SKILL.md +113 -0
- package/skills/coding/coding-standards/evals.md +34 -0
- package/skills/coding/prototype/SKILL.md +86 -0
- package/skills/coding/prototype/evals.md +42 -0
- package/skills/coding/prototype/references/LOGIC.md +67 -0
- package/skills/coding/prototype/references/UI.md +112 -0
- package/skills/coding/tdd/SKILL.md +101 -0
- package/skills/coding/tdd/evals.md +41 -0
- package/skills/coding/tdd/references/mocking.md +59 -0
- package/skills/coding/tdd/references/tests.md +77 -0
- package/skills/context/context-handoff/SKILL.md +126 -0
- package/skills/context/context-handoff/evals.md +57 -0
- package/skills/context/context-handoff/scripts/new-handoff.sh +26 -0
- package/skills/context/context-prime/SKILL.md +95 -0
- package/skills/context/context-prime/evals.md +54 -0
- package/skills/git/file-ticket/SKILL.md +107 -0
- package/skills/git/file-ticket/evals.md +54 -0
- package/skills/git/fix-issue/SKILL.md +106 -0
- package/skills/git/fix-issue/evals.md +71 -0
- package/skills/git/ship/SKILL.md +92 -0
- package/skills/git/ship/evals.md +72 -0
- package/skills/meta/clio-dev/SKILL.md +91 -0
- package/skills/meta/clio-dev/evals.md +45 -0
- package/skills/meta/clio-test/SKILL.md +130 -0
- package/skills/meta/clio-test/evals.md +43 -0
- package/skills/meta/clio-test/references/harness.md +97 -0
- package/skills/meta/clio-test/references/test-map.md +59 -0
- package/skills/meta/credentials/SKILL.md +125 -0
- package/skills/meta/credentials/evals.md +104 -0
- package/skills/meta/find-skills/SKILL.md +72 -0
- package/skills/meta/find-skills/evals.md +47 -0
- package/skills/meta/herdr/SKILL.md +127 -0
- package/skills/meta/herdr/evals.md +38 -0
- package/skills/meta/skill-craft/SKILL.md +102 -0
- package/skills/meta/skill-craft/evals.md +41 -0
- package/skills/planning/architecture/SKILL.md +129 -0
- package/skills/planning/architecture/evals.md +36 -0
- package/skills/planning/backlog/SKILL.md +90 -0
- package/skills/planning/backlog/evals.md +43 -0
- package/skills/planning/prd/SKILL.md +82 -0
- package/skills/planning/prd/evals.md +49 -0
- package/skills/planning/product-intent/SKILL.md +112 -0
- package/skills/planning/product-intent/evals.md +36 -0
- package/skills/planning/tech-spec/SKILL.md +115 -0
- package/skills/planning/tech-spec/evals.md +47 -0
- package/skills/registry.yaml +136 -0
- package/skills/research/arxiv-literature/SKILL.md +104 -0
- package/skills/research/arxiv-literature/evals.md +58 -0
- package/skills/research/experiment-protocol/SKILL.md +122 -0
- package/skills/research/experiment-protocol/evals.md +91 -0
- package/skills/research/scientific-debugging/SKILL.md +119 -0
- package/skills/research/scientific-debugging/evals.md +138 -0
- package/skills/research/scientific-modernization/SKILL.md +138 -0
- package/skills/research/scientific-modernization/evals.md +84 -0
- package/skills/skill-marketplace.json +12 -20
- package/skills/workflow/design-council/SKILL.md +139 -0
- package/skills/workflow/design-council/evals.md +97 -0
- package/skills/workflow/grill-me/SKILL.md +186 -0
- package/skills/workflow/grill-me/evals.md +78 -0
- package/skills/workflow/workflow-distiller/SKILL.md +136 -0
- package/skills/workflow/workflow-distiller/evals.md +107 -0
- package/src/cli/acp.ts +115 -0
- package/src/cli/agents.ts +60 -0
- package/src/cli/args.ts +280 -0
- package/src/cli/argv.ts +170 -0
- package/src/cli/ask.ts +34 -0
- package/src/cli/auth.ts +468 -0
- package/src/cli/bootstrap-generate.ts +498 -0
- package/src/cli/clio.ts +96 -0
- package/src/cli/components.ts +195 -0
- package/src/cli/config-inspect.ts +336 -0
- package/src/cli/config.ts +72 -0
- package/src/cli/configure-interop.ts +67 -0
- package/src/cli/configure-layout.ts +172 -0
- package/src/cli/configure.ts +1525 -0
- package/src/cli/context-clear.ts +76 -0
- package/src/cli/context-index.ts +100 -0
- package/src/cli/context.ts +321 -0
- package/src/cli/default-target.ts +55 -0
- package/src/cli/docs.ts +346 -0
- package/src/cli/doctor.ts +54 -0
- package/src/cli/eval.ts +315 -0
- package/src/cli/evidence.ts +225 -0
- package/src/cli/evolve.ts +219 -0
- package/src/cli/extensions.ts +214 -0
- package/src/cli/fleet.ts +868 -0
- package/src/cli/index.ts +338 -0
- package/src/cli/init.ts +219 -0
- package/src/cli/initial-message.ts +47 -0
- package/src/cli/internal-dispatch.ts +55 -0
- package/src/cli/memory.ts +182 -0
- package/src/cli/models.ts +257 -0
- package/src/cli/modes/json-stream.ts +121 -0
- package/src/cli/modes/jsonl.ts +9 -0
- package/src/cli/modes/print.ts +604 -0
- package/src/cli/oauth-manual-input.ts +72 -0
- package/src/cli/oauth-select.ts +36 -0
- package/src/cli/output-guard.ts +48 -0
- package/src/cli/paths.ts +44 -0
- package/src/cli/provider-target.ts +113 -0
- package/src/cli/removal.ts +72 -0
- package/src/cli/reset.ts +253 -0
- package/src/cli/run.ts +497 -0
- package/src/cli/share.ts +243 -0
- package/src/cli/shared.ts +60 -0
- package/src/cli/skills-eval.ts +1240 -0
- package/src/cli/skills.ts +485 -0
- package/src/cli/steer-channel.ts +86 -0
- package/src/cli/targets.ts +1154 -0
- package/src/cli/text-layout.ts +112 -0
- package/src/cli/trace.ts +322 -0
- package/src/cli/uninstall.ts +405 -0
- package/src/cli/upgrade.ts +286 -0
- package/src/cli/usage.ts +876 -0
- package/src/cli/validate-model.ts +28 -0
- package/src/cli/version.ts +7 -0
- package/src/cli/wiki-generate.ts +510 -0
- package/src/core/agent-environment.ts +7 -0
- package/src/core/artifact-paths.ts +43 -0
- package/src/core/bash-exec.ts +313 -0
- package/src/core/boot-trace.ts +42 -0
- package/src/core/bus-events.ts +744 -0
- package/src/core/bus-trace.ts +53 -0
- package/src/core/c-header-language.ts +265 -0
- package/src/core/clio-repo.ts +78 -0
- package/src/core/commit-attribution.ts +157 -0
- package/src/core/compile-cache.ts +159 -0
- package/src/core/config.ts +1722 -0
- package/src/core/context-floor.ts +33 -0
- package/src/core/defaults.ts +659 -0
- package/src/core/domain-loader.ts +177 -0
- package/src/core/event-bus.ts +94 -0
- package/src/core/file-references.ts +282 -0
- package/src/core/git-commit-attribution.ts +362 -0
- package/src/core/guardrails.ts +185 -0
- package/src/core/headless-permission.ts +21 -0
- package/src/core/incomplete-installation.ts +45 -0
- package/src/core/init.ts +223 -0
- package/src/core/json-payload.ts +58 -0
- package/src/core/package-root.ts +54 -0
- package/src/core/path-canonical.ts +37 -0
- package/src/core/process-identity.ts +41 -0
- package/src/core/recent-models.ts +91 -0
- package/src/core/residency-protection.ts +56 -0
- package/src/core/resolve-config-value.ts +88 -0
- package/src/core/response-schema.ts +279 -0
- package/src/core/run-overrides.ts +77 -0
- package/src/core/safe-exec.ts +205 -0
- package/src/core/safe-id.ts +37 -0
- package/src/core/safe-resource-write.ts +162 -0
- package/src/core/session-routing.ts +339 -0
- package/src/core/settings-layers.ts +354 -0
- package/src/core/shared-bus.ts +8 -0
- package/src/core/shell-quote.ts +26 -0
- package/src/core/skill-activation.ts +190 -0
- package/src/core/startup-timer.ts +37 -0
- package/src/core/state-file-lock.ts +219 -0
- package/src/core/termination.ts +275 -0
- package/src/core/timers.ts +38 -0
- package/src/core/tool-names.ts +65 -0
- package/src/core/verification-scripts.ts +13 -0
- package/src/core/workspace-files.ts +492 -0
- package/src/core/xdg.ts +177 -0
- package/src/domains/agents/builtins/architect.md +4 -4
- package/src/domains/agents/builtins/coder.md +3 -2
- package/src/domains/agents/builtins/debugger.md +1 -1
- package/src/domains/agents/builtins/documenter.md +1 -1
- package/src/domains/agents/builtins/git-master.md +3 -3
- package/src/domains/agents/builtins/provenance.md +1 -1
- package/src/domains/agents/builtins/researcher.md +1 -1
- package/src/domains/agents/builtins/scout.md +1 -1
- package/src/domains/agents/builtins/tester.md +1 -1
- package/src/domains/agents/builtins/verifier.md +1 -1
- package/src/domains/agents/builtins/wiki-writer.md +1 -1
- package/src/domains/agents/catalog.ts +172 -0
- package/src/domains/agents/contract.ts +16 -0
- package/src/domains/agents/extension.ts +110 -0
- package/src/domains/agents/fleet-commands.ts +154 -0
- package/src/domains/agents/fleet-contract.ts +797 -0
- package/src/domains/agents/frontmatter.ts +40 -0
- package/src/domains/agents/index.ts +102 -0
- package/src/domains/agents/manifest.ts +6 -0
- package/src/domains/agents/recipe-schema.ts +202 -0
- package/src/domains/agents/recipe.ts +125 -0
- package/src/domains/agents/registry.ts +111 -0
- package/src/domains/agents/result-contract.ts +1265 -0
- package/src/domains/agents/spec.ts +371 -0
- package/src/domains/agents/write-boundary.ts +109 -0
- package/src/domains/components/diff.ts +99 -0
- package/src/domains/components/hash.ts +21 -0
- package/src/domains/components/index.ts +3 -0
- package/src/domains/components/scan.ts +304 -0
- package/src/domains/components/snapshot.ts +108 -0
- package/src/domains/components/types.ts +82 -0
- package/src/domains/config/agent-namespace.ts +13 -0
- package/src/domains/config/classify.ts +106 -0
- package/src/domains/config/contract.ts +22 -0
- package/src/domains/config/extension.ts +142 -0
- package/src/domains/config/index.ts +23 -0
- package/src/domains/config/keybindings.ts +200 -0
- package/src/domains/config/manifest.ts +6 -0
- package/src/domains/config/watcher.ts +41 -0
- package/src/domains/context/adoption.ts +880 -0
- package/src/domains/context/bootstrap-prompt.ts +273 -0
- package/src/domains/context/bootstrap.ts +1502 -0
- package/src/domains/context/clear.ts +91 -0
- package/src/domains/context/clio-md.ts +429 -0
- package/src/domains/context/codewiki/artifact.ts +400 -0
- package/src/domains/context/codewiki/build-worker-protocol.ts +24 -0
- package/src/domains/context/codewiki/build-worker.ts +54 -0
- package/src/domains/context/codewiki/cmake.ts +356 -0
- package/src/domains/context/codewiki/cooperative.ts +46 -0
- package/src/domains/context/codewiki/coordinator.ts +182 -0
- package/src/domains/context/codewiki/digest.ts +135 -0
- package/src/domains/context/codewiki/grammar-assets.ts +33 -0
- package/src/domains/context/codewiki/indexer.ts +1529 -0
- package/src/domains/context/codewiki/paths.ts +67 -0
- package/src/domains/context/codewiki/schema.ts +80 -0
- package/src/domains/context/codewiki/tree-sitter.ts +771 -0
- package/src/domains/context/contract.ts +68 -0
- package/src/domains/context/excluded-dirs.ts +1 -0
- package/src/domains/context/extension.ts +358 -0
- package/src/domains/context/fingerprint.ts +198 -0
- package/src/domains/context/index.ts +154 -0
- package/src/domains/context/init-options.ts +69 -0
- package/src/domains/context/manifest.ts +6 -0
- package/src/domains/context/operator-profile.ts +157 -0
- package/src/domains/context/project-metadata.ts +529 -0
- package/src/domains/context/project-rules.ts +227 -0
- package/src/domains/context/prompt-context.ts +61 -0
- package/src/domains/context/refresh.ts +178 -0
- package/src/domains/context/runtime.ts +17 -0
- package/src/domains/context/sibling-files.ts +10 -0
- package/src/domains/context/state.ts +237 -0
- package/src/domains/context/wiki/assemble.ts +303 -0
- package/src/domains/context/wiki/frontmatter.ts +210 -0
- package/src/domains/context/wiki/generate.ts +595 -0
- package/src/domains/context/wiki/layout.ts +111 -0
- package/src/domains/context/wiki/meta.ts +221 -0
- package/src/domains/context/wiki/plan-store.ts +217 -0
- package/src/domains/context/wiki/plan.ts +250 -0
- package/src/domains/context/wiki/prompts.ts +283 -0
- package/src/domains/context/wiki/staleness.ts +198 -0
- package/src/domains/dispatch/active-route-planner.ts +168 -0
- package/src/domains/dispatch/admission-queue.ts +150 -0
- package/src/domains/dispatch/admission.ts +334 -0
- package/src/domains/dispatch/agent-candidates.ts +307 -0
- package/src/domains/dispatch/agent-ledger-hub.ts +71 -0
- package/src/domains/dispatch/agent-ledger-store.ts +257 -0
- package/src/domains/dispatch/agent-ledger.ts +212 -0
- package/src/domains/dispatch/agent-plan-adapter.ts +85 -0
- package/src/domains/dispatch/assignment-events.ts +132 -0
- package/src/domains/dispatch/assignment-reconcile.ts +76 -0
- package/src/domains/dispatch/assignment-store.ts +162 -0
- package/src/domains/dispatch/assignment.ts +278 -0
- package/src/domains/dispatch/backoff.ts +59 -0
- package/src/domains/dispatch/batch-store.ts +134 -0
- package/src/domains/dispatch/batch-tracker.ts +66 -0
- package/src/domains/dispatch/capability-match.ts +155 -0
- package/src/domains/dispatch/capacity-lease.ts +413 -0
- package/src/domains/dispatch/code-step-store.ts +52 -0
- package/src/domains/dispatch/code-step.ts +387 -0
- package/src/domains/dispatch/contract.ts +278 -0
- package/src/domains/dispatch/dedup.ts +133 -0
- package/src/domains/dispatch/event-pump.ts +251 -0
- package/src/domains/dispatch/execution-handoff.ts +29 -0
- package/src/domains/dispatch/execution-plan.ts +328 -0
- package/src/domains/dispatch/execution-role.ts +277 -0
- package/src/domains/dispatch/execution-scheduler.ts +583 -0
- package/src/domains/dispatch/extension.ts +6006 -0
- package/src/domains/dispatch/failure-classification.ts +147 -0
- package/src/domains/dispatch/fleet-commit-attribution.ts +56 -0
- package/src/domains/dispatch/fleet-plan.ts +225 -0
- package/src/domains/dispatch/fleet-preflight.ts +394 -0
- package/src/domains/dispatch/gate-decisions.ts +1040 -0
- package/src/domains/dispatch/gate-role-prompts.ts +58 -0
- package/src/domains/dispatch/heartbeat.ts +27 -0
- package/src/domains/dispatch/index.ts +137 -0
- package/src/domains/dispatch/joint-route-adapter.ts +114 -0
- package/src/domains/dispatch/joint-route-resolver.ts +283 -0
- package/src/domains/dispatch/manifest.ts +6 -0
- package/src/domains/dispatch/orphan-recovery.ts +266 -0
- package/src/domains/dispatch/outcome.ts +109 -0
- package/src/domains/dispatch/phase-timing.ts +47 -0
- package/src/domains/dispatch/placement.ts +202 -0
- package/src/domains/dispatch/receipt-findings.ts +255 -0
- package/src/domains/dispatch/receipt-integrity.ts +383 -0
- package/src/domains/dispatch/recovery-candidates.ts +74 -0
- package/src/domains/dispatch/reproducibility.ts +48 -0
- package/src/domains/dispatch/reservation-store.ts +501 -0
- package/src/domains/dispatch/route-approval.ts +177 -0
- package/src/domains/dispatch/route-candidates.ts +57 -0
- package/src/domains/dispatch/route-decision.ts +1079 -0
- package/src/domains/dispatch/route-facts.ts +187 -0
- package/src/domains/dispatch/route-history.ts +238 -0
- package/src/domains/dispatch/route-observer.ts +488 -0
- package/src/domains/dispatch/route-policy.ts +327 -0
- package/src/domains/dispatch/route-quality.ts +256 -0
- package/src/domains/dispatch/route-readiness.ts +120 -0
- package/src/domains/dispatch/routing-intent.ts +287 -0
- package/src/domains/dispatch/run-identity.ts +56 -0
- package/src/domains/dispatch/scout-transition.ts +130 -0
- package/src/domains/dispatch/state.ts +302 -0
- package/src/domains/dispatch/tool-stats.ts +132 -0
- package/src/domains/dispatch/transport.ts +210 -0
- package/src/domains/dispatch/types.ts +799 -0
- package/src/domains/dispatch/validation-grounding.ts +154 -0
- package/src/domains/dispatch/validation.ts +721 -0
- package/src/domains/dispatch/worker-protocol.ts +275 -0
- package/src/domains/dispatch/worker-spawn.ts +585 -0
- package/src/domains/dispatch/write-boundary-enforcer.ts +97 -0
- package/src/domains/dispatch/write-boundary.ts +426 -0
- package/src/domains/eval/artifacts/redact.ts +42 -0
- package/src/domains/eval/artifacts/store.ts +173 -0
- package/src/domains/eval/compare/compare.ts +33 -0
- package/src/domains/eval/compare/gates.ts +66 -0
- package/src/domains/eval/compare/thresholds.ts +92 -0
- package/src/domains/eval/harness-metrics.ts +62 -0
- package/src/domains/eval/index.ts +66 -0
- package/src/domains/eval/metrics/chaos-stream.ts +93 -0
- package/src/domains/eval/metrics/context.ts +34 -0
- package/src/domains/eval/metrics/coverage.ts +21 -0
- package/src/domains/eval/metrics/evidence.ts +182 -0
- package/src/domains/eval/metrics/fleet-loop-stream.ts +196 -0
- package/src/domains/eval/metrics/invariants.ts +736 -0
- package/src/domains/eval/metrics/latency.ts +4 -0
- package/src/domains/eval/metrics/token-stream.ts +139 -0
- package/src/domains/eval/metrics/tokens.ts +51 -0
- package/src/domains/eval/metrics/tool-calls.ts +7 -0
- package/src/domains/eval/proactive-memory.ts +279 -0
- package/src/domains/eval/provenance.ts +35 -0
- package/src/domains/eval/report.ts +234 -0
- package/src/domains/eval/reports/json.ts +5 -0
- package/src/domains/eval/reports/junit.ts +23 -0
- package/src/domains/eval/reports/markdown.ts +20 -0
- package/src/domains/eval/reports/swe-jsonl.ts +16 -0
- package/src/domains/eval/reports/text.ts +25 -0
- package/src/domains/eval/run-compare.ts +274 -0
- package/src/domains/eval/runner.ts +244 -0
- package/src/domains/eval/runners/clio-run.ts +148 -0
- package/src/domains/eval/runners/context-index.ts +56 -0
- package/src/domains/eval/runners/context-init.ts +203 -0
- package/src/domains/eval/runners/external-command.ts +350 -0
- package/src/domains/eval/runtime-paths.ts +171 -0
- package/src/domains/eval/schema/artifact.ts +75 -0
- package/src/domains/eval/schema/suite.ts +235 -0
- package/src/domains/eval/schema/validate.ts +339 -0
- package/src/domains/eval/store.ts +392 -0
- package/src/domains/eval/suites/load.ts +74 -0
- package/src/domains/eval/suites/matrix.ts +19 -0
- package/src/domains/eval/suites/resolve.ts +44 -0
- package/src/domains/eval/suites/run.ts +325 -0
- package/src/domains/eval/task-file.ts +186 -0
- package/src/domains/eval/types.ts +140 -0
- package/src/domains/eval/verifiers/command.ts +27 -0
- package/src/domains/eval/verifiers/file-exists.ts +6 -0
- package/src/domains/eval/verifiers/patch.ts +22 -0
- package/src/domains/eval/workspaces/git.ts +41 -0
- package/src/domains/eval/workspaces/local.ts +17 -0
- package/src/domains/eval/workspaces/temp-copy.ts +29 -0
- package/src/domains/evidence/build.ts +1754 -0
- package/src/domains/evidence/eval.ts +605 -0
- package/src/domains/evidence/failure-attribution.ts +54 -0
- package/src/domains/evidence/finish-contract-map.ts +42 -0
- package/src/domains/evidence/index.ts +55 -0
- package/src/domains/evidence/provenance.ts +200 -0
- package/src/domains/evidence/redact.ts +81 -0
- package/src/domains/evidence/store.ts +291 -0
- package/src/domains/evidence/types.ts +325 -0
- package/src/domains/evolution/SELF_EDIT_GATE.md +91 -0
- package/src/domains/evolution/index.ts +26 -0
- package/src/domains/evolution/manifest.ts +154 -0
- package/src/domains/evolution/validate.ts +304 -0
- package/src/domains/extensions/contract.ts +22 -0
- package/src/domains/extensions/discovery.ts +193 -0
- package/src/domains/extensions/extension.ts +46 -0
- package/src/domains/extensions/index.ts +34 -0
- package/src/domains/extensions/manager.ts +30 -0
- package/src/domains/extensions/manifest.ts +6 -0
- package/src/domains/extensions/resources.ts +38 -0
- package/src/domains/extensions/state.ts +224 -0
- package/src/domains/extensions/types.ts +83 -0
- package/src/domains/interop/consent.ts +185 -0
- package/src/domains/interop/contract.ts +29 -0
- package/src/domains/interop/detect.ts +173 -0
- package/src/domains/interop/extension.ts +59 -0
- package/src/domains/interop/index.ts +34 -0
- package/src/domains/interop/manifest.ts +6 -0
- package/src/domains/interop/registry.ts +139 -0
- package/src/domains/interop/state.ts +92 -0
- package/src/domains/interop/types.ts +109 -0
- package/src/domains/lifecycle/contract.ts +11 -0
- package/src/domains/lifecycle/doctor.ts +536 -0
- package/src/domains/lifecycle/extension.ts +23 -0
- package/src/domains/lifecycle/index.ts +21 -0
- package/src/domains/lifecycle/install-method.ts +51 -0
- package/src/domains/lifecycle/manifest.ts +6 -0
- package/src/domains/lifecycle/migrations/2026-08-18-lmstudio-runtime-id.ts +52 -0
- package/src/domains/lifecycle/migrations/index.ts +114 -0
- package/src/domains/lifecycle/state.ts +93 -0
- package/src/domains/lifecycle/upgrade-notice.ts +35 -0
- package/src/domains/lifecycle/version.ts +35 -0
- package/src/domains/memory/index.ts +128 -0
- package/src/domains/memory/operations.ts +143 -0
- package/src/domains/memory/prompt-section.ts +86 -0
- package/src/domains/memory/proposal.ts +180 -0
- package/src/domains/memory/store.ts +163 -0
- package/src/domains/memory/task-bank.ts +229 -0
- package/src/domains/memory/task-memory-handoff.ts +222 -0
- package/src/domains/memory/task-memory-policy.ts +602 -0
- package/src/domains/memory/task-memory-status.ts +58 -0
- package/src/domains/memory/task-memory-telemetry.ts +346 -0
- package/src/domains/memory/task-memory-trace.ts +89 -0
- package/src/domains/memory/types.ts +74 -0
- package/src/domains/memory/validate.ts +297 -0
- package/src/domains/middleware/budget.ts +216 -0
- package/src/domains/middleware/contract.ts +35 -0
- package/src/domains/middleware/dispatch-nudge.ts +469 -0
- package/src/domains/middleware/extension.ts +129 -0
- package/src/domains/middleware/hook-receipts.ts +70 -0
- package/src/domains/middleware/hooks-io.ts +186 -0
- package/src/domains/middleware/hooks.ts +432 -0
- package/src/domains/middleware/index.ts +154 -0
- package/src/domains/middleware/manifest.ts +6 -0
- package/src/domains/middleware/memory-intervention.ts +701 -0
- package/src/domains/middleware/rules.ts +42 -0
- package/src/domains/middleware/runtime.ts +333 -0
- package/src/domains/middleware/skills-reminder.ts +202 -0
- package/src/domains/middleware/snapshot.ts +71 -0
- package/src/domains/middleware/stalled-turn.ts +71 -0
- package/src/domains/middleware/task-board-reminder.ts +87 -0
- package/src/domains/middleware/task-nudge.ts +77 -0
- package/src/domains/middleware/tool-choice-control.ts +42 -0
- package/src/domains/middleware/types.ts +131 -0
- package/src/domains/middleware/validate.ts +329 -0
- package/src/domains/observability/accountability.ts +67 -0
- package/src/domains/observability/contract.ts +168 -0
- package/src/domains/observability/cost.ts +206 -0
- package/src/domains/observability/evidence-index.ts +123 -0
- package/src/domains/observability/extension.ts +281 -0
- package/src/domains/observability/index.ts +78 -0
- package/src/domains/observability/manifest.ts +10 -0
- package/src/domains/observability/metrics.ts +37 -0
- package/src/domains/observability/projection.ts +477 -0
- package/src/domains/observability/telemetry.ts +89 -0
- package/src/domains/observability/trace-store.ts +1454 -0
- package/src/domains/prompts/compiler.ts +614 -0
- package/src/domains/prompts/contract.ts +46 -0
- package/src/domains/prompts/extension.ts +336 -0
- package/src/domains/prompts/fragment-loader.ts +138 -0
- package/src/domains/prompts/fragments/identity/clio.md +5 -6
- package/src/domains/prompts/fragments/identity/docs-routing.md +10 -0
- package/src/domains/prompts/fragments/identity/self-awareness.md +19 -0
- package/src/domains/prompts/fragments/operating/contract.md +4 -41
- package/src/domains/prompts/fragments/operating/delegation.md +42 -0
- package/src/domains/prompts/fragments/operating/skills.md +26 -0
- package/src/domains/prompts/fragments/operating/worker.md +16 -0
- package/src/domains/prompts/fragments/safety/auto-edit.md +5 -5
- package/src/domains/prompts/fragments/safety/full-auto.md +3 -3
- package/src/domains/prompts/fragments/safety/read-only.md +4 -4
- package/src/domains/prompts/fragments/safety/suggest.md +2 -2
- package/src/domains/prompts/fragments/wiki/page.md +10 -0
- package/src/domains/prompts/fragments/wiki/plan.md +10 -0
- package/src/domains/prompts/hash.ts +73 -0
- package/src/domains/prompts/index.ts +32 -0
- package/src/domains/prompts/manifest.ts +8 -0
- package/src/domains/prompts/memory-intervention.ts +57 -0
- package/src/domains/prompts/preload.ts +82 -0
- package/src/domains/providers/auth/api-key.ts +47 -0
- package/src/domains/providers/auth/backend-file.ts +79 -0
- package/src/domains/providers/auth/backend-memory.ts +72 -0
- package/src/domains/providers/auth/boot-status.ts +65 -0
- package/src/domains/providers/auth/index.ts +37 -0
- package/src/domains/providers/auth/oauth.ts +37 -0
- package/src/domains/providers/auth/storage.ts +726 -0
- package/src/domains/providers/capabilities.ts +58 -0
- package/src/domains/providers/catalog.ts +162 -0
- package/src/domains/providers/contract.ts +162 -0
- package/src/domains/providers/credentials.ts +20 -0
- package/src/domains/providers/eligibility.ts +24 -0
- package/src/domains/providers/errors.ts +64 -0
- package/src/domains/providers/extension.ts +548 -0
- package/src/domains/providers/index.ts +142 -0
- package/src/domains/providers/knowledge-base-path.ts +102 -0
- package/src/domains/providers/manifest.ts +6 -0
- package/src/domains/providers/model-capabilities.ts +136 -0
- package/src/domains/providers/model-discovery.ts +144 -0
- package/src/domains/providers/model-family.ts +32 -0
- package/src/domains/providers/model-runtime-capabilities.ts +723 -0
- package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +120 -37
- package/src/domains/providers/plugins.ts +61 -0
- package/src/domains/providers/probe/fingerprint.ts +64 -0
- package/src/domains/providers/probe/http.ts +114 -0
- package/src/domains/providers/probe/reasoning.ts +111 -0
- package/src/domains/providers/registry.ts +187 -0
- package/src/domains/providers/resolver.ts +156 -0
- package/src/domains/providers/runtime-resolution.ts +720 -0
- package/src/domains/providers/runtimes/antigravity/antigravity-code.ts +90 -0
- package/src/domains/providers/runtimes/boot-manifest.ts +55 -0
- package/src/domains/providers/runtimes/builtins.ts +78 -0
- package/src/domains/providers/runtimes/claude/claude-code.ts +38 -0
- package/src/domains/providers/runtimes/claude/claude-sdk.ts +38 -0
- package/src/domains/providers/runtimes/claude/common.ts +55 -0
- package/src/domains/providers/runtimes/cloud/alcf.ts +171 -0
- package/src/domains/providers/runtimes/cloud/anthropic-max.ts +71 -0
- package/src/domains/providers/runtimes/cloud/anthropic.ts +43 -0
- package/src/domains/providers/runtimes/cloud/bedrock.ts +35 -0
- package/src/domains/providers/runtimes/cloud/deepseek.ts +50 -0
- package/src/domains/providers/runtimes/cloud/google.ts +41 -0
- package/src/domains/providers/runtimes/cloud/groq.ts +46 -0
- package/src/domains/providers/runtimes/cloud/mistral.ts +46 -0
- package/src/domains/providers/runtimes/cloud/openai-codex.ts +49 -0
- package/src/domains/providers/runtimes/cloud/openai.ts +46 -0
- package/src/domains/providers/runtimes/cloud/openrouter.ts +111 -0
- package/src/domains/providers/runtimes/common/lmstudio-http.ts +423 -0
- package/src/domains/providers/runtimes/common/local-synth.ts +164 -0
- package/src/domains/providers/runtimes/common/probe-helpers.ts +474 -0
- package/src/domains/providers/runtimes/local-native/lemonade-anthropic.ts +27 -0
- package/src/domains/providers/runtimes/local-native/lemonade-openai.ts +26 -0
- package/src/domains/providers/runtimes/local-native/llamacpp-anthropic.ts +88 -0
- package/src/domains/providers/runtimes/local-native/llamacpp-completion.ts +207 -0
- package/src/domains/providers/runtimes/local-native/llamacpp-embed.ts +180 -0
- package/src/domains/providers/runtimes/local-native/llamacpp-rerank.ts +126 -0
- package/src/domains/providers/runtimes/local-native/llamacpp.ts +98 -0
- package/src/domains/providers/runtimes/local-native/lmstudio.ts +241 -0
- package/src/domains/providers/runtimes/local-native/ollama-native.ts +117 -0
- package/src/domains/providers/runtimes/local-native/sglang.ts +26 -0
- package/src/domains/providers/runtimes/local-native/vllm.ts +27 -0
- package/src/domains/providers/runtimes/protocol/anthropic-compat.ts +104 -0
- package/src/domains/providers/runtimes/protocol/anthropic-messages.ts +28 -0
- package/src/domains/providers/runtimes/protocol/bedrock.ts +26 -0
- package/src/domains/providers/runtimes/protocol/google.ts +27 -0
- package/src/domains/providers/runtimes/protocol/openai-compat.ts +145 -0
- package/src/domains/providers/support.ts +241 -0
- package/src/domains/providers/thinking-control-policy.ts +85 -0
- package/src/domains/providers/types/capability-flags.ts +114 -0
- package/src/domains/providers/types/cost-provenance.ts +7 -0
- package/src/domains/providers/types/inference.ts +41 -0
- package/src/domains/providers/types/knowledge-base.ts +156 -0
- package/src/domains/providers/types/local-model-quirks.ts +216 -0
- package/src/domains/providers/types/runtime-descriptor.ts +181 -0
- package/src/domains/providers/types/target-descriptor.ts +60 -0
- package/src/domains/resources/collision.ts +92 -0
- package/src/domains/resources/common-loader.ts +140 -0
- package/src/domains/resources/contract.ts +24 -0
- package/src/domains/resources/extension.ts +59 -0
- package/src/domains/resources/index.ts +74 -0
- package/src/domains/resources/loader.ts +85 -0
- package/src/domains/resources/manifest.ts +6 -0
- package/src/domains/resources/prompts/loader.ts +246 -0
- package/src/domains/resources/prompts/substitute.ts +1 -0
- package/src/domains/resources/skills/content-hash.ts +107 -0
- package/src/domains/resources/skills/evals.ts +175 -0
- package/src/domains/resources/skills/install.ts +427 -0
- package/src/domains/resources/skills/loader.ts +985 -0
- package/src/domains/resources/skills/marketplace.ts +301 -0
- package/src/domains/resources/skills/provenance-pin.ts +115 -0
- package/src/domains/safety/action-classifier.ts +270 -0
- package/src/domains/safety/approval-axis.ts +23 -0
- package/src/domains/safety/audit.ts +493 -0
- package/src/domains/safety/autonomy.ts +174 -0
- package/src/domains/safety/call-target.ts +59 -0
- package/src/domains/safety/contract.ts +65 -0
- package/src/domains/safety/damage-control.ts +44 -0
- package/src/domains/safety/default-path-policy.ts +117 -0
- package/src/domains/safety/extension.ts +383 -0
- package/src/domains/safety/finish-contract-registration.ts +128 -0
- package/src/domains/safety/finish-contract.ts +554 -0
- package/src/domains/safety/index.ts +44 -0
- package/src/domains/safety/loop-detector.ts +143 -0
- package/src/domains/safety/manifest.ts +6 -0
- package/src/domains/safety/path-policy.ts +164 -0
- package/src/domains/safety/policy-engine.ts +994 -0
- package/src/domains/safety/project-policy.ts +391 -0
- package/src/domains/safety/protected-artifacts-registration.ts +189 -0
- package/src/domains/safety/protected-artifacts.ts +993 -0
- package/src/domains/safety/rejection-feedback.ts +102 -0
- package/src/domains/safety/rigor.ts +72 -0
- package/src/domains/safety/rule-compiler.ts +48 -0
- package/src/domains/safety/rule-pack-loader.ts +92 -0
- package/src/domains/safety/run-effects.ts +197 -0
- package/src/domains/safety/scope.ts +61 -0
- package/src/domains/scheduling/budget.ts +35 -0
- package/src/domains/scheduling/cluster.ts +175 -0
- package/src/domains/scheduling/contract.ts +30 -0
- package/src/domains/scheduling/extension.ts +91 -0
- package/src/domains/scheduling/index.ts +12 -0
- package/src/domains/scheduling/manifest.ts +6 -0
- package/src/domains/session/archive-readers.ts +208 -0
- package/src/domains/session/checkpoint.ts +38 -0
- package/src/domains/session/compaction/auto.ts +89 -0
- package/src/domains/session/compaction/branch-summary.ts +187 -0
- package/src/domains/session/compaction/compact.ts +493 -0
- package/src/domains/session/compaction/cut-point.ts +172 -0
- package/src/domains/session/compaction/defaults.ts +34 -0
- package/src/domains/session/compaction/mask-observations.ts +265 -0
- package/src/domains/session/compaction/session-entries.ts +18 -0
- package/src/domains/session/compaction/tokens.ts +148 -0
- package/src/domains/session/context-accounting.ts +583 -0
- package/src/domains/session/context-ledger.ts +362 -0
- package/src/domains/session/contract.ts +159 -0
- package/src/domains/session/cwd-fallback.ts +65 -0
- package/src/domains/session/decision-board.ts +190 -0
- package/src/domains/session/entries.ts +572 -0
- package/src/domains/session/extension.ts +403 -0
- package/src/domains/session/history.ts +172 -0
- package/src/domains/session/index.ts +84 -0
- package/src/domains/session/manager.ts +128 -0
- package/src/domains/session/manifest.ts +6 -0
- package/src/domains/session/migrations/index.ts +27 -0
- package/src/domains/session/prompt-manifest.ts +173 -0
- package/src/domains/session/protected-artifact-journal.ts +241 -0
- package/src/domains/session/protected-artifacts.ts +90 -0
- package/src/domains/session/retry.ts +230 -0
- package/src/domains/session/session-artifacts.ts +107 -0
- package/src/domains/session/task-board.ts +569 -0
- package/src/domains/session/tree/active-path.ts +137 -0
- package/src/domains/session/tree/fork.ts +159 -0
- package/src/domains/session/tree/manager.ts +128 -0
- package/src/domains/session/tree/navigator.ts +177 -0
- package/src/domains/session/tree/preview.ts +259 -0
- package/src/domains/session/usage.ts +148 -0
- package/src/domains/session/workspace/git-probe.ts +167 -0
- package/src/domains/session/workspace/index.ts +4 -0
- package/src/domains/session/workspace/project-type.ts +217 -0
- package/src/domains/session/workspace/snapshot.ts +74 -0
- package/src/domains/share/archive.ts +684 -0
- package/src/domains/share/contract.ts +9 -0
- package/src/domains/share/extension.ts +26 -0
- package/src/domains/share/index.ts +28 -0
- package/src/domains/share/manifest.ts +6 -0
- package/src/domains/user-tasks/store.ts +297 -0
- package/src/engine/acp/adapter.ts +397 -0
- package/src/engine/acp/errors.ts +128 -0
- package/src/engine/acp/event-mapper.ts +154 -0
- package/src/engine/acp/server.ts +2238 -0
- package/src/engine/acp/tool-mediator.ts +686 -0
- package/src/engine/acp/transport.ts +775 -0
- package/src/engine/acp/types.ts +196 -0
- package/src/engine/agent.ts +29 -0
- package/src/engine/ai.ts +207 -0
- package/src/engine/alcf-oauth.ts +205 -0
- package/src/engine/antigravity/subprocess-runtime.ts +205 -0
- package/src/engine/api-registry.ts +223 -0
- package/src/engine/apis/degraded-inference.ts +95 -0
- package/src/engine/apis/index.ts +16 -0
- package/src/engine/apis/llamacpp-residency.ts +292 -0
- package/src/engine/apis/lmstudio-residency.ts +76 -0
- package/src/engine/apis/lmstudio.ts +243 -0
- package/src/engine/apis/ollama-native.ts +528 -0
- package/src/engine/apis/openai-completions.ts +795 -0
- package/src/engine/apis/output-budget.ts +99 -0
- package/src/engine/apis/residency-lock.ts +29 -0
- package/src/engine/apis/residency.ts +776 -0
- package/src/engine/apis/resident-models.ts +42 -0
- package/src/engine/apis/sampling-overrides.ts +38 -0
- package/src/engine/apis/types.ts +16 -0
- package/src/engine/claude/sdk-runtime.ts +685 -0
- package/src/engine/claude/session-id.ts +5 -0
- package/src/engine/claude/subprocess-runtime.ts +316 -0
- package/src/engine/claude/tool-safety.ts +400 -0
- package/src/engine/env-api-keys.ts +98 -0
- package/src/engine/external-subprocess.ts +23 -0
- package/src/engine/gemma-channel-filter.ts +223 -0
- package/src/engine/harmony-response.ts +165 -0
- package/src/engine/instrumented-tui.ts +192 -0
- package/src/engine/loop-guard.ts +1127 -0
- package/src/engine/messages.ts +14 -0
- package/src/engine/models.ts +42 -0
- package/src/engine/oauth.ts +196 -0
- package/src/engine/pi-mono-names.ts +13 -0
- package/src/engine/prompt-templates.ts +1 -0
- package/src/engine/provider-payload.ts +272 -0
- package/src/engine/select-list.ts +53 -0
- package/src/engine/session.ts +834 -0
- package/src/engine/strip-tokenizer-sentinels.ts +125 -0
- package/src/engine/truncate.ts +9 -0
- package/src/engine/tui.ts +111 -0
- package/src/engine/types.ts +63 -0
- package/src/engine/worker-events.ts +100 -0
- package/src/engine/worker-runtime-capabilities.ts +5 -0
- package/src/engine/worker-runtime.ts +1022 -0
- package/src/engine/worker-tools.ts +208 -0
- package/src/entry/boot-options.ts +50 -0
- package/src/entry/orchestrator.ts +2011 -0
- package/src/interactive/application-controller.ts +433 -0
- package/src/interactive/bus-notices.ts +265 -0
- package/src/interactive/chat-loop-messages.ts +626 -0
- package/src/interactive/chat-loop-policy.ts +18 -0
- package/src/interactive/chat-loop.ts +1090 -0
- package/src/interactive/chat-panel.ts +1820 -0
- package/src/interactive/chat-renderer.ts +1228 -0
- package/src/interactive/clio-editor.ts +161 -0
- package/src/interactive/command-fallbacks.ts +23 -0
- package/src/interactive/command-output.ts +85 -0
- package/src/interactive/context-activity.ts +203 -0
- package/src/interactive/context-meter.ts +223 -0
- package/src/interactive/context-overlay.ts +246 -0
- package/src/interactive/cost-overlay.ts +279 -0
- package/src/interactive/dispatch-board.ts +1319 -0
- package/src/interactive/dispatch-steering.ts +86 -0
- package/src/interactive/editor-bash.ts +46 -0
- package/src/interactive/editor-steer.ts +51 -0
- package/src/interactive/editor-submit.ts +507 -0
- package/src/interactive/export-html/ansi-to-html.ts +161 -0
- package/src/interactive/export-html/index.ts +51 -0
- package/src/interactive/export-html/template.ts +45 -0
- package/src/interactive/export-html/tool-renderer.ts +54 -0
- package/src/interactive/external-editor.ts +67 -0
- package/src/interactive/follow-up-queue-panel.ts +75 -0
- package/src/interactive/footer/dashboard.ts +538 -0
- package/src/interactive/footer/notifications.ts +275 -0
- package/src/interactive/footer/widgets.ts +1264 -0
- package/src/interactive/footer-panel.ts +187 -0
- package/src/interactive/format-time.ts +97 -0
- package/src/interactive/index.ts +11 -0
- package/src/interactive/interactive-application.ts +939 -0
- package/src/interactive/interactive-event-projection.ts +385 -0
- package/src/interactive/interactive-input-runtime.ts +307 -0
- package/src/interactive/interactive-presentation.ts +584 -0
- package/src/interactive/interactive-shell.ts +388 -0
- package/src/interactive/interactive-slash-runtime.ts +554 -0
- package/src/interactive/interactive-subscriptions.ts +111 -0
- package/src/interactive/interactive-tickers.ts +183 -0
- package/src/interactive/keybinding-manager.ts +496 -0
- package/src/interactive/layout.ts +57 -0
- package/src/interactive/leader-key.ts +143 -0
- package/src/interactive/loop-guard-interrupt.ts +101 -0
- package/src/interactive/memory-overlay.ts +280 -0
- package/src/interactive/overlay-ask-user-lifecycle.ts +110 -0
- package/src/interactive/overlay-auth-lifecycle.ts +308 -0
- package/src/interactive/overlay-frame.ts +502 -0
- package/src/interactive/overlay-general-openers.ts +274 -0
- package/src/interactive/overlay-key-routing.ts +216 -0
- package/src/interactive/overlay-lifecycle.ts +426 -0
- package/src/interactive/overlay-model-selectors.ts +127 -0
- package/src/interactive/overlay-permission-lifecycle.ts +345 -0
- package/src/interactive/overlay-resource-openers.ts +112 -0
- package/src/interactive/overlay-session-lifecycle.ts +327 -0
- package/src/interactive/overlay-transitions.ts +57 -0
- package/src/interactive/overlays/agents.ts +69 -0
- package/src/interactive/overlays/ask-user.ts +921 -0
- package/src/interactive/overlays/auth-dialog.ts +221 -0
- package/src/interactive/overlays/context-reset.ts +108 -0
- package/src/interactive/overlays/cwd-fallback.ts +141 -0
- package/src/interactive/overlays/decisions.ts +300 -0
- package/src/interactive/overlays/extensions.ts +54 -0
- package/src/interactive/overlays/help-reference.ts +145 -0
- package/src/interactive/overlays/interop.ts +139 -0
- package/src/interactive/overlays/keybinding-detail.ts +90 -0
- package/src/interactive/overlays/list-overlay.ts +606 -0
- package/src/interactive/overlays/message-picker.ts +155 -0
- package/src/interactive/overlays/model-selector.ts +1109 -0
- package/src/interactive/overlays/prompts.ts +72 -0
- package/src/interactive/overlays/session-selector-search.ts +31 -0
- package/src/interactive/overlays/session-selector.ts +284 -0
- package/src/interactive/overlays/settings.ts +3451 -0
- package/src/interactive/overlays/skills-hub.ts +237 -0
- package/src/interactive/overlays/tree-selector.ts +422 -0
- package/src/interactive/permission-overlay.ts +170 -0
- package/src/interactive/render-trace.ts +534 -0
- package/src/interactive/renderers/branch-summary.ts +86 -0
- package/src/interactive/renderers/code-ink.ts +420 -0
- package/src/interactive/renderers/compaction-summary.ts +112 -0
- package/src/interactive/renderers/diff.ts +137 -0
- package/src/interactive/renderers/mermaid.ts +53 -0
- package/src/interactive/renderers/notice.ts +28 -0
- package/src/interactive/renderers/retry-status.ts +31 -0
- package/src/interactive/renderers/structured.ts +89 -0
- package/src/interactive/renderers/tool-execution.ts +1357 -0
- package/src/interactive/renderers/worker-entry.ts +280 -0
- package/src/interactive/session-last-turn.ts +186 -0
- package/src/interactive/session-switch-settlement.ts +10 -0
- package/src/interactive/session-transcript.ts +105 -0
- package/src/interactive/session-usage-reseed.ts +74 -0
- package/src/interactive/slash-autocomplete.ts +256 -0
- package/src/interactive/slash-commands.ts +1407 -0
- package/src/interactive/slash-spec.ts +424 -0
- package/src/interactive/status/controller.ts +260 -0
- package/src/interactive/status/index.ts +23 -0
- package/src/interactive/status/state-machine.ts +530 -0
- package/src/interactive/status/summary.ts +170 -0
- package/src/interactive/status/types.ts +121 -0
- package/src/interactive/status/verbs.ts +163 -0
- package/src/interactive/status/watchdog.ts +24 -0
- package/src/interactive/stdout-backpressure.ts +99 -0
- package/src/interactive/stream-pacer.ts +530 -0
- package/src/interactive/stream-pacing-policy.ts +66 -0
- package/src/interactive/tasks-overlay.ts +540 -0
- package/src/interactive/terminal-lease.ts +485 -0
- package/src/interactive/theme/components.ts +55 -0
- package/src/interactive/theme/glyphs.ts +70 -0
- package/src/interactive/theme/index.ts +15 -0
- package/src/interactive/theme/labels.ts +88 -0
- package/src/interactive/theme/rules.ts +138 -0
- package/src/interactive/theme/segments.ts +88 -0
- package/src/interactive/theme/tokens.ts +121 -0
- package/src/interactive/thinking-level-policy.ts +6 -0
- package/src/interactive/tool-prose-loop.ts +122 -0
- package/src/interactive/tool-prose-registration.ts +48 -0
- package/src/interactive/turn-context.ts +855 -0
- package/src/interactive/turn-middleware.ts +283 -0
- package/src/interactive/turn-persistence.ts +458 -0
- package/src/interactive/turn-queues.ts +181 -0
- package/src/interactive/turn-recovery.ts +323 -0
- package/src/interactive/turn-runtime.ts +869 -0
- package/src/interactive/turn-state.ts +133 -0
- package/src/interactive/view/artifacts.ts +1452 -0
- package/src/interactive/view/view-overlay.ts +735 -0
- package/src/interactive/welcome-dashboard.ts +537 -0
- package/src/interactive/worker-receipts.ts +143 -0
- package/src/interactive/worker-replay.ts +111 -0
- package/src/interactive/worker-share.ts +100 -0
- package/src/interactive/worker-stream.ts +446 -0
- package/src/interactive/workspace-facts.ts +141 -0
- package/src/tools/agent-tools.ts +402 -0
- package/src/tools/artifact.ts +103 -0
- package/src/tools/ask-user.ts +666 -0
- package/src/tools/bash.ts +204 -0
- package/src/tools/bootstrap.ts +77 -0
- package/src/tools/builtin-tool-catalog.ts +265 -0
- package/src/tools/codewiki/code-nav-surface.ts +29 -0
- package/src/tools/codewiki/code-nav.ts +549 -0
- package/src/tools/codewiki/shared.ts +118 -0
- package/src/tools/compete-worktrees.ts +898 -0
- package/src/tools/context/docs-engine.ts +630 -0
- package/src/tools/context/index.ts +514 -0
- package/src/tools/context/surface.ts +19 -0
- package/src/tools/core-bootstrap.ts +168 -0
- package/src/tools/credential-present.ts +151 -0
- package/src/tools/dispatch-admission.ts +533 -0
- package/src/tools/dispatch-arguments.ts +228 -0
- package/src/tools/dispatch-background.ts +54 -0
- package/src/tools/dispatch-event-text.ts +6 -0
- package/src/tools/dispatch-plan.ts +746 -0
- package/src/tools/dispatch-run-events.ts +238 -0
- package/src/tools/dispatch-runner.ts +2370 -0
- package/src/tools/dispatch-scout-admission.ts +295 -0
- package/src/tools/dispatch-scout.ts +461 -0
- package/src/tools/dispatch-types.ts +77 -0
- package/src/tools/dispatch.ts +244 -0
- package/src/tools/edit-diff.ts +534 -0
- package/src/tools/edit.ts +128 -0
- package/src/tools/executables.ts +22 -0
- package/src/tools/file-mutation-queue.ts +35 -0
- package/src/tools/find.ts +294 -0
- package/src/tools/grep.ts +495 -0
- package/src/tools/ignore-policy.ts +169 -0
- package/src/tools/lazy-tool.ts +60 -0
- package/src/tools/ledger.ts +206 -0
- package/src/tools/ls.ts +110 -0
- package/src/tools/monitor-surface.ts +36 -0
- package/src/tools/monitor.ts +767 -0
- package/src/tools/network-policy.ts +29 -0
- package/src/tools/observation.ts +421 -0
- package/src/tools/observers.ts +99 -0
- package/src/tools/path-utils.ts +79 -0
- package/src/tools/policy.ts +154 -0
- package/src/tools/profiles.ts +108 -0
- package/src/tools/read.ts +173 -0
- package/src/tools/registry.ts +1169 -0
- package/src/tools/result-shaping.ts +204 -0
- package/src/tools/safe-exec.ts +128 -0
- package/src/tools/spawn-hygiene.ts +182 -0
- package/src/tools/steer-surface.ts +17 -0
- package/src/tools/steer.ts +77 -0
- package/src/tools/tasks.ts +265 -0
- package/src/tools/truncate-utf8.ts +17 -0
- package/src/tools/truncate.ts +43 -0
- package/src/tools/verify/frontend.ts +687 -0
- package/src/tools/verify/index.ts +31 -0
- package/src/tools/verify/scripts.ts +108 -0
- package/src/tools/verify/surface.ts +46 -0
- package/src/tools/web-fetch-surface.ts +23 -0
- package/src/tools/web-fetch.ts +759 -0
- package/src/tools/worker-evidence.ts +215 -0
- package/src/tools/write.ts +57 -0
- package/src/utils/exif-orientation.ts +192 -0
- package/src/utils/git.ts +24 -0
- package/src/utils/image-resize.ts +148 -0
- package/src/utils/photon.ts +103 -0
- package/src/worker/control-lane.ts +22 -0
- package/src/worker/entry.ts +294 -0
- package/src/worker/event-projection.ts +56 -0
- package/src/worker/heartbeat.ts +24 -0
- package/src/worker/ledger-mirror.ts +95 -0
- package/src/worker/ndjson.ts +40 -0
- package/src/worker/protocol.ts +529 -0
- package/src/worker/resource-facts.ts +79 -0
- package/src/worker/runtime-registry.ts +12 -0
- package/src/worker/spec-contract.ts +675 -0
- package/src/worker/stdin-demux.ts +316 -0
- package/dist/acp-UMLFVA3F.js +0 -92
- package/dist/chunk-262G75JS.js +0 -35
- package/dist/chunk-6CWDTGUC.js +0 -20
- package/dist/chunk-BYEU76JP.js +0 -899
- package/dist/chunk-GNGDQYDU.js +0 -34688
- package/dist/chunk-GOTUR54M.js +0 -9
- package/dist/chunk-NI4DEJMC.js +0 -88
- package/dist/chunk-TPOCL34A.js +0 -2876
- package/dist/chunk-UU3R62TT.js +0 -128
- package/dist/clio-KIQ5SNDS.js +0 -53
- package/dist/configure-G4E3A2PG.js +0 -27
- package/dist/doctor-M5HJJZOL.js +0 -61
- package/dist/domains/agents/builtins/architect.md +0 -33
- package/dist/domains/agents/builtins/coder.md +0 -31
- package/dist/domains/agents/builtins/context-bootstrap.md +0 -38
- package/dist/domains/agents/builtins/debugger.md +0 -30
- package/dist/domains/agents/builtins/documenter.md +0 -31
- package/dist/domains/agents/builtins/git-master.md +0 -30
- package/dist/domains/agents/builtins/provenance.md +0 -30
- package/dist/domains/agents/builtins/researcher.md +0 -71
- package/dist/domains/agents/builtins/scout.md +0 -42
- package/dist/domains/agents/builtins/tester.md +0 -31
- package/dist/domains/agents/builtins/verifier.md +0 -30
- package/dist/domains/agents/builtins/wiki-writer.md +0 -41
- package/dist/fleet-preflight-CLIAX7YR.js +0 -21
- package/dist/preload-KZVHET2B.js +0 -11
- package/dist/version-ROZ6CZKH.js +0 -16
- package/docs/html/agents_blueprint.html +0 -936
- package/docs/html/alcf_blueprint.html +0 -324
- package/docs/html/architecture_blueprint.html +0 -850
- package/docs/html/commands_blueprint.html +0 -794
- package/docs/html/config_knobs_audit_blueprint.html +0 -178
- package/docs/html/configuration_blueprint.html +0 -1080
- package/docs/html/context_blueprint.html +0 -603
- package/docs/html/documentation_blueprint.html +0 -832
- package/docs/html/environment_blueprint.html +0 -404
- package/docs/html/eval_blueprint.html +0 -743
- package/docs/html/evals_internal_blueprint.html +0 -190
- package/docs/html/evolution_blueprint.html +0 -674
- package/docs/html/extensions_blueprint.html +0 -2065
- package/docs/html/fleet_dispatch_blueprint.html +0 -286
- package/docs/html/index.html +0 -919
- package/docs/html/lifecycle_blueprint.html +0 -723
- package/docs/html/memory_blueprint.html +0 -699
- package/docs/html/middleware_blueprint.html +0 -664
- package/docs/html/models_blueprint.html +0 -2366
- package/docs/html/observability_blueprint.html +0 -683
- package/docs/html/provider_adapter_blueprint.html +0 -245
- package/docs/html/safety_blueprint.html +0 -1386
- package/docs/html/shared.css +0 -571
- package/docs/html/shared.js +0 -143
- package/docs/html/skills_blueprint.html +0 -671
- package/docs/html/soak_blueprint.html +0 -182
- package/docs/html/tool_usage_blueprint.html +0 -350
- package/docs/html/tools_blueprint.html +0 -2249
- package/docs/html/trace_blueprint.html +0 -235
- package/docs/html/tui_design_blueprint.html +0 -314
- package/docs/html/validation_blueprint.html +0 -961
- package/docs/html/worker_dispatch_blueprint.html +0 -231
- package/skills/git/commit-crafting/SKILL.md +0 -79
- package/skills/git/commit-crafting/evals.md +0 -92
- package/skills/git/create-pr/SKILL.md +0 -116
- package/skills/git/create-pr/evals.md +0 -114
- package/skills/git/investigate-issue/SKILL.md +0 -139
- package/skills/git/investigate-issue/evals.md +0 -94
- package/skills/git/review-changes/SKILL.md +0 -103
- package/skills/git/review-changes/evals.md +0 -85
|
@@ -0,0 +1,1820 @@
|
|
|
1
|
+
import { performance } from "node:perf_hooks";
|
|
2
|
+
import type { OutputVerbosity } from "../core/defaults.js";
|
|
3
|
+
import { SKILL_SUGGESTION_PREFIX } from "../core/skill-activation.js";
|
|
4
|
+
import { rawDurationMs } from "../core/timers.js";
|
|
5
|
+
import { estimateReasoningTextTokens, extractReasoningTokens } from "../domains/session/context-accounting.js";
|
|
6
|
+
import { type Component, Markdown, truncateToWidth, wrapTextWithAnsi } from "../engine/tui.js";
|
|
7
|
+
import type { AgentMessage } from "../engine/types.js";
|
|
8
|
+
import type { ChatLoopEvent, RetryStatusPayload } from "./chat-loop.js";
|
|
9
|
+
import { extractText, isSelfExplainingAbort } from "./chat-loop-messages.js";
|
|
10
|
+
import type { ApprovalRequestView } from "./permission-overlay.js";
|
|
11
|
+
import { codeInk } from "./renderers/code-ink.js";
|
|
12
|
+
import { createMermaidMarkdownTransform } from "./renderers/mermaid.js";
|
|
13
|
+
import { styleTaggedNotice } from "./renderers/notice.js";
|
|
14
|
+
import { formatRetryStatus } from "./renderers/retry-status.js";
|
|
15
|
+
import {
|
|
16
|
+
classifyResourceRead,
|
|
17
|
+
renderToolAwaitingApproval,
|
|
18
|
+
renderToolCallHeader,
|
|
19
|
+
renderToolExecution,
|
|
20
|
+
renderToolRunningStatus,
|
|
21
|
+
renderToolStreamingExecution,
|
|
22
|
+
renderToolSubline,
|
|
23
|
+
} from "./renderers/tool-execution.js";
|
|
24
|
+
import { renderWorkerEntryLines } from "./renderers/worker-entry.js";
|
|
25
|
+
import { INLINE_STATUS_INDENT_COLS, type StatusPhase, type VerbRender } from "./status/index.js";
|
|
26
|
+
import { clioTheme, fgSequence, GLYPH, markdownTheme, SGR_DIM, SGR_RESET } from "./theme/index.js";
|
|
27
|
+
import { type WorkerEntryState, workerAskedByModel } from "./worker-stream.js";
|
|
28
|
+
|
|
29
|
+
// Fenced code reaches the screen through pi-tui's Markdown component, which
|
|
30
|
+
// exposes the MarkdownTheme.highlightCode hook: it hands over the raw fence
|
|
31
|
+
// text plus its language tag before pi-tui draws the fence borders and indent.
|
|
32
|
+
// Wiring code ink through that hook colors only the ink, so the fence frame,
|
|
33
|
+
// indentation, and width behavior stay pi-tui's and nothing post-processes
|
|
34
|
+
// already-rendered output.
|
|
35
|
+
const CHAT_MARKDOWN_THEME = markdownTheme(clioTheme(), (code, lang) => codeInk(lang, code.split("\n")));
|
|
36
|
+
const CHAT_MARKDOWN_OPTIONS = {
|
|
37
|
+
transform: createMermaidMarkdownTransform(clioTheme()),
|
|
38
|
+
renderLatex: true,
|
|
39
|
+
} as const;
|
|
40
|
+
// TuiAltScreen uses Pi's OSC 133 prompt-start marker for semantic prompt
|
|
41
|
+
// navigation. The sequence is zero-width and stripped before terminal output.
|
|
42
|
+
const OSC133_PROMPT_START = "\x1b]133;A\x07";
|
|
43
|
+
|
|
44
|
+
// Prefix and rail SGR constants, previously re-exported by the deleted
|
|
45
|
+
// palette.ts. Composing them from fgSequence/GLYPH here yields byte-identical
|
|
46
|
+
// sequences to what palette.js produced, so the transcript renders unchanged.
|
|
47
|
+
const RESET = SGR_RESET;
|
|
48
|
+
const DIM = SGR_DIM;
|
|
49
|
+
const TEAL = fgSequence("accent");
|
|
50
|
+
const BLUE_REASON = fgSequence("reason");
|
|
51
|
+
const GREEN_OK = fgSequence("success");
|
|
52
|
+
const AMBER = fgSequence("warning");
|
|
53
|
+
const RED_CRIT = fgSequence("error");
|
|
54
|
+
const AGENT_GLYPH = GLYPH.agent;
|
|
55
|
+
const USER_GLYPH = GLYPH.user;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* An assistant turn is a sequence of text and tool segments interleaved in
|
|
59
|
+
* pi-agent-core event order. A tool turn emits assistant `message_update`
|
|
60
|
+
* events carrying `toolcall_*` formation, then `message_end`, then the
|
|
61
|
+
* `tool_execution_*` lifecycle before the next assistant message. Tool calls
|
|
62
|
+
* therefore sit BETWEEN the assistant's pre-tool narration and the post-tool summary. Storing a flat
|
|
63
|
+
* `text` buffer + `tools[]` array (pre-refactor) collapsed that order: all
|
|
64
|
+
* text across the turn concatenated into one line with every tool block
|
|
65
|
+
* appended at the end. The segment list preserves the stream order instead.
|
|
66
|
+
*
|
|
67
|
+
* Each text segment tracks whether it has been finalized by a `message_end`.
|
|
68
|
+
* Streaming deltas render as plain lines; only finalized text is piped
|
|
69
|
+
* through the Markdown renderer. Partial markdown (unclosed fence, half-typed
|
|
70
|
+
* bullet) would otherwise paint garbage at ~60 fps under streaming.
|
|
71
|
+
*/
|
|
72
|
+
export type ReasoningTokenProvenance = "provider" | "estimated" | "mixed";
|
|
73
|
+
|
|
74
|
+
export interface ChatPanelTurnUsage {
|
|
75
|
+
inputTokens: number;
|
|
76
|
+
outputTokens: number;
|
|
77
|
+
cacheReadTokens: number;
|
|
78
|
+
cacheWriteTokens: number;
|
|
79
|
+
reasoningTokens?: number;
|
|
80
|
+
reasoningTokenProvenance?: ReasoningTokenProvenance;
|
|
81
|
+
/** Model calls the totals were summed over; absent when the count is unknown. */
|
|
82
|
+
modelCalls?: number;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface ChatPanelRenderMetrics {
|
|
86
|
+
durationMs: number;
|
|
87
|
+
cacheHit: boolean;
|
|
88
|
+
entriesRendered: number;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
type TextSegment = {
|
|
92
|
+
kind: "text";
|
|
93
|
+
text: string;
|
|
94
|
+
finalized: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Lazy pi-tui Markdown instance owned by the segment. Markdown caches its
|
|
97
|
+
* output by (text, width) internally, so reusing the instance keeps the
|
|
98
|
+
* per-frame cost O(1) for stable segments and lets the active entry
|
|
99
|
+
* invalidate only the tail segment's cache on re-canonicalization.
|
|
100
|
+
*/
|
|
101
|
+
md?: Markdown;
|
|
102
|
+
/**
|
|
103
|
+
* Wrapped output for every source line except the last, plus the width and
|
|
104
|
+
* source-line count it was built at. A streaming segment only ever grows at
|
|
105
|
+
* its tail: source lines before the last one are terminated by a newline and
|
|
106
|
+
* can never change, yet every frame re-wrapped all of them. On a 16k-char
|
|
107
|
+
* answer that was 5-22 ms per frame to reproduce identical rows.
|
|
108
|
+
*/
|
|
109
|
+
wrapCache?: { width: number; completedLines: number; lines: string[] };
|
|
110
|
+
/**
|
|
111
|
+
* A protocol suggestion line and the answer prose beneath it, when the model
|
|
112
|
+
* opened its reply with both in one segment (which is what the skills prompt
|
|
113
|
+
* asks for). The answer half is a segment of its own so it keeps its own
|
|
114
|
+
* Markdown and wrap caches; it is cached here rather than rebuilt per frame.
|
|
115
|
+
*/
|
|
116
|
+
suggestionSplit?: { suggestion: string; answer: TextSegment };
|
|
117
|
+
};
|
|
118
|
+
type ToolSegment = {
|
|
119
|
+
kind: "tool";
|
|
120
|
+
id: string;
|
|
121
|
+
name: string;
|
|
122
|
+
args: unknown;
|
|
123
|
+
/** Final result from `tool_execution_end`; undefined while the call is in flight. */
|
|
124
|
+
result?: unknown;
|
|
125
|
+
/** True once `tool_execution_end` has landed (success or error). */
|
|
126
|
+
finished: boolean;
|
|
127
|
+
/** Pi streamed the call row before execution and later starts this same row. */
|
|
128
|
+
executionStarted: boolean;
|
|
129
|
+
/** The assistant stream closed this call's argument block. */
|
|
130
|
+
argsComplete: boolean;
|
|
131
|
+
/**
|
|
132
|
+
* True when the segment was force-settled without its own end event
|
|
133
|
+
* (blocked at admission, aborted mid-batch, id reused). A late
|
|
134
|
+
* `tool_execution_end` may upgrade such a segment with the call's true
|
|
135
|
+
* result; a segment finished by its own end event is never overwritten.
|
|
136
|
+
* The explicit `| undefined` allows the upgrade path to clear the flag
|
|
137
|
+
* under `exactOptionalPropertyTypes: true`.
|
|
138
|
+
*/
|
|
139
|
+
settledWithoutResult?: boolean | undefined;
|
|
140
|
+
/** True when the finished result was an error. Meaningful only after `finished`. */
|
|
141
|
+
isError: boolean;
|
|
142
|
+
/** When true, render the full structured block instead of the collapsed subline. */
|
|
143
|
+
expanded: boolean;
|
|
144
|
+
/** Wall-clock start time captured by the chat panel for live duration display. */
|
|
145
|
+
startedAtMs?: number;
|
|
146
|
+
/** Completed call duration in milliseconds (event-supplied or measured locally). */
|
|
147
|
+
durationMs?: number;
|
|
148
|
+
/** Persisted result summary (bytes, truncated, offloadPath, observation) from the chat loop. */
|
|
149
|
+
resultSummary?: Record<string, unknown> | undefined;
|
|
150
|
+
/**
|
|
151
|
+
* Latest cumulative Pi result from `tool_execution_update`, including the
|
|
152
|
+
* display content and structured progress details. Cleared
|
|
153
|
+
* back to `undefined` on `tool_execution_end` so the finished `result`
|
|
154
|
+
* takes over. Only consumed when `!finished && expanded`. The explicit
|
|
155
|
+
* `| undefined` is required under `exactOptionalPropertyTypes: true` so
|
|
156
|
+
* the clear path can re-assign `undefined` without a `delete`.
|
|
157
|
+
*/
|
|
158
|
+
partialResult?: unknown;
|
|
159
|
+
/**
|
|
160
|
+
* True while the call is parked at the permission gate. Set/cleared by
|
|
161
|
+
* `tool_approval_state` events and cleared by any settle so a denied or
|
|
162
|
+
* resumed call never keeps the awaiting-approval styling. Meaningful only
|
|
163
|
+
* while `!finished`.
|
|
164
|
+
*/
|
|
165
|
+
awaitingApproval?: boolean | undefined;
|
|
166
|
+
/** Live, redacted approval facts. Never reconstructed during replay. */
|
|
167
|
+
approvalView?: ApprovalRequestView | undefined;
|
|
168
|
+
settlement?: "blocked" | "aborted" | "orphaned" | undefined;
|
|
169
|
+
/**
|
|
170
|
+
* The admission verdict's short reason, present only on a settlement the
|
|
171
|
+
* registry actually rejected. A blocked row without it states that something
|
|
172
|
+
* was refused and leaves the operator no way to learn why.
|
|
173
|
+
*/
|
|
174
|
+
blockReason?: string | undefined;
|
|
175
|
+
/** View-only marker: historical calls render mutation diffs without live color. */
|
|
176
|
+
replayed?: true;
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* A turn's terminal-error marker (`[error] ...`, `[aborted] ...`,
|
|
180
|
+
* `[stopped: length] ...`) carried as its own segment so it renders in the
|
|
181
|
+
* error token instead of being piped through Markdown as plain prose. Kept
|
|
182
|
+
* distinct from streamed text: the error text is not model output, it is Clio
|
|
183
|
+
* reporting why the turn ended.
|
|
184
|
+
*/
|
|
185
|
+
type ErrorSegment = {
|
|
186
|
+
kind: "error";
|
|
187
|
+
text: string;
|
|
188
|
+
};
|
|
189
|
+
type AssistantSegment = TextSegment | ToolSegment | ErrorSegment;
|
|
190
|
+
type ReplayBlockRenderer = (width: number) => string[];
|
|
191
|
+
type AssistantStatusLine = { phase: StatusPhase; verb: string; toneHint: VerbRender["toneHint"] };
|
|
192
|
+
|
|
193
|
+
type TranscriptEntry =
|
|
194
|
+
| { role: "user"; text: string }
|
|
195
|
+
| { role: "retryStatus"; status: RetryStatusPayload }
|
|
196
|
+
| {
|
|
197
|
+
role: "assistant";
|
|
198
|
+
segments: AssistantSegment[];
|
|
199
|
+
/**
|
|
200
|
+
* Raw thinking content from `thinking_delta` events plus
|
|
201
|
+
* `thinking` blocks captured on `message_end`. Renders live while
|
|
202
|
+
* the turn is pending: a folded `Thinking (N tokens)…` marker by
|
|
203
|
+
* default, or the tail of the reasoning down a dim `│ ` rail if
|
|
204
|
+
* expanded via `toggleLastThinking()` (Ctrl+T). Once the turn
|
|
205
|
+
* settles it collapses to a static `Thinking...` marker (folded) or
|
|
206
|
+
* a head-anchored rail (expanded), mirroring the pi-coding-agent
|
|
207
|
+
* reference which streams thinking from the partial message.
|
|
208
|
+
*/
|
|
209
|
+
thinking: string;
|
|
210
|
+
/**
|
|
211
|
+
* Whether the thinking block renders as the full body (true) or
|
|
212
|
+
* the one-line dim marker (false/undefined). Toggled by
|
|
213
|
+
* `toggleLastThinking()`. New thinking inherits the panel-level
|
|
214
|
+
* visibility mode until Ctrl+T toggles it again.
|
|
215
|
+
*/
|
|
216
|
+
expandedThinking?: boolean;
|
|
217
|
+
pending: boolean;
|
|
218
|
+
statusLine?: AssistantStatusLine | null | undefined;
|
|
219
|
+
isError: boolean;
|
|
220
|
+
turnUsage?: ChatPanelTurnUsage;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* A dispatched worker's attributed block. The panel owns only the fold
|
|
224
|
+
* state; `state` is the live object the worker-stream reducer mutates, so a
|
|
225
|
+
* streaming delta reaches the screen without copying the entry per frame.
|
|
226
|
+
* The panel is told when that happened through `applyWorkerState`.
|
|
227
|
+
*/
|
|
228
|
+
| { role: "worker"; state: WorkerEntryState; folded: boolean }
|
|
229
|
+
/**
|
|
230
|
+
* A block the caller renders itself. Most are settled the moment they are
|
|
231
|
+
* appended, but a few (the operator's `!` bash row) keep mutating the state
|
|
232
|
+
* their closure reads until the work behind them finishes. Such a block
|
|
233
|
+
* declares `isLive`, which keeps it out of the frozen prefix and keeps the
|
|
234
|
+
* panel's time-keyed tick running while it is unsettled.
|
|
235
|
+
*/
|
|
236
|
+
| { role: "replayBlock"; renderBlock: ReplayBlockRenderer; isLive?: (() => boolean) | undefined };
|
|
237
|
+
|
|
238
|
+
type WorkerTranscriptEntry = Extract<TranscriptEntry, { role: "worker" }>;
|
|
239
|
+
|
|
240
|
+
export interface ChatPanel extends Component {
|
|
241
|
+
appendUser(text: string): void;
|
|
242
|
+
/**
|
|
243
|
+
* Append a caller-rendered block. Pass `isLive` when the closure reads state
|
|
244
|
+
* that keeps changing after the append, so the panel keeps re-rendering it
|
|
245
|
+
* instead of treating the first frame as final.
|
|
246
|
+
*/
|
|
247
|
+
appendReplayBlock(renderBlock: ReplayBlockRenderer, isLive?: () => boolean): void;
|
|
248
|
+
applyEvent(event: ChatLoopEvent): void;
|
|
249
|
+
/** Mark a just-rehydrated tool segment so its mutation diff remains plain. */
|
|
250
|
+
markToolReplayed?(toolCallId: string): void;
|
|
251
|
+
/**
|
|
252
|
+
* Place or refresh a worker's block. The first call for an assignment
|
|
253
|
+
* inserts the entry (agent origin nests under the tool segment named by
|
|
254
|
+
* `state.parentToolCallId`, everything else appends); later calls only
|
|
255
|
+
* invalidate the render, because the reducer mutates the same state object.
|
|
256
|
+
*/
|
|
257
|
+
applyWorkerState(state: WorkerEntryState): void;
|
|
258
|
+
/**
|
|
259
|
+
* Every worker block on the transcript, oldest first, live or replayed.
|
|
260
|
+
* `/share` selects from this rather than from the reducer's routing table,
|
|
261
|
+
* so what the operator can share is exactly what the operator can see.
|
|
262
|
+
*/
|
|
263
|
+
workerStates(): ReadonlyArray<WorkerEntryState>;
|
|
264
|
+
setStatusLine(line: AssistantStatusLine | null): void;
|
|
265
|
+
toggleLastToolExpanded(): boolean;
|
|
266
|
+
toggleAllToolsExpanded(): boolean;
|
|
267
|
+
/**
|
|
268
|
+
* Force every tool segment into its collapsed one-line form. Replay
|
|
269
|
+
* (`rehydrateChatPanelFromTurns`) calls this so a resumed or forked
|
|
270
|
+
* transcript uses compact ledger summaries instead of auto-expanding every
|
|
271
|
+
* historical non-resource call. Idempotent.
|
|
272
|
+
*/
|
|
273
|
+
collapseAllTools(): void;
|
|
274
|
+
/**
|
|
275
|
+
* Flip thinking-bearing assistant turns between the one-line dim marker
|
|
276
|
+
* and the full rail-prefixed body. The target visibility is panel-level
|
|
277
|
+
* sticky state, then applied to current thinking history so Ctrl+T behaves
|
|
278
|
+
* like a transcript-level thinking visibility toggle.
|
|
279
|
+
*/
|
|
280
|
+
toggleLastThinking(): boolean;
|
|
281
|
+
toggleAllThinking(): boolean;
|
|
282
|
+
/** Current panel-level live-thinking visibility used by presentation pacing. */
|
|
283
|
+
isThinkingExpanded(): boolean;
|
|
284
|
+
/** Toggle whether expanded live tool bodies include cumulative partial output. */
|
|
285
|
+
toggleLiveToolOutput(): boolean;
|
|
286
|
+
/** Clears the visible transcript. /new uses this after rotating the session. */
|
|
287
|
+
reset(): void;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export interface ChatPanelOptions {
|
|
291
|
+
/**
|
|
292
|
+
* Resolves the user-visible key string for the `clio.tool.expand`
|
|
293
|
+
* action, which folds and unfolds the newest tool call or worker block.
|
|
294
|
+
* Returning a non-empty string surfaces a dim ` (<key>)` hint on the one
|
|
295
|
+
* surface the key would act on: the latest finished collapsed tool subline,
|
|
296
|
+
* or the newest folded worker card. Returning undefined or an empty string
|
|
297
|
+
* suppresses the hint. Called per render so live keybinding changes flow
|
|
298
|
+
* through.
|
|
299
|
+
*/
|
|
300
|
+
getToolExpandKey?: () => string | undefined;
|
|
301
|
+
/** Live transcript detail mode. Settings changes take effect on the next frame. */
|
|
302
|
+
getOutputVerbosity?: () => OutputVerbosity;
|
|
303
|
+
/** Receives measured panel render cost; no FPS claim is made by the panel. */
|
|
304
|
+
onRenderMetrics?: (metrics: ChatPanelRenderMetrics) => void;
|
|
305
|
+
/** Clock injection for deterministic duration tests. Defaults to Date.now. */
|
|
306
|
+
now?: () => number;
|
|
307
|
+
/**
|
|
308
|
+
* Render every expanded tool body in full, without the live view's
|
|
309
|
+
* middle-elision or character truncation. `/export` builds a throwaway panel
|
|
310
|
+
* with this set so the written transcript reproduces the complete tool output
|
|
311
|
+
* instead of the terminal's bounded view.
|
|
312
|
+
*/
|
|
313
|
+
unboundedToolBodies?: boolean;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* An abort reaches the registry as a rejection, so it arrives here as a block
|
|
318
|
+
* whose reason names it (`run aborted before the operator decided`). Matching
|
|
319
|
+
* that is safe in a way that matching the tool's own output is not: this string
|
|
320
|
+
* is composed by Clio, never by the command that ran.
|
|
321
|
+
*/
|
|
322
|
+
const ABORTED_REASON_RE = /\babort(?:ed)?\b/i;
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Classify a finished tool call from the registry's admission verdict, which
|
|
326
|
+
* the turn runtime stamps onto the event as `outcome`. Only a call the registry
|
|
327
|
+
* refused settles as blocked; a call that executed and failed is an ordinary
|
|
328
|
+
* error and carries no settlement. Events with no verdict (replayed history, a
|
|
329
|
+
* surface that resolves tools without telemetry) settle as nothing rather than
|
|
330
|
+
* guessing.
|
|
331
|
+
*/
|
|
332
|
+
function toolSettlement(event: {
|
|
333
|
+
outcome?: unknown;
|
|
334
|
+
blockReason?: unknown;
|
|
335
|
+
}): { settlement: "blocked" | "aborted"; reason?: string } | undefined {
|
|
336
|
+
if (event.outcome !== "blocked") return undefined;
|
|
337
|
+
const reason = typeof event.blockReason === "string" && event.blockReason.trim().length > 0 ? event.blockReason : null;
|
|
338
|
+
const settlement = reason !== null && ABORTED_REASON_RE.test(reason) ? "aborted" : "blocked";
|
|
339
|
+
return reason === null ? { settlement } : { settlement, reason };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function extractAssistantText(message: unknown): string {
|
|
343
|
+
if (!message || typeof message !== "object" || !("role" in message) || message.role !== "assistant") return "";
|
|
344
|
+
if (!("content" in message) || !Array.isArray(message.content)) return "";
|
|
345
|
+
return message.content
|
|
346
|
+
.filter((item): item is { type: "text"; text: string } => item?.type === "text" && typeof item.text === "string")
|
|
347
|
+
.map((item) => item.text)
|
|
348
|
+
.join("");
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function extractAssistantThinking(message: unknown): string {
|
|
352
|
+
if (!message || typeof message !== "object" || !("role" in message) || message.role !== "assistant") return "";
|
|
353
|
+
if (!("content" in message) || !Array.isArray(message.content)) return "";
|
|
354
|
+
return message.content
|
|
355
|
+
.filter(
|
|
356
|
+
(item): item is { type: "thinking"; thinking: string } =>
|
|
357
|
+
item?.type === "thinking" && typeof item.thinking === "string",
|
|
358
|
+
)
|
|
359
|
+
.map((item) => item.thinking)
|
|
360
|
+
.join("");
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function finiteToken(value: unknown): number {
|
|
364
|
+
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, value) : 0;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function assistantUsage(message: unknown): ChatPanelTurnUsage | undefined {
|
|
368
|
+
if (!message || typeof message !== "object" || (message as { role?: unknown }).role !== "assistant") return undefined;
|
|
369
|
+
const record = message as Record<string, unknown>;
|
|
370
|
+
const usage = record.usage && typeof record.usage === "object" ? (record.usage as Record<string, unknown>) : undefined;
|
|
371
|
+
const thinking = extractAssistantThinking(message);
|
|
372
|
+
const reportedReasoning = usage ? extractReasoningTokens(usage) : null;
|
|
373
|
+
const estimatedReasoning = reportedReasoning === null ? estimateReasoningTextTokens(thinking) : null;
|
|
374
|
+
const inputTokens = finiteToken(usage?.input);
|
|
375
|
+
const outputTokens = finiteToken(usage?.output);
|
|
376
|
+
const cacheReadTokens = finiteToken(usage?.cacheRead);
|
|
377
|
+
const cacheWriteTokens = finiteToken(usage?.cacheWrite);
|
|
378
|
+
if (
|
|
379
|
+
inputTokens + outputTokens + cacheReadTokens + cacheWriteTokens === 0 &&
|
|
380
|
+
reportedReasoning === null &&
|
|
381
|
+
estimatedReasoning === null
|
|
382
|
+
) {
|
|
383
|
+
return undefined;
|
|
384
|
+
}
|
|
385
|
+
const turnUsage: ChatPanelTurnUsage = { inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, modelCalls: 1 };
|
|
386
|
+
if (reportedReasoning !== null) {
|
|
387
|
+
turnUsage.reasoningTokens = reportedReasoning;
|
|
388
|
+
turnUsage.reasoningTokenProvenance = "provider";
|
|
389
|
+
} else if (estimatedReasoning !== null) {
|
|
390
|
+
turnUsage.reasoningTokens = estimatedReasoning;
|
|
391
|
+
turnUsage.reasoningTokenProvenance = "estimated";
|
|
392
|
+
}
|
|
393
|
+
return turnUsage;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function aggregateAssistantUsage(messages: unknown): ChatPanelTurnUsage | undefined {
|
|
397
|
+
if (!Array.isArray(messages)) return undefined;
|
|
398
|
+
const total: ChatPanelTurnUsage = { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 };
|
|
399
|
+
let found = false;
|
|
400
|
+
let provider = false;
|
|
401
|
+
let estimated = false;
|
|
402
|
+
for (const message of messages) {
|
|
403
|
+
const usage = assistantUsage(message);
|
|
404
|
+
if (!usage) continue;
|
|
405
|
+
found = true;
|
|
406
|
+
total.modelCalls = (total.modelCalls ?? 0) + (usage.modelCalls ?? 1);
|
|
407
|
+
total.inputTokens += usage.inputTokens;
|
|
408
|
+
total.outputTokens += usage.outputTokens;
|
|
409
|
+
total.cacheReadTokens += usage.cacheReadTokens;
|
|
410
|
+
total.cacheWriteTokens += usage.cacheWriteTokens;
|
|
411
|
+
if (usage.reasoningTokens !== undefined) {
|
|
412
|
+
total.reasoningTokens = (total.reasoningTokens ?? 0) + usage.reasoningTokens;
|
|
413
|
+
if (usage.reasoningTokenProvenance === "provider") provider = true;
|
|
414
|
+
else estimated = true;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (!found) return undefined;
|
|
418
|
+
if (provider || estimated)
|
|
419
|
+
total.reasoningTokenProvenance = provider && estimated ? "mixed" : provider ? "provider" : "estimated";
|
|
420
|
+
return total;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function extractAssistantTerminalError(message: unknown): string {
|
|
424
|
+
if (!message || typeof message !== "object" || !("role" in message) || message.role !== "assistant") return "";
|
|
425
|
+
const stopReason = (message as { stopReason?: unknown }).stopReason;
|
|
426
|
+
if (stopReason !== "error" && stopReason !== "aborted" && stopReason !== "length") return "";
|
|
427
|
+
if (stopReason === "length") {
|
|
428
|
+
return "[stopped: length] Model target hit its generation/output limit before a complete response. This is not a safety denial. Continue with a shorter answer or lower thinking; use /context compact if the context meter is also near full.";
|
|
429
|
+
}
|
|
430
|
+
const raw = (message as { errorMessage?: unknown }).errorMessage;
|
|
431
|
+
if (isSelfExplainingAbort({ stopReason, errorMessage: raw, text: extractText(message as AgentMessage) })) return "";
|
|
432
|
+
const reason = typeof raw === "string" && raw.length > 0 ? raw : "unknown error";
|
|
433
|
+
return stopReason === "aborted" ? `[aborted] ${reason}` : `[error] ${reason}`;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function scopeTerminalErrorAfterSuccessfulTool(
|
|
437
|
+
entry: Extract<TranscriptEntry, { role: "assistant" }>,
|
|
438
|
+
terminalError: string,
|
|
439
|
+
): string {
|
|
440
|
+
if (!terminalError.startsWith("[error] ")) return terminalError;
|
|
441
|
+
const successfulTools = entry.segments.filter(
|
|
442
|
+
(segment): segment is ToolSegment => segment.kind === "tool" && segment.finished && !segment.isError,
|
|
443
|
+
);
|
|
444
|
+
if (successfulTools.length === 0) return terminalError;
|
|
445
|
+
|
|
446
|
+
const reason = terminalError.slice("[error] ".length);
|
|
447
|
+
const timedOut = /\b(?:timed?\s*out|timeout)\b/i.test(reason);
|
|
448
|
+
const modelFailure = timedOut
|
|
449
|
+
? "main model response timed out after successful tool result"
|
|
450
|
+
: `main model response failed after successful tool result: ${reason}`;
|
|
451
|
+
const detachedDispatchSucceeded = successfulTools.some(
|
|
452
|
+
(segment) =>
|
|
453
|
+
segment.name === "dispatch" &&
|
|
454
|
+
segment.args !== null &&
|
|
455
|
+
typeof segment.args === "object" &&
|
|
456
|
+
(segment.args as { detach?: unknown }).detach === true,
|
|
457
|
+
);
|
|
458
|
+
return `[error] ${modelFailure}${detachedDispatchSucceeded ? "; detached runs continue" : ""}`;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function hasVisibleOutput(entry: Extract<TranscriptEntry, { role: "assistant" }>): boolean {
|
|
462
|
+
for (const seg of entry.segments) {
|
|
463
|
+
if (seg.kind === "tool") return true;
|
|
464
|
+
if (seg.kind === "text" && seg.text.trim().length > 0) return true;
|
|
465
|
+
if (seg.kind === "error" && seg.text.trim().length > 0) return true;
|
|
466
|
+
}
|
|
467
|
+
return false;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/** Index of the newest assistant entry, optionally restricted to ones that rendered something. */
|
|
471
|
+
function lastAssistantIndex(
|
|
472
|
+
transcript: ReadonlyArray<TranscriptEntry>,
|
|
473
|
+
options: { withOutput?: boolean } = {},
|
|
474
|
+
): number | null {
|
|
475
|
+
for (let index = transcript.length - 1; index >= 0; index -= 1) {
|
|
476
|
+
const entry = transcript[index];
|
|
477
|
+
if (entry?.role !== "assistant") continue;
|
|
478
|
+
if (options.withOutput === true && !hasVisibleOutput(entry)) continue;
|
|
479
|
+
return index;
|
|
480
|
+
}
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function hasStreamingText(entry: Extract<TranscriptEntry, { role: "assistant" }>): boolean {
|
|
485
|
+
const tail = entry.segments[entry.segments.length - 1];
|
|
486
|
+
return tail?.kind === "text" && !tail.finalized && tail.text.trim().length > 0;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Split a reply that opens with the skill-suggestion protocol line into that
|
|
491
|
+
* line and the answer beneath it.
|
|
492
|
+
*
|
|
493
|
+
* The prompt asks the model to *begin its reply* with the suggestion, so the
|
|
494
|
+
* usual shape is one segment holding both. Classifying that whole segment as
|
|
495
|
+
* advisory left the turn with no voice glyph at all: the suggestion did not
|
|
496
|
+
* claim it and the answer never got the chance. Returns null when the segment
|
|
497
|
+
* is only a suggestion, which stays advisory in full.
|
|
498
|
+
*/
|
|
499
|
+
function skillSuggestionSplit(seg: TextSegment): { suggestion: string; answer: TextSegment } | null {
|
|
500
|
+
if (!seg.text.startsWith(SKILL_SUGGESTION_PREFIX)) return null;
|
|
501
|
+
const breakIndex = seg.text.indexOf("\n");
|
|
502
|
+
if (breakIndex < 0) return null;
|
|
503
|
+
// A model that puts a blank line between the suggestion and the answer left
|
|
504
|
+
// the remainder opening with a newline, so the first rendered row was empty
|
|
505
|
+
// and took the glyph the answer's own text row was owed.
|
|
506
|
+
const answerText = seg.text.slice(breakIndex + 1).replace(/^\n+/, "");
|
|
507
|
+
if (answerText.trim().length === 0) return null;
|
|
508
|
+
const split = seg.suggestionSplit ?? {
|
|
509
|
+
suggestion: "",
|
|
510
|
+
answer: { kind: "text", text: answerText, finalized: seg.finalized } as TextSegment,
|
|
511
|
+
};
|
|
512
|
+
seg.suggestionSplit = split;
|
|
513
|
+
split.suggestion = seg.text.slice(0, breakIndex);
|
|
514
|
+
const answer = split.answer;
|
|
515
|
+
if (answer.text === answerText && answer.finalized === seg.finalized) return split;
|
|
516
|
+
// The answer half follows the same cache rules as any other segment: the
|
|
517
|
+
// streaming wrap cache assumes append-only text, so a rewrite or a
|
|
518
|
+
// finalization drops it and a delta keeps it.
|
|
519
|
+
const appendOnly = !seg.finalized && answer.finalized === seg.finalized && answerText.startsWith(answer.text);
|
|
520
|
+
answer.text = answerText;
|
|
521
|
+
answer.finalized = seg.finalized;
|
|
522
|
+
if (!appendOnly) delete answer.wrapCache;
|
|
523
|
+
if (answer.md) answer.md.setText(answerText);
|
|
524
|
+
return split;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function renderTextSegmentLines(seg: TextSegment, width: number): string[] {
|
|
528
|
+
if (!seg.finalized) {
|
|
529
|
+
const source = seg.text.split("\n");
|
|
530
|
+
// The final element is the live tail; everything before it is newline-
|
|
531
|
+
// terminated and frozen.
|
|
532
|
+
const completedCount = source.length - 1;
|
|
533
|
+
const cache = seg.wrapCache;
|
|
534
|
+
const reusable = cache !== undefined && cache.width === width && cache.completedLines <= completedCount;
|
|
535
|
+
const completed = reusable ? cache.lines.slice() : [];
|
|
536
|
+
for (let i = reusable ? cache.completedLines : 0; i < completedCount; i += 1) {
|
|
537
|
+
for (const line of wrapTextWithAnsi(source[i] ?? "", width)) completed.push(line);
|
|
538
|
+
}
|
|
539
|
+
seg.wrapCache = { width, completedLines: completedCount, lines: completed };
|
|
540
|
+
const wrapped = completed.slice();
|
|
541
|
+
for (const line of wrapTextWithAnsi(source[completedCount] ?? "", width)) wrapped.push(line);
|
|
542
|
+
return wrapped;
|
|
543
|
+
}
|
|
544
|
+
if (!seg.md) {
|
|
545
|
+
seg.md = new Markdown(seg.text, 0, 0, CHAT_MARKDOWN_THEME, undefined, CHAT_MARKDOWN_OPTIONS);
|
|
546
|
+
}
|
|
547
|
+
// pi-tui Markdown right-pads lines to the render width. If a long streaming
|
|
548
|
+
// reply has already scrolled, flipping the finalized segment from unpadded
|
|
549
|
+
// plain text to padded Markdown changes historical rows and forces a full
|
|
550
|
+
// redraw on terminals that cannot clear scrollback. Trim only that render
|
|
551
|
+
// padding so finalized prose remains byte-stable with the streamed shape.
|
|
552
|
+
return seg.md.render(width).map((line) => line.replace(/ +$/, ""));
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Render a terminal-error segment in the error token. Terminal markers such as
|
|
557
|
+
* `[error] ...`, `[aborted] ...`, and `[stopped: length] ...` render as red
|
|
558
|
+
* message text rather than plain markdown, so a failed turn is visibly a
|
|
559
|
+
* failure. Each source line wraps to width and carries the error color.
|
|
560
|
+
*/
|
|
561
|
+
function renderErrorSegmentLines(seg: ErrorSegment, width: number): string[] {
|
|
562
|
+
const out: string[] = [];
|
|
563
|
+
for (const line of seg.text.split("\n")) {
|
|
564
|
+
for (const wrapped of wrapTextWithAnsi(line, width)) {
|
|
565
|
+
out.push(`${RED_CRIT}${wrapped}${RESET}`);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
return out;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const CLIO_PREFIX = `${TEAL}${AGENT_GLYPH}${RESET} `;
|
|
572
|
+
const CLIO_PREFIX_ERROR = `${RED_CRIT}${AGENT_GLYPH}${RESET} `;
|
|
573
|
+
const USER_PREFIX = `${TEAL}${USER_GLYPH}${RESET} `;
|
|
574
|
+
const PROSE_GUTTER = " ";
|
|
575
|
+
const PROSE_GUTTER_WIDTH = 2;
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Give transcript prose a fixed two-cell ownership gutter. Callers render the
|
|
579
|
+
* content at `width - 2`, then this function spends those reserved cells on
|
|
580
|
+
* either the turn glyph or a hanging indent. Tool ledgers never pass through
|
|
581
|
+
* here, so their existing full-width grammar remains untouched.
|
|
582
|
+
*/
|
|
583
|
+
function hangProseLines(lines: string[], firstPrefix?: string): string[] {
|
|
584
|
+
return lines.map((line, index) => `${index === 0 && firstPrefix !== undefined ? firstPrefix : PROSE_GUTTER}${line}`);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Static marker used when thinking is folded. This matches pi-coding-agent's
|
|
589
|
+
* hidden-thinking presentation and avoids previewing reasoning content.
|
|
590
|
+
*/
|
|
591
|
+
const THINKING_HIDDEN_LABEL = `Thinking${GLYPH.ellipsis}`;
|
|
592
|
+
const THINKING_LINE_LIMIT = 12;
|
|
593
|
+
const REASONING_CHARS_PER_TOKEN = 4;
|
|
594
|
+
|
|
595
|
+
function estimateThinkingTokens(thinking: string): number {
|
|
596
|
+
return estimateReasoningTextTokens(thinking) ?? Math.max(1, Math.round(thinking.length / REASONING_CHARS_PER_TOKEN));
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function reasoningDisplay(usage: ChatPanelTurnUsage | undefined, thinking: string): { tokens: number; marker: string } {
|
|
600
|
+
if (usage?.reasoningTokens !== undefined) {
|
|
601
|
+
return {
|
|
602
|
+
tokens: usage.reasoningTokens,
|
|
603
|
+
marker: usage.reasoningTokenProvenance === "provider" ? "provider-reported" : "mixed/estimated",
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
return { tokens: estimateThinkingTokens(thinking), marker: "estimated" };
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Render the assistant turn's thinking block. Collapsed (default) returns a
|
|
611
|
+
* single dim `Thinking...` marker. Expanded returns the full body dimmed and
|
|
612
|
+
* prefixed with a dim `│ ` rail, capped at `THINKING_LINE_LIMIT` lines with a
|
|
613
|
+
* tail `... N more lines hidden` overflow message. Mirrors the tool toggle's
|
|
614
|
+
* lab-notebook minimalism: no colored glyphs, no boxes.
|
|
615
|
+
*/
|
|
616
|
+
function renderThinkingLines(
|
|
617
|
+
thinking: string,
|
|
618
|
+
expanded: boolean,
|
|
619
|
+
width: number,
|
|
620
|
+
streaming: boolean,
|
|
621
|
+
usage?: ChatPanelTurnUsage,
|
|
622
|
+
): string[] {
|
|
623
|
+
if (thinking.length === 0) return [];
|
|
624
|
+
const dimWrap = (s: string): string => `${DIM}${s}${RESET}`;
|
|
625
|
+
if (!expanded) {
|
|
626
|
+
const lineBudget = Math.max(1, width);
|
|
627
|
+
const display = reasoningDisplay(usage, thinking);
|
|
628
|
+
const label = streaming
|
|
629
|
+
? `Thinking (${display.tokens} tokens)${display.marker === "provider-reported" ? "" : " ≈ estimated"}…`
|
|
630
|
+
: THINKING_HIDDEN_LABEL;
|
|
631
|
+
return [dimWrap(truncateToWidth(label, lineBudget, GLYPH.ellipsis, false))];
|
|
632
|
+
}
|
|
633
|
+
const splitLines = thinking.split("\n");
|
|
634
|
+
let visible: string[];
|
|
635
|
+
if (streaming) {
|
|
636
|
+
if (splitLines.length > THINKING_LINE_LIMIT) {
|
|
637
|
+
const hiddenCount = splitLines.length - THINKING_LINE_LIMIT;
|
|
638
|
+
visible = [`… ${hiddenCount} earlier lines hidden`, ...splitLines.slice(-THINKING_LINE_LIMIT)];
|
|
639
|
+
} else {
|
|
640
|
+
visible = splitLines;
|
|
641
|
+
}
|
|
642
|
+
} else {
|
|
643
|
+
visible =
|
|
644
|
+
splitLines.length > THINKING_LINE_LIMIT
|
|
645
|
+
? [...splitLines.slice(0, THINKING_LINE_LIMIT), `... ${splitLines.length - THINKING_LINE_LIMIT} more lines hidden`]
|
|
646
|
+
: splitLines;
|
|
647
|
+
}
|
|
648
|
+
const out: string[] = [];
|
|
649
|
+
const bodyWidth = Math.max(1, width - 2);
|
|
650
|
+
for (const raw of visible) {
|
|
651
|
+
const wrappedLines = raw.length === 0 ? [""] : wrapTextWithAnsi(raw, bodyWidth);
|
|
652
|
+
for (const wrapped of wrappedLines) {
|
|
653
|
+
out.push(`${BLUE_REASON}│ ${RESET}${DIM}${wrapped}${RESET}`);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
return out;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* `in` is the sum of every model call the turn made, not the size of one
|
|
661
|
+
* prompt. A long agentic turn makes dozens of calls that each resend a growing
|
|
662
|
+
* context, so the total runs far past the model's context window and reads as
|
|
663
|
+
* one impossible request unless the call count is beside it. One live turn
|
|
664
|
+
* reported 717676 input tokens against a 500k window; it was 65 calls of about
|
|
665
|
+
* 11k, which the preceding one-call turns had already shown.
|
|
666
|
+
*/
|
|
667
|
+
function renderTurnUsageLine(usage: ChatPanelTurnUsage, width: number, verbosity: OutputVerbosity): string[] {
|
|
668
|
+
if (verbosity === "minimal") return [];
|
|
669
|
+
if (verbosity === "default") {
|
|
670
|
+
const receipt = truncateToWidth(
|
|
671
|
+
` turn · in ${usage.inputTokens} · out ${usage.outputTokens}`,
|
|
672
|
+
width,
|
|
673
|
+
GLYPH.ellipsis,
|
|
674
|
+
false,
|
|
675
|
+
);
|
|
676
|
+
return [`${DIM}${receipt}${RESET}`];
|
|
677
|
+
}
|
|
678
|
+
const calls = usage.modelCalls !== undefined && usage.modelCalls > 1 ? ` over ${usage.modelCalls} calls` : "";
|
|
679
|
+
// The label stays separated from the count in both provenances. Deriving the
|
|
680
|
+
// separator from the `≈` marker glued them together whenever the provider
|
|
681
|
+
// reported a total, which is the common case, and rendered `reason0 provider`.
|
|
682
|
+
// The field is named for reasoning tokens rather than `reason`, which the
|
|
683
|
+
// memory step rows already use for a fixed decision vocabulary.
|
|
684
|
+
//
|
|
685
|
+
// A turn that spent no reasoning tokens states nothing by naming the
|
|
686
|
+
// provenance of zero, and at narrow widths `reasoning 0 provider` orphaned
|
|
687
|
+
// the word `provider` on its own line. Zero suppresses the whole suffix, the
|
|
688
|
+
// same rule the caveat below already follows.
|
|
689
|
+
const reason =
|
|
690
|
+
usage.reasoningTokens !== undefined && usage.reasoningTokens > 0
|
|
691
|
+
? ` reasoning ${usage.reasoningTokenProvenance === "provider" ? `${usage.reasoningTokens} provider` : `≈${usage.reasoningTokens} estimated`}`
|
|
692
|
+
: "";
|
|
693
|
+
const cache =
|
|
694
|
+
usage.cacheReadTokens > 0 || usage.cacheWriteTokens > 0
|
|
695
|
+
? ` cache ${usage.cacheReadTokens}/${usage.cacheWriteTokens}`
|
|
696
|
+
: "";
|
|
697
|
+
// The caveat is about reasoning text the panel displayed. A turn that spent
|
|
698
|
+
// no reasoning tokens displayed none, so appending it there warned about
|
|
699
|
+
// something absent and cost a wrapped line per turn at narrow widths.
|
|
700
|
+
const caveat =
|
|
701
|
+
usage.reasoningTokens !== undefined && usage.reasoningTokens > 0
|
|
702
|
+
? " · reasoning text is a UI excerpt, not a verification"
|
|
703
|
+
: "";
|
|
704
|
+
return wrapTextWithAnsi(
|
|
705
|
+
`${DIM} turn · in ${usage.inputTokens}${calls} · out ${usage.outputTokens}${cache}${reason}${caveat}${RESET}`,
|
|
706
|
+
width,
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/** The indent resolveInlineVerb budgets against when it fits the verb to the terminal. */
|
|
711
|
+
const STATUS_INDENT = " ".repeat(INLINE_STATUS_INDENT_COLS);
|
|
712
|
+
|
|
713
|
+
function styleStatusVerb(text: string, toneHint: VerbRender["toneHint"]): string {
|
|
714
|
+
if (toneHint === "error") return `${RED_CRIT}${text}${RESET}`;
|
|
715
|
+
if (toneHint === "warn") return `${AMBER}${text}${RESET}`;
|
|
716
|
+
if (toneHint === "ok") return `${GREEN_OK}${text}${RESET}`;
|
|
717
|
+
return `${DIM}${text}${RESET}`;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
function renderToolSegmentLines(
|
|
721
|
+
seg: ToolSegment,
|
|
722
|
+
width: number,
|
|
723
|
+
expandKey: string | undefined,
|
|
724
|
+
latestHintToolId: string | null,
|
|
725
|
+
nowMs: number,
|
|
726
|
+
unboundedToolBodies: boolean,
|
|
727
|
+
verbosity: OutputVerbosity,
|
|
728
|
+
liveToolOutput: boolean,
|
|
729
|
+
): string[] {
|
|
730
|
+
const hintKey = seg.id === latestHintToolId ? expandKey : undefined;
|
|
731
|
+
const expanded = verbosity === "verbose" || (verbosity !== "minimal" && seg.expanded);
|
|
732
|
+
const elapsedMs = seg.startedAtMs !== undefined ? Math.max(0, rawDurationMs(seg.startedAtMs, nowMs)) : undefined;
|
|
733
|
+
const phase: "forming" | "ready" | "running" = seg.executionStarted
|
|
734
|
+
? "running"
|
|
735
|
+
: seg.argsComplete
|
|
736
|
+
? "ready"
|
|
737
|
+
: "forming";
|
|
738
|
+
// A parked call is not executing: the awaiting-approval line replaces the
|
|
739
|
+
// counting elapsed spinner in both collapsed and expanded form (there is no
|
|
740
|
+
// body or partial output to expand while the call sits at the gate).
|
|
741
|
+
if (!seg.finished && seg.awaitingApproval === true) {
|
|
742
|
+
return renderToolAwaitingApproval(
|
|
743
|
+
{ toolCallId: seg.id, toolName: seg.name, args: seg.args },
|
|
744
|
+
width,
|
|
745
|
+
seg.approvalView,
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
if (!expanded) {
|
|
749
|
+
return renderToolSubline(
|
|
750
|
+
seg.finished
|
|
751
|
+
? {
|
|
752
|
+
toolCallId: seg.id,
|
|
753
|
+
toolName: seg.name,
|
|
754
|
+
args: seg.args,
|
|
755
|
+
result: seg.result,
|
|
756
|
+
isError: seg.isError,
|
|
757
|
+
durationMs: seg.durationMs,
|
|
758
|
+
resultSummary: seg.resultSummary,
|
|
759
|
+
outcome: seg.settlement,
|
|
760
|
+
blockReason: seg.blockReason,
|
|
761
|
+
}
|
|
762
|
+
: { toolCallId: seg.id, toolName: seg.name, args: seg.args, elapsedMs, phase },
|
|
763
|
+
width,
|
|
764
|
+
hintKey,
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
if (!seg.finished) {
|
|
768
|
+
if (liveToolOutput && seg.partialResult !== undefined) {
|
|
769
|
+
return renderToolStreamingExecution(
|
|
770
|
+
{ toolCallId: seg.id, toolName: seg.name, args: seg.args, elapsedMs, phase },
|
|
771
|
+
width,
|
|
772
|
+
seg.partialResult,
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
const call = { toolCallId: seg.id, toolName: seg.name, args: seg.args, elapsedMs, phase };
|
|
776
|
+
return liveToolOutput ? renderToolCallHeader(call, width) : renderToolRunningStatus(call, width);
|
|
777
|
+
}
|
|
778
|
+
return renderToolExecution(
|
|
779
|
+
{
|
|
780
|
+
toolCallId: seg.id,
|
|
781
|
+
toolName: seg.name,
|
|
782
|
+
args: seg.args,
|
|
783
|
+
result: seg.result,
|
|
784
|
+
isError: seg.isError,
|
|
785
|
+
durationMs: seg.durationMs,
|
|
786
|
+
resultSummary: seg.resultSummary,
|
|
787
|
+
outcome: seg.settlement,
|
|
788
|
+
blockReason: seg.blockReason,
|
|
789
|
+
},
|
|
790
|
+
width,
|
|
791
|
+
{ unbounded: unboundedToolBodies, diffStyle: seg.replayed === true ? "plain" : "color" },
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Whether a worker block draws its one-line card this frame. `/output verbose`
|
|
797
|
+
* opens every block and `/output minimal` folds every block, so the entry's own
|
|
798
|
+
* fold decides only in between.
|
|
799
|
+
*/
|
|
800
|
+
function workerEntryFolded(entry: WorkerTranscriptEntry, verbosity: OutputVerbosity): boolean {
|
|
801
|
+
if (verbosity === "verbose") return false;
|
|
802
|
+
if (verbosity === "minimal") return true;
|
|
803
|
+
return entry.folded;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
function renderEntryLines(
|
|
807
|
+
entry: TranscriptEntry,
|
|
808
|
+
width: number,
|
|
809
|
+
expandKey: string | undefined,
|
|
810
|
+
latestHintToolId: string | null,
|
|
811
|
+
latestFoldedWorkerId: string | null,
|
|
812
|
+
nowMs: number,
|
|
813
|
+
unboundedToolBodies: boolean,
|
|
814
|
+
verbosity: OutputVerbosity,
|
|
815
|
+
liveToolOutput: boolean,
|
|
816
|
+
): string[] {
|
|
817
|
+
if (entry.role === "replayBlock") {
|
|
818
|
+
return entry.renderBlock(width);
|
|
819
|
+
}
|
|
820
|
+
if (entry.role === "user") {
|
|
821
|
+
const contentWidth = Math.max(1, width - PROSE_GUTTER_WIDTH);
|
|
822
|
+
const lines: string[] = [];
|
|
823
|
+
for (const sourceLine of entry.text.split("\n")) lines.push(...wrapTextWithAnsi(sourceLine, contentWidth));
|
|
824
|
+
const rendered = hangProseLines(lines, USER_PREFIX);
|
|
825
|
+
if (rendered[0] !== undefined) rendered[0] = `${OSC133_PROMPT_START}${rendered[0]}`;
|
|
826
|
+
return rendered;
|
|
827
|
+
}
|
|
828
|
+
if (entry.role === "retryStatus") {
|
|
829
|
+
return wrapTextWithAnsi(formatRetryStatus(entry.status), width);
|
|
830
|
+
}
|
|
831
|
+
if (entry.role === "worker") {
|
|
832
|
+
return renderWorkerEntryLines(entry.state, width, {
|
|
833
|
+
folded: workerEntryFolded(entry, verbosity),
|
|
834
|
+
...(expandKey !== undefined && entry.state.assignmentId === latestFoldedWorkerId ? { expandKey } : {}),
|
|
835
|
+
unbounded: unboundedToolBodies,
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
// A settled assistant entry that rendered nothing at all contributes nothing.
|
|
839
|
+
// A mid-turn notice splits the transcript, so the events after it open a
|
|
840
|
+
// fresh entry that a stopped turn never fills; that entry used to reach the
|
|
841
|
+
// tail below and print a lone agent bubble under the notice.
|
|
842
|
+
if (
|
|
843
|
+
!entry.pending &&
|
|
844
|
+
entry.thinking.length === 0 &&
|
|
845
|
+
entry.turnUsage === undefined &&
|
|
846
|
+
!hasVisibleOutput(entry) &&
|
|
847
|
+
entry.segments.length === 0
|
|
848
|
+
) {
|
|
849
|
+
return [];
|
|
850
|
+
}
|
|
851
|
+
const lines: string[] = [];
|
|
852
|
+
// Thinking renders BEFORE assistant text/tool segments so the folded marker
|
|
853
|
+
// or expanded rail sits above the response, matching the order the
|
|
854
|
+
// pi-coding-agent reference uses. It streams live while `pending === true`
|
|
855
|
+
// (folded shows a dynamic token count; expanded tail-anchors the tail) and
|
|
856
|
+
// collapses to a static marker / head-anchored rail once the turn settles.
|
|
857
|
+
// The generic "thinking" status verb is suppressed while this marker is
|
|
858
|
+
// active so only one indicator shows (see `shouldRenderStatus` below).
|
|
859
|
+
if (entry.thinking.length > 0) {
|
|
860
|
+
lines.push(
|
|
861
|
+
...renderThinkingLines(
|
|
862
|
+
entry.thinking,
|
|
863
|
+
verbosity === "verbose" || (verbosity !== "minimal" && entry.expandedThinking === true),
|
|
864
|
+
width,
|
|
865
|
+
entry.pending,
|
|
866
|
+
entry.turnUsage,
|
|
867
|
+
),
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
const clioPrefix = entry.isError ? CLIO_PREFIX_ERROR : CLIO_PREFIX;
|
|
871
|
+
const proseWidth = Math.max(1, width - PROSE_GUTTER_WIDTH);
|
|
872
|
+
let labeled = false;
|
|
873
|
+
for (const seg of entry.segments) {
|
|
874
|
+
if (seg.kind === "tool") {
|
|
875
|
+
lines.push(
|
|
876
|
+
...renderToolSegmentLines(
|
|
877
|
+
seg,
|
|
878
|
+
width,
|
|
879
|
+
expandKey,
|
|
880
|
+
latestHintToolId,
|
|
881
|
+
nowMs,
|
|
882
|
+
unboundedToolBodies,
|
|
883
|
+
verbosity,
|
|
884
|
+
liveToolOutput,
|
|
885
|
+
),
|
|
886
|
+
);
|
|
887
|
+
continue;
|
|
888
|
+
}
|
|
889
|
+
// Text and error segments share the reply-prefix bookkeeping: the first
|
|
890
|
+
// substantive one carries the agent glyph and every later one hangs plain.
|
|
891
|
+
// A leading skill-suggestion protocol line is advisory rather than the
|
|
892
|
+
// answer, so it renders in place without claiming the turn's voice glyph.
|
|
893
|
+
if (seg.kind === "text" && seg.text.length === 0) continue;
|
|
894
|
+
const split = seg.kind === "text" ? skillSuggestionSplit(seg) : null;
|
|
895
|
+
if (split) {
|
|
896
|
+
// The suggestion line hangs plain and the answer beneath it is ordinary
|
|
897
|
+
// prose, so it claims the glyph when nothing else has.
|
|
898
|
+
lines.push(...hangProseLines(wrapTextWithAnsi(split.suggestion, proseWidth)));
|
|
899
|
+
const answerLines = renderTextSegmentLines(split.answer, proseWidth);
|
|
900
|
+
if (answerLines.length === 0) continue;
|
|
901
|
+
lines.push(...(labeled ? hangProseLines(answerLines) : hangProseLines(answerLines, clioPrefix)));
|
|
902
|
+
labeled = true;
|
|
903
|
+
continue;
|
|
904
|
+
}
|
|
905
|
+
const rendered =
|
|
906
|
+
seg.kind === "text" ? renderTextSegmentLines(seg, proseWidth) : renderErrorSegmentLines(seg, proseWidth);
|
|
907
|
+
if (rendered.length === 0) continue;
|
|
908
|
+
const isSkillSuggestion = seg.kind === "text" && seg.text.startsWith(SKILL_SUGGESTION_PREFIX);
|
|
909
|
+
if (!labeled && !isSkillSuggestion) {
|
|
910
|
+
lines.push(...hangProseLines(rendered, clioPrefix));
|
|
911
|
+
labeled = true;
|
|
912
|
+
} else {
|
|
913
|
+
lines.push(...hangProseLines(rendered));
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
if (entry.turnUsage && !entry.pending) lines.push(...renderTurnUsageLine(entry.turnUsage, width, verbosity));
|
|
917
|
+
const shouldRenderStatus =
|
|
918
|
+
entry.pending &&
|
|
919
|
+
entry.statusLine !== null &&
|
|
920
|
+
entry.statusLine !== undefined &&
|
|
921
|
+
!(entry.statusLine.phase === "writing" && hasStreamingText(entry)) &&
|
|
922
|
+
!(entry.statusLine.phase === "thinking" && entry.thinking.length > 0);
|
|
923
|
+
if (!labeled && !hasVisibleOutput(entry)) {
|
|
924
|
+
lines.push(clioPrefix.trimEnd());
|
|
925
|
+
if (shouldRenderStatus) {
|
|
926
|
+
lines.push(
|
|
927
|
+
`${STATUS_INDENT}${styleStatusVerb(entry.statusLine?.verb ?? "", entry.statusLine?.toneHint ?? "muted")}`,
|
|
928
|
+
);
|
|
929
|
+
}
|
|
930
|
+
} else if (shouldRenderStatus) {
|
|
931
|
+
lines.push(`${STATUS_INDENT}${styleStatusVerb(entry.statusLine?.verb ?? "", entry.statusLine?.toneHint ?? "muted")}`);
|
|
932
|
+
}
|
|
933
|
+
return lines;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
export function createChatPanel(options: ChatPanelOptions = {}): ChatPanel {
|
|
937
|
+
const transcript: TranscriptEntry[] = [];
|
|
938
|
+
/** Assignment to its placed block, in placement order, so a streaming delta is O(1) to route. */
|
|
939
|
+
const workerEntries = new Map<string, WorkerTranscriptEntry>();
|
|
940
|
+
let dirty = true;
|
|
941
|
+
let cachedWidth: number | undefined;
|
|
942
|
+
let cachedLines: string[] = [];
|
|
943
|
+
let cachedExpandKey: string | undefined;
|
|
944
|
+
let cachedVerbosity: OutputVerbosity | undefined;
|
|
945
|
+
let cachedLiveToolOutput: boolean | undefined;
|
|
946
|
+
let cachedTick = 0;
|
|
947
|
+
/**
|
|
948
|
+
* Did the last executed render put a counting elapsed line on screen? It is
|
|
949
|
+
* what decides whether the render key carries a time tick at all, so a
|
|
950
|
+
* transcript with nothing running keeps the old mutation-only invalidation.
|
|
951
|
+
*/
|
|
952
|
+
let renderedRunningTool = false;
|
|
953
|
+
const entryRenderCache = new Map<TranscriptEntry, { key: string; lines: string[] }>();
|
|
954
|
+
/**
|
|
955
|
+
* The cache follows the transcript instead of stopping at a fixed 256
|
|
956
|
+
* entries, which re-rendered everything past the cap on every dirty frame
|
|
957
|
+
* (10 ms/frame at 800 entries). The ceiling bounds worst-case memory at
|
|
958
|
+
* roughly 4096 rendered entries; past it, the excess only re-renders on
|
|
959
|
+
* full-rebuild events (width change, expand-all), which the frozen prefix
|
|
960
|
+
* below makes rare rather than per-frame.
|
|
961
|
+
*/
|
|
962
|
+
const MIN_ENTRY_RENDER_CACHE = 256;
|
|
963
|
+
const MAX_ENTRY_RENDER_CACHE = 4096;
|
|
964
|
+
const entryCacheCapacity = (): number =>
|
|
965
|
+
Math.max(MIN_ENTRY_RENDER_CACHE, Math.min(MAX_ENTRY_RENDER_CACHE, transcript.length));
|
|
966
|
+
/**
|
|
967
|
+
* Regular-screen windowed-tail build: the lines of every settled
|
|
968
|
+
* leading entry are baked into one frozen prefix, so a dirty frame re-renders
|
|
969
|
+
* only the live tail and re-emits the prefix by reference. TuiMainScreen still
|
|
970
|
+
* receives the full line array, deliberately: the renderer keeps every
|
|
971
|
+
* line in `previousLines` and full-redraws (clearing scrollback) when the
|
|
972
|
+
* head shrinks. Fullscreen mode instead gives the transcript its own pi-tui
|
|
973
|
+
* ScrollView. The freeze is dropped whenever a frozen entry is invalidated or
|
|
974
|
+
* the render key changes.
|
|
975
|
+
*/
|
|
976
|
+
let frozen: { lines: string[]; through: number; key: string } | null = null;
|
|
977
|
+
let thinkingExpanded = false;
|
|
978
|
+
let liveToolOutput = true;
|
|
979
|
+
const unboundedToolBodies = options.unboundedToolBodies === true;
|
|
980
|
+
|
|
981
|
+
const markDirty = (): void => {
|
|
982
|
+
dirty = true;
|
|
983
|
+
};
|
|
984
|
+
const invalidateEntryCache = (entry: TranscriptEntry): void => {
|
|
985
|
+
entryRenderCache.delete(entry);
|
|
986
|
+
if (frozen !== null && transcript.indexOf(entry) < frozen.through) frozen = null;
|
|
987
|
+
};
|
|
988
|
+
/** Full drop of both render caches; used by the toggle paths that touch many entries. */
|
|
989
|
+
const clearRenderCaches = (): void => {
|
|
990
|
+
entryRenderCache.clear();
|
|
991
|
+
frozen = null;
|
|
992
|
+
};
|
|
993
|
+
/**
|
|
994
|
+
* A mutation is about to land on the tail entry without an explicit
|
|
995
|
+
* invalidation. If the freeze extends over the whole transcript, that tail
|
|
996
|
+
* entry is frozen and the freeze must go.
|
|
997
|
+
*/
|
|
998
|
+
const unfreezeTail = (): void => {
|
|
999
|
+
if (frozen !== null && frozen.through >= transcript.length) frozen = null;
|
|
1000
|
+
};
|
|
1001
|
+
|
|
1002
|
+
const resolveExpandKey = (): string | undefined => {
|
|
1003
|
+
const key = options.getToolExpandKey?.();
|
|
1004
|
+
if (typeof key !== "string" || key.length === 0) return undefined;
|
|
1005
|
+
return key;
|
|
1006
|
+
};
|
|
1007
|
+
|
|
1008
|
+
const now = (): number => options.now?.() ?? Date.now();
|
|
1009
|
+
|
|
1010
|
+
/**
|
|
1011
|
+
* Force an in-flight tool segment to a settled error line. A call blocked at
|
|
1012
|
+
* admission (loop guard, safety) or one whose `tool_execution_end` never
|
|
1013
|
+
* arrives (aborted mid-batch, a model that reuses a tool-call id) would
|
|
1014
|
+
* otherwise stay a counting `· N.Ns` running line forever. Settling it with
|
|
1015
|
+
* its OWN elapsed gives it the same visual grammar as any other error
|
|
1016
|
+
* (`✗ · <ms>`), so a blocked call reads like the failure it is.
|
|
1017
|
+
*/
|
|
1018
|
+
const settleUnfinishedToolSegment = (seg: ToolSegment, settlement: "aborted" | "orphaned" = "orphaned"): void => {
|
|
1019
|
+
if (seg.finished) return;
|
|
1020
|
+
seg.finished = true;
|
|
1021
|
+
seg.isError = true;
|
|
1022
|
+
seg.settledWithoutResult = true;
|
|
1023
|
+
if (seg.durationMs === undefined && seg.startedAtMs !== undefined) {
|
|
1024
|
+
seg.durationMs = Math.max(1, now() - seg.startedAtMs);
|
|
1025
|
+
}
|
|
1026
|
+
if (seg.result === undefined)
|
|
1027
|
+
seg.result = "(no result: the call did not complete; execution was aborted, blocked, or orphaned)";
|
|
1028
|
+
seg.settlement = settlement;
|
|
1029
|
+
seg.partialResult = undefined;
|
|
1030
|
+
seg.awaitingApproval = undefined;
|
|
1031
|
+
seg.approvalView = undefined;
|
|
1032
|
+
};
|
|
1033
|
+
|
|
1034
|
+
/**
|
|
1035
|
+
* Locate the most recent tool segment with this call id anywhere in the
|
|
1036
|
+
* transcript. A mid-turn notice entry (safety-net block, approval parked,
|
|
1037
|
+
* context-engine notice) splits the transcript, so an in-flight call's
|
|
1038
|
+
* segment can live in an earlier assistant entry than the tail. Unfinished
|
|
1039
|
+
* segments win over finished ones so an id the model reuses binds to the
|
|
1040
|
+
* live call, not the settled one. Among finished segments only a
|
|
1041
|
+
* force-settled one (no end event of its own) is returned: a late true
|
|
1042
|
+
* result may upgrade the synthetic settle, but a segment that finished
|
|
1043
|
+
* with its own result is never rewritten after the fact.
|
|
1044
|
+
*/
|
|
1045
|
+
const findToolSegmentOwner = (toolCallId: string): { segment: ToolSegment; entry: TranscriptEntry } | undefined => {
|
|
1046
|
+
let settledMatch: { segment: ToolSegment; entry: TranscriptEntry } | undefined;
|
|
1047
|
+
for (let entryIndex = transcript.length - 1; entryIndex >= 0; entryIndex -= 1) {
|
|
1048
|
+
const entry = transcript[entryIndex];
|
|
1049
|
+
if (entry?.role !== "assistant") continue;
|
|
1050
|
+
for (let segIndex = entry.segments.length - 1; segIndex >= 0; segIndex -= 1) {
|
|
1051
|
+
const seg = entry.segments[segIndex];
|
|
1052
|
+
if (seg?.kind !== "tool" || seg.id !== toolCallId) continue;
|
|
1053
|
+
if (!seg.finished) return { segment: seg, entry };
|
|
1054
|
+
if (seg.settledWithoutResult === true) settledMatch ??= { segment: seg, entry };
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
return settledMatch;
|
|
1058
|
+
};
|
|
1059
|
+
|
|
1060
|
+
const ensureAssistant = (): Extract<TranscriptEntry, { role: "assistant" }> => {
|
|
1061
|
+
const last = transcript[transcript.length - 1];
|
|
1062
|
+
if (last && last.role === "assistant") {
|
|
1063
|
+
// Callers mutate the returned entry (pending, thinking, segments)
|
|
1064
|
+
// without always invalidating; a fully-frozen transcript would keep
|
|
1065
|
+
// serving the settled render of this tail entry.
|
|
1066
|
+
unfreezeTail();
|
|
1067
|
+
return last;
|
|
1068
|
+
}
|
|
1069
|
+
const entry: Extract<TranscriptEntry, { role: "assistant" }> = {
|
|
1070
|
+
role: "assistant",
|
|
1071
|
+
segments: [],
|
|
1072
|
+
thinking: "",
|
|
1073
|
+
expandedThinking: thinkingExpanded,
|
|
1074
|
+
pending: false,
|
|
1075
|
+
isError: false,
|
|
1076
|
+
};
|
|
1077
|
+
transcript.push(entry);
|
|
1078
|
+
return entry;
|
|
1079
|
+
};
|
|
1080
|
+
|
|
1081
|
+
const appendTextDelta = (entry: Extract<TranscriptEntry, { role: "assistant" }>, delta: string): void => {
|
|
1082
|
+
if (delta.length === 0) return;
|
|
1083
|
+
invalidateEntryCache(entry);
|
|
1084
|
+
const tail = entry.segments[entry.segments.length - 1];
|
|
1085
|
+
if (tail && tail.kind === "text" && !tail.finalized) {
|
|
1086
|
+
tail.text += delta;
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
entry.segments.push({ kind: "text", text: delta, finalized: false });
|
|
1090
|
+
};
|
|
1091
|
+
|
|
1092
|
+
/**
|
|
1093
|
+
* Canonicalize a streamed text segment from a completed assistant message.
|
|
1094
|
+
* When streaming produced a prefix of the final text (the common case),
|
|
1095
|
+
* the tail segment is overwritten and flipped to finalized so the next
|
|
1096
|
+
* render pipes it through Markdown. When the message arrived fully formed
|
|
1097
|
+
* with no deltas (non-streaming test path, synthetic notices), a fresh
|
|
1098
|
+
* finalized text segment is appended after any tool segments that may
|
|
1099
|
+
* have landed in this turn already. `replaceTail` forces the overwrite for
|
|
1100
|
+
* messages the chat loop rewrote after streaming (locked-turn markup
|
|
1101
|
+
* sanitation): the streamed tail is dead text there, not a prefix.
|
|
1102
|
+
*/
|
|
1103
|
+
const canonicalizeMessageText = (
|
|
1104
|
+
entry: Extract<TranscriptEntry, { role: "assistant" }>,
|
|
1105
|
+
text: string,
|
|
1106
|
+
replaceTail = false,
|
|
1107
|
+
): void => {
|
|
1108
|
+
if (text.length === 0) return;
|
|
1109
|
+
const tail = entry.segments[entry.segments.length - 1];
|
|
1110
|
+
if (tail?.kind === "text" && !tail.finalized && (replaceTail || text.startsWith(tail.text))) {
|
|
1111
|
+
tail.text = text;
|
|
1112
|
+
tail.finalized = true;
|
|
1113
|
+
// The streaming wrap cache assumes append-only text. This is the one
|
|
1114
|
+
// path that rewrites it wholesale, and finalized segments render through
|
|
1115
|
+
// Markdown instead, so the cache is dead here either way.
|
|
1116
|
+
delete tail.wrapCache;
|
|
1117
|
+
if (tail.md) tail.md.setText(text);
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
entry.segments.push({ kind: "text", text, finalized: true });
|
|
1121
|
+
};
|
|
1122
|
+
|
|
1123
|
+
/**
|
|
1124
|
+
* Append the turn's terminal-error marker as its own error segment so the
|
|
1125
|
+
* render path styles it in the error token rather than piping it through
|
|
1126
|
+
* Markdown as prose. Guards against a duplicate when the same marker arrives
|
|
1127
|
+
* twice for one settled turn.
|
|
1128
|
+
*/
|
|
1129
|
+
const appendErrorSegment = (entry: Extract<TranscriptEntry, { role: "assistant" }>, text: string): void => {
|
|
1130
|
+
const tail = entry.segments[entry.segments.length - 1];
|
|
1131
|
+
if (tail?.kind === "error" && tail.text === text) return;
|
|
1132
|
+
entry.segments.push({ kind: "error", text });
|
|
1133
|
+
};
|
|
1134
|
+
|
|
1135
|
+
/**
|
|
1136
|
+
* Who advertises the fold key this frame: the newest folded worker card, or
|
|
1137
|
+
* the newest finished collapsed tool subline with no worker card behind it.
|
|
1138
|
+
* One surface at most, because the key reaches the newest foldable thing of
|
|
1139
|
+
* either kind, and a chord shown anywhere else would open something the
|
|
1140
|
+
* operator was not looking at. An already-open newest card advertises
|
|
1141
|
+
* nothing, since folding it again needs no invitation.
|
|
1142
|
+
*/
|
|
1143
|
+
const expandHintOwner = (): { toolId: string | null; workerId: string | null } => {
|
|
1144
|
+
let workerMayOwn = true;
|
|
1145
|
+
for (let entryIndex = transcript.length - 1; entryIndex >= 0; entryIndex -= 1) {
|
|
1146
|
+
const entry = transcript[entryIndex];
|
|
1147
|
+
if (entry?.role === "worker") {
|
|
1148
|
+
return { toolId: null, workerId: workerMayOwn && entry.folded ? entry.state.assignmentId : null };
|
|
1149
|
+
}
|
|
1150
|
+
if (entry?.role !== "assistant") continue;
|
|
1151
|
+
for (let segIndex = entry.segments.length - 1; segIndex >= 0; segIndex -= 1) {
|
|
1152
|
+
const seg = entry.segments[segIndex];
|
|
1153
|
+
if (seg?.kind !== "tool") continue;
|
|
1154
|
+
if (seg.finished && !seg.expanded) return { toolId: seg.id, workerId: null };
|
|
1155
|
+
workerMayOwn = false;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
return { toolId: null, workerId: null };
|
|
1159
|
+
};
|
|
1160
|
+
|
|
1161
|
+
/**
|
|
1162
|
+
* Where a newly seen worker block belongs. An agent-origin run nests under
|
|
1163
|
+
* the assistant entry holding the tool call that spawned it, behind any
|
|
1164
|
+
* sibling blocks the same call already placed, so a fan-out reads top to
|
|
1165
|
+
* bottom in spawn order. Everything else, including a run whose parent call
|
|
1166
|
+
* is no longer in the transcript (a detached collect landing turns later),
|
|
1167
|
+
* appends at the tail. null means "append".
|
|
1168
|
+
*/
|
|
1169
|
+
const workerInsertionIndex = (state: WorkerEntryState): number | null => {
|
|
1170
|
+
const parentToolCallId = state.parentToolCallId;
|
|
1171
|
+
if (parentToolCallId === undefined) return null;
|
|
1172
|
+
const owner = findToolSegmentOwner(parentToolCallId);
|
|
1173
|
+
if (owner === undefined) return null;
|
|
1174
|
+
const parentIndex = transcript.indexOf(owner.entry);
|
|
1175
|
+
if (parentIndex < 0) return null;
|
|
1176
|
+
let index = parentIndex + 1;
|
|
1177
|
+
while (transcript[index]?.role === "worker") index += 1;
|
|
1178
|
+
return index >= transcript.length ? null : index;
|
|
1179
|
+
};
|
|
1180
|
+
|
|
1181
|
+
/** True when the entry owns the tool the expand hint currently points at. */
|
|
1182
|
+
const entryContainsHint = (entry: TranscriptEntry, latestHintToolId: string | null): boolean =>
|
|
1183
|
+
latestHintToolId !== null &&
|
|
1184
|
+
entry.role === "assistant" &&
|
|
1185
|
+
entry.segments.some((segment) => segment.kind === "tool" && segment.id === latestHintToolId);
|
|
1186
|
+
|
|
1187
|
+
/** True when the entry renders at least one counting elapsed line this frame. */
|
|
1188
|
+
const entryHasRunningTool = (entry: TranscriptEntry): boolean =>
|
|
1189
|
+
(entry.role === "assistant" &&
|
|
1190
|
+
entry.segments.some(
|
|
1191
|
+
(segment) => segment.kind === "tool" && !segment.finished && segment.startedAtMs !== undefined,
|
|
1192
|
+
)) ||
|
|
1193
|
+
(entry.role === "replayBlock" && entry.isLive?.() === true);
|
|
1194
|
+
|
|
1195
|
+
/**
|
|
1196
|
+
* Settled entries whose render is a pure function of the base key. A live
|
|
1197
|
+
* worker block is excluded for the same reason a running tool is: the
|
|
1198
|
+
* reducer mutates its state object in place, so a cached render would keep
|
|
1199
|
+
* serving the answer as it looked several deltas ago. A replay block that
|
|
1200
|
+
* declares itself live is excluded on the same grounds.
|
|
1201
|
+
*/
|
|
1202
|
+
const entryIsStable = (entry: TranscriptEntry): boolean =>
|
|
1203
|
+
entry.role === "user" ||
|
|
1204
|
+
(entry.role === "replayBlock" && entry.isLive?.() !== true) ||
|
|
1205
|
+
(entry.role === "worker" && !entry.state.pending) ||
|
|
1206
|
+
(entry.role === "assistant" &&
|
|
1207
|
+
!entry.pending &&
|
|
1208
|
+
!entry.segments.some((segment) => segment.kind === "tool" && !segment.finished));
|
|
1209
|
+
|
|
1210
|
+
const render = (width: number): string[] => {
|
|
1211
|
+
const startedAt = performance.now();
|
|
1212
|
+
const expandKey = resolveExpandKey();
|
|
1213
|
+
const verbosity = options.getOutputVerbosity?.() ?? "default";
|
|
1214
|
+
const nowMs = now();
|
|
1215
|
+
// `dirty` is set on mutation and never on a tick, so without time in the
|
|
1216
|
+
// key a running tool's elapsed counter advanced only when something
|
|
1217
|
+
// unrelated invalidated the panel. The tick is the same 100 ms bucket
|
|
1218
|
+
// dispatch-board.ts uses for running rows, taken off the injectable
|
|
1219
|
+
// clock so a fixed clock still produces byte-stable output, and it is
|
|
1220
|
+
// pinned to 0 whenever nothing is counting so a settled transcript
|
|
1221
|
+
// re-renders no more often than it did before.
|
|
1222
|
+
const tick = renderedRunningTool ? Math.floor(nowMs / 100) : 0;
|
|
1223
|
+
// The hit guard runs before any transcript scan. expandHintOwner
|
|
1224
|
+
// walks the whole transcript when the newest tool is expanded or absent, and
|
|
1225
|
+
// it is not part of the panel-level key, so computing it above the guard cost
|
|
1226
|
+
// a full scan per frame for a value the early return discards.
|
|
1227
|
+
if (
|
|
1228
|
+
!dirty &&
|
|
1229
|
+
cachedWidth === width &&
|
|
1230
|
+
cachedExpandKey === expandKey &&
|
|
1231
|
+
cachedVerbosity === verbosity &&
|
|
1232
|
+
cachedLiveToolOutput === liveToolOutput &&
|
|
1233
|
+
cachedTick === tick
|
|
1234
|
+
) {
|
|
1235
|
+
options.onRenderMetrics?.({ durationMs: performance.now() - startedAt, cacheHit: true, entriesRendered: 0 });
|
|
1236
|
+
return cachedLines;
|
|
1237
|
+
}
|
|
1238
|
+
const { toolId: latestHintToolId, workerId: latestFoldedWorkerId } = expandHintOwner();
|
|
1239
|
+
// The hint id is deliberately NOT part of the shared key: it changes on
|
|
1240
|
+
// every finished collapsed tool, and keying every entry on it re-rendered
|
|
1241
|
+
// the entire transcript per tool completion. Only the entry that contains
|
|
1242
|
+
// the hint tool renders differently, so only that entry's key carries it.
|
|
1243
|
+
// The tick stays out of the entry key: a settled entry renders the same
|
|
1244
|
+
// bytes at every tick, and keying it on time would drop the entry cache
|
|
1245
|
+
// and the frozen prefix ten times a second. Only the panel-level guard
|
|
1246
|
+
// above is time-keyed, so a tick re-renders the live tail and nothing else.
|
|
1247
|
+
const baseKey = `${width}|${expandKey ?? ""}|${verbosity}|${liveToolOutput}`;
|
|
1248
|
+
const capacity = entryCacheCapacity();
|
|
1249
|
+
if (frozen !== null && frozen.key !== baseKey) frozen = null;
|
|
1250
|
+
const out: string[] = frozen === null ? [] : frozen.lines.slice();
|
|
1251
|
+
const startIndex = frozen === null ? 0 : frozen.through;
|
|
1252
|
+
// The freeze extends over the contiguous run of stable, hint-free leading
|
|
1253
|
+
// entries; it is captured after the loop from what this frame rendered.
|
|
1254
|
+
let freezeThrough = startIndex;
|
|
1255
|
+
let freezeLineCount = out.length;
|
|
1256
|
+
let freezeOpen = frozen !== null || startIndex === 0;
|
|
1257
|
+
let entriesRendered = 0;
|
|
1258
|
+
// Only entries at or past `startIndex` can hold a running tool: the frozen
|
|
1259
|
+
// prefix is by construction a run of stable entries, and stable means no
|
|
1260
|
+
// unfinished tool segment.
|
|
1261
|
+
let sawRunningTool = false;
|
|
1262
|
+
for (let i = startIndex; i < transcript.length; i += 1) {
|
|
1263
|
+
const entry = transcript[i];
|
|
1264
|
+
if (!entry) continue;
|
|
1265
|
+
if (!sawRunningTool && entryHasRunningTool(entry)) sawRunningTool = true;
|
|
1266
|
+
// Folded worker cards are a list, not a series of blocks: a fan-out of
|
|
1267
|
+
// five scouts costs five rows, which is what makes the folded default
|
|
1268
|
+
// worth having. Anything else keeps the blank line between entries.
|
|
1269
|
+
const previous = i > 0 ? transcript[i - 1] : undefined;
|
|
1270
|
+
const stacksOnPrevious =
|
|
1271
|
+
entry.role === "worker" &&
|
|
1272
|
+
previous?.role === "worker" &&
|
|
1273
|
+
workerEntryFolded(entry, verbosity) &&
|
|
1274
|
+
workerEntryFolded(previous, verbosity);
|
|
1275
|
+
if (i > 0 && !stacksOnPrevious) out.push("");
|
|
1276
|
+
const containsHint =
|
|
1277
|
+
entryContainsHint(entry, latestHintToolId) ||
|
|
1278
|
+
(entry.role === "worker" && entry.state.assignmentId === latestFoldedWorkerId);
|
|
1279
|
+
const entryKey = containsHint ? `${baseKey}|hint:${latestHintToolId}|${latestFoldedWorkerId}` : baseKey;
|
|
1280
|
+
const cached = entryRenderCache.get(entry);
|
|
1281
|
+
const cacheable = i >= transcript.length - capacity && entry.role !== "replayBlock" && entryIsStable(entry);
|
|
1282
|
+
if (cacheable && cached?.key === entryKey) {
|
|
1283
|
+
// A spread here is slower than a loop for large arrays and blows the
|
|
1284
|
+
// stack outright for a single entry that renders enough lines.
|
|
1285
|
+
for (const line of cached.lines) out.push(line);
|
|
1286
|
+
} else {
|
|
1287
|
+
entriesRendered += 1;
|
|
1288
|
+
const renderedEntry = renderEntryLines(
|
|
1289
|
+
entry,
|
|
1290
|
+
width,
|
|
1291
|
+
expandKey,
|
|
1292
|
+
latestHintToolId,
|
|
1293
|
+
latestFoldedWorkerId,
|
|
1294
|
+
nowMs,
|
|
1295
|
+
unboundedToolBodies,
|
|
1296
|
+
verbosity,
|
|
1297
|
+
liveToolOutput,
|
|
1298
|
+
);
|
|
1299
|
+
for (const line of renderedEntry) out.push(line);
|
|
1300
|
+
if (cacheable) {
|
|
1301
|
+
entryRenderCache.set(entry, { key: entryKey, lines: renderedEntry });
|
|
1302
|
+
while (entryRenderCache.size > capacity) {
|
|
1303
|
+
const oldest = entryRenderCache.keys().next().value;
|
|
1304
|
+
if (oldest === undefined) break;
|
|
1305
|
+
entryRenderCache.delete(oldest);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
if (freezeOpen && i === freezeThrough && entryIsStable(entry) && !containsHint) {
|
|
1310
|
+
freezeThrough = i + 1;
|
|
1311
|
+
freezeLineCount = out.length;
|
|
1312
|
+
} else {
|
|
1313
|
+
freezeOpen = false;
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
frozen = freezeThrough > 0 ? { lines: out.slice(0, freezeLineCount), through: freezeThrough, key: baseKey } : null;
|
|
1317
|
+
cachedLines = out;
|
|
1318
|
+
cachedWidth = width;
|
|
1319
|
+
cachedExpandKey = expandKey;
|
|
1320
|
+
cachedVerbosity = verbosity;
|
|
1321
|
+
cachedLiveToolOutput = liveToolOutput;
|
|
1322
|
+
cachedTick = tick;
|
|
1323
|
+
renderedRunningTool = sawRunningTool;
|
|
1324
|
+
dirty = false;
|
|
1325
|
+
options.onRenderMetrics?.({ durationMs: performance.now() - startedAt, cacheHit: false, entriesRendered });
|
|
1326
|
+
return out;
|
|
1327
|
+
};
|
|
1328
|
+
|
|
1329
|
+
return {
|
|
1330
|
+
appendUser(text: string): void {
|
|
1331
|
+
transcript.push({ role: "user", text });
|
|
1332
|
+
markDirty();
|
|
1333
|
+
},
|
|
1334
|
+
appendReplayBlock(renderBlock: ReplayBlockRenderer, isLive?: () => boolean): void {
|
|
1335
|
+
transcript.push({ role: "replayBlock", renderBlock, isLive });
|
|
1336
|
+
markDirty();
|
|
1337
|
+
},
|
|
1338
|
+
applyWorkerState(state: WorkerEntryState): void {
|
|
1339
|
+
const existing = workerEntries.get(state.assignmentId);
|
|
1340
|
+
if (existing !== undefined) {
|
|
1341
|
+
// The reducer mutated the same state object this entry already holds,
|
|
1342
|
+
// so nothing is re-linked; only the cached render is now stale.
|
|
1343
|
+
invalidateEntryCache(existing);
|
|
1344
|
+
markDirty();
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
const entry: WorkerTranscriptEntry = { role: "worker", state, folded: workerAskedByModel(state) };
|
|
1348
|
+
workerEntries.set(state.assignmentId, entry);
|
|
1349
|
+
const at = workerInsertionIndex(state);
|
|
1350
|
+
if (at === null) {
|
|
1351
|
+
transcript.push(entry);
|
|
1352
|
+
} else {
|
|
1353
|
+
transcript.splice(at, 0, entry);
|
|
1354
|
+
// A frozen prefix is a run of indices. Inserting inside it renumbers
|
|
1355
|
+
// every entry behind the cut, so the freeze has to go.
|
|
1356
|
+
if (frozen !== null && at < frozen.through) frozen = null;
|
|
1357
|
+
}
|
|
1358
|
+
markDirty();
|
|
1359
|
+
},
|
|
1360
|
+
workerStates(): ReadonlyArray<WorkerEntryState> {
|
|
1361
|
+
return [...workerEntries.values()].map((entry) => entry.state);
|
|
1362
|
+
},
|
|
1363
|
+
toggleLastToolExpanded(): boolean {
|
|
1364
|
+
// Ctrl+O owns the newest foldable thing, whichever kind it is. A worker
|
|
1365
|
+
// block the operator just watched land is what they mean by "expand
|
|
1366
|
+
// that", not the tool call two screens up that spawned it.
|
|
1367
|
+
for (let entryIndex = transcript.length - 1; entryIndex >= 0; entryIndex -= 1) {
|
|
1368
|
+
const entry = transcript[entryIndex];
|
|
1369
|
+
if (entry?.role === "worker") {
|
|
1370
|
+
entry.folded = !entry.folded;
|
|
1371
|
+
clearRenderCaches();
|
|
1372
|
+
markDirty();
|
|
1373
|
+
return true;
|
|
1374
|
+
}
|
|
1375
|
+
if (entry?.role !== "assistant") continue;
|
|
1376
|
+
for (let segIndex = entry.segments.length - 1; segIndex >= 0; segIndex -= 1) {
|
|
1377
|
+
const seg = entry.segments[segIndex];
|
|
1378
|
+
if (seg?.kind !== "tool") continue;
|
|
1379
|
+
seg.expanded = !seg.expanded;
|
|
1380
|
+
clearRenderCaches();
|
|
1381
|
+
markDirty();
|
|
1382
|
+
return true;
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
return false;
|
|
1386
|
+
},
|
|
1387
|
+
toggleAllToolsExpanded(): boolean {
|
|
1388
|
+
const tools: ToolSegment[] = [];
|
|
1389
|
+
const workers: WorkerTranscriptEntry[] = [];
|
|
1390
|
+
for (const entry of transcript) {
|
|
1391
|
+
if (entry.role === "worker") {
|
|
1392
|
+
workers.push(entry);
|
|
1393
|
+
continue;
|
|
1394
|
+
}
|
|
1395
|
+
if (entry.role !== "assistant") continue;
|
|
1396
|
+
for (const seg of entry.segments) {
|
|
1397
|
+
if (seg.kind === "tool") tools.push(seg);
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
if (tools.length === 0 && workers.length === 0) return false;
|
|
1401
|
+
const expand = tools.some((seg) => !seg.expanded) || workers.some((entry) => entry.folded);
|
|
1402
|
+
for (const seg of tools) seg.expanded = expand;
|
|
1403
|
+
for (const entry of workers) entry.folded = !expand;
|
|
1404
|
+
clearRenderCaches();
|
|
1405
|
+
markDirty();
|
|
1406
|
+
return true;
|
|
1407
|
+
},
|
|
1408
|
+
collapseAllTools(): void {
|
|
1409
|
+
for (const entry of transcript) {
|
|
1410
|
+
if (entry.role === "worker") {
|
|
1411
|
+
// The settled view for a worker is its origin default, not
|
|
1412
|
+
// universally folded: the operator's own run is the one block
|
|
1413
|
+
// /run exists to show them.
|
|
1414
|
+
entry.folded = workerAskedByModel(entry.state);
|
|
1415
|
+
continue;
|
|
1416
|
+
}
|
|
1417
|
+
if (entry.role !== "assistant") continue;
|
|
1418
|
+
for (const seg of entry.segments) {
|
|
1419
|
+
if (seg.kind === "tool") seg.expanded = false;
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
clearRenderCaches();
|
|
1423
|
+
markDirty();
|
|
1424
|
+
},
|
|
1425
|
+
toggleLastThinking(): boolean {
|
|
1426
|
+
for (let entryIndex = transcript.length - 1; entryIndex >= 0; entryIndex -= 1) {
|
|
1427
|
+
const entry = transcript[entryIndex];
|
|
1428
|
+
if (entry?.role !== "assistant") continue;
|
|
1429
|
+
if (entry.thinking.length === 0) continue;
|
|
1430
|
+
entry.expandedThinking = entry.expandedThinking !== true;
|
|
1431
|
+
thinkingExpanded = entry.expandedThinking === true;
|
|
1432
|
+
clearRenderCaches();
|
|
1433
|
+
markDirty();
|
|
1434
|
+
return true;
|
|
1435
|
+
}
|
|
1436
|
+
thinkingExpanded = !thinkingExpanded;
|
|
1437
|
+
return true;
|
|
1438
|
+
},
|
|
1439
|
+
toggleAllThinking(): boolean {
|
|
1440
|
+
const entries: Array<Extract<TranscriptEntry, { role: "assistant" }>> = [];
|
|
1441
|
+
for (const entry of transcript) {
|
|
1442
|
+
if (entry.role === "assistant" && entry.thinking.length > 0) entries.push(entry);
|
|
1443
|
+
}
|
|
1444
|
+
if (entries.length === 0) {
|
|
1445
|
+
thinkingExpanded = !thinkingExpanded;
|
|
1446
|
+
return true;
|
|
1447
|
+
}
|
|
1448
|
+
const expand = entries.some((entry) => entry.expandedThinking !== true);
|
|
1449
|
+
for (const entry of entries) entry.expandedThinking = expand;
|
|
1450
|
+
thinkingExpanded = expand;
|
|
1451
|
+
clearRenderCaches();
|
|
1452
|
+
markDirty();
|
|
1453
|
+
return true;
|
|
1454
|
+
},
|
|
1455
|
+
isThinkingExpanded: () => thinkingExpanded,
|
|
1456
|
+
toggleLiveToolOutput(): boolean {
|
|
1457
|
+
liveToolOutput = !liveToolOutput;
|
|
1458
|
+
markDirty();
|
|
1459
|
+
return liveToolOutput;
|
|
1460
|
+
},
|
|
1461
|
+
reset(): void {
|
|
1462
|
+
transcript.length = 0;
|
|
1463
|
+
workerEntries.clear();
|
|
1464
|
+
clearRenderCaches();
|
|
1465
|
+
markDirty();
|
|
1466
|
+
},
|
|
1467
|
+
markToolReplayed(toolCallId: string): void {
|
|
1468
|
+
const owner = findToolSegmentOwner(toolCallId);
|
|
1469
|
+
if (!owner || owner.segment.finished) return;
|
|
1470
|
+
invalidateEntryCache(owner.entry);
|
|
1471
|
+
owner.segment.replayed = true;
|
|
1472
|
+
markDirty();
|
|
1473
|
+
},
|
|
1474
|
+
applyEvent(event: ChatLoopEvent): void {
|
|
1475
|
+
if (event.type === "agent_status") {
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1478
|
+
if (event.type === "notice") {
|
|
1479
|
+
// Transcript notices are first-class advisory lines, not assistant
|
|
1480
|
+
// messages: render with the bracketed-tag treatment replay lines get.
|
|
1481
|
+
if (event.surface !== "transcript") return;
|
|
1482
|
+
const text = event.text;
|
|
1483
|
+
transcript.push({
|
|
1484
|
+
role: "replayBlock",
|
|
1485
|
+
renderBlock: (width) => wrapTextWithAnsi(styleTaggedNotice(text), width),
|
|
1486
|
+
});
|
|
1487
|
+
markDirty();
|
|
1488
|
+
return;
|
|
1489
|
+
}
|
|
1490
|
+
if (event.type === "queued_user_turn") {
|
|
1491
|
+
// A queued steer or follow-up the engine just injected. Rendering it
|
|
1492
|
+
// here, at injection time, keeps the transcript in the order the
|
|
1493
|
+
// model saw: enqueue time shows the text only in the queue panel.
|
|
1494
|
+
transcript.push({ role: "user", text: event.text });
|
|
1495
|
+
markDirty();
|
|
1496
|
+
return;
|
|
1497
|
+
}
|
|
1498
|
+
if (event.type === "message_update") {
|
|
1499
|
+
const assistantEvent = event.assistantMessageEvent as {
|
|
1500
|
+
type?: unknown;
|
|
1501
|
+
contentIndex?: unknown;
|
|
1502
|
+
partial?: { content?: unknown };
|
|
1503
|
+
};
|
|
1504
|
+
if (
|
|
1505
|
+
assistantEvent.type !== "toolcall_start" &&
|
|
1506
|
+
assistantEvent.type !== "toolcall_delta" &&
|
|
1507
|
+
assistantEvent.type !== "toolcall_end"
|
|
1508
|
+
) {
|
|
1509
|
+
return;
|
|
1510
|
+
}
|
|
1511
|
+
const index = typeof assistantEvent.contentIndex === "number" ? assistantEvent.contentIndex : -1;
|
|
1512
|
+
const content = Array.isArray(assistantEvent.partial?.content) ? assistantEvent.partial.content : [];
|
|
1513
|
+
const block = content[index];
|
|
1514
|
+
if (block === null || typeof block !== "object" || Array.isArray(block)) return;
|
|
1515
|
+
const streamed = block as { type?: unknown; id?: unknown; name?: unknown; arguments?: unknown };
|
|
1516
|
+
if (streamed.type !== "toolCall" || typeof streamed.id !== "string" || streamed.id.length === 0) return;
|
|
1517
|
+
const owner = findToolSegmentOwner(streamed.id);
|
|
1518
|
+
const existing = owner?.segment;
|
|
1519
|
+
if (existing && !existing.finished && !existing.executionStarted) {
|
|
1520
|
+
if (owner) invalidateEntryCache(owner.entry);
|
|
1521
|
+
existing.name = typeof streamed.name === "string" && streamed.name.length > 0 ? streamed.name : existing.name;
|
|
1522
|
+
existing.args = streamed.arguments ?? existing.args;
|
|
1523
|
+
existing.argsComplete = assistantEvent.type === "toolcall_end";
|
|
1524
|
+
} else if (existing === undefined) {
|
|
1525
|
+
const assistant = ensureAssistant();
|
|
1526
|
+
assistant.pending = true;
|
|
1527
|
+
assistant.segments.push({
|
|
1528
|
+
kind: "tool",
|
|
1529
|
+
id: streamed.id,
|
|
1530
|
+
name: typeof streamed.name === "string" && streamed.name.length > 0 ? streamed.name : "tool",
|
|
1531
|
+
args: streamed.arguments ?? {},
|
|
1532
|
+
finished: false,
|
|
1533
|
+
executionStarted: false,
|
|
1534
|
+
argsComplete: assistantEvent.type === "toolcall_end",
|
|
1535
|
+
isError: false,
|
|
1536
|
+
expanded: false,
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
markDirty();
|
|
1540
|
+
return;
|
|
1541
|
+
}
|
|
1542
|
+
if (event.type === "text_delta") {
|
|
1543
|
+
const assistant = ensureAssistant();
|
|
1544
|
+
assistant.pending = true;
|
|
1545
|
+
appendTextDelta(assistant, event.delta);
|
|
1546
|
+
markDirty();
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
if (event.type === "thinking_delta") {
|
|
1550
|
+
// Capture for downstream consumers but never render inline.
|
|
1551
|
+
const assistant = ensureAssistant();
|
|
1552
|
+
assistant.pending = true;
|
|
1553
|
+
assistant.thinking += event.delta;
|
|
1554
|
+
assistant.expandedThinking = thinkingExpanded;
|
|
1555
|
+
markDirty();
|
|
1556
|
+
return;
|
|
1557
|
+
}
|
|
1558
|
+
if (event.type === "message_start" && event.message.role === "assistant") {
|
|
1559
|
+
ensureAssistant().pending = true;
|
|
1560
|
+
markDirty();
|
|
1561
|
+
return;
|
|
1562
|
+
}
|
|
1563
|
+
if (event.type === "tool_execution_start") {
|
|
1564
|
+
const streamedOwner = findToolSegmentOwner(event.toolCallId);
|
|
1565
|
+
if (streamedOwner !== undefined && !streamedOwner.segment.finished && !streamedOwner.segment.executionStarted) {
|
|
1566
|
+
invalidateEntryCache(streamedOwner.entry);
|
|
1567
|
+
const streamed = streamedOwner.segment;
|
|
1568
|
+
streamed.name = event.toolName;
|
|
1569
|
+
streamed.args = event.args;
|
|
1570
|
+
streamed.executionStarted = true;
|
|
1571
|
+
streamed.argsComplete = true;
|
|
1572
|
+
streamed.startedAtMs = now();
|
|
1573
|
+
streamed.expanded = classifyResourceRead(event.toolName, event.args) === null;
|
|
1574
|
+
markDirty();
|
|
1575
|
+
return;
|
|
1576
|
+
}
|
|
1577
|
+
// A model that reuses a tool-call id across calls would leave the
|
|
1578
|
+
// prior same-id segment unsettled (its end matches the first segment
|
|
1579
|
+
// on lookup). Settle any such orphan now, wherever it lives, so it
|
|
1580
|
+
// does not linger as a counting running line while this call runs.
|
|
1581
|
+
for (const entry of transcript) {
|
|
1582
|
+
if (entry.role !== "assistant") continue;
|
|
1583
|
+
for (const seg of entry.segments) {
|
|
1584
|
+
if (seg.kind === "tool" && seg.id === event.toolCallId && !seg.finished) settleUnfinishedToolSegment(seg);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
const assistant = ensureAssistant();
|
|
1588
|
+
// Compact resource reads (SKILL.md, CLIO-CODER.md, AGENTS.md, docs/) stay
|
|
1589
|
+
// collapsed to one labeled line until explicitly expanded.
|
|
1590
|
+
const expanded = classifyResourceRead(event.toolName, event.args) === null;
|
|
1591
|
+
assistant.pending = true;
|
|
1592
|
+
assistant.segments.push({
|
|
1593
|
+
kind: "tool",
|
|
1594
|
+
id: event.toolCallId,
|
|
1595
|
+
name: event.toolName,
|
|
1596
|
+
args: event.args,
|
|
1597
|
+
finished: false,
|
|
1598
|
+
executionStarted: true,
|
|
1599
|
+
argsComplete: true,
|
|
1600
|
+
isError: false,
|
|
1601
|
+
expanded,
|
|
1602
|
+
startedAtMs: now(),
|
|
1603
|
+
});
|
|
1604
|
+
markDirty();
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
if (event.type === "tool_approval_state") {
|
|
1608
|
+
// Only the entry that owns the segment can change. Clearing the whole
|
|
1609
|
+
// 256-entry cache re-rendered every settled turn in the transcript;
|
|
1610
|
+
// on a 400-turn session that was 26.7 ms per event, and
|
|
1611
|
+
// tool_execution_update fires on every output tick of a running command.
|
|
1612
|
+
const owner = findToolSegmentOwner(event.toolCallId);
|
|
1613
|
+
if (owner) invalidateEntryCache(owner.entry);
|
|
1614
|
+
const tool = owner?.segment;
|
|
1615
|
+
if (tool && !tool.finished) {
|
|
1616
|
+
tool.awaitingApproval = event.state === "awaiting-approval" ? true : undefined;
|
|
1617
|
+
tool.approvalView = event.state === "awaiting-approval" ? event.view : undefined;
|
|
1618
|
+
markDirty();
|
|
1619
|
+
}
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1622
|
+
if (event.type === "tool_execution_update") {
|
|
1623
|
+
// pi-agent emits `partialResult` as a cumulative AgentToolResult.
|
|
1624
|
+
// Preserve that full envelope so the renderer can use structured
|
|
1625
|
+
// progress details as well as content. Replace rather than append:
|
|
1626
|
+
// Pi's update semantics are cumulative, and appending would duplicate
|
|
1627
|
+
// every earlier snapshot.
|
|
1628
|
+
const owner = findToolSegmentOwner(event.toolCallId);
|
|
1629
|
+
if (owner) invalidateEntryCache(owner.entry);
|
|
1630
|
+
const tool = owner?.segment;
|
|
1631
|
+
if (tool && !tool.finished) {
|
|
1632
|
+
tool.partialResult = event.partialResult;
|
|
1633
|
+
}
|
|
1634
|
+
markDirty();
|
|
1635
|
+
return;
|
|
1636
|
+
}
|
|
1637
|
+
if (event.type === "tool_execution_end") {
|
|
1638
|
+
const owner = findToolSegmentOwner(event.toolCallId);
|
|
1639
|
+
if (owner) invalidateEntryCache(owner.entry);
|
|
1640
|
+
const tool = owner?.segment;
|
|
1641
|
+
if (tool) {
|
|
1642
|
+
tool.result = event.result;
|
|
1643
|
+
tool.isError = event.isError;
|
|
1644
|
+
// The chat loop enriches tool_execution_end with durationMs, the
|
|
1645
|
+
// persisted resultSummary (bytes, truncated, offloadPath,
|
|
1646
|
+
// observation counts), and the registry's admission verdict;
|
|
1647
|
+
// carry them so the ledger line and replay render identical facts.
|
|
1648
|
+
const enriched = event as {
|
|
1649
|
+
durationMs?: unknown;
|
|
1650
|
+
resultSummary?: unknown;
|
|
1651
|
+
outcome?: unknown;
|
|
1652
|
+
blockReason?: unknown;
|
|
1653
|
+
};
|
|
1654
|
+
// Settlement is that verdict, never an inference from result text.
|
|
1655
|
+
// The text of a tool result is the tool's own output: `node --test`
|
|
1656
|
+
// prints `cancelled 0` on every run and a linter can print
|
|
1657
|
+
// "blocked" for its own reasons, so matching those words there
|
|
1658
|
+
// labelled ordinary command failures as permission blocks and
|
|
1659
|
+
// suppressed the output that would have explained them.
|
|
1660
|
+
const settled = toolSettlement(enriched);
|
|
1661
|
+
tool.settlement = settled?.settlement;
|
|
1662
|
+
tool.blockReason = settled?.reason;
|
|
1663
|
+
tool.finished = true;
|
|
1664
|
+
// The true result replaces a synthetic settle; from here on the
|
|
1665
|
+
// segment is model-finished and immutable to later end events.
|
|
1666
|
+
tool.settledWithoutResult = undefined;
|
|
1667
|
+
if (typeof enriched.durationMs === "number" && Number.isFinite(enriched.durationMs)) {
|
|
1668
|
+
tool.durationMs = enriched.durationMs;
|
|
1669
|
+
} else if (tool.startedAtMs !== undefined) {
|
|
1670
|
+
const elapsed = Math.max(0, now() - tool.startedAtMs);
|
|
1671
|
+
if (elapsed > 0) tool.durationMs = elapsed;
|
|
1672
|
+
}
|
|
1673
|
+
if (
|
|
1674
|
+
enriched.resultSummary !== null &&
|
|
1675
|
+
typeof enriched.resultSummary === "object" &&
|
|
1676
|
+
!Array.isArray(enriched.resultSummary)
|
|
1677
|
+
) {
|
|
1678
|
+
tool.resultSummary = enriched.resultSummary as Record<string, unknown>;
|
|
1679
|
+
}
|
|
1680
|
+
// Drop the streaming buffer once the final result has landed; the
|
|
1681
|
+
// expanded render switches to `renderToolExecution` and stays
|
|
1682
|
+
// stable instead of churning through partial-frame layout. A
|
|
1683
|
+
// denied park settles here too, so the awaiting styling must go.
|
|
1684
|
+
tool.partialResult = undefined;
|
|
1685
|
+
tool.awaitingApproval = undefined;
|
|
1686
|
+
tool.approvalView = undefined;
|
|
1687
|
+
}
|
|
1688
|
+
markDirty();
|
|
1689
|
+
return;
|
|
1690
|
+
}
|
|
1691
|
+
if (event.type === "message_end") {
|
|
1692
|
+
const current = transcript[transcript.length - 1];
|
|
1693
|
+
let completedStreamedArgs = false;
|
|
1694
|
+
if (current?.role === "assistant") {
|
|
1695
|
+
for (const segment of current.segments) {
|
|
1696
|
+
if (segment.kind !== "tool" || segment.finished || segment.executionStarted || segment.argsComplete) continue;
|
|
1697
|
+
segment.argsComplete = true;
|
|
1698
|
+
completedStreamedArgs = true;
|
|
1699
|
+
}
|
|
1700
|
+
if (completedStreamedArgs) invalidateEntryCache(current);
|
|
1701
|
+
}
|
|
1702
|
+
const text = extractAssistantText(event.message);
|
|
1703
|
+
const thinking = extractAssistantThinking(event.message);
|
|
1704
|
+
const extractedTerminalError = extractAssistantTerminalError(event.message);
|
|
1705
|
+
const terminalError =
|
|
1706
|
+
current?.role === "assistant"
|
|
1707
|
+
? scopeTerminalErrorAfterSuccessfulTool(current, extractedTerminalError)
|
|
1708
|
+
: extractedTerminalError;
|
|
1709
|
+
const usage = assistantUsage(event.message);
|
|
1710
|
+
if (text.length === 0 && thinking.length === 0 && terminalError.length === 0 && usage === undefined) {
|
|
1711
|
+
if (completedStreamedArgs) markDirty();
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1714
|
+
const assistant = ensureAssistant();
|
|
1715
|
+
// message_end rewrites exactly one entry: the assistant it lands on.
|
|
1716
|
+
invalidateEntryCache(assistant);
|
|
1717
|
+
if (usage !== undefined) assistant.turnUsage = usage;
|
|
1718
|
+
if (terminalError.length > 0) assistant.isError = true;
|
|
1719
|
+
if (thinking.length > 0) {
|
|
1720
|
+
assistant.thinking = thinking;
|
|
1721
|
+
assistant.expandedThinking = thinkingExpanded;
|
|
1722
|
+
}
|
|
1723
|
+
// The chat loop marks messages it sanitized after streaming (dead
|
|
1724
|
+
// tool-call markup on a synthesis-locked turn); the streamed tail
|
|
1725
|
+
// must be replaced, not kept alongside a duplicate segment.
|
|
1726
|
+
const sanitized = (event as { lockedSynthesisSanitized?: unknown }).lockedSynthesisSanitized === true;
|
|
1727
|
+
if (text.length > 0) canonicalizeMessageText(assistant, text, sanitized);
|
|
1728
|
+
if (terminalError.length > 0) appendErrorSegment(assistant, terminalError);
|
|
1729
|
+
markDirty();
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
if (event.type === "retry_status") {
|
|
1733
|
+
const last = transcript[transcript.length - 1];
|
|
1734
|
+
if (last?.role === "retryStatus" && last.status.attempt === event.status.attempt) {
|
|
1735
|
+
last.status = event.status;
|
|
1736
|
+
} else {
|
|
1737
|
+
transcript.push({ role: "retryStatus", status: event.status });
|
|
1738
|
+
}
|
|
1739
|
+
markDirty();
|
|
1740
|
+
return;
|
|
1741
|
+
}
|
|
1742
|
+
if (event.type === "agent_end") {
|
|
1743
|
+
// agent_end can touch many entries, but it names every one it touches:
|
|
1744
|
+
// the usage caption's target, the later entries whose caption it
|
|
1745
|
+
// removes, and any entry it settles or un-pends below. Each is
|
|
1746
|
+
// invalidated at the point of mutation instead of dropping the whole
|
|
1747
|
+
// cache and re-rendering settled history.
|
|
1748
|
+
const runUsage = aggregateAssistantUsage(event.messages);
|
|
1749
|
+
if (runUsage !== undefined) {
|
|
1750
|
+
// The usage line is a caption on rendered output, so it goes on the
|
|
1751
|
+
// last entry that rendered any. A mid-turn notice splits entries, and
|
|
1752
|
+
// a turn that stops after one leaves an empty tail entry behind: the
|
|
1753
|
+
// run total landed there while the entry above kept its own
|
|
1754
|
+
// message_end line, so one turn printed the identical line twice, once
|
|
1755
|
+
// on each side of the notice. Entries after the caption rendered
|
|
1756
|
+
// nothing and must not carry a second copy.
|
|
1757
|
+
const index = lastAssistantIndex(transcript, { withOutput: true }) ?? lastAssistantIndex(transcript);
|
|
1758
|
+
const target = index === null ? undefined : transcript[index];
|
|
1759
|
+
if (target?.role === "assistant") {
|
|
1760
|
+
invalidateEntryCache(target);
|
|
1761
|
+
target.turnUsage = runUsage;
|
|
1762
|
+
}
|
|
1763
|
+
for (let after = (index ?? -1) + 1; after < transcript.length; after += 1) {
|
|
1764
|
+
const later = transcript[after];
|
|
1765
|
+
if (later?.role !== "assistant") continue;
|
|
1766
|
+
if (later.turnUsage !== undefined) invalidateEntryCache(later);
|
|
1767
|
+
delete later.turnUsage;
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
// The run is over: no tool can still be executing anywhere in the
|
|
1771
|
+
// transcript, not just in the tail entry (a mid-turn notice splits
|
|
1772
|
+
// entries). Settle any tool segment whose `tool_execution_end` never
|
|
1773
|
+
// arrived (blocked at admission, or cut off by an abort) so the
|
|
1774
|
+
// ledger never leaves a running line counting past the turn's end,
|
|
1775
|
+
// and clear `pending` everywhere so no earlier entry keeps rendering
|
|
1776
|
+
// live thinking or status.
|
|
1777
|
+
const runWasAborted =
|
|
1778
|
+
Array.isArray(event.messages) &&
|
|
1779
|
+
event.messages.some(
|
|
1780
|
+
(message) =>
|
|
1781
|
+
message && typeof message === "object" && (message as { stopReason?: unknown }).stopReason === "aborted",
|
|
1782
|
+
);
|
|
1783
|
+
for (const entry of transcript) {
|
|
1784
|
+
if (entry.role !== "assistant") continue;
|
|
1785
|
+
for (const seg of entry.segments) {
|
|
1786
|
+
if (seg.kind === "tool" && !seg.finished) {
|
|
1787
|
+
invalidateEntryCache(entry);
|
|
1788
|
+
settleUnfinishedToolSegment(seg, runWasAborted ? "aborted" : "orphaned");
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
if (entry.pending) {
|
|
1792
|
+
invalidateEntryCache(entry);
|
|
1793
|
+
entry.pending = false;
|
|
1794
|
+
entry.statusLine = null;
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
markDirty();
|
|
1798
|
+
}
|
|
1799
|
+
},
|
|
1800
|
+
setStatusLine(line): void {
|
|
1801
|
+
if (line) {
|
|
1802
|
+
const assistant = ensureAssistant();
|
|
1803
|
+
assistant.pending = true;
|
|
1804
|
+
assistant.statusLine = line;
|
|
1805
|
+
markDirty();
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
const last = transcript[transcript.length - 1];
|
|
1809
|
+
if (last && last.role === "assistant") {
|
|
1810
|
+
unfreezeTail();
|
|
1811
|
+
last.statusLine = null;
|
|
1812
|
+
markDirty();
|
|
1813
|
+
}
|
|
1814
|
+
},
|
|
1815
|
+
render,
|
|
1816
|
+
invalidate(): void {
|
|
1817
|
+
markDirty();
|
|
1818
|
+
},
|
|
1819
|
+
};
|
|
1820
|
+
}
|