@iowarp/clio-coder 0.4.3 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -0
- package/README.md +1 -1
- package/dist/{acp-H2NGRPWO.js → acp-WNAYYF4F.js} +4 -5
- package/dist/{agents-TL5LLUQP.js → agents-3OKXHLOI.js} +33 -31
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-E5SW4HMS.js → auth-VKNNMGPU.js} +10 -11
- package/dist/{builtins-IA7V7FUC.js → builtins-WGALA46I.js} +4 -4
- package/dist/{chunk-F5JHEYZM.js → chunk-23L32XTI.js} +10 -7
- package/dist/{chunk-DWUOQKRU.js → chunk-25QBEXRS.js} +2 -2
- package/dist/{chunk-LLDJM5XK.js → chunk-26QSH3EJ.js} +2 -2
- package/dist/{chunk-N2Z7HLVY.js → chunk-2ASED4PZ.js} +10 -10
- package/dist/{chunk-MCEPRMZW.js → chunk-2CU2H6KE.js} +2 -2
- package/dist/chunk-2DSOYNFC.js +108 -0
- package/dist/{chunk-LDJG7DW3.js → chunk-2ZSONWVL.js} +4 -4
- package/dist/{chunk-GI7YYQ3F.js → chunk-36CT5VVL.js} +324 -41
- package/dist/{chunk-4UVU7BJ5.js → chunk-3GY4F45V.js} +2 -2
- package/dist/{chunk-PUVDKJ2Y.js → chunk-3ODX73FK.js} +3 -5
- package/dist/{chunk-IUE3Y34X.js → chunk-3UNOLWNZ.js} +2 -2
- package/dist/{chunk-6PTFB5VS.js → chunk-3UUXNFEX.js} +8 -4
- package/dist/{chunk-2APPQIER.js → chunk-4M6Z5QVF.js} +4 -4
- package/dist/{chunk-K6BSR66V.js → chunk-4NSRCOYP.js} +4 -1
- package/dist/{chunk-JGRC33J2.js → chunk-54X7T7DK.js} +12 -3
- package/dist/{chunk-YQWYVTMC.js → chunk-5636DCO5.js} +4 -4
- package/dist/{chunk-7E7I3WLS.js → chunk-57XXR6DR.js} +23 -22
- package/dist/{chunk-7ZYNNDKC.js → chunk-5TUB6SLS.js} +2 -2
- package/dist/{chunk-VW6DOEDG.js → chunk-6OSVSQL5.js} +11 -2
- package/dist/{chunk-L47TF46W.js → chunk-6PAZTBPA.js} +14 -2
- package/dist/{chunk-XIVNBFZS.js → chunk-6Q3CYFD3.js} +34 -16
- package/dist/{chunk-RSJ25QSL.js → chunk-6QOTUPRG.js} +155 -36
- package/dist/{chunk-JKKCYP3C.js → chunk-6UINWWS6.js} +10 -4
- package/dist/{chunk-WBKFA554.js → chunk-72YIHOZQ.js} +2 -2
- package/dist/{chunk-PAJQJ7BS.js → chunk-75W7L2E2.js} +246 -697
- package/dist/{chunk-RRNP2ANY.js → chunk-7UGL4MB5.js} +4 -4
- package/dist/{chunk-EKCHAPYA.js → chunk-AUPNRN7C.js} +2 -2
- package/dist/{chunk-ZDN3Y73Y.js → chunk-BJVFZO5U.js} +6 -48
- package/dist/{chunk-IWT4SF4R.js → chunk-CUSRQKPU.js} +64 -2
- package/dist/{chunk-SKHCAU7K.js → chunk-DQOVN6KV.js} +3 -2
- package/dist/{chunk-ZWPRK62N.js → chunk-DT3LWJOB.js} +4 -4
- package/dist/chunk-DXKJURES.js +671 -0
- package/dist/{chunk-BEPZRGGU.js → chunk-EL24TAU4.js} +2 -2
- package/dist/{chunk-OML5D5V5.js → chunk-ELWDPP3Y.js} +5 -5
- package/dist/{chunk-NDINPTJ4.js → chunk-ELZVTCGV.js} +5 -4
- package/dist/chunk-EXLD33WO.js +381 -0
- package/dist/{chunk-IJNZMHLA.js → chunk-FEAXX7B6.js} +2 -2
- package/dist/{chunk-TM6LQDI3.js → chunk-FFUPXJC4.js} +74 -323
- package/dist/chunk-GX5WYQO4.js +59 -0
- package/dist/chunk-I2DWJ4GM.js +390 -0
- package/dist/{chunk-TXOTCRLG.js → chunk-I5FWO7L5.js} +5 -5
- package/dist/chunk-IRXAATOX.js +539 -0
- package/dist/chunk-IXIY2H4R.js +44 -0
- package/dist/{chunk-KK4JZPBQ.js → chunk-IZXGRF7P.js} +76 -10
- package/dist/{chunk-5TSRNF4G.js → chunk-JCI2ROMZ.js} +164 -6
- package/dist/{chunk-INY6HTFL.js → chunk-JEIYHLOR.js} +2 -2
- package/dist/{chunk-MUW2BDDH.js → chunk-JQLNNIKT.js} +2 -2
- package/dist/{chunk-XPWWI35G.js → chunk-JSD46VO2.js} +19 -8
- package/dist/{chunk-VA5FNYMT.js → chunk-JT2RFCC5.js} +39 -2
- package/dist/{chunk-V2ANDPVT.js → chunk-K6T2ZAMZ.js} +168 -6
- package/dist/{chunk-B4OAX3SI.js → chunk-KFV5L5SK.js} +13 -6
- package/dist/{chunk-JEQ3XTHC.js → chunk-LLXSDWXS.js} +2 -2
- package/dist/{chunk-E3TPLWFX.js → chunk-LTIKRKFL.js} +3 -3
- package/dist/{chunk-NIQJ66N4.js → chunk-N56KALIC.js} +17 -17
- package/dist/{chunk-JDAY6FIL.js → chunk-NAI6ZFCY.js} +13 -9
- package/dist/{chunk-AF4YM7Z4.js → chunk-NRO2BJRH.js} +2490 -2182
- package/dist/{chunk-WRBAGUNF.js → chunk-NXIMQY5W.js} +2 -2
- package/dist/{chunk-I7ZPNEJM.js → chunk-NXYCB2VD.js} +5 -5
- package/dist/chunk-ODGTEFFI.js +50 -0
- package/dist/{chunk-2VIKGWFZ.js → chunk-OEJSLEPW.js} +2 -2
- package/dist/{chunk-KKOJXO6R.js → chunk-OMQNJVKW.js} +4 -2
- package/dist/{chunk-54CBCGIR.js → chunk-Q4WO54TA.js} +132 -77
- package/dist/{chunk-B4VEBZKF.js → chunk-QUFRYSWI.js} +12 -6
- package/dist/{chunk-42FMPA75.js → chunk-QZWQA4DE.js} +2 -2
- package/dist/{chunk-HEQY7ZFI.js → chunk-RAY4OVGZ.js} +2 -2
- package/dist/{chunk-G76U63X4.js → chunk-RQCKCSRL.js} +7 -7
- package/dist/{chunk-64I3JVYM.js → chunk-RXTN6AKH.js} +2 -2
- package/dist/{chunk-UPZU6GE4.js → chunk-RZDWV63N.js} +3 -3
- package/dist/{chunk-2UH2KFUP.js → chunk-S6PYF2XF.js} +2 -2
- package/dist/{chunk-JSC3U7TI.js → chunk-TOIVGRUX.js} +2 -2
- package/dist/{chunk-7DRAWPTZ.js → chunk-TQAHXW6Y.js} +1 -1
- package/dist/{chunk-JIEGK6UF.js → chunk-U6TMQNSI.js} +48 -4
- package/dist/{chunk-AX2THNSA.js → chunk-UEPWCCTY.js} +4 -4
- package/dist/{chunk-QWGDJJYJ.js → chunk-USR47QNF.js} +4 -4
- package/dist/{chunk-Y3CBHOR6.js → chunk-V6HJFQZE.js} +2 -2
- package/dist/chunk-V76WTFTW.js +318 -0
- package/dist/{chunk-NMJXSHBJ.js → chunk-W54I7H25.js} +2 -2
- package/dist/{chunk-2UG5F4C5.js → chunk-XULDXHTN.js} +24 -12
- package/dist/chunk-XXYSBZIQ.js +283 -0
- package/dist/{chunk-MWUZBSAQ.js → chunk-Y55JBDO5.js} +331 -51
- package/dist/{chunk-W6RRQCPQ.js → chunk-YD5GIKET.js} +4 -4
- package/dist/{chunk-CE5AX47J.js → chunk-YECAMM3D.js} +2 -2
- package/dist/{chunk-WCXUNS7U.js → chunk-YNFKXPEC.js} +6 -6
- package/dist/cli/index.js +36 -35
- package/dist/{clio-CMMK4KRR.js → clio-QLICPCF5.js} +2 -2
- package/dist/{code-nav-MDZNQS33.js → code-nav-IJR2DBPR.js} +6 -6
- package/dist/{components-UCUQ4QXW.js → components-2TGAI2RC.js} +3 -4
- package/dist/{config-SVM5P5YI.js → config-IUA6OYNS.js} +52 -47
- package/dist/{configure-LE3IK2TJ.js → configure-VEPX4NMX.js} +14 -15
- package/dist/{context-VNCR7KAG.js → context-2DKHWH2T.js} +23 -23
- package/dist/{context-2OHRKS42.js → context-4MPR7WKB.js} +48 -42
- package/dist/{context-E3VC7RX5.js → context-BOYF5EJM.js} +11 -11
- package/dist/{context-clear-BW4O37TG.js → context-clear-S4ZJCQUX.js} +48 -42
- package/dist/{context-working-set-VDS25HXZ.js → context-working-set-3I3FYX6Y.js} +8 -8
- package/dist/detail-A7JAVSIG.js +98 -0
- package/dist/{dispatch-runner-5AHT53RF.js → dispatch-runner-RJ5I2F2O.js} +71 -58
- package/dist/{docs-PD3EXDKU.js → docs-SPOV3BAN.js} +3 -5
- package/dist/{doctor-WNNVO6FY.js → doctor-DKICC2SN.js} +54 -31
- package/dist/{eval-7G7SGAYO.js → eval-OQOQUDHK.js} +47 -56
- package/dist/{evidence-VD6736FQ.js → evidence-4DQ25GUQ.js} +53 -150
- package/dist/evidence-4F5USFKH.js +208 -0
- package/dist/{evolve-AL3NGVRL.js → evolve-GSS52E5J.js} +44 -41
- package/dist/{extensions-MOVJ32NM.js → extensions-G7MFLYHT.js} +4 -5
- package/dist/{fleet-QZHUMAGI.js → fleet-Q37YHHAQ.js} +74 -68
- package/dist/{fleet-commands-BAYT5FJZ.js → fleet-commands-G7E4N7SM.js} +14 -11
- package/dist/{fleet-decisions-IREVMRU4.js → fleet-decisions-O7M6QBA2.js} +6 -6
- package/dist/{fleet-graph-YCTT3HTI.js → fleet-graph-TOUBW6OW.js} +13 -13
- package/dist/{fleet-inspect-QVJTDAVB.js → fleet-inspect-SW33JJNI.js} +43 -39
- package/dist/{fleet-preflight-25QAFPK4.js → fleet-preflight-CV2655TW.js} +2 -3
- package/dist/{fleet-validate-5O57AAJ7.js → fleet-validate-KESZX2YH.js} +16 -16
- package/dist/{fleet-verify-CPH2W2T6.js → fleet-verify-UQPMTVE3.js} +42 -38
- package/dist/{fleet-view-SWBR3VGQ.js → fleet-view-MN2VG4MR.js} +43 -39
- package/dist/{init-J477LKZH.js → init-PXEXQSBF.js} +60 -54
- package/dist/{interop-3FCM6XLG.js → interop-ZG5T62U3.js} +6 -7
- package/dist/inventory-C26CFDRR.js +101 -0
- package/dist/{library-QUQEIUG6.js → library-B2W4N74O.js} +16 -17
- package/dist/{memory-SGGSEP65.js → memory-YCANYS5A.js} +45 -42
- package/dist/{models-HEKUAXXK.js → models-GERTU3YI.js} +20 -22
- package/dist/{monitor-HKU57TYQ.js → monitor-CPNIUULB.js} +50 -44
- package/dist/{orchestrator-VDFAEFAI.js → orchestrator-J4BSH4WQ.js} +491 -1182
- package/dist/{panes-DN2SSFOH.js → panes-BOHAEGYC.js} +3 -3
- package/dist/{panes-TALGNPZT.js → panes-NXSLDQZ2.js} +5 -6
- package/dist/{paths-NBMFAIEZ.js → paths-VSUWNC22.js} +3 -4
- package/dist/{reset-EAJFFJVB.js → reset-TNWTB5LU.js} +5 -6
- package/dist/{resources-OVKSEFVE.js → resources-4PXNMD5G.js} +14 -14
- package/dist/{run-7DP7ZF2J.js → run-D6XJ34CN.js} +79 -83
- package/dist/{share-WML67FT3.js → share-2NWMJJEE.js} +14 -15
- package/dist/{skills-SG662R2K.js → skills-KR7WON5G.js} +18 -19
- package/dist/{skills-eval-VVZEUU46.js → skills-eval-O2ZNOLDS.js} +49 -46
- package/dist/{skills-inventory-I2E23GET.js → skills-inventory-ZZOUBK7O.js} +14 -14
- package/dist/{slash-commands-S7MBJDQK.js → slash-commands-ZXPJD64J.js} +32 -23
- package/dist/{steer-2LQOMCPB.js → steer-XA25PSCS.js} +3 -3
- package/dist/{support-CC2UJBJ6.js → support-7EMVWYG2.js} +2 -2
- package/dist/{targets-4QC3HIEW.js → targets-OMH2XCSN.js} +25 -27
- package/dist/tasks-IPAGMEIX.js +36 -0
- package/dist/{terminal-lease-TUHIJ6Y2.js → terminal-lease-C2J3JYRE.js} +4 -4
- package/dist/{tools-TFGJICCU.js → tools-EFFEAIDP.js} +5 -6
- package/dist/{uninstall-5PEVOE5B.js → uninstall-HALS6BLF.js} +7 -8
- package/dist/{upgrade-M4WXY6KN.js → upgrade-MS72RJEP.js} +14 -11
- package/dist/{usage-N7ZNVLEM.js → usage-NHG6MCJM.js} +60 -51
- package/dist/{verifiers-DJTP4XX6.js → verifiers-7AUNVXDY.js} +150 -17
- package/dist/{verify-RWE4PPEK.js → verify-FWYGPKMR.js} +12 -10
- package/dist/{web-fetch-MPARV2K7.js → web-fetch-V4FKSDAV.js} +4 -4
- package/dist/{wiki-generate-C7IQOXSP.js → wiki-generate-743CIGJW.js} +63 -56
- package/dist/{with-panes-4GCGSL7J.js → with-panes-BDQEWBRT.js} +2 -2
- package/dist/worker/entry.js +36 -33
- package/docs/README.md +3 -2
- package/docs/architecture/acp.md +17 -0
- package/docs/architecture/artifact-versions.md +1 -1
- package/docs/architecture/dispatch-typed-intent.md +1 -1
- package/docs/architecture/evidence-and-memory.md +1 -1
- package/docs/architecture/observability.md +7 -1
- package/docs/architecture/prompt-envelope-and-tools.md +15 -5
- package/docs/architecture/safety-model.md +10 -17
- package/docs/architecture/tui-design.md +1 -1
- package/docs/guide/built-in-agents.md +8 -8
- package/docs/guide/commands-and-modes.md +17 -2
- package/docs/guide/configuration-and-targets.md +3 -1
- package/docs/guide/configuration-reference.md +10 -5
- package/docs/guide/environment-variables.md +2 -2
- package/docs/guide/tool-usage.md +78 -3
- package/docs/history/config-knobs-audit.md +2 -2
- package/docs/process/development-pipeline.md +6 -1
- package/docs/process/git-commit-provenance.md +15 -0
- package/docs/process/release-cut-checklist.md +207 -0
- package/docs/process/scientific-validation.md +18 -17
- package/evals/behavioral-machinery-support.ts +1 -0
- package/evals/behavioral-machinery.yaml +1 -1
- package/package.json +1 -1
- package/skills/coding/coding-standards/SKILL.md +14 -4
- package/skills/context/context-handoff/SKILL.md +3 -3
- package/skills/context/context-prime/SKILL.md +3 -3
- package/skills/planning/architecture/SKILL.md +5 -5
- package/skills/planning/backlog/SKILL.md +4 -4
- package/skills/planning/prd/SKILL.md +4 -3
- package/skills/planning/product-intent/SKILL.md +8 -8
- package/skills/planning/tech-spec/SKILL.md +4 -4
- package/skills/registry.yaml +32 -32
- package/skills/research/arxiv-literature/SKILL.md +3 -3
- package/skills/research/experiment-protocol/SKILL.md +3 -3
- package/skills/research/scientific-debugging/SKILL.md +3 -2
- package/skills/research/scientific-modernization/SKILL.md +3 -3
- package/skills/skill-marketplace.json +16 -16
- package/skills/workflow/cut-it/SKILL.md +3 -4
- package/skills/workflow/design-council/SKILL.md +5 -10
- package/skills/workflow/grill-me/SKILL.md +14 -15
- package/skills/workflow/workflow-distiller/SKILL.md +4 -4
- package/src/cli/args.ts +0 -8
- package/src/cli/configure.ts +2 -1
- package/src/cli/doctor-state-size.ts +1 -12
- package/src/cli/doctor-validation-contract.ts +28 -0
- package/src/cli/doctor.ts +5 -0
- package/src/cli/evidence-detail.ts +1 -75
- package/src/cli/evidence-inventory.ts +1 -167
- package/src/cli/index.ts +2 -0
- package/src/cli/run.ts +0 -2
- package/src/cli/targets.ts +1 -1
- package/src/cli/tasks.ts +84 -0
- package/src/cli/upgrade.ts +10 -5
- package/src/cli/usage.ts +6 -0
- package/src/cli/verifiers.ts +147 -1
- package/src/cli/wiki-generate.ts +1 -0
- package/src/core/commit-attribution.ts +41 -1
- package/src/core/git-commit-attribution.ts +46 -3
- package/src/core/run-overrides.ts +0 -5
- package/src/core/skill-activation.ts +3 -0
- package/src/core/tool-names.ts +5 -2
- package/src/domains/agents/builtins/architect.md +1 -1
- package/src/domains/agents/builtins/coder.md +1 -1
- package/src/domains/agents/builtins/documenter.md +1 -1
- package/src/domains/agents/builtins/git-master.md +1 -1
- package/src/domains/agents/builtins/provenance.md +7 -7
- package/src/domains/agents/builtins/tester.md +1 -1
- package/src/domains/agents/builtins/verifier.md +2 -2
- package/src/domains/agents/builtins/wiki-writer.md +4 -3
- package/src/domains/context/extension.ts +31 -7
- package/src/domains/context/refresh.ts +3 -0
- package/src/domains/context/wiki/frontmatter.ts +5 -2
- package/src/domains/context/wiki/generate.ts +6 -0
- package/src/domains/context/wiki/prompts.ts +43 -0
- package/src/domains/dispatch/active-route-planner.ts +4 -0
- package/src/domains/dispatch/code-step.ts +11 -4
- package/src/domains/dispatch/contract.ts +31 -7
- package/src/domains/dispatch/execution-scheduler.ts +2 -0
- package/src/domains/dispatch/extension.ts +50 -27
- package/src/domains/dispatch/fleet-commit-attribution.ts +5 -0
- package/src/domains/dispatch/fleet-run.ts +1 -0
- package/src/domains/dispatch/host-verification.ts +114 -13
- package/src/domains/dispatch/intent.ts +28 -18
- package/src/domains/dispatch/orphan-recovery.ts +2 -0
- package/src/domains/dispatch/receipt-integrity.ts +4 -0
- package/src/domains/dispatch/reservation-store.ts +5 -3
- package/src/domains/dispatch/state.ts +15 -2
- package/src/domains/dispatch/types.ts +17 -2
- package/src/domains/eval/runners/clio-run.ts +12 -9
- package/src/domains/eval/runners/context-index.ts +2 -7
- package/src/domains/eval/runners/context-init.ts +3 -6
- package/src/domains/eval/runners/external-command.ts +29 -11
- package/src/domains/evidence/build.ts +102 -15
- package/src/domains/evidence/detail.ts +69 -0
- package/src/domains/evidence/eval.ts +13 -1
- package/src/domains/evidence/finish-contract-map.ts +5 -1
- package/src/domains/evidence/inventory.ts +167 -0
- package/src/domains/evidence/store.ts +16 -0
- package/src/domains/evidence/types.ts +12 -0
- package/src/domains/extensions/resources.ts +7 -0
- package/src/domains/middleware/marketplace-offer.ts +20 -1
- package/src/domains/middleware/runtime.ts +7 -3
- package/src/domains/mux/detect.ts +3 -6
- package/src/domains/observability/accountability.ts +15 -1
- package/src/domains/observability/contract.ts +52 -7
- package/src/domains/observability/evidence-index.ts +10 -0
- package/src/domains/observability/extension.ts +9 -5
- package/src/domains/observability/projection.ts +394 -45
- package/src/{interactive → domains/observability}/worker-progress.ts +3 -3
- package/src/domains/prompts/fragments/operating/contract.md +2 -0
- package/src/domains/prompts/fragments/wiki/page.md +8 -0
- package/src/domains/providers/model-discovery.ts +1 -4
- package/src/domains/providers/models/local-models/clio-coder-local-coding-targets.yaml +16 -14
- package/src/domains/providers/runtimes/claude/claude-code.ts +9 -0
- package/src/domains/providers/support.ts +1 -1
- package/src/domains/providers/types/runtime-descriptor.ts +1 -1
- package/src/domains/safety/action-classifier.ts +7 -0
- package/src/domains/safety/default-path-policy.ts +2 -0
- package/src/domains/safety/finish-contract-registration.ts +29 -14
- package/src/domains/safety/finish-contract.ts +252 -40
- package/src/domains/safety/index.ts +20 -1
- package/src/domains/safety/policy-engine.ts +26 -6
- package/src/domains/safety/rigor.ts +53 -39
- package/src/domains/safety/validation-contract.ts +388 -0
- package/src/domains/session/archive-readers.ts +10 -1
- package/src/domains/session/decision-board.ts +101 -2
- package/src/domains/session/entries.ts +44 -7
- package/src/domains/session/extension.ts +4 -4
- package/src/domains/session/handoff.ts +2 -1
- package/src/domains/session/manager.ts +2 -3
- package/src/domains/session/task-board.ts +14 -1
- package/src/domains/session/tree/fork.ts +1 -2
- package/src/domains/session/tree/navigator.ts +1 -1
- package/src/domains/user-tasks/acceptance.ts +56 -0
- package/src/domains/user-tasks/active-acceptance.ts +40 -0
- package/src/domains/user-tasks/store.ts +34 -3
- package/src/engine/acp/adapter.ts +24 -6
- package/src/engine/acp/server.ts +21 -4
- package/src/engine/acp/transport.ts +53 -8
- package/src/engine/acp/types.ts +4 -0
- package/src/engine/apis/ollama-native.ts +15 -0
- package/src/engine/claude/subprocess-runtime.ts +107 -60
- package/src/engine/external-subprocess.ts +12 -4
- package/src/entry/orchestrator.ts +11 -2
- package/src/interactive/dispatch-board.ts +46 -717
- package/src/interactive/fleet-run-preview.ts +2 -1
- package/src/interactive/interactive-application.ts +3 -2
- package/src/interactive/interactive-presentation.ts +55 -12
- package/src/interactive/interactive-slash-runtime.ts +2 -1
- package/src/interactive/oracle.ts +5 -2
- package/src/interactive/overlays/fleet-run-approval.ts +3 -2
- package/src/interactive/overlays/message-picker.ts +2 -2
- package/src/interactive/overlays/settings.ts +2 -2
- package/src/interactive/overlays/tree-selector.ts +2 -2
- package/src/interactive/renderers/branch-summary.ts +1 -1
- package/src/interactive/slash-autocomplete.ts +4 -6
- package/src/interactive/slash-commands.ts +25 -39
- package/src/interactive/slash-spec.ts +28 -0
- package/src/interactive/view/artifacts.ts +2 -0
- package/src/interactive/worker-stream.ts +7 -3
- package/src/tools/bootstrap.ts +4 -0
- package/src/tools/builtin-tool-catalog.ts +31 -0
- package/src/tools/compete-worktrees.ts +7 -1
- package/src/tools/core-bootstrap.ts +16 -0
- package/src/tools/decide.ts +136 -0
- package/src/tools/dispatch-admission.ts +21 -0
- package/src/tools/dispatch-plan.ts +5 -2
- package/src/tools/dispatch-runner.ts +15 -1
- package/src/tools/dispatch-types.ts +6 -0
- package/src/tools/evidence.ts +96 -0
- package/src/tools/limitation.ts +76 -0
- package/src/tools/policy.ts +9 -0
- package/src/tools/presentation.ts +3 -0
- package/src/tools/registry.ts +1 -1
- package/src/tools/result-shaping.ts +17 -5
- package/src/tools/task-worktree.ts +13 -3
- package/src/tools/tasks.ts +10 -1
- package/src/tools/verify/authoring.ts +170 -83
- package/src/tools/verify/catalog.ts +122 -5
- package/src/tools/verify/index.ts +2 -1
- package/src/tools/verify/numeric.ts +298 -0
- package/src/tools/verify/perf.ts +143 -0
- package/src/tools/verify/scripts.ts +229 -2
- package/dist/chunk-7DICMOS6.js +0 -313
- package/dist/chunk-RVG5JXAL.js +0 -41
- package/dist/chunk-T56WDKA5.js +0 -183
- package/dist/chunk-VPKWYKEY.js +0 -169
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import { isAbsolute, normalize, relative, resolve } from "node:path";
|
|
25
|
+
import { decisionRationale } from "./decision-board.js";
|
|
25
26
|
import type { DecisionLedgerEntry, SessionEntry } from "./entries.js";
|
|
26
27
|
import { filterEntriesToActivePath } from "./tree/active-path.js";
|
|
27
28
|
|
|
@@ -480,7 +481,7 @@ export function mergeHandoffDecisions(
|
|
|
480
481
|
if (decision.status !== "active") continue;
|
|
481
482
|
const name = decision.label ?? decision.key;
|
|
482
483
|
const summary = boundHandoffString(`${name}: ${decision.value}`).text;
|
|
483
|
-
const rationale = decision
|
|
484
|
+
const rationale = decisionRationale(decision);
|
|
484
485
|
settled.push({
|
|
485
486
|
summary,
|
|
486
487
|
...(rationale ? { rationale: boundHandoffString(rationale).text } : {}),
|
|
@@ -111,9 +111,8 @@ export function appendTurn(state: SessionManagerState, input: TurnInput): ClioTu
|
|
|
111
111
|
|
|
112
112
|
/**
|
|
113
113
|
* Append a rich SessionEntry via the engine writer. Non-message kinds are
|
|
114
|
-
* written as JSON lines to current.jsonl
|
|
115
|
-
*
|
|
116
|
-
* branch points too.
|
|
114
|
+
* written as JSON lines to current.jsonl and do not project into tree.json;
|
|
115
|
+
* the /fork picker offers assistant message turns only.
|
|
117
116
|
*/
|
|
118
117
|
export function appendEntry(state: SessionManagerState, input: SessionEntryInput): SessionEntry {
|
|
119
118
|
const turnId = input.turnId ?? newTurnId();
|
|
@@ -29,6 +29,7 @@ export interface TaskBoardTask {
|
|
|
29
29
|
status: TaskLedgerStatus;
|
|
30
30
|
origin?: "agent" | "user";
|
|
31
31
|
userTaskId?: string;
|
|
32
|
+
requiredValidationEvidence?: TaskLedgerValidationEvidence[];
|
|
32
33
|
/** Block or drop reason; empty for pending/active/completed tasks. */
|
|
33
34
|
reason?: string;
|
|
34
35
|
/** Evidence note recorded when the task was completed. */
|
|
@@ -64,7 +65,7 @@ export interface TaskBoardCounts {
|
|
|
64
65
|
export type TaskBoardMutation =
|
|
65
66
|
| { op: "plan"; title: string; tasks: ReadonlyArray<string> }
|
|
66
67
|
| { op: "add"; tasks: ReadonlyArray<string> }
|
|
67
|
-
| { op: "pick"; title: string; userTaskId: string }
|
|
68
|
+
| { op: "pick"; title: string; userTaskId: string; verification?: ReadonlyArray<{ check: string; timeoutMs: number }> }
|
|
68
69
|
| { op: "start"; id: string }
|
|
69
70
|
| { op: "done"; id: string; evidence: string }
|
|
70
71
|
| { op: "block"; id: string; reason: string }
|
|
@@ -179,6 +180,7 @@ function boardStatus(tasks: ReadonlyArray<TaskBoardTask>): TaskLedgerStatus {
|
|
|
179
180
|
export function toTaskLedgerEntryFields(board: TaskBoardSnapshot, now: Date): TaskLedgerEntryFields {
|
|
180
181
|
const evidence: TaskLedgerValidationEvidence[] = [];
|
|
181
182
|
const subgoals: TaskLedgerGoal[] = board.tasks.map((task) => {
|
|
183
|
+
evidence.push(...(task.requiredValidationEvidence ?? []).map((item) => ({ ...item })));
|
|
182
184
|
if (task.status === "completed" && task.evidence) {
|
|
183
185
|
evidence.push({
|
|
184
186
|
id: `${task.id}.evidence`,
|
|
@@ -308,6 +310,8 @@ function toEntryView(entry: {
|
|
|
308
310
|
origin: goal.origin ?? "agent",
|
|
309
311
|
};
|
|
310
312
|
if (goal.userTaskId) task.userTaskId = goal.userTaskId;
|
|
313
|
+
const required = entry.requiredValidationEvidence.filter((item) => item.id.startsWith(`${goal.id}.acceptance.`));
|
|
314
|
+
if (required.length > 0) task.requiredValidationEvidence = required.map((item) => ({ ...item }));
|
|
311
315
|
if (goal.description) task.reason = goal.description;
|
|
312
316
|
const evidence = evidenceByTask.get(goal.id);
|
|
313
317
|
if (evidence) task.evidence = evidence;
|
|
@@ -390,6 +394,15 @@ function applyMutation(
|
|
|
390
394
|
origin: "user",
|
|
391
395
|
userTaskId: mutation.userTaskId,
|
|
392
396
|
};
|
|
397
|
+
if (mutation.verification && mutation.verification.length > 0) {
|
|
398
|
+
picked.requiredValidationEvidence = mutation.verification.map((item, index) => ({
|
|
399
|
+
id: `${picked.id}.acceptance.${index}`,
|
|
400
|
+
description: `Operator acceptance: ${item.check}`,
|
|
401
|
+
command: item.check,
|
|
402
|
+
status: "pending",
|
|
403
|
+
notes: `timeoutMs=${item.timeoutMs}`,
|
|
404
|
+
}));
|
|
405
|
+
}
|
|
393
406
|
return {
|
|
394
407
|
board: {
|
|
395
408
|
boardId: board?.boardId ?? "",
|
|
@@ -14,8 +14,7 @@ import { entryBelongsToPath } from "./active-path.js";
|
|
|
14
14
|
* install it as current.
|
|
15
15
|
*
|
|
16
16
|
* Used by SessionContract.fork (current session) and the /fork message
|
|
17
|
-
* picker
|
|
18
|
-
* bundle wiring stays thin.
|
|
17
|
+
* picker overlay. Kept out of extension.ts so the bundle wiring stays thin.
|
|
19
18
|
*/
|
|
20
19
|
export interface ForkInput {
|
|
21
20
|
/** The state we are forking from. Must carry a live writer. */
|
|
@@ -6,7 +6,7 @@ import type { ResolvedLabel } from "./manager.js";
|
|
|
6
6
|
* Purely functional tree-walk helpers. Given the raw SessionTreeNode[] that
|
|
7
7
|
* the engine persists plus a label map and a leaf pointer, produce a
|
|
8
8
|
* render-friendly snapshot. No I/O. Used by SessionContract.tree() to
|
|
9
|
-
* deliver a serializable structure to
|
|
9
|
+
* deliver a serializable structure to the /tree overlay renderer.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DISPATCH_INTENT_TIMEOUT_MIN_MS,
|
|
3
|
+
type DispatchIntentCheckBound,
|
|
4
|
+
type DispatchIntentVerification,
|
|
5
|
+
normalizeIntentExpectedOutputs,
|
|
6
|
+
normalizeIntentVerification,
|
|
7
|
+
} from "../dispatch/intent.js";
|
|
8
|
+
|
|
9
|
+
export interface UserTaskAcceptance {
|
|
10
|
+
expectedOutputs: string[];
|
|
11
|
+
verification: DispatchIntentVerification[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Validate stored acceptance without consulting a catalog that may have changed. */
|
|
15
|
+
export function normalizeUserTaskAcceptance(raw: unknown): UserTaskAcceptance {
|
|
16
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
17
|
+
throw new Error("acceptance must be an object");
|
|
18
|
+
}
|
|
19
|
+
const value = raw as Record<string, unknown>;
|
|
20
|
+
if (Object.keys(value).some((key) => key !== "expectedOutputs" && key !== "verification")) {
|
|
21
|
+
throw new Error("acceptance contains an unknown field");
|
|
22
|
+
}
|
|
23
|
+
if (!Array.isArray(value.expectedOutputs) || !Array.isArray(value.verification)) {
|
|
24
|
+
throw new Error("acceptance requires expectedOutputs and verification arrays");
|
|
25
|
+
}
|
|
26
|
+
const checks = new Map<string, DispatchIntentCheckBound>();
|
|
27
|
+
const rawVerification = value.verification.map((entry: unknown) => {
|
|
28
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
|
|
29
|
+
throw new Error("acceptance verification must contain check and timeoutMs");
|
|
30
|
+
}
|
|
31
|
+
const item = entry as Record<string, unknown>;
|
|
32
|
+
if (Object.keys(item).some((key) => key !== "check" && key !== "timeoutMs")) {
|
|
33
|
+
throw new Error("acceptance verification contains an unknown field");
|
|
34
|
+
}
|
|
35
|
+
if (
|
|
36
|
+
typeof item.check !== "string" ||
|
|
37
|
+
typeof item.timeoutMs !== "number" ||
|
|
38
|
+
!Number.isSafeInteger(item.timeoutMs) ||
|
|
39
|
+
item.timeoutMs <= 0
|
|
40
|
+
) {
|
|
41
|
+
throw new Error("acceptance verification requires a check and a positive integer timeoutMs");
|
|
42
|
+
}
|
|
43
|
+
checks.set(item.check.trim(), {
|
|
44
|
+
id: item.check.trim(),
|
|
45
|
+
timeoutMs: Math.max(DISPATCH_INTENT_TIMEOUT_MIN_MS, item.timeoutMs, checks.get(item.check.trim())?.timeoutMs ?? 0),
|
|
46
|
+
});
|
|
47
|
+
return { check: item.check, timeout_ms: item.timeoutMs };
|
|
48
|
+
});
|
|
49
|
+
const expectedOutputs = normalizeIntentExpectedOutputs(value.expectedOutputs);
|
|
50
|
+
if (!Array.isArray(expectedOutputs))
|
|
51
|
+
throw new Error(expectedOutputs.ok ? "invalid acceptance outputs" : expectedOutputs.message);
|
|
52
|
+
const verification = normalizeIntentVerification(rawVerification, checks);
|
|
53
|
+
if (!Array.isArray(verification))
|
|
54
|
+
throw new Error(verification.ok ? "invalid acceptance verification" : verification.message);
|
|
55
|
+
return { expectedOutputs, verification };
|
|
56
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { isSessionEntry } from "../session/entries.js";
|
|
2
|
+
import type { TaskBoardSnapshot } from "../session/task-board.js";
|
|
3
|
+
import type { UserTaskAcceptance } from "./acceptance.js";
|
|
4
|
+
import type { UserTask } from "./store.js";
|
|
5
|
+
|
|
6
|
+
/** Keep a task's acceptance through the turn that finishes or blocks its board row. */
|
|
7
|
+
export function activeUserTaskAcceptance(
|
|
8
|
+
tasks: ReadonlyArray<UserTask>,
|
|
9
|
+
board: TaskBoardSnapshot | null,
|
|
10
|
+
sessionId: string | null,
|
|
11
|
+
window: ReadonlyArray<unknown>,
|
|
12
|
+
): UserTaskAcceptance | undefined {
|
|
13
|
+
if (!board || !sessionId) return undefined;
|
|
14
|
+
const touched = new Set<string>();
|
|
15
|
+
for (const entry of window) {
|
|
16
|
+
if (!isSessionEntry(entry) || entry.kind !== "message" || entry.role !== "tool_call") continue;
|
|
17
|
+
if (!entry.payload || typeof entry.payload !== "object") continue;
|
|
18
|
+
const payload = entry.payload as { name?: string; toolName?: string; args?: { action?: string; id?: string } };
|
|
19
|
+
if (
|
|
20
|
+
(payload.name ?? payload.toolName) === "tasks" &&
|
|
21
|
+
payload.args?.id &&
|
|
22
|
+
["done", "block", "drop"].includes(payload.args.action ?? "")
|
|
23
|
+
)
|
|
24
|
+
touched.add(payload.args.id);
|
|
25
|
+
}
|
|
26
|
+
const linked = new Set(
|
|
27
|
+
board.tasks
|
|
28
|
+
.filter((task) => task.status === "pending" || task.status === "active" || touched.has(task.id))
|
|
29
|
+
.map((task) => task.userTaskId),
|
|
30
|
+
);
|
|
31
|
+
const acceptance = tasks
|
|
32
|
+
.filter((task) => task.handedSessionId === sessionId && linked.has(task.id))
|
|
33
|
+
.flatMap((task) => (task.acceptance ? [task.acceptance] : []));
|
|
34
|
+
return acceptance.length > 0
|
|
35
|
+
? {
|
|
36
|
+
expectedOutputs: [...new Set(acceptance.flatMap((item) => item.expectedOutputs))],
|
|
37
|
+
verification: acceptance.flatMap((item) => item.verification),
|
|
38
|
+
}
|
|
39
|
+
: undefined;
|
|
40
|
+
}
|
|
@@ -2,6 +2,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { CLIO_PROJECT_DIR } from "../../core/artifact-paths.js";
|
|
4
4
|
import { atomicWrite } from "../../engine/session.js";
|
|
5
|
+
import { normalizeUserTaskAcceptance, type UserTaskAcceptance } from "./acceptance.js";
|
|
5
6
|
|
|
6
7
|
export const USER_TASKS_FILE_VERSION = 1 as const;
|
|
7
8
|
export const USER_TASKS_RELATIVE_PATH = `${CLIO_PROJECT_DIR}/user-tasks.json`;
|
|
@@ -12,6 +13,7 @@ export interface UserTask {
|
|
|
12
13
|
id: string;
|
|
13
14
|
title: string;
|
|
14
15
|
note?: string;
|
|
16
|
+
acceptance?: UserTaskAcceptance;
|
|
15
17
|
status: UserTaskStatus;
|
|
16
18
|
createdAt: string;
|
|
17
19
|
updatedAt: string;
|
|
@@ -37,7 +39,8 @@ export interface UserTasksStore {
|
|
|
37
39
|
readonly path: string;
|
|
38
40
|
snapshot(): ReadonlyArray<UserTask>;
|
|
39
41
|
get(id: string): UserTask | null;
|
|
40
|
-
add(title: string, note?: string): UserTask;
|
|
42
|
+
add(title: string, note?: string, acceptance?: UserTaskAcceptance): UserTask;
|
|
43
|
+
setAcceptance(id: string, acceptance: UserTaskAcceptance): UserTask;
|
|
41
44
|
hand(id: string, sessionId?: string): UserTask;
|
|
42
45
|
done(id: string): UserTask;
|
|
43
46
|
drop(id: string): UserTask;
|
|
@@ -65,6 +68,7 @@ const TASK_KEYS = new Set([
|
|
|
65
68
|
"id",
|
|
66
69
|
"title",
|
|
67
70
|
"note",
|
|
71
|
+
"acceptance",
|
|
68
72
|
"status",
|
|
69
73
|
"createdAt",
|
|
70
74
|
"updatedAt",
|
|
@@ -86,6 +90,13 @@ function isOptionalString(value: unknown): boolean {
|
|
|
86
90
|
|
|
87
91
|
function isUserTask(value: unknown): value is UserTask {
|
|
88
92
|
if (!isRecord(value) || !hasOnlyKeys(value, TASK_KEYS)) return false;
|
|
93
|
+
if (value.acceptance !== undefined) {
|
|
94
|
+
try {
|
|
95
|
+
normalizeUserTaskAcceptance(value.acceptance);
|
|
96
|
+
} catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
89
100
|
return (
|
|
90
101
|
typeof value.id === "string" &&
|
|
91
102
|
/^u[1-9]\d*$/.test(value.id) &&
|
|
@@ -138,7 +149,7 @@ function parseFile(body: string, path: string): UserTasksFile {
|
|
|
138
149
|
}
|
|
139
150
|
|
|
140
151
|
function copyTask(task: UserTask): UserTask {
|
|
141
|
-
return { ...task };
|
|
152
|
+
return { ...task, ...(task.acceptance ? { acceptance: normalizeUserTaskAcceptance(task.acceptance) } : {}) };
|
|
142
153
|
}
|
|
143
154
|
|
|
144
155
|
function initialFile(): UserTasksFile {
|
|
@@ -219,7 +230,8 @@ export function createUserTasksStore(deps: UserTasksStoreDeps): UserTasksStore {
|
|
|
219
230
|
const task = load().tasks.find((candidate) => candidate.id === id);
|
|
220
231
|
return task ? copyTask(task) : null;
|
|
221
232
|
},
|
|
222
|
-
add(title: string, note?: string): UserTask {
|
|
233
|
+
add(title: string, note?: string, acceptance?: UserTaskAcceptance): UserTask {
|
|
234
|
+
const normalizedAcceptance = acceptance === undefined ? undefined : normalizeAcceptance(acceptance);
|
|
223
235
|
const normalizedTitle = title.trim();
|
|
224
236
|
if (normalizedTitle.length === 0) throw new UserTasksStoreError("operator task title cannot be empty");
|
|
225
237
|
const normalizedNote = note?.trim();
|
|
@@ -229,6 +241,7 @@ export function createUserTasksStore(deps: UserTasksStoreDeps): UserTasksStore {
|
|
|
229
241
|
id: `u${file.nextId}`,
|
|
230
242
|
title: normalizedTitle,
|
|
231
243
|
...(normalizedNote ? { note: normalizedNote } : {}),
|
|
244
|
+
...(normalizedAcceptance ? { acceptance: normalizedAcceptance } : {}),
|
|
232
245
|
status: "open",
|
|
233
246
|
createdAt: timestamp,
|
|
234
247
|
updatedAt: timestamp,
|
|
@@ -238,6 +251,16 @@ export function createUserTasksStore(deps: UserTasksStoreDeps): UserTasksStore {
|
|
|
238
251
|
save(file);
|
|
239
252
|
return copyTask(task);
|
|
240
253
|
},
|
|
254
|
+
setAcceptance(id: string, acceptance: UserTaskAcceptance): UserTask {
|
|
255
|
+
const normalized = normalizeAcceptance(acceptance);
|
|
256
|
+
const file = load();
|
|
257
|
+
const task = file.tasks.find((item) => item.id === id);
|
|
258
|
+
if (!task) throw new UserTasksStoreError(`operator task ${id} was not found`);
|
|
259
|
+
task.acceptance = normalized;
|
|
260
|
+
task.updatedAt = now();
|
|
261
|
+
save(file);
|
|
262
|
+
return copyTask(task);
|
|
263
|
+
},
|
|
241
264
|
hand(id: string, sessionId?: string): UserTask {
|
|
242
265
|
return mutate(id, ["open"], "handed", sessionId);
|
|
243
266
|
},
|
|
@@ -295,3 +318,11 @@ export function createUserTasksStore(deps: UserTasksStoreDeps): UserTasksStore {
|
|
|
295
318
|
},
|
|
296
319
|
};
|
|
297
320
|
}
|
|
321
|
+
|
|
322
|
+
function normalizeAcceptance(raw: unknown): UserTaskAcceptance {
|
|
323
|
+
try {
|
|
324
|
+
return normalizeUserTaskAcceptance(raw);
|
|
325
|
+
} catch (error) {
|
|
326
|
+
throw new UserTasksStoreError(error instanceof Error ? error.message : String(error), { cause: error });
|
|
327
|
+
}
|
|
328
|
+
}
|
|
@@ -134,13 +134,15 @@ function flattenPrompt(input: AcpDelegationRunInput): string {
|
|
|
134
134
|
return parts.join("\n\n");
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
function emptyUsage(): AcpDelegationUsage {
|
|
137
|
+
export function emptyUsage(): AcpDelegationUsage {
|
|
138
138
|
return {
|
|
139
139
|
inputTokens: 0,
|
|
140
140
|
outputTokens: 0,
|
|
141
141
|
cacheReadTokens: 0,
|
|
142
142
|
cacheWriteTokens: 0,
|
|
143
143
|
reasoningTokens: 0,
|
|
144
|
+
totalTokens: 0,
|
|
145
|
+
costUsd: 0,
|
|
144
146
|
};
|
|
145
147
|
}
|
|
146
148
|
|
|
@@ -148,13 +150,29 @@ function finite(value: unknown): number {
|
|
|
148
150
|
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
149
151
|
}
|
|
150
152
|
|
|
151
|
-
|
|
153
|
+
/** Same field the ACP server side and `sumRunUsage` read for a dollar figure; cost is never re-derived here. */
|
|
154
|
+
function costTotal(raw: Record<string, unknown>): number {
|
|
155
|
+
const cost = raw.cost;
|
|
156
|
+
return isRecord(cost) ? finite(cost.total) : 0;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function mergeUsage(into: AcpDelegationUsage, raw: unknown): void {
|
|
152
160
|
if (!isRecord(raw)) return;
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
161
|
+
const input = finite(raw.input) + finite(raw.inputTokens) + finite(raw.input_tokens);
|
|
162
|
+
const output = finite(raw.output) + finite(raw.outputTokens) + finite(raw.output_tokens);
|
|
163
|
+
const cacheRead = finite(raw.cacheRead) + finite(raw.cacheReadTokens) + finite(raw.cache_read_tokens);
|
|
164
|
+
const cacheWrite = finite(raw.cacheWrite) + finite(raw.cacheWriteTokens) + finite(raw.cache_write_tokens);
|
|
165
|
+
into.inputTokens += input;
|
|
166
|
+
into.outputTokens += output;
|
|
167
|
+
into.cacheReadTokens += cacheRead;
|
|
168
|
+
into.cacheWriteTokens += cacheWrite;
|
|
157
169
|
into.reasoningTokens += finite(raw.reasoning) + finite(raw.reasoningTokens) + finite(raw.reasoning_tokens);
|
|
170
|
+
// Same rule as the ACP server accumulator: prefer the peer's explicit total,
|
|
171
|
+
// otherwise sum the four billed categories (reasoning excluded, since a peer
|
|
172
|
+
// that reports it separately still counts it inside output).
|
|
173
|
+
const explicitTotal = finite(raw.totalTokens) + finite(raw.total_tokens);
|
|
174
|
+
into.totalTokens += explicitTotal > 0 ? explicitTotal : input + output + cacheRead + cacheWrite;
|
|
175
|
+
into.costUsd += costTotal(raw);
|
|
158
176
|
}
|
|
159
177
|
|
|
160
178
|
function errorMessage(value: unknown): string {
|
package/src/engine/acp/server.ts
CHANGED
|
@@ -60,7 +60,7 @@ export interface AcpServerChat {
|
|
|
60
60
|
onEvent(handler: (event: AcpServerEvent) => void): () => void;
|
|
61
61
|
isStreaming(): boolean;
|
|
62
62
|
getSessionId(): string | null;
|
|
63
|
-
/** Replace provider context and the next persisted parent
|
|
63
|
+
/** Replace provider context and the next persisted parent for a new or loaded session. */
|
|
64
64
|
resetForSession?(leafTurnId: string | null, replayMessages?: ReadonlyArray<AgentMessage>): void;
|
|
65
65
|
dispose?(): void;
|
|
66
66
|
}
|
|
@@ -1406,6 +1406,7 @@ function installPermissionBridge(input: {
|
|
|
1406
1406
|
toolCallSnapshot: (wireId: string) => AcpToolCallSnapshot | null;
|
|
1407
1407
|
permissionTimeoutMs: number;
|
|
1408
1408
|
expireActivePrompt: () => void;
|
|
1409
|
+
cancelActivePrompt: (reason: string) => void;
|
|
1409
1410
|
}): AcpPermissionBridge {
|
|
1410
1411
|
if (!input.toolRegistry) return { unregister: () => {}, cancelPending: () => {} };
|
|
1411
1412
|
let pendingCancel: ((reason: string) => void) | null = null;
|
|
@@ -1573,6 +1574,17 @@ function installPermissionBridge(input: {
|
|
|
1573
1574
|
// client mint `allow-always` and get a grant this server never
|
|
1574
1575
|
// offered as an option.
|
|
1575
1576
|
const answer = outcome.response.outcome;
|
|
1577
|
+
if (answer.outcome === "cancelled") {
|
|
1578
|
+
const reason = "ACP client cancelled permission";
|
|
1579
|
+
// Abort before releasing the parked tools so their results cannot
|
|
1580
|
+
// start another model request while session/cancel is in flight.
|
|
1581
|
+
input.cancelActivePrompt(reason);
|
|
1582
|
+
emitResolution({ status: "denied", decidedBy: "cancelled", reason });
|
|
1583
|
+
queuedRequestIds.clear();
|
|
1584
|
+
queuedRequestDetails.clear();
|
|
1585
|
+
input.toolRegistry?.cancelParkedCalls(reason);
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1576
1588
|
if (answer.outcome === "selected" && answer.optionId === "allow-once") {
|
|
1577
1589
|
emitResolution({ status: "granted", decidedBy: "acp-client" });
|
|
1578
1590
|
await input.toolRegistry?.resumeParkedCalls({
|
|
@@ -1695,6 +1707,10 @@ export async function serveClioAcpAgent(options: ClioAcpServerOptions): Promise<
|
|
|
1695
1707
|
activePromptState.permissionExpired = true;
|
|
1696
1708
|
options.chat.cancel();
|
|
1697
1709
|
},
|
|
1710
|
+
cancelActivePrompt: (reason) => {
|
|
1711
|
+
const session = activeSessionId === null ? undefined : sessions.get(activeSessionId);
|
|
1712
|
+
if (session) cancelSession(session, reason);
|
|
1713
|
+
},
|
|
1698
1714
|
});
|
|
1699
1715
|
/**
|
|
1700
1716
|
* Sends one frame of the opt-in stream. Every forwarded channel goes through
|
|
@@ -2128,9 +2144,8 @@ export async function serveClioAcpAgent(options: ClioAcpServerOptions): Promise<
|
|
|
2128
2144
|
|
|
2129
2145
|
options.transport.onRequest("session/new", (params) => {
|
|
2130
2146
|
requireInitialized();
|
|
2131
|
-
//
|
|
2132
|
-
// session
|
|
2133
|
-
// so the honest answer is to refuse and let the client start a process.
|
|
2147
|
+
// The chat instance can bind only one session at a time. Closing that
|
|
2148
|
+
// session releases the slot; the next creation resets its conversation.
|
|
2134
2149
|
if (sessionCreated) {
|
|
2135
2150
|
throw new AcpRequestError(-32000, "this server hosts one session per process", { code: "session_limit" });
|
|
2136
2151
|
}
|
|
@@ -2144,6 +2159,7 @@ export async function serveClioAcpAgent(options: ClioAcpServerOptions): Promise<
|
|
|
2144
2159
|
if (safeStoredIdentifier(id, ACP_MAX_SESSION_ID_BYTES) === null) {
|
|
2145
2160
|
throw new AcpRequestError(-32000, "session creation failed", { code: "internal_error" });
|
|
2146
2161
|
}
|
|
2162
|
+
options.chat.resetForSession?.(null);
|
|
2147
2163
|
const autonomy = options.autonomy?.() ?? DEFAULT_AUTONOMY_LEVEL;
|
|
2148
2164
|
const boundTarget = safeConfiguredIdentifier(meta?.target ?? route.target, ACP_MAX_TARGET_ID_BYTES);
|
|
2149
2165
|
const boundModel = safeConfiguredIdentifier(meta?.model ?? route.model, ACP_MAX_MODEL_ID_BYTES);
|
|
@@ -2529,6 +2545,7 @@ export async function serveClioAcpAgent(options: ClioAcpServerOptions): Promise<
|
|
|
2529
2545
|
if (options.session?.current()?.id === session.id) await options.session.close();
|
|
2530
2546
|
sessions.delete(session.id);
|
|
2531
2547
|
if (boundSessionId === session.id) boundSessionId = null;
|
|
2548
|
+
sessionCreated = false;
|
|
2532
2549
|
closedSessionIds.add(session.id);
|
|
2533
2550
|
return {};
|
|
2534
2551
|
});
|
|
@@ -534,6 +534,10 @@ export function createStdioTransport(
|
|
|
534
534
|
}
|
|
535
535
|
|
|
536
536
|
class StreamJsonRpcPeerTransport implements AcpJsonRpcPeerTransport {
|
|
537
|
+
private outputPaused = false;
|
|
538
|
+
private queuedOutputBytes = 0;
|
|
539
|
+
private sinkOutputBytes = 0;
|
|
540
|
+
private readonly outputQueue: string[] = [];
|
|
537
541
|
private nextId = 1;
|
|
538
542
|
private buffer = "";
|
|
539
543
|
/** True while the bytes of an oversized line are being dropped up to its terminating newline. */
|
|
@@ -626,18 +630,56 @@ class StreamJsonRpcPeerTransport implements AcpJsonRpcPeerTransport {
|
|
|
626
630
|
}
|
|
627
631
|
|
|
628
632
|
private write(message: AcpJsonRpcMessage): void {
|
|
633
|
+
if (this.closed) return;
|
|
629
634
|
const line = `${JSON.stringify(message)}\n`;
|
|
630
|
-
if (this.
|
|
631
|
-
|
|
632
|
-
|
|
635
|
+
if (this.outputPaused) {
|
|
636
|
+
const bytes = Buffer.byteLength(line, "utf8");
|
|
637
|
+
// Include bytes already held by the sink in the existing frame budget.
|
|
638
|
+
if (this.queuedOutputBytes + this.sinkOutputBytes + bytes > ACP_MAX_INPUT_LINE_BYTES) {
|
|
639
|
+
this.markClosed(new AcpProcessError("ACP output buffer exceeded byte limit"));
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
633
642
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
// C001 §6 leaves stdout backpressure out of this profile).
|
|
638
|
-
this.output.write(line);
|
|
643
|
+
this.outputQueue.push(line);
|
|
644
|
+
this.queuedOutputBytes += Buffer.byteLength(line, "utf8");
|
|
645
|
+
if (!this.outputPaused) this.flushOutput();
|
|
639
646
|
}
|
|
640
647
|
|
|
648
|
+
private readonly flushOutput = (): void => {
|
|
649
|
+
this.outputPaused = false;
|
|
650
|
+
while (!this.closed && !this.outputPaused && this.outputQueue.length > 0) {
|
|
651
|
+
const line = this.outputQueue.shift();
|
|
652
|
+
if (line === undefined) break;
|
|
653
|
+
this.queuedOutputBytes -= Buffer.byteLength(line, "utf8");
|
|
654
|
+
try {
|
|
655
|
+
// Stdout can count buffered strings in UTF-16 units rather than bytes.
|
|
656
|
+
// Retain their byte count until drain, bounding earlier writes by at
|
|
657
|
+
// most three UTF-8 bytes per unit still held by the sink.
|
|
658
|
+
this.sinkOutputBytes = Math.min(this.sinkOutputBytes, this.output.writableLength * 3);
|
|
659
|
+
let ready: boolean;
|
|
660
|
+
if (this.writeOverride) {
|
|
661
|
+
this.writeOverride(line);
|
|
662
|
+
// The stdout guard writes to this sink but does not return write's boolean.
|
|
663
|
+
ready = !this.output.writableNeedDrain;
|
|
664
|
+
} else {
|
|
665
|
+
ready = this.output.write(line);
|
|
666
|
+
}
|
|
667
|
+
this.sinkOutputBytes =
|
|
668
|
+
this.output.writableLength === 0 ? 0 : this.sinkOutputBytes + Buffer.byteLength(line, "utf8");
|
|
669
|
+
if (!ready && !this.closed) {
|
|
670
|
+
if (this.queuedOutputBytes + this.sinkOutputBytes > ACP_MAX_INPUT_LINE_BYTES) {
|
|
671
|
+
this.markClosed(new AcpProcessError("ACP output buffer exceeded byte limit"));
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
this.outputPaused = true;
|
|
675
|
+
this.output.once("drain", this.flushOutput);
|
|
676
|
+
}
|
|
677
|
+
} catch (err) {
|
|
678
|
+
this.markClosed(new AcpProcessError(`ACP output write failed: ${errorMessage(err)}`));
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
|
|
641
683
|
private consume(chunk: string): void {
|
|
642
684
|
this.buffer += chunk;
|
|
643
685
|
for (;;) {
|
|
@@ -757,6 +799,9 @@ class StreamJsonRpcPeerTransport implements AcpJsonRpcPeerTransport {
|
|
|
757
799
|
private markClosed(reason: unknown): void {
|
|
758
800
|
if (this.isClosed) return;
|
|
759
801
|
this.isClosed = true;
|
|
802
|
+
this.output.removeListener("drain", this.flushOutput);
|
|
803
|
+
this.outputQueue.length = 0;
|
|
804
|
+
this.queuedOutputBytes = 0;
|
|
760
805
|
this.failAll(reason);
|
|
761
806
|
for (const handler of this.closeHandlers) handler();
|
|
762
807
|
}
|
package/src/engine/acp/types.ts
CHANGED
|
@@ -194,6 +194,10 @@ export interface AcpDelegationUsage {
|
|
|
194
194
|
cacheReadTokens: number;
|
|
195
195
|
cacheWriteTokens: number;
|
|
196
196
|
reasoningTokens: number;
|
|
197
|
+
/** Peer-reported total when present, otherwise input+output+cacheRead+cacheWrite, matching the ACP server side. */
|
|
198
|
+
totalTokens: number;
|
|
199
|
+
/** Sum of the peer's `usage.cost.total`; stays 0 when the peer reports no cost. */
|
|
200
|
+
costUsd: number;
|
|
197
201
|
}
|
|
198
202
|
|
|
199
203
|
export interface AcpDelegationResult {
|
|
@@ -41,6 +41,7 @@ import { mergeSamplingOverride } from "./sampling-overrides.js";
|
|
|
41
41
|
import type { EngineApiProvider } from "./types.js";
|
|
42
42
|
|
|
43
43
|
const REASONING_CHARS_PER_TOKEN = 4;
|
|
44
|
+
const ownedModelsByTarget = new Map<string, Set<string>>();
|
|
44
45
|
|
|
45
46
|
interface ClioRuntimeMetadata {
|
|
46
47
|
clioCoder?: {
|
|
@@ -293,7 +294,10 @@ async function listResidentOllamaModels(client: OllamaEvictClient): Promise<Resi
|
|
|
293
294
|
* weights release.
|
|
294
295
|
*/
|
|
295
296
|
async function unloadOllamaModel(baseUrl: string, modelId: string, headers?: Record<string, string>): Promise<void> {
|
|
297
|
+
const owned = ownedModelsByTarget.get(residencyTargetKey("ollama-native", baseUrl));
|
|
298
|
+
if (!owned?.has(modelId)) return;
|
|
296
299
|
await ollamaEvictClient(baseUrl, headers).generate({ model: modelId, prompt: "", keep_alive: 0, stream: false });
|
|
300
|
+
owned.delete(modelId);
|
|
297
301
|
}
|
|
298
302
|
|
|
299
303
|
function mapStopReason(reason: string | undefined, hadToolCall: boolean): AssistantMessage["stopReason"] {
|
|
@@ -466,8 +470,19 @@ function runStream(
|
|
|
466
470
|
else emitText(segment.content);
|
|
467
471
|
}
|
|
468
472
|
};
|
|
473
|
+
let recordedOwnership = false;
|
|
469
474
|
for await (const chunk of iterator) {
|
|
470
475
|
const response = chunk as ChatResponse;
|
|
476
|
+
if (!recordedOwnership && model.baseUrl) {
|
|
477
|
+
const targetKey = residencyTargetKey("ollama-native", model.baseUrl);
|
|
478
|
+
let owned = ownedModelsByTarget.get(targetKey);
|
|
479
|
+
if (!owned) {
|
|
480
|
+
owned = new Set<string>();
|
|
481
|
+
ownedModelsByTarget.set(targetKey, owned);
|
|
482
|
+
}
|
|
483
|
+
owned.add(response.model || model.id);
|
|
484
|
+
recordedOwnership = true;
|
|
485
|
+
}
|
|
471
486
|
const msg = response.message;
|
|
472
487
|
if (msg?.thinking && msg.thinking.length > 0) {
|
|
473
488
|
emitThinking(msg.thinking);
|