@iowarp/clio-coder 0.3.8 → 0.3.9
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 +49 -0
- package/README.md +7 -3
- package/dist/{acp-U67UHUK2.js → acp-7LOELQFP.js} +6 -6
- package/dist/{agents-YU6SGALZ.js → agents-FIBG2SHA.js} +27 -25
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-5ZPJOIVG.js → auth-OI4LIH2I.js} +11 -12
- package/dist/{builtins-C6JMZVV6.js → builtins-AD25UL3C.js} +5 -5
- package/dist/{chunk-5FR74PWO.js → chunk-2JDWVJND.js} +2 -2
- package/dist/chunk-3DPEIQKN.js +113 -0
- package/dist/{chunk-4SPRNWDE.js → chunk-3DUR4WUA.js} +15 -15
- package/dist/{chunk-VHN4MY6O.js → chunk-3MRC2YSQ.js} +2 -2
- package/dist/{chunk-5DHKRSMQ.js → chunk-3UUY7R3Z.js} +11 -7
- package/dist/{chunk-IGWKHNIQ.js → chunk-3V5AYSEQ.js} +8 -8
- package/dist/{chunk-FHJEP5SW.js → chunk-465CC7FK.js} +8 -5
- package/dist/{chunk-5WIGXA4T.js → chunk-47CMYGET.js} +111 -4
- package/dist/{chunk-A3WNZD3P.js → chunk-4H6ULJ3H.js} +67 -21
- package/dist/{chunk-TB5666IT.js → chunk-4LJX2PUC.js} +3 -3
- package/dist/{chunk-XWSF374K.js → chunk-56KB5IJP.js} +2 -2
- package/dist/{chunk-SPULKLCF.js → chunk-5DQRIYDZ.js} +2 -2
- package/dist/{chunk-2HEJ2F35.js → chunk-5HFBWUMU.js} +20 -8
- package/dist/{chunk-WNIJTQQK.js → chunk-5PVQ4SRS.js} +78 -6
- package/dist/{chunk-DYIM5TJT.js → chunk-5QKCQQ3E.js} +262 -6
- package/dist/{chunk-TYPGUK6W.js → chunk-5T7RBWN2.js} +111 -5
- package/dist/{chunk-IIZWH4XA.js → chunk-774ILSRL.js} +2 -2
- package/dist/chunk-7C6RYZGQ.js +391 -0
- package/dist/{chunk-TANS5ZJS.js → chunk-AD7Y7STJ.js} +3 -3
- package/dist/{chunk-RWSI4YD7.js → chunk-AEYBF3TB.js} +33 -12
- package/dist/{chunk-DGSYXYMX.js → chunk-AMKHQW3C.js} +2 -2
- package/dist/{chunk-VWZOAB7K.js → chunk-B5XRQOLB.js} +7 -7
- package/dist/{chunk-WXY7KU3G.js → chunk-BVDVID7E.js} +2 -2
- package/dist/{chunk-PMDBGQSJ.js → chunk-CA42X6KT.js} +2 -2
- package/dist/{chunk-HLE42MG7.js → chunk-D73KXYPF.js} +3 -3
- package/dist/{chunk-5Q2VVUKB.js → chunk-DG4M6ZUE.js} +3 -3
- package/dist/{chunk-ME6CCNFO.js → chunk-EBOC7MT3.js} +6 -6
- package/dist/{chunk-MXKJU4JB.js → chunk-ECUO3KDP.js} +48 -7
- package/dist/{chunk-26LEYJZH.js → chunk-FALJGAWU.js} +2 -2
- package/dist/{chunk-GWS3VEIW.js → chunk-FWDFM5ZU.js} +24 -3
- package/dist/{chunk-7RGZWPB6.js → chunk-GAYUJ7LE.js} +67 -13
- package/dist/{chunk-VCBR6CU7.js → chunk-HAY4ZE2P.js} +2 -2
- package/dist/{chunk-FBVTI2TJ.js → chunk-HCBCAYZU.js} +11 -130
- package/dist/{chunk-WSB3FPX7.js → chunk-HJB5IUKP.js} +32 -136
- package/dist/{chunk-J3YUBZWY.js → chunk-HKO36JWF.js} +33 -5
- package/dist/{chunk-E77JEWSD.js → chunk-HPCTNZM2.js} +6 -36
- package/dist/{chunk-FJ3H4MN5.js → chunk-IHKBWSXF.js} +2 -2
- package/dist/{chunk-NMPKI6XL.js → chunk-JEQQR47K.js} +37 -18
- package/dist/{chunk-3BINW3FP.js → chunk-KV2AOLDF.js} +24 -4
- package/dist/{chunk-YS5VLNH5.js → chunk-LXPJXFM5.js} +7 -7
- package/dist/{chunk-7RFXX52T.js → chunk-MIX5N5AC.js} +271 -41
- package/dist/{chunk-K4XHGFR5.js → chunk-MLOK6ZOS.js} +1297 -218
- package/dist/{chunk-ZNLWCMVZ.js → chunk-MV2VUEJC.js} +2 -2
- package/dist/{chunk-MVVUPGPW.js → chunk-MXHC5QYU.js} +6 -6
- package/dist/{chunk-5H3GB5BO.js → chunk-N3PBVRTZ.js} +4 -382
- package/dist/{chunk-2HFZQUHL.js → chunk-N5XKWMDW.js} +17 -7
- package/dist/{chunk-5C3AQNDW.js → chunk-NNNWO6F2.js} +124 -36
- package/dist/{chunk-GU2UIAFZ.js → chunk-NQ6UCCOD.js} +3 -3
- package/dist/{chunk-N22QMJKY.js → chunk-NZU6YDNV.js} +4 -4
- package/dist/{chunk-ZVJ5BLO2.js → chunk-O6I4CIEU.js} +151 -13
- package/dist/{chunk-XN3L4EYL.js → chunk-OEDBCISO.js} +2 -2
- package/dist/{chunk-VAWNZU7Z.js → chunk-P3JGPQFL.js} +2 -2
- package/dist/{chunk-IJ7RPIYJ.js → chunk-PNY46YEY.js} +20 -3
- package/dist/{chunk-U6MBIEMB.js → chunk-PZ4I4JE2.js} +56 -35
- package/dist/{chunk-GPIEI3LY.js → chunk-QQ7EKM72.js} +2 -2
- package/dist/{chunk-WLFILSD5.js → chunk-R7LNVMCS.js} +66 -28
- package/dist/{chunk-GOXNB3AO.js → chunk-RAPCMZL4.js} +75 -4
- package/dist/chunk-RKKLTLYB.js +45 -0
- package/dist/{chunk-TT36MB5S.js → chunk-RKRLDWD3.js} +3 -1
- package/dist/{chunk-TTHACPOM.js → chunk-S4COXYBG.js} +456 -18
- package/dist/{chunk-WWCZ5F23.js → chunk-T3Z6VAAF.js} +69 -10
- package/dist/{chunk-GN57SG4G.js → chunk-TD7UE2L5.js} +9 -7
- package/dist/{chunk-TLQJPP24.js → chunk-TEO2TLVN.js} +523 -322
- package/dist/{chunk-FCSXB6T2.js → chunk-UOSL25KY.js} +14 -2
- package/dist/{chunk-KTYTFRMB.js → chunk-VKBMFOYV.js} +17 -15
- package/dist/{chunk-PT7HYKEM.js → chunk-VO2LKSTM.js} +2 -2
- package/dist/{chunk-P43ETTHK.js → chunk-VPTUJU4P.js} +2 -2
- package/dist/{chunk-WEH5XRJQ.js → chunk-WIE7ZOSW.js} +2 -2
- package/dist/{chunk-EMYUUSFG.js → chunk-WXCJ7VME.js} +5 -5
- package/dist/{chunk-4DGYLA73.js → chunk-XDOQXGFO.js} +22 -7
- package/dist/{chunk-JOZYP4GM.js → chunk-YKOFT37S.js} +5 -5
- package/dist/chunk-YSEHGPCT.js +127 -0
- package/dist/{chunk-HFSBBKSQ.js → chunk-YW7UVM5V.js} +138 -3
- package/dist/cli/index.js +27 -27
- package/dist/{clio-QVTYJ57A.js → clio-LT5V7SSZ.js} +6 -6
- package/dist/{code-nav-FGGFIE7L.js → code-nav-LMW275PA.js} +4 -4
- package/dist/{config-LW5IJFQN.js → config-RXS5T3JT.js} +71 -43
- package/dist/{configure-7XIZCOU4.js → configure-2WYWSCSD.js} +14 -15
- package/dist/{context-Y6Y7QPR6.js → context-I3BTOTCS.js} +12 -12
- package/dist/{context-L3WL3X7K.js → context-MVOORGMF.js} +34 -33
- package/dist/{context-N52ZA626.js → context-PALKKQYL.js} +20 -20
- package/dist/{context-clear-MBQRLSDQ.js → context-clear-N2WOYZ2K.js} +34 -33
- package/dist/{context-index-HVMFQHK3.js → context-index-HNG3MOME.js} +2 -2
- package/dist/{context-working-set-GS6DSO7F.js → context-working-set-MIEVECVZ.js} +10 -11
- package/dist/{dispatch-runner-22ZCNOM3.js → dispatch-runner-VVA4SRRH.js} +34 -33
- package/dist/doctor-TWBWFK5V.js +165 -0
- package/dist/{eval-BEC2WHDA.js → eval-IJ5VEZDJ.js} +2016 -142
- package/dist/{evidence-REJUMSKM.js → evidence-L5APPXNV.js} +29 -28
- package/dist/{evolve-PY5ZBA5K.js → evolve-RGNKFJ52.js} +29 -28
- package/dist/{extensions-HVKU65YU.js → extensions-7WYWUX5A.js} +9 -3
- package/dist/{fleet-7WZEWRFA.js → fleet-6CNVBZZP.js} +87 -55
- package/dist/{fleet-commands-UVHWM76J.js → fleet-commands-L2SXSYEI.js} +6 -6
- package/dist/{fleet-graph-6ULH7PES.js → fleet-graph-2J3OOIPO.js} +14 -12
- package/dist/{fleet-preflight-J53T6CCE.js → fleet-preflight-CZRJ4JP5.js} +3 -4
- package/dist/{fleet-validate-72PC4SLA.js → fleet-validate-C5RI6DP7.js} +16 -15
- package/dist/{init-OG3TPGQG.js → init-VBN2ACVA.js} +50 -48
- package/dist/{library-CNTMPLRF.js → library-JHGUMLY2.js} +13 -11
- package/dist/{memory-6IS7F275.js → memory-K4OQIYWG.js} +31 -30
- package/dist/{models-ENRJDA5W.js → models-2NCZUWDD.js} +23 -22
- package/dist/{monitor-XLDVO7TN.js → monitor-MMVTJABD.js} +35 -34
- package/dist/{orchestrator-6KSPYRHA.js → orchestrator-ZKBPCHW6.js} +1627 -294
- package/dist/{reset-RZ4ER727.js → reset-DD5JGOY3.js} +3 -3
- package/dist/{run-Y2CNK5RU.js → run-QEGNX7FL.js} +56 -55
- package/dist/{share-A55GYP6Z.js → share-JKD3BQMW.js} +13 -11
- package/dist/{skills-ALC5J6AT.js → skills-LMQIKDOZ.js} +14 -12
- package/dist/{skills-eval-JPBEBYQU.js → skills-eval-I7X2774U.js} +34 -32
- package/dist/{steer-GGWFUJUD.js → steer-CF5TDANS.js} +3 -3
- package/dist/{support-MIETYA5E.js → support-I7LOJLIF.js} +4 -4
- package/dist/{targets-VGNXIR3S.js → targets-RUSR6B5Z.js} +58 -32
- package/dist/{terminal-lease-WOBR64YA.js → terminal-lease-QYVORFR4.js} +6 -4
- package/dist/{trace-PNCASAXC.js → trace-ODOQIVIW.js} +61 -6
- package/dist/{upgrade-FUSUAGHR.js → upgrade-XANW3FXB.js} +17 -16
- package/dist/{usage-N4MKVHKD.js → usage-4H7ZRXQT.js} +88 -44
- package/dist/{verifiers-YAWOJ3H2.js → verifiers-UZXNBZEB.js} +6 -6
- package/dist/{verify-LTDHYBGY.js → verify-BVKWTNDL.js} +5 -5
- package/dist/{wiki-generate-6M7GHTBJ.js → wiki-generate-MY7WV2QI.js} +49 -47
- package/dist/worker/entry.js +29 -33
- package/docs/alcf-provider.md +1 -1
- package/docs/architecture.md +1 -1
- package/docs/artifact-versions.md +6 -1
- package/docs/built-in-agents.md +1 -1
- package/docs/capacity-and-scheduling.md +23 -2
- package/docs/commands-and-modes.md +1 -1
- package/docs/configuration-and-targets.md +30 -3
- package/docs/context-engine.md +63 -4
- package/docs/documentation-coverage.md +3 -3
- package/docs/documentation-guide.md +1 -1
- package/docs/environment-variables.md +2 -0
- package/docs/eval-runner.md +262 -11
- package/docs/evals-internal.md +72 -2
- package/docs/evidence-and-memory.md +11 -10
- package/docs/evolution.md +1 -1
- package/docs/extensions-and-sharing.md +3 -1
- package/docs/fleet-dispatch.md +4 -4
- package/docs/installation-and-lifecycle.md +1 -1
- package/docs/middleware-and-components.md +1 -1
- package/docs/model-catalog.md +1 -1
- package/docs/observability.md +53 -2
- package/docs/proactive-memory.md +127 -14
- package/docs/prompt-envelope-and-tools.md +19 -1
- package/docs/provider-adapter-cookbook.md +1 -1
- package/docs/release-cut-checklist.md +19 -3
- package/docs/safety-model.md +1 -1
- package/docs/scientific-validation.md +1 -1
- package/docs/skills-marketplace.md +1 -1
- package/docs/tool-usage.md +1 -1
- package/docs/trace-store.md +1 -1
- package/docs/troubleshooting.md +87 -0
- package/docs/tui-design.md +1 -1
- package/docs/worker-dispatch-mechanics.md +1 -1
- package/package.json +2 -1
- package/src/cli/agents.ts +1 -1
- package/src/cli/config-inspect.ts +33 -6
- package/src/cli/config.ts +1 -1
- package/src/cli/doctor-state-size.ts +82 -0
- package/src/cli/doctor.ts +3 -1
- package/src/cli/eval.ts +80 -16
- package/src/cli/extensions.ts +5 -1
- package/src/cli/fleet.ts +32 -3
- package/src/cli/targets.ts +44 -13
- package/src/cli/trace.ts +63 -4
- package/src/cli/usage.ts +63 -14
- package/src/core/bus-events.ts +29 -1
- package/src/core/cache-telemetry.ts +42 -0
- package/src/core/config.ts +18 -0
- package/src/core/defaults.ts +36 -6
- package/src/core/endpoint-key.ts +27 -0
- package/src/core/residency-target-key.ts +25 -0
- package/src/core/response-schema.ts +36 -2
- package/src/domains/config/classify.ts +3 -0
- package/src/domains/context/codewiki/coordinator.ts +12 -4
- package/src/domains/dispatch/admission.ts +40 -3
- package/src/domains/dispatch/capacity-lease.ts +98 -9
- package/src/domains/dispatch/contract.ts +11 -0
- package/src/domains/dispatch/execution-plan.ts +44 -4
- package/src/domains/dispatch/extension.ts +166 -42
- package/src/domains/dispatch/fleet-run.ts +23 -3
- package/src/domains/dispatch/heartbeat.ts +32 -8
- package/src/domains/dispatch/index.ts +3 -0
- package/src/domains/dispatch/orphan-recovery.ts +5 -0
- package/src/domains/dispatch/reservation-store.ts +116 -8
- package/src/domains/dispatch/state.ts +4 -0
- package/src/domains/dispatch/worker-spawn.ts +25 -11
- package/src/domains/dispatch/write-boundary-enforcer.ts +20 -3
- package/src/domains/dispatch/write-boundary.ts +62 -1
- package/src/domains/eval/artifacts/store.ts +62 -0
- package/src/domains/eval/compare/behavioral.ts +224 -0
- package/src/domains/eval/compare/compare.ts +355 -2
- package/src/domains/eval/compare/envelope.ts +128 -0
- package/src/domains/eval/compare/gates.ts +24 -6
- package/src/domains/eval/compare/thresholds.ts +30 -3
- package/src/domains/eval/execution-provenance.ts +240 -0
- package/src/domains/eval/metrics/aggregate.ts +136 -0
- package/src/domains/eval/metrics/call-ledger-stream.ts +112 -0
- package/src/domains/eval/metrics/tracked.ts +413 -0
- package/src/domains/eval/provenance.ts +117 -0
- package/src/domains/eval/reports/comparison.ts +128 -0
- package/src/domains/eval/reports/junit.ts +17 -3
- package/src/domains/eval/reports/markdown.ts +3 -3
- package/src/domains/eval/reports/text.ts +14 -0
- package/src/domains/eval/run-compare.ts +20 -0
- package/src/domains/eval/runners/clio-run.ts +127 -0
- package/src/domains/eval/runners/external-command.ts +28 -3
- package/src/domains/eval/schema/adapter.ts +111 -0
- package/src/domains/eval/schema/artifact.ts +20 -0
- package/src/domains/eval/schema/behavioral-metrics.ts +204 -0
- package/src/domains/eval/schema/behavioral.ts +520 -0
- package/src/domains/eval/schema/execution-envelope.ts +194 -0
- package/src/domains/eval/schema/serving.ts +74 -0
- package/src/domains/eval/schema/suite.ts +38 -8
- package/src/domains/eval/schema/validate.ts +58 -3
- package/src/domains/eval/schema/verdict.ts +237 -0
- package/src/domains/eval/suites/resolve.ts +2 -0
- package/src/domains/eval/suites/run.ts +264 -33
- package/src/domains/eval/verifiers/command.ts +2 -1
- package/src/domains/eval/workspaces/temp-copy.ts +145 -13
- package/src/domains/evidence/build.ts +2 -13
- package/src/domains/evidence/eval.ts +2 -12
- package/src/domains/evidence/findings-markdown.ts +33 -0
- package/src/domains/evidence/run-trust.ts +7 -113
- package/src/domains/evidence/trust-projection.ts +2 -2
- package/src/domains/extensions/compatibility.ts +285 -0
- package/src/domains/extensions/discovery.ts +38 -3
- package/src/domains/extensions/resources.ts +1 -1
- package/src/domains/extensions/state.ts +12 -3
- package/src/domains/extensions/types.ts +2 -0
- package/src/domains/lifecycle/doctor.ts +69 -1
- package/src/domains/memory/index.ts +14 -0
- package/src/domains/memory/task-bank-promotion.ts +64 -0
- package/src/domains/memory/task-memory-policy.ts +77 -8
- package/src/domains/memory/task-memory-spend.ts +131 -0
- package/src/domains/memory/task-memory-status.ts +7 -0
- package/src/domains/memory/task-memory-telemetry.ts +2 -0
- package/src/domains/middleware/index.ts +1 -0
- package/src/domains/middleware/memory-intervention.ts +69 -5
- package/src/domains/middleware/memory-step-endpoint.ts +71 -0
- package/src/domains/observability/background-memory-usage.ts +140 -0
- package/src/domains/observability/cost.ts +1 -1
- package/src/domains/observability/index.ts +7 -0
- package/src/domains/observability/out-of-turn-usage.ts +51 -2
- package/src/domains/observability/trace-store.ts +192 -2
- package/src/domains/prompts/compiler.ts +100 -13
- package/src/domains/providers/endpoint-capacity.ts +96 -0
- package/src/domains/providers/index.ts +10 -0
- package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +243 -1
- package/src/domains/providers/runtime-resolution.ts +8 -1
- package/src/domains/providers/runtimes/common/probe-helpers.ts +31 -9
- package/src/domains/providers/runtimes/local-native/llamacpp-anthropic.ts +1 -1
- package/src/domains/providers/runtimes/local-native/llamacpp-completion.ts +1 -1
- package/src/domains/providers/runtimes/local-native/llamacpp-embed.ts +1 -1
- package/src/domains/providers/runtimes/local-native/llamacpp-rerank.ts +1 -1
- package/src/domains/providers/runtimes/local-native/llamacpp.ts +4 -1
- package/src/domains/providers/runtimes/local-native/lmstudio.ts +4 -1
- package/src/domains/providers/runtimes/local-native/ollama-native.ts +6 -1
- package/src/domains/providers/types/capability-flags.ts +2 -0
- package/src/domains/providers/types/target-descriptor.ts +2 -0
- package/src/domains/resources/prompts/loader.ts +95 -33
- package/src/domains/safety/call-target.ts +52 -0
- package/src/domains/safety/run-effects.ts +35 -4
- package/src/domains/session/context-accounting.ts +52 -1
- package/src/domains/session/context-ledger.ts +37 -13
- package/src/domains/session/index.ts +6 -0
- package/src/domains/session/prompt-cache.ts +140 -0
- package/src/domains/session/prompt-manifest.ts +42 -0
- package/src/engine/acp/adapter.ts +18 -3
- package/src/engine/ai.ts +35 -0
- package/src/engine/apis/llamacpp-residency.ts +55 -3
- package/src/engine/apis/lmstudio.ts +25 -5
- package/src/engine/apis/ollama-native.ts +2 -1
- package/src/engine/apis/openai-completions.ts +80 -17
- package/src/engine/apis/residency-lock.ts +3 -1
- package/src/engine/apis/residency.ts +34 -1
- package/src/engine/provider-payload.ts +29 -1
- package/src/entry/orchestrator.ts +176 -30
- package/src/interactive/chat-loop-messages.ts +26 -7
- package/src/interactive/chat-loop.ts +318 -41
- package/src/interactive/chat-panel.ts +62 -8
- package/src/interactive/clio-editor.ts +45 -8
- package/src/interactive/context-activity.ts +5 -1
- package/src/interactive/context-meter.ts +1 -1
- package/src/interactive/context-overlay.ts +40 -10
- package/src/interactive/cost-overlay.ts +64 -6
- package/src/interactive/dispatch-board.ts +84 -12
- package/src/interactive/fleet-run-preview.ts +41 -15
- package/src/interactive/handoff-round.ts +41 -2
- package/src/interactive/interactive-application.ts +24 -1
- package/src/interactive/interactive-input-runtime.ts +8 -0
- package/src/interactive/interactive-presentation.ts +4 -0
- package/src/interactive/interactive-shell.ts +20 -17
- package/src/interactive/interactive-slash-runtime.ts +27 -4
- package/src/interactive/memory-overlay.ts +8 -0
- package/src/interactive/mutation-preview.ts +295 -0
- package/src/interactive/overlay-general-openers.ts +16 -0
- package/src/interactive/overlay-key-routing.ts +38 -0
- package/src/interactive/overlay-lifecycle.ts +38 -5
- package/src/interactive/overlay-permission-lifecycle.ts +22 -2
- package/src/interactive/overlay-session-lifecycle.ts +73 -9
- package/src/interactive/overlays/ask-user.ts +91 -19
- package/src/interactive/overlays/help-reference.ts +4 -0
- package/src/interactive/overlays/prompts.ts +11 -1
- package/src/interactive/overlays/settings.ts +35 -1
- package/src/interactive/permission-hint.ts +34 -2
- package/src/interactive/permission-overlay.ts +159 -9
- package/src/interactive/prewarm.ts +197 -0
- package/src/interactive/render-trace.ts +162 -15
- package/src/interactive/renderers/tool-execution.ts +4 -0
- package/src/interactive/side-question.ts +58 -1
- package/src/interactive/status/controller.ts +11 -0
- package/src/interactive/status/state-machine.ts +54 -2
- package/src/interactive/status/types.ts +7 -0
- package/src/interactive/terminal-lease.ts +2 -0
- package/src/interactive/turn-context.ts +299 -31
- package/src/interactive/turn-persistence.ts +14 -4
- package/src/interactive/turn-prewarm.ts +364 -0
- package/src/interactive/turn-queues.ts +7 -4
- package/src/interactive/turn-runtime.ts +8 -1
- package/src/interactive/turn-state.ts +23 -0
- package/src/interactive/view/view-overlay.ts +28 -3
- package/src/tools/ask-user.ts +43 -2
- package/src/tools/dispatch-plan.ts +17 -9
- package/src/tools/dispatch-scout.ts +1 -1
- package/src/tools/registry.ts +16 -0
- package/dist/chunk-AOCYTWAV.js +0 -449
- package/dist/chunk-HWUFFB6L.js +0 -83
- package/dist/chunk-R346GLFC.js +0 -31
- package/dist/doctor-M7YEDGAE.js +0 -91
|
@@ -27,6 +27,14 @@ export interface PromptTemplate {
|
|
|
27
27
|
sourceInfo: ResourceSourceInfo;
|
|
28
28
|
/** False for a project compatibility root until the operator opts in; such a template refuses to expand. */
|
|
29
29
|
trusted: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Why this template cannot be expanded, when a load-time failure left it
|
|
32
|
+
* with no usable body. It is loaded anyway, so the namespaced command is
|
|
33
|
+
* recognized and invoking it reports this reason instead of "not a command"
|
|
34
|
+
* (issue #245). `content` is empty on such a template and nothing may send
|
|
35
|
+
* it to a model.
|
|
36
|
+
*/
|
|
37
|
+
unavailable?: string;
|
|
30
38
|
argumentHint?: string;
|
|
31
39
|
}
|
|
32
40
|
|
|
@@ -154,24 +162,28 @@ function contained(root: string, candidate: string): boolean {
|
|
|
154
162
|
return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative));
|
|
155
163
|
}
|
|
156
164
|
|
|
157
|
-
/**
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
+
/**
|
|
166
|
+
* The outcome of a step that may leave the template with no usable body.
|
|
167
|
+
* `reason` is the one sentence that becomes both the diagnostic the `/prompts`
|
|
168
|
+
* overlay renders and the refusal the operator gets when they invoke the
|
|
169
|
+
* command, so the two can never disagree.
|
|
170
|
+
*/
|
|
171
|
+
type BodyResolution = { body: string } | { reason: string };
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Resolve only existing paths contained by the declaring extension package.
|
|
175
|
+
*
|
|
176
|
+
* The safety half is unchanged: a missing or escaping reference never expands
|
|
177
|
+
* and never reads outside the package. What changed is what the caller does
|
|
178
|
+
* with the failure, which used to be dropping the template entirely.
|
|
179
|
+
*/
|
|
180
|
+
function resolveExtensionReferences(body: string, root: PromptTemplateRoot): BodyResolution {
|
|
181
|
+
if (root.rootPath === undefined || !body.includes(EXTENSION_ROOT_TOKEN)) return { body };
|
|
165
182
|
let canonicalRoot: string;
|
|
166
183
|
try {
|
|
167
184
|
canonicalRoot = realpathSync(root.rootPath);
|
|
168
185
|
} catch (error) {
|
|
169
|
-
|
|
170
|
-
type: "warning",
|
|
171
|
-
message: `extension root could not be resolved: ${error instanceof Error ? error.message : String(error)}`,
|
|
172
|
-
path: filePath,
|
|
173
|
-
});
|
|
174
|
-
return null;
|
|
186
|
+
return { reason: `extension root could not be resolved: ${error instanceof Error ? error.message : String(error)}` };
|
|
175
187
|
}
|
|
176
188
|
let invalid: string | null = null;
|
|
177
189
|
const resolved = body.replace(EXTENSION_ROOT_REFERENCE, (reference, suffix: string | undefined) => {
|
|
@@ -185,14 +197,44 @@ function resolveExtensionReferences(
|
|
|
185
197
|
return candidate;
|
|
186
198
|
});
|
|
187
199
|
if (invalid !== null) {
|
|
188
|
-
|
|
189
|
-
type: "warning",
|
|
190
|
-
message: `prompt template has an unresolved or escaping extension reference: ${invalid}`,
|
|
191
|
-
path: filePath,
|
|
192
|
-
});
|
|
193
|
-
return null;
|
|
200
|
+
return { reason: `prompt template has an unresolved or escaping extension reference: ${invalid}` };
|
|
194
201
|
}
|
|
195
|
-
return resolved;
|
|
202
|
+
return { body: resolved };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* A template that exists and cannot run.
|
|
207
|
+
*
|
|
208
|
+
* Dropping it made the operator's namespaced command answer "not a command",
|
|
209
|
+
* so an extension author's single bad `${extensionRoot}` path read to their
|
|
210
|
+
* users as "the prompt was never installed" (issue #245). The template is
|
|
211
|
+
* loaded with an empty body and the reason attached instead, which is the same
|
|
212
|
+
* shape an untrusted project template already had, so the existing refusal
|
|
213
|
+
* path carries it to the operator with no new machinery.
|
|
214
|
+
*/
|
|
215
|
+
function unavailableTemplate(
|
|
216
|
+
name: string,
|
|
217
|
+
filePath: string,
|
|
218
|
+
root: PromptTemplateRoot,
|
|
219
|
+
reason: string,
|
|
220
|
+
diagnostics: ResourceDiagnostic[],
|
|
221
|
+
description?: string,
|
|
222
|
+
): ResourceCandidate<PromptTemplate> {
|
|
223
|
+
diagnostics.push({ type: "warning", message: reason, path: filePath });
|
|
224
|
+
const sourceInfo: ResourceSourceInfo = sourceInfoForRoot(root, filePath);
|
|
225
|
+
return {
|
|
226
|
+
name,
|
|
227
|
+
value: {
|
|
228
|
+
name,
|
|
229
|
+
description: description ?? "Prompt template (unavailable)",
|
|
230
|
+
content: "",
|
|
231
|
+
filePath,
|
|
232
|
+
sourceInfo,
|
|
233
|
+
trusted: root.trusted !== false,
|
|
234
|
+
unavailable: `${reason} (${filePath})`,
|
|
235
|
+
},
|
|
236
|
+
source: sourceInfo,
|
|
237
|
+
};
|
|
196
238
|
}
|
|
197
239
|
|
|
198
240
|
function loadPromptFile(
|
|
@@ -202,15 +244,9 @@ function loadPromptFile(
|
|
|
202
244
|
): ResourceCandidate<PromptTemplate> | null {
|
|
203
245
|
const ext = path.extname(filePath).toLowerCase();
|
|
204
246
|
if (ext !== ".md") return null;
|
|
205
|
-
let raw: string;
|
|
206
|
-
try {
|
|
207
|
-
raw = readFileSync(filePath, "utf8");
|
|
208
|
-
} catch (err) {
|
|
209
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
210
|
-
diagnostics.push({ type: "warning", message: `prompt template could not be read: ${reason}`, path: filePath });
|
|
211
|
-
return null;
|
|
212
|
-
}
|
|
213
247
|
|
|
248
|
+
// The name comes before the read, so a file that exists but cannot be read
|
|
249
|
+
// still has a command the operator can invoke and be told why.
|
|
214
250
|
const relativePath = path.relative(root.path, filePath);
|
|
215
251
|
if (
|
|
216
252
|
relativePath === "" ||
|
|
@@ -218,21 +254,35 @@ function loadPromptFile(
|
|
|
218
254
|
relativePath.startsWith(`..${path.sep}`) ||
|
|
219
255
|
path.isAbsolute(relativePath)
|
|
220
256
|
) {
|
|
257
|
+
// The one failure that stays a drop: the path is outside the discovery
|
|
258
|
+
// root, so there is no name here to offer a command under.
|
|
221
259
|
diagnostics.push({ type: "warning", message: "prompt template escaped its discovery root", path: filePath });
|
|
222
260
|
return null;
|
|
223
261
|
}
|
|
224
262
|
const name = relativePath.slice(0, -ext.length).split(path.sep).join(":");
|
|
225
263
|
if (name.trim().length === 0) return null;
|
|
264
|
+
|
|
265
|
+
let raw: string;
|
|
266
|
+
try {
|
|
267
|
+
raw = readFileSync(filePath, "utf8");
|
|
268
|
+
} catch (err) {
|
|
269
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
270
|
+
return unavailableTemplate(name, filePath, root, `prompt template could not be read: ${reason}`, diagnostics);
|
|
271
|
+
}
|
|
272
|
+
|
|
226
273
|
const { frontmatter, body } = splitOptionalFrontmatter(raw, filePath, diagnostics);
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
274
|
+
const declaredDescription = stringField(frontmatter, "description");
|
|
275
|
+
const resolution = resolveExtensionReferences(body, root);
|
|
276
|
+
if ("reason" in resolution) {
|
|
277
|
+
return unavailableTemplate(name, filePath, root, resolution.reason, diagnostics, declaredDescription ?? undefined);
|
|
278
|
+
}
|
|
279
|
+
const description = declaredDescription ?? fallbackDescription(resolution.body);
|
|
230
280
|
const argumentHint = stringField(frontmatter, "argument-hint") ?? stringField(frontmatter, "argumentHint");
|
|
231
281
|
const sourceInfo: ResourceSourceInfo = sourceInfoForRoot(root, filePath);
|
|
232
282
|
const template: PromptTemplate = {
|
|
233
283
|
name,
|
|
234
284
|
description,
|
|
235
|
-
content:
|
|
285
|
+
content: resolution.body.trim(),
|
|
236
286
|
filePath,
|
|
237
287
|
sourceInfo,
|
|
238
288
|
trusted: root.trusted !== false,
|
|
@@ -330,6 +380,18 @@ export function expandPromptTemplateInput(input: string, templates: PromptTempla
|
|
|
330
380
|
if (!command) return { expanded: false, text: input, args: [], diagnostics: templates.diagnostics };
|
|
331
381
|
const template = templates.items.find((entry) => entry.name === command.name);
|
|
332
382
|
if (!template) return { expanded: false, text: input, args: [], diagnostics: templates.diagnostics };
|
|
383
|
+
// A load-time failure refuses before anything else, and before the trust
|
|
384
|
+
// check, because there is no body here to be trusted or untrusted.
|
|
385
|
+
if (template.unavailable !== undefined) {
|
|
386
|
+
const message = `prompt template /${template.name} cannot run: ${template.unavailable}`;
|
|
387
|
+
return {
|
|
388
|
+
expanded: false,
|
|
389
|
+
text: input,
|
|
390
|
+
args: [],
|
|
391
|
+
diagnostics: templates.diagnostics,
|
|
392
|
+
refusal: { template, message },
|
|
393
|
+
};
|
|
394
|
+
}
|
|
333
395
|
if (!template.trusted) {
|
|
334
396
|
const message = `prompt template ${template.name} comes from an untrusted project root; set skills.trustProjectCompatRoots to use it`;
|
|
335
397
|
return {
|
|
@@ -31,6 +31,58 @@ function oneLine(value: string): string {
|
|
|
31
31
|
return value.replace(/\s+/g, " ").trim();
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/** Columns one tab occupies when a multi-line surface expands it. */
|
|
35
|
+
const DISPLAY_TAB_WIDTH = 4;
|
|
36
|
+
|
|
37
|
+
/** Stands in for a control byte that was neutralized, so the cut is visible. */
|
|
38
|
+
const CONTROL_BYTE_PLACEHOLDER = "·";
|
|
39
|
+
|
|
40
|
+
export interface SanitizedDisplayText {
|
|
41
|
+
text: string;
|
|
42
|
+
/** A control byte or escape sequence was neutralized. */
|
|
43
|
+
neutralized: boolean;
|
|
44
|
+
/** A tab was expanded, so column positions are display positions. */
|
|
45
|
+
tabsExpanded: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Neutralize escape sequences and control bytes while keeping line structure.
|
|
50
|
+
* {@link sanitizeCallTargetText} is the one-line form for a card; this is the
|
|
51
|
+
* form a multi-line operator surface needs, where the newlines are the content
|
|
52
|
+
* rather than whitespace to collapse. Tabs become spaces because a bordered
|
|
53
|
+
* frame measures visible width and a literal tab makes that measurement a lie.
|
|
54
|
+
* Every other C0 byte and DEL becomes a visible placeholder, so a payload
|
|
55
|
+
* cannot style, reposition, or spoof the surface that is approving it.
|
|
56
|
+
*/
|
|
57
|
+
export function sanitizeMultilineDisplayText(value: string): SanitizedDisplayText {
|
|
58
|
+
const escapesStripped = value.replace(OSC_PATTERN, "").replace(CSI_PATTERN, "");
|
|
59
|
+
let neutralized = escapesStripped.length !== value.length;
|
|
60
|
+
// CRLF is line structure, not an injection, so it normalizes rather than
|
|
61
|
+
// showing a placeholder at the end of every line of a Windows-authored file.
|
|
62
|
+
const stripped = escapesStripped.replace(/\r\n/g, "\n");
|
|
63
|
+
let tabsExpanded = false;
|
|
64
|
+
let out = "";
|
|
65
|
+
for (const ch of stripped) {
|
|
66
|
+
if (ch === "\n") {
|
|
67
|
+
out += ch;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (ch === "\t") {
|
|
71
|
+
out += " ".repeat(DISPLAY_TAB_WIDTH);
|
|
72
|
+
tabsExpanded = true;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
76
|
+
if (code < 0x20 || code === 0x7f) {
|
|
77
|
+
out += CONTROL_BYTE_PLACEHOLDER;
|
|
78
|
+
neutralized = true;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
out += ch;
|
|
82
|
+
}
|
|
83
|
+
return { text: out, neutralized, tabsExpanded };
|
|
84
|
+
}
|
|
85
|
+
|
|
34
86
|
/**
|
|
35
87
|
* Neutralize escape sequences and control bytes in a pre-composed target
|
|
36
88
|
* string and collapse it to one line. Used where a description crosses a
|
|
@@ -76,6 +76,23 @@ export interface RunEffects {
|
|
|
76
76
|
* run there is no path left in the checkout that the record can clear.
|
|
77
77
|
*/
|
|
78
78
|
writeRecordComplete: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Successful opaque calls that made the write record incomplete. The call id
|
|
81
|
+
* and tool name are retained so a later write-boundary verdict can explain
|
|
82
|
+
* exactly why attribution was downgraded instead of exposing only a boolean.
|
|
83
|
+
*/
|
|
84
|
+
writeRecordDowngrades: ReadonlyArray<WriteRecordDowngrade>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface WriteRecordDowngrade {
|
|
88
|
+
reason: "opaque_tool_succeeded";
|
|
89
|
+
tool: string;
|
|
90
|
+
toolCallId: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface RunEffectsRecorderOptions {
|
|
94
|
+
/** Fires once, when the first successful opaque call opens the record. */
|
|
95
|
+
onWriteRecordDowngraded?(downgrade: WriteRecordDowngrade): void;
|
|
79
96
|
}
|
|
80
97
|
|
|
81
98
|
export interface RunEffectsRecorder {
|
|
@@ -87,6 +104,8 @@ export interface RunEffectsRecorder {
|
|
|
87
104
|
}
|
|
88
105
|
|
|
89
106
|
interface PendingEffects {
|
|
107
|
+
tool: string;
|
|
108
|
+
toolCallId: string;
|
|
90
109
|
paths: ReadonlyArray<string>;
|
|
91
110
|
validationCommand: string | null;
|
|
92
111
|
verificationCommand: string | null;
|
|
@@ -204,13 +223,13 @@ function validationCommandOf(
|
|
|
204
223
|
* when its result comes back clean: a blocked write and a red command are not
|
|
205
224
|
* evidence that the workspace changed or that a check passed.
|
|
206
225
|
*/
|
|
207
|
-
export function createRunEffectsRecorder(cwd: string): RunEffectsRecorder {
|
|
226
|
+
export function createRunEffectsRecorder(cwd: string, options: RunEffectsRecorderOptions = {}): RunEffectsRecorder {
|
|
208
227
|
const pending = new Map<string, PendingEffects>();
|
|
209
228
|
const mutatedPaths = new Set<string>();
|
|
210
229
|
const failedMutationPaths = new Set<string>();
|
|
211
230
|
const validationCommands = new Set<string>();
|
|
212
231
|
const verificationCommands = new Set<string>();
|
|
213
|
-
|
|
232
|
+
const writeRecordDowngrades = new Map<string, WriteRecordDowngrade>();
|
|
214
233
|
return {
|
|
215
234
|
start(toolCallId, toolName, args) {
|
|
216
235
|
const paths = mutationTargets(toolName, args);
|
|
@@ -223,6 +242,8 @@ export function createRunEffectsRecorder(cwd: string): RunEffectsRecorder {
|
|
|
223
242
|
// A bash call may re-base its relative paths with its own cwd argument.
|
|
224
243
|
const base = typeof args?.cwd === "string" && args.cwd.length > 0 ? path.resolve(cwd, args.cwd) : cwd;
|
|
225
244
|
pending.set(toolCallId, {
|
|
245
|
+
tool: toolName,
|
|
246
|
+
toolCallId,
|
|
226
247
|
paths: paths.map((target) => path.resolve(base, target)),
|
|
227
248
|
validationCommand,
|
|
228
249
|
verificationCommand,
|
|
@@ -240,7 +261,16 @@ export function createRunEffectsRecorder(cwd: string): RunEffectsRecorder {
|
|
|
240
261
|
// Success-gated on purpose. A shell command the safety policy blocked,
|
|
241
262
|
// or one that came back an error, never reached the filesystem, so it
|
|
242
263
|
// leaves the record closed.
|
|
243
|
-
if (effects.opaque)
|
|
264
|
+
if (effects.opaque) {
|
|
265
|
+
const downgrade: WriteRecordDowngrade = {
|
|
266
|
+
reason: "opaque_tool_succeeded",
|
|
267
|
+
tool: effects.tool,
|
|
268
|
+
toolCallId: effects.toolCallId,
|
|
269
|
+
};
|
|
270
|
+
const recordWasComplete = writeRecordDowngrades.size === 0;
|
|
271
|
+
writeRecordDowngrades.set(effects.toolCallId, downgrade);
|
|
272
|
+
if (recordWasComplete) options.onWriteRecordDowngraded?.({ ...downgrade });
|
|
273
|
+
}
|
|
244
274
|
for (const target of effects.paths) mutatedPaths.add(target);
|
|
245
275
|
if (effects.validationCommand !== null) validationCommands.add(effects.validationCommand);
|
|
246
276
|
if (effects.verificationCommand !== null) verificationCommands.add(effects.verificationCommand);
|
|
@@ -253,7 +283,8 @@ export function createRunEffectsRecorder(cwd: string): RunEffectsRecorder {
|
|
|
253
283
|
failedMutationPaths: new Set([...failedMutationPaths].filter((target) => !mutatedPaths.has(target))),
|
|
254
284
|
validationCommands: new Set(validationCommands),
|
|
255
285
|
verificationCommands: new Set(verificationCommands),
|
|
256
|
-
writeRecordComplete:
|
|
286
|
+
writeRecordComplete: writeRecordDowngrades.size === 0,
|
|
287
|
+
writeRecordDowngrades: [...writeRecordDowngrades.values()].map((entry) => ({ ...entry })),
|
|
257
288
|
};
|
|
258
289
|
},
|
|
259
290
|
};
|
|
@@ -72,6 +72,19 @@ export interface ContextSnapshot {
|
|
|
72
72
|
splits: Record<string, "estimated" | "exact" | "reconciled">;
|
|
73
73
|
};
|
|
74
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Divergence record. `estimatedTokens` is the chars/4 prompt-side total this
|
|
77
|
+
* snapshot was captured with and is never rewritten by a reconcile, so the
|
|
78
|
+
* two figures stay comparable. `reconciledTokens` is the provider's own
|
|
79
|
+
* prompt token count for the call this snapshot was reconciled against
|
|
80
|
+
* (cached prompt tokens folded back in), and `divergenceRatio` is
|
|
81
|
+
* `reconciledTokens / estimatedTokens`. A ratio above 1 is the estimator
|
|
82
|
+
* under-counting, which is what issue #227 recorded as a provider overflow.
|
|
83
|
+
*/
|
|
84
|
+
estimatedTokens?: number | undefined;
|
|
85
|
+
reconciledTokens?: number | undefined;
|
|
86
|
+
divergenceRatio?: number | undefined;
|
|
87
|
+
|
|
75
88
|
promptHash?: string | undefined;
|
|
76
89
|
toolSignature?: string | undefined;
|
|
77
90
|
messageRange?: { start: number; end: number } | undefined;
|
|
@@ -332,6 +345,31 @@ export function getLatestContextSnapshot(meta: SessionMeta): ContextSnapshot | n
|
|
|
332
345
|
return list.at(-1) ?? null;
|
|
333
346
|
}
|
|
334
347
|
|
|
348
|
+
/**
|
|
349
|
+
* The window a previous process measured this session's prompts against, when
|
|
350
|
+
* the backend itself reported it as the one it had open (issue #227).
|
|
351
|
+
*
|
|
352
|
+
* A resumed session re-resolves its target before discovery has reported a
|
|
353
|
+
* loaded window, so the first turn budgets against the probed server-wide
|
|
354
|
+
* figure, which on a multi-slot backend is a multiple of what the model is
|
|
355
|
+
* actually serving. The snapshot ledger already recorded the number the
|
|
356
|
+
* backend named, so the resume carries it forward instead of re-probing.
|
|
357
|
+
* Scoped to the same target and model: a different selection has no claim on
|
|
358
|
+
* that measurement, and a live loaded window always outranks this one, so a
|
|
359
|
+
* model reloaded at a new size corrects on the next resolution.
|
|
360
|
+
*/
|
|
361
|
+
export function lastLoadedContextWindow(meta: SessionMeta, providerId: string, modelId: string): number | null {
|
|
362
|
+
const snapshots = getContextSnapshots(meta);
|
|
363
|
+
for (let i = snapshots.length - 1; i >= 0; i -= 1) {
|
|
364
|
+
const snapshot = snapshots[i];
|
|
365
|
+
if (snapshot?.contextWindowSource !== "loaded") continue;
|
|
366
|
+
if (snapshot.providerId !== providerId || snapshot.modelId !== modelId) continue;
|
|
367
|
+
const window = snapshot.effectiveContextWindow;
|
|
368
|
+
if (typeof window === "number" && Number.isFinite(window) && window > 0) return window;
|
|
369
|
+
}
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
|
|
335
373
|
export function buildSnapshotCategories(inputs: {
|
|
336
374
|
systemPrompt?: string | undefined;
|
|
337
375
|
promptSegments?: ReadonlyArray<{ id: string; tokenEstimate: number }> | undefined;
|
|
@@ -456,6 +494,7 @@ export function captureContextSnapshot(input: CaptureContextSnapshotInput): Cont
|
|
|
456
494
|
compactionThreshold: input.compactionThreshold,
|
|
457
495
|
});
|
|
458
496
|
return {
|
|
497
|
+
estimatedTokens: sumSplitTokens(categories),
|
|
459
498
|
snapshotId: nextSnapshotId(),
|
|
460
499
|
sessionId: input.sessionId,
|
|
461
500
|
turnId: input.turnId,
|
|
@@ -480,9 +519,13 @@ export function captureContextSnapshot(input: CaptureContextSnapshotInput): Cont
|
|
|
480
519
|
};
|
|
481
520
|
}
|
|
482
521
|
|
|
522
|
+
function sumSplitTokens(categories: ContextSnapshot["categories"]): number {
|
|
523
|
+
return SNAPSHOT_SPLIT_KEYS.reduce((sum, key) => sum + (categories[key] ?? 0), 0);
|
|
524
|
+
}
|
|
525
|
+
|
|
483
526
|
/** Sum of the prompt-side categories (everything except reserve/free/streaming). */
|
|
484
527
|
export function snapshotInputTokens(snapshot: ContextSnapshot): number {
|
|
485
|
-
return
|
|
528
|
+
return sumSplitTokens(snapshot.categories);
|
|
486
529
|
}
|
|
487
530
|
|
|
488
531
|
export function reconcileSnapshot(snapshot: ContextSnapshot, usage: Usage): ContextSnapshot {
|
|
@@ -572,9 +615,17 @@ export function reconcileSnapshot(snapshot: ContextSnapshot, usage: Usage): Cont
|
|
|
572
615
|
{} as Record<string, "estimated" | "exact" | "reconciled">,
|
|
573
616
|
);
|
|
574
617
|
|
|
618
|
+
// The estimate this snapshot was captured with, kept whole across repeated
|
|
619
|
+
// reconciles so the recorded divergence always compares the provider count
|
|
620
|
+
// against chars/4 rather than against a previously normalized total.
|
|
621
|
+
const estimatedTokens = snapshot.estimatedTokens ?? sumSplitTokens(snapshot.categories);
|
|
622
|
+
|
|
575
623
|
return {
|
|
576
624
|
...snapshot,
|
|
577
625
|
categories: updatedCategories,
|
|
626
|
+
estimatedTokens,
|
|
627
|
+
reconciledTokens: exactInput,
|
|
628
|
+
divergenceRatio: estimatedTokens > 0 ? Math.round((exactInput / estimatedTokens) * 1000) / 1000 : undefined,
|
|
578
629
|
sources: {
|
|
579
630
|
total: "reconciled",
|
|
580
631
|
splits: splitsSources,
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* proportional reconciliation the footer bar already performs.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
+
import type { BackendCacheVerdict, BackendCompletionTimings } from "../../core/cache-telemetry.js";
|
|
19
20
|
import type { ContextWindowSlots, ContextWindowSource } from "../providers/index.js";
|
|
20
21
|
import { DEFAULT_COMPACTION_THRESHOLD } from "./compaction/auto.js";
|
|
21
22
|
|
|
@@ -100,6 +101,22 @@ export interface BuildContextLedgerInput {
|
|
|
100
101
|
} | null;
|
|
101
102
|
/** Prompt-cache honesty line; null until a turn has compiled and settled. */
|
|
102
103
|
promptCache?: PromptCacheStats | null;
|
|
104
|
+
/** Last pre-warm since the previous settled run; null when none has run. */
|
|
105
|
+
prewarm?: PrewarmStats | null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* One pre-warm's result. It is deliberately not a token category: the pre-warm
|
|
110
|
+
* spends no window space and adds no message, it only says the backend has
|
|
111
|
+
* already prefilled the prefix the next turn will send.
|
|
112
|
+
*/
|
|
113
|
+
export interface PrewarmStats {
|
|
114
|
+
/** Prompt tokens the backend processed; null when it reported no counts. */
|
|
115
|
+
tokens: number | null;
|
|
116
|
+
/** Wall clock the round took. */
|
|
117
|
+
ms: number;
|
|
118
|
+
/** True when the operator submitted before the round settled. */
|
|
119
|
+
aborted: boolean;
|
|
103
120
|
}
|
|
104
121
|
|
|
105
122
|
/**
|
|
@@ -117,13 +134,17 @@ export interface PromptCacheStats {
|
|
|
117
134
|
cacheWriteTokens: number | null;
|
|
118
135
|
/** Provider-reported uncached input tokens for the last run. */
|
|
119
136
|
uncachedInputTokens: number | null;
|
|
137
|
+
/** Serving-backend timings for the run's last API call; null when none were captured. */
|
|
138
|
+
backend: BackendCompletionTimings | null;
|
|
139
|
+
/** Backend prompt tokens not served from cache, derived only when cache reads were reported. */
|
|
140
|
+
uncachedPrefillTokens: number | null;
|
|
120
141
|
/**
|
|
121
|
-
* Cache verdict of the run's first API call, classified
|
|
122
|
-
* provider
|
|
123
|
-
* settled run. "Shell reused
|
|
124
|
-
* combination the overlay
|
|
142
|
+
* Cache verdict of the run's first API call, classified by
|
|
143
|
+
* `backendCacheVerdict` from normalized provider usage and captured serving
|
|
144
|
+
* backend timings when available. Null before any settled run. "Shell reused
|
|
145
|
+
* but backend cold" is the dishonest combination the overlay warns about.
|
|
125
146
|
*/
|
|
126
|
-
backendVerdict:
|
|
147
|
+
backendVerdict: BackendCacheVerdict | null;
|
|
127
148
|
/** Cache disturbances Clio expected before the last settled run. */
|
|
128
149
|
expectedColdReasons?: ReadonlyArray<string>;
|
|
129
150
|
}
|
|
@@ -185,6 +206,8 @@ export interface ContextLedger {
|
|
|
185
206
|
trigger: string;
|
|
186
207
|
} | null;
|
|
187
208
|
promptCache: PromptCacheStats | null;
|
|
209
|
+
/** Last pre-warm since the previous settled run; null when none has run. */
|
|
210
|
+
prewarm: PrewarmStats | null;
|
|
188
211
|
}
|
|
189
212
|
|
|
190
213
|
/** Maps a prompt segment id to the ledger bucket it belongs to. */
|
|
@@ -332,14 +355,6 @@ export function buildContextLedger(input: BuildContextLedgerInput): ContextLedge
|
|
|
332
355
|
}
|
|
333
356
|
|
|
334
357
|
const meter: ContextLedgerGroup[] = [...groups];
|
|
335
|
-
if (reserveTokens > 0) {
|
|
336
|
-
meter.push({
|
|
337
|
-
category: "reserve",
|
|
338
|
-
label: CONTEXT_CATEGORY_LABEL.reserve,
|
|
339
|
-
tokens: reserveTokens,
|
|
340
|
-
percent: percentOf(reserveTokens, window),
|
|
341
|
-
});
|
|
342
|
-
}
|
|
343
358
|
if (window > 0) {
|
|
344
359
|
meter.push({
|
|
345
360
|
category: "free",
|
|
@@ -348,6 +363,14 @@ export function buildContextLedger(input: BuildContextLedgerInput): ContextLedge
|
|
|
348
363
|
percent: percentOf(freeTokens, window),
|
|
349
364
|
});
|
|
350
365
|
}
|
|
366
|
+
if (reserveTokens > 0) {
|
|
367
|
+
meter.push({
|
|
368
|
+
category: "reserve",
|
|
369
|
+
label: CONTEXT_CATEGORY_LABEL.reserve,
|
|
370
|
+
tokens: reserveTokens,
|
|
371
|
+
percent: percentOf(reserveTokens, window),
|
|
372
|
+
});
|
|
373
|
+
}
|
|
351
374
|
|
|
352
375
|
return {
|
|
353
376
|
provider: input.provider ?? null,
|
|
@@ -369,5 +392,6 @@ export function buildContextLedger(input: BuildContextLedgerInput): ContextLedge
|
|
|
369
392
|
meter,
|
|
370
393
|
lastCompaction: input.lastCompaction ?? null,
|
|
371
394
|
promptCache: input.promptCache ?? null,
|
|
395
|
+
prewarm: input.prewarm ?? null,
|
|
372
396
|
};
|
|
373
397
|
}
|
|
@@ -61,6 +61,12 @@ export type {
|
|
|
61
61
|
export { isSessionEntry, isSessionHeader, SESSION_ENTRY_KINDS } from "./entries.js";
|
|
62
62
|
export { SessionManifest } from "./manifest.js";
|
|
63
63
|
export { CURRENT_SESSION_FORMAT_VERSION, runMigrations } from "./migrations/index.js";
|
|
64
|
+
export type { ExpectedColdReasonCount, PromptCacheTelemetry } from "./prompt-cache.js";
|
|
65
|
+
export {
|
|
66
|
+
foldPromptCacheTelemetry,
|
|
67
|
+
hasPromptCacheTelemetry,
|
|
68
|
+
topExpectedColdReason,
|
|
69
|
+
} from "./prompt-cache.js";
|
|
64
70
|
export {
|
|
65
71
|
protectedArtifactEntryFromArtifact,
|
|
66
72
|
protectedArtifactFromSessionEntry,
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BackendCacheVerdict,
|
|
3
|
+
type BackendCompletionTimings,
|
|
4
|
+
type CacheVerdictCounts,
|
|
5
|
+
emptyCacheVerdictCounts,
|
|
6
|
+
uncachedPrefillTokens,
|
|
7
|
+
} from "../../core/cache-telemetry.js";
|
|
8
|
+
import type { SessionEntry } from "./entries.js";
|
|
9
|
+
|
|
10
|
+
/** Durable prompt-cache facts folded across assistant calls in one ledger slice. */
|
|
11
|
+
export interface PromptCacheTelemetry {
|
|
12
|
+
/** Sum of authoritative backend prefill work, or null when no call reported cache reads. */
|
|
13
|
+
uncachedPrefillTokens: number | null;
|
|
14
|
+
/** Calls contributing to `uncachedPrefillTokens`; distinguishes no evidence from a measured zero. */
|
|
15
|
+
uncachedPrefillCalls: number;
|
|
16
|
+
/** Counts of the verdict persisted on each assistant call. */
|
|
17
|
+
verdictCounts: CacheVerdictCounts;
|
|
18
|
+
/** Number of assistant calls carrying a recognized persisted verdict. */
|
|
19
|
+
verdictCalls: number;
|
|
20
|
+
/** How often each expected cache disturbance was stamped. */
|
|
21
|
+
expectedColdReasonCounts: Readonly<Record<string, number>>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ExpectedColdReasonCount {
|
|
25
|
+
reason: string;
|
|
26
|
+
count: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
30
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function finiteNonNegative(value: unknown): value is number {
|
|
34
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function backendTimings(value: unknown): BackendCompletionTimings | null {
|
|
38
|
+
if (!isRecord(value)) return null;
|
|
39
|
+
if (
|
|
40
|
+
!finiteNonNegative(value.promptTokens) ||
|
|
41
|
+
!finiteNonNegative(value.predictedTokens) ||
|
|
42
|
+
!finiteNonNegative(value.promptMs) ||
|
|
43
|
+
!finiteNonNegative(value.predictedMs) ||
|
|
44
|
+
(value.source !== "llamacpp-timings" && value.source !== "lmstudio-timings")
|
|
45
|
+
) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const cachedTokens = value.cachedTokens;
|
|
49
|
+
if (cachedTokens !== null && !finiteNonNegative(cachedTokens)) return null;
|
|
50
|
+
if (typeof cachedTokens === "number" && cachedTokens > value.promptTokens) return null;
|
|
51
|
+
return {
|
|
52
|
+
promptTokens: value.promptTokens,
|
|
53
|
+
cachedTokens,
|
|
54
|
+
predictedTokens: value.predictedTokens,
|
|
55
|
+
promptMs: value.promptMs,
|
|
56
|
+
predictedMs: value.predictedMs,
|
|
57
|
+
source: value.source,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function cacheVerdict(value: unknown): BackendCacheVerdict | null {
|
|
62
|
+
switch (value) {
|
|
63
|
+
case "hot":
|
|
64
|
+
case "partial":
|
|
65
|
+
case "cold":
|
|
66
|
+
case "small":
|
|
67
|
+
return value;
|
|
68
|
+
default:
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** True when the fold contains at least one durable cache fact. */
|
|
74
|
+
export function hasPromptCacheTelemetry(telemetry: PromptCacheTelemetry): boolean {
|
|
75
|
+
return (
|
|
76
|
+
telemetry.verdictCalls > 0 ||
|
|
77
|
+
telemetry.uncachedPrefillCalls > 0 ||
|
|
78
|
+
Object.keys(telemetry.expectedColdReasonCounts).length > 0
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Most frequent expected disturbance, with deterministic lexical tie-breaking. */
|
|
83
|
+
export function topExpectedColdReason(telemetry: PromptCacheTelemetry): ExpectedColdReasonCount | null {
|
|
84
|
+
const ranked = Object.entries(telemetry.expectedColdReasonCounts).sort(
|
|
85
|
+
([leftReason, leftCount], [rightReason, rightCount]) =>
|
|
86
|
+
rightCount - leftCount || leftReason.localeCompare(rightReason),
|
|
87
|
+
);
|
|
88
|
+
const first = ranked[0];
|
|
89
|
+
return first === undefined ? null : { reason: first[0], count: first[1] };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Fold persisted assistant `promptCache` payloads without reclassifying them.
|
|
94
|
+
* Callers provide the ledger slice they mean, such as one active branch or one
|
|
95
|
+
* report window, so every surface shares parsing while retaining its own scope.
|
|
96
|
+
*/
|
|
97
|
+
export function foldPromptCacheTelemetry(entries: ReadonlyArray<SessionEntry>): PromptCacheTelemetry {
|
|
98
|
+
const verdictCounts = emptyCacheVerdictCounts();
|
|
99
|
+
const expectedColdReasonCounts: Record<string, number> = {};
|
|
100
|
+
let verdictCalls = 0;
|
|
101
|
+
let uncachedTotal = 0;
|
|
102
|
+
let uncachedPrefillCalls = 0;
|
|
103
|
+
|
|
104
|
+
for (const entry of entries) {
|
|
105
|
+
if (entry.kind !== "message" || entry.role !== "assistant" || !isRecord(entry.payload)) continue;
|
|
106
|
+
const promptCache = entry.payload.promptCache;
|
|
107
|
+
if (!isRecord(promptCache)) continue;
|
|
108
|
+
|
|
109
|
+
const verdict = cacheVerdict(promptCache.backendVerdict);
|
|
110
|
+
if (verdict !== null) {
|
|
111
|
+
verdictCounts[verdict] += 1;
|
|
112
|
+
verdictCalls += 1;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const backend = backendTimings(promptCache.backend);
|
|
116
|
+
const uncached = uncachedPrefillTokens(backend);
|
|
117
|
+
if (uncached !== null) {
|
|
118
|
+
uncachedTotal += uncached;
|
|
119
|
+
uncachedPrefillCalls += 1;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!Array.isArray(promptCache.expectedColdReasons)) continue;
|
|
123
|
+
const reasons = new Set(
|
|
124
|
+
promptCache.expectedColdReasons.filter(
|
|
125
|
+
(reason): reason is string => typeof reason === "string" && reason.trim().length > 0,
|
|
126
|
+
),
|
|
127
|
+
);
|
|
128
|
+
for (const reason of reasons) {
|
|
129
|
+
expectedColdReasonCounts[reason] = (expectedColdReasonCounts[reason] ?? 0) + 1;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
uncachedPrefillTokens: uncachedPrefillCalls > 0 ? uncachedTotal : null,
|
|
135
|
+
uncachedPrefillCalls,
|
|
136
|
+
verdictCounts,
|
|
137
|
+
verdictCalls,
|
|
138
|
+
expectedColdReasonCounts,
|
|
139
|
+
};
|
|
140
|
+
}
|