@iowarp/clio-coder 0.3.0 → 0.3.1
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 +33 -0
- package/README.md +50 -53
- package/dist/acp-FPR54DGL.js +138 -0
- package/dist/{agents-Q4MYPMUW.js → agents-OGPIHPJH.js} +33 -25
- package/dist/assets/codewiki.json +1 -0
- package/dist/assets/grammars/notices/@vscode__tree-sitter-wasm/LICENSE +21 -0
- package/dist/assets/grammars/notices/@vscode__tree-sitter-wasm/cgmanifest.json +16 -0
- package/dist/assets/grammars/notices/tree-sitter-wasms/LICENSE +24 -0
- package/dist/assets/grammars/tree-sitter-c-sharp.wasm +0 -0
- package/dist/assets/grammars/tree-sitter-c.wasm +0 -0
- package/dist/assets/grammars/tree-sitter-cpp.wasm +0 -0
- package/dist/assets/grammars/tree-sitter-go.wasm +0 -0
- package/dist/assets/grammars/tree-sitter-java.wasm +0 -0
- package/dist/assets/grammars/tree-sitter-javascript.wasm +0 -0
- package/dist/assets/grammars/tree-sitter-python.wasm +0 -0
- package/dist/assets/grammars/tree-sitter-ruby.wasm +0 -0
- package/dist/assets/grammars/tree-sitter-rust.wasm +0 -0
- package/dist/assets/grammars/tree-sitter-tsx.wasm +0 -0
- package/dist/assets/grammars/tree-sitter-typescript.wasm +0 -0
- package/dist/assets/grammars/tree-sitter.wasm +0 -0
- package/dist/{auth-O6HYIJ6J.js → auth-IC3K6NIZ.js} +22 -15
- package/dist/{chunk-KKKPTZLM.js → chunk-2STGEDFV.js} +8 -1
- package/dist/chunk-3I7MS7N2.js +40 -0
- package/dist/chunk-3R73A4XB.js +69 -0
- package/dist/{chunk-26BZQOAD.js → chunk-474KN5II.js} +86 -115
- package/dist/{chunk-NI4DEJMC.js → chunk-4QKXUHSR.js} +26 -3
- package/dist/{chunk-X3BR7HWV.js → chunk-5WIGXA4T.js} +7 -1
- package/dist/{chunk-TFSSEXL6.js → chunk-673JJUWJ.js} +14 -4
- package/dist/{chunk-GNGDQYDU.js → chunk-7SS2CTV2.js} +27922 -1249
- package/dist/{chunk-7RBKDI66.js → chunk-ASND7OZK.js} +104 -12
- package/dist/{chunk-X3NE4WVW.js → chunk-BNAZZHFG.js} +7 -1
- package/dist/{chunk-RQDWMVRB.js → chunk-BSU2YIWB.js} +9 -3
- package/dist/{chunk-CLJ5HLUD.js → chunk-C4PTHK7P.js} +24 -5
- package/dist/{chunk-QV47YRF4.js → chunk-CGKSTWHD.js} +7 -1
- package/dist/{chunk-BYEU76JP.js → chunk-DKGKUHFA.js} +34 -9
- package/dist/chunk-EYOKLTMF.js +170 -0
- package/dist/{chunk-PS4PFJQP.js → chunk-FCIH3BIZ.js} +15258 -14130
- package/dist/{chunk-2J63S4SF.js → chunk-FST4FYJB.js} +82 -14
- package/dist/{chunk-3DANZDGR.js → chunk-G34LV2PF.js} +34 -9
- package/dist/{chunk-BBUH4VAA.js → chunk-GAEBEQVI.js} +34 -21
- package/dist/chunk-GB6QRBXN.js +8543 -0
- package/dist/chunk-GEP36Y4X.js +12796 -0
- package/dist/{chunk-TKHQ4DGZ.js → chunk-H6F6BYOH.js} +496 -121
- package/dist/{chunk-UGYAX5YI.js → chunk-HQQID6OA.js} +10 -5
- package/dist/chunk-IHXBNWMM.js +137 -0
- package/dist/{chunk-ME6DNWIU.js → chunk-IS3ONKU3.js} +7 -1
- package/dist/{chunk-DXQNI4PC.js → chunk-IWHMRKLL.js} +8 -1
- package/dist/chunk-K2ITRMHZ.js +642 -0
- package/dist/{chunk-KBR4MZZR.js → chunk-K5XEMXTI.js} +7 -1
- package/dist/{chunk-AMFR5YA3.js → chunk-KKNLWXI6.js} +24 -10
- package/dist/{chunk-JWHCJDCI.js → chunk-KY56HMHH.js} +145 -61
- package/dist/{chunk-E3NYWENM.js → chunk-LU4TK2PR.js} +52 -7
- package/dist/{chunk-UWIJNAOB.js → chunk-M5T5VO65.js} +109 -42
- package/dist/{chunk-JPOWPFCU.js → chunk-MAR7Y6HW.js} +38 -16
- package/dist/chunk-N6F52NLF.js +6951 -0
- package/dist/{chunk-UHTSULZS.js → chunk-NMOX6HFD.js} +20 -7
- package/dist/{chunk-WD6AJM35.js → chunk-OLBBMFRD.js} +30 -13
- package/dist/{chunk-VOO7NYPP.js → chunk-PFEFKVGL.js} +22 -6
- package/dist/{chunk-6CWDTGUC.js → chunk-PIUMUEMV.js} +7 -1
- package/dist/chunk-PV4JUBVJ.js +462 -0
- package/dist/{chunk-527KG6XR.js → chunk-Q3RUPKEJ.js} +22 -4
- package/dist/{chunk-D5YD55AR.js → chunk-Q5WJOSJ7.js} +11 -1
- package/dist/{chunk-5N2FG33Q.js → chunk-R346GLFC.js} +7 -1
- package/dist/{chunk-4UQA7NCT.js → chunk-R5KLMSBV.js} +7 -2
- package/dist/{chunk-PIDUD6M2.js → chunk-RDLVBZEO.js} +11 -6
- package/dist/{chunk-O4EJEDHO.js → chunk-RPTR2H26.js} +40 -31
- package/dist/{chunk-HBU5MTAM.js → chunk-SVNXH4L2.js} +7 -1
- package/dist/{chunk-XNISANGE.js → chunk-TEKV33Q5.js} +122 -57
- package/dist/chunk-TXOTCRLG.js +15 -0
- package/dist/{chunk-VPAWTYLY.js → chunk-WEPFGWHJ.js} +7 -1
- package/dist/{chunk-HMYNFFY4.js → chunk-XPLRXC72.js} +7 -2
- package/dist/{chunk-UU3R62TT.js → chunk-XYWBQRDM.js} +13 -4
- package/dist/{chunk-7BHLZB3A.js → chunk-ZPY3JZ5E.js} +32 -10
- package/dist/cli/index.js +39 -33
- package/dist/clio-BZVGEUFJ.js +58 -0
- package/dist/{components-JVHMUBEB.js → components-DMAOEKFB.js} +26 -9
- package/dist/{config-ZFCDBMDC.js → config-IRUQ7SE4.js} +42 -31
- package/dist/configure-S7S6F6CL.js +32 -0
- package/dist/{context-CDXTP2MP.js → context-3KWFLHJG.js} +17 -11
- package/dist/{context-E3KIFVXI.js → context-5RADCKTR.js} +33 -28
- package/dist/{context-clear-3F4PLXOS.js → context-clear-7TSNPAAI.js} +36 -28
- package/dist/{context-index-Q7YSYTR3.js → context-index-W4RLWOQH.js} +10 -4
- package/dist/{docs-YIETIWZI.js → docs-5AWSPS37.js} +16 -10
- package/dist/doctor-UC5NAJYQ.js +77 -0
- package/dist/{eval-B3KZZESM.js → eval-U6TJHRLX.js} +77 -24
- package/dist/{evidence-V67CHM35.js → evidence-YEGUW4L3.js} +34 -26
- package/dist/{evolve-YDZSUQYA.js → evolve-TXARCTPG.js} +39 -26
- package/dist/{extensions-SRG7XCAH.js → extensions-OZFJ3A3G.js} +13 -7
- package/dist/{fleet-CA2CRTVG.js → fleet-6G3DHNYE.js} +45 -37
- package/dist/fleet-preflight-DSNT37JK.js +23 -0
- package/dist/{init-2OZDJE2D.js → init-KZ5QTF6M.js} +42 -34
- package/dist/{memory-3PIQQAKX.js → memory-73ESV5YC.js} +35 -27
- package/dist/{models-DY35XI7Y.js → models-A4PVNWJK.js} +34 -23
- package/dist/{paths-5OMXW7Z4.js → paths-C4H6IV77.js} +12 -6
- package/dist/preload-6WVMHX3A.js +13 -0
- package/dist/{reset-PIFYNOS3.js → reset-BGW6OGMV.js} +17 -11
- package/dist/{run-3VSPP24F.js → run-YTPEYQOH.js} +60 -48
- package/dist/{share-D36RQCXM.js → share-YIFFV4NQ.js} +16 -10
- package/dist/{skills-F2MRLELY.js → skills-2V6RA3OQ.js} +18 -11
- package/dist/{skills-eval-E2ZTW4PL.js → skills-eval-S2TVJO4F.js} +45 -36
- package/dist/{targets-DZMEZAH4.js → targets-TYXLPB23.js} +61 -59
- package/dist/{trace-7NYCUI2J.js → trace-GGOJ6Q6Z.js} +9 -3
- package/dist/{uninstall-AD3JWHBB.js → uninstall-LLLT4F4W.js} +14 -8
- package/dist/{upgrade-WYYBKGDY.js → upgrade-33G2LMM5.js} +45 -23
- package/dist/{usage-ULIDAGFF.js → usage-ZAFSXKKG.js} +35 -27
- package/dist/version-KGEG7EPO.js +21 -0
- package/dist/{wiki-generate-PKFIX6OB.js → wiki-generate-NUQCVOQ3.js} +55 -45
- package/dist/worker/entry.js +101 -43
- package/docs/README.md +12 -9
- package/docs/acp.md +28 -5
- package/docs/alcf-provider.md +1 -1
- package/docs/architecture.md +23 -2
- package/docs/artifact-placement.md +101 -0
- package/docs/artifact-versions.md +1 -1
- package/docs/built-in-agents.md +4 -2
- package/docs/capacity-and-scheduling.md +1 -1
- package/docs/commands-and-modes.md +125 -41
- package/docs/configuration-and-targets.md +88 -18
- package/docs/context-engine.md +6 -2
- package/docs/development-pipeline.md +77 -0
- package/docs/documentation-coverage.md +4 -4
- package/docs/documentation-guide.md +3 -3
- package/docs/environment-variables.md +2 -1
- package/docs/eval-runner.md +3 -3
- package/docs/evals-internal.md +2 -2
- package/docs/evidence-and-memory.md +6 -2
- package/docs/evolution.md +1 -1
- package/docs/exit-codes-and-output.md +1 -1
- package/docs/extensions-and-sharing.md +18 -11
- package/docs/fleet-demo-runbook.md +22 -23
- package/docs/fleet-dispatch.md +111 -12
- package/docs/glossary.md +107 -15
- package/docs/html/agents_blueprint.html +3 -3
- package/docs/html/alcf_blueprint.html +3 -3
- package/docs/html/architecture_blueprint.html +3 -3
- package/docs/html/commands_blueprint.html +187 -42
- package/docs/html/configuration_blueprint.html +7 -7
- package/docs/html/context_blueprint.html +3 -3
- package/docs/html/documentation_blueprint.html +1 -1
- package/docs/html/environment_blueprint.html +3 -3
- package/docs/html/eval_blueprint.html +4 -4
- package/docs/html/evals_internal_blueprint.html +3 -3
- package/docs/html/evolution_blueprint.html +3 -3
- package/docs/html/extensions_blueprint.html +3 -3
- package/docs/html/fleet_dispatch_blueprint.html +8 -8
- package/docs/html/index.html +5 -5
- package/docs/html/lifecycle_blueprint.html +4 -4
- package/docs/html/memory_blueprint.html +3 -3
- package/docs/html/middleware_blueprint.html +3 -3
- package/docs/html/models_blueprint.html +2 -2
- package/docs/html/observability_blueprint.html +2 -2
- package/docs/html/provider_adapter_blueprint.html +3 -3
- package/docs/html/safety_blueprint.html +3 -3
- package/docs/html/skills_blueprint.html +2 -2
- package/docs/html/soak_blueprint.html +3 -3
- package/docs/html/tool_usage_blueprint.html +4 -4
- package/docs/html/tools_blueprint.html +2 -2
- package/docs/html/trace_blueprint.html +3 -3
- package/docs/html/tui_design_blueprint.html +100 -40
- package/docs/html/validation_blueprint.html +1 -1
- package/docs/html/worker_dispatch_blueprint.html +3 -3
- package/docs/installation-and-lifecycle.md +62 -11
- package/docs/middleware-and-components.md +1 -1
- package/docs/model-catalog.md +2 -2
- package/docs/observability.md +3 -3
- package/docs/proactive-memory.md +12 -12
- package/docs/prompt-envelope-and-tools.md +2 -2
- package/docs/provider-adapter-cookbook.md +1 -1
- package/docs/release-cut-checklist.md +4 -0
- package/docs/safety-model.md +21 -5
- package/docs/scientific-validation.md +3 -3
- package/docs/session-lifecycle.md +2 -2
- package/docs/skills-marketplace.md +1 -1
- package/docs/time-conventions.md +101 -0
- package/docs/tool-usage.md +5 -4
- package/docs/trace-store.md +1 -1
- package/docs/troubleshooting.md +1 -1
- package/docs/tui-design.md +83 -27
- package/docs/worker-dispatch-mechanics.md +3 -3
- package/package.json +16 -16
- package/skills/README.md +5 -6
- package/skills/git/file-ticket/SKILL.md +107 -0
- package/skills/git/file-ticket/evals.md +54 -0
- package/skills/git/fix-issue/SKILL.md +106 -0
- package/skills/git/fix-issue/evals.md +71 -0
- package/skills/git/ship/SKILL.md +92 -0
- package/skills/git/ship/evals.md +72 -0
- package/skills/skill-marketplace.json +12 -20
- package/src/cli/acp.ts +88 -0
- package/src/cli/agents.ts +60 -0
- package/src/cli/args.ts +280 -0
- package/src/cli/argv.ts +170 -0
- package/src/cli/ask.ts +34 -0
- package/src/cli/auth.ts +468 -0
- package/src/cli/bootstrap-generate.ts +498 -0
- package/src/cli/clio.ts +34 -0
- package/src/cli/components.ts +195 -0
- package/src/cli/config-inspect.ts +330 -0
- package/src/cli/config.ts +72 -0
- package/src/cli/configure-interop.ts +67 -0
- package/src/cli/configure-layout.ts +172 -0
- package/src/cli/configure.ts +1487 -0
- package/src/cli/context-clear.ts +76 -0
- package/src/cli/context-index.ts +102 -0
- package/src/cli/context.ts +316 -0
- package/src/cli/default-target.ts +63 -0
- package/src/cli/docs.ts +340 -0
- package/src/cli/doctor.ts +54 -0
- package/src/cli/eval.ts +315 -0
- package/src/cli/evidence.ts +222 -0
- package/src/cli/evolve.ts +219 -0
- package/src/cli/extensions.ts +205 -0
- package/src/cli/fleet.ts +827 -0
- package/src/cli/index.ts +309 -0
- package/src/cli/init.ts +219 -0
- package/src/cli/initial-message.ts +47 -0
- package/src/cli/internal-dispatch.ts +55 -0
- package/src/cli/memory.ts +180 -0
- package/src/cli/models.ts +257 -0
- package/src/cli/modes/json-stream.ts +85 -0
- package/src/cli/modes/jsonl.ts +9 -0
- package/src/cli/modes/print.ts +589 -0
- package/src/cli/oauth-manual-input.ts +72 -0
- package/src/cli/oauth-select.ts +36 -0
- package/src/cli/output-guard.ts +48 -0
- package/src/cli/paths.ts +44 -0
- package/src/cli/provider-target.ts +113 -0
- package/src/cli/removal.ts +72 -0
- package/src/cli/reset.ts +253 -0
- package/src/cli/run.ts +497 -0
- package/src/cli/share.ts +243 -0
- package/src/cli/shared.ts +60 -0
- package/src/cli/skills-eval.ts +1225 -0
- package/src/cli/skills.ts +470 -0
- package/src/cli/steer-channel.ts +86 -0
- package/src/cli/targets.ts +1150 -0
- package/src/cli/text-layout.ts +102 -0
- package/src/cli/trace.ts +274 -0
- package/src/cli/uninstall.ts +405 -0
- package/src/cli/upgrade.ts +270 -0
- package/src/cli/usage.ts +869 -0
- package/src/cli/validate-model.ts +28 -0
- package/src/cli/version.ts +7 -0
- package/src/cli/wiki-generate.ts +509 -0
- package/src/core/artifact-paths.ts +43 -0
- package/src/core/bash-exec.ts +242 -0
- package/src/core/boot-trace.ts +37 -0
- package/src/core/bus-events.ts +728 -0
- package/src/core/bus-trace.ts +53 -0
- package/src/core/c-header-language.ts +265 -0
- package/src/core/clio-repo.ts +78 -0
- package/src/core/config.ts +1593 -0
- package/src/core/context-floor.ts +33 -0
- package/src/core/defaults.ts +625 -0
- package/src/core/domain-loader.ts +170 -0
- package/src/core/event-bus.ts +94 -0
- package/src/core/file-references.ts +282 -0
- package/src/core/guardrails.ts +185 -0
- package/src/core/headless-permission.ts +21 -0
- package/src/core/init.ts +223 -0
- package/src/core/json-payload.ts +58 -0
- package/src/core/package-root.ts +54 -0
- package/src/core/path-canonical.ts +37 -0
- package/src/core/process-identity.ts +41 -0
- package/src/core/recent-models.ts +91 -0
- package/src/core/release.ts +2 -0
- package/src/core/residency-protection.ts +56 -0
- package/src/core/resolve-config-value.ts +88 -0
- package/src/core/response-schema.ts +279 -0
- package/src/core/run-overrides.ts +77 -0
- package/src/core/safe-exec.ts +193 -0
- package/src/core/safe-id.ts +37 -0
- package/src/core/safe-resource-write.ts +162 -0
- package/src/core/session-routing.ts +339 -0
- package/src/core/settings-layers.ts +220 -0
- package/src/core/shared-bus.ts +8 -0
- package/src/core/shell-quote.ts +26 -0
- package/src/core/skill-activation.ts +190 -0
- package/src/core/startup-timer.ts +37 -0
- package/src/core/state-file-lock.ts +207 -0
- package/src/core/termination.ts +202 -0
- package/src/core/timers.ts +38 -0
- package/src/core/tool-names.ts +65 -0
- package/src/core/verification-scripts.ts +13 -0
- package/src/core/workspace-files.ts +492 -0
- package/src/core/xdg.ts +177 -0
- package/src/domains/agents/builtins/architect.md +4 -4
- package/src/domains/agents/builtins/coder.md +3 -2
- package/src/domains/agents/builtins/debugger.md +1 -1
- package/src/domains/agents/builtins/documenter.md +1 -1
- package/src/domains/agents/builtins/git-master.md +3 -3
- package/src/domains/agents/builtins/provenance.md +1 -1
- package/src/domains/agents/builtins/researcher.md +1 -1
- package/src/domains/agents/builtins/scout.md +1 -1
- package/src/domains/agents/builtins/tester.md +1 -1
- package/src/domains/agents/builtins/verifier.md +1 -1
- package/src/domains/agents/builtins/wiki-writer.md +1 -1
- package/src/domains/agents/catalog.ts +172 -0
- package/src/domains/agents/contract.ts +16 -0
- package/src/domains/agents/extension.ts +110 -0
- package/src/domains/agents/fleet-commands.ts +154 -0
- package/src/domains/agents/fleet-contract.ts +797 -0
- package/src/domains/agents/frontmatter.ts +40 -0
- package/src/domains/agents/index.ts +102 -0
- package/src/domains/agents/manifest.ts +6 -0
- package/src/domains/agents/recipe-schema.ts +202 -0
- package/src/domains/agents/recipe.ts +125 -0
- package/src/domains/agents/registry.ts +111 -0
- package/src/domains/agents/result-contract.ts +1265 -0
- package/src/domains/agents/spec.ts +371 -0
- package/src/domains/agents/write-boundary.ts +109 -0
- package/src/domains/components/diff.ts +99 -0
- package/src/domains/components/hash.ts +21 -0
- package/src/domains/components/index.ts +3 -0
- package/src/domains/components/scan.ts +304 -0
- package/src/domains/components/snapshot.ts +108 -0
- package/src/domains/components/types.ts +82 -0
- package/src/domains/config/agent-namespace.ts +13 -0
- package/src/domains/config/classify.ts +94 -0
- package/src/domains/config/contract.ts +22 -0
- package/src/domains/config/extension.ts +136 -0
- package/src/domains/config/index.ts +13 -0
- package/src/domains/config/keybindings.ts +164 -0
- package/src/domains/config/manifest.ts +6 -0
- package/src/domains/config/watcher.ts +41 -0
- package/src/domains/context/adoption.ts +880 -0
- package/src/domains/context/bootstrap-prompt.ts +273 -0
- package/src/domains/context/bootstrap.ts +1409 -0
- package/src/domains/context/clear.ts +86 -0
- package/src/domains/context/clio-md.ts +327 -0
- package/src/domains/context/codewiki/cmake.ts +356 -0
- package/src/domains/context/codewiki/cooperative.ts +46 -0
- package/src/domains/context/codewiki/digest.ts +135 -0
- package/src/domains/context/codewiki/grammar-assets.ts +33 -0
- package/src/domains/context/codewiki/indexer.ts +1614 -0
- package/src/domains/context/codewiki/tree-sitter.ts +771 -0
- package/src/domains/context/contract.ts +62 -0
- package/src/domains/context/excluded-dirs.ts +1 -0
- package/src/domains/context/extension.ts +407 -0
- package/src/domains/context/fingerprint.ts +196 -0
- package/src/domains/context/index.ts +164 -0
- package/src/domains/context/init-options.ts +69 -0
- package/src/domains/context/manifest.ts +6 -0
- package/src/domains/context/operator-profile.ts +157 -0
- package/src/domains/context/project-metadata.ts +510 -0
- package/src/domains/context/project-rules.ts +227 -0
- package/src/domains/context/prompt-context.ts +62 -0
- package/src/domains/context/refresh.ts +170 -0
- package/src/domains/context/sibling-files.ts +10 -0
- package/src/domains/context/state.ts +237 -0
- package/src/domains/context/wiki/assemble.ts +303 -0
- package/src/domains/context/wiki/frontmatter.ts +210 -0
- package/src/domains/context/wiki/generate.ts +590 -0
- package/src/domains/context/wiki/layout.ts +111 -0
- package/src/domains/context/wiki/meta.ts +221 -0
- package/src/domains/context/wiki/plan-store.ts +217 -0
- package/src/domains/context/wiki/plan.ts +250 -0
- package/src/domains/context/wiki/prompts.ts +270 -0
- package/src/domains/context/wiki/staleness.ts +198 -0
- package/src/domains/dispatch/active-route-planner.ts +168 -0
- package/src/domains/dispatch/admission-queue.ts +150 -0
- package/src/domains/dispatch/admission.ts +334 -0
- package/src/domains/dispatch/agent-candidates.ts +307 -0
- package/src/domains/dispatch/agent-ledger-hub.ts +71 -0
- package/src/domains/dispatch/agent-ledger-store.ts +257 -0
- package/src/domains/dispatch/agent-ledger.ts +212 -0
- package/src/domains/dispatch/agent-plan-adapter.ts +85 -0
- package/src/domains/dispatch/assignment-events.ts +132 -0
- package/src/domains/dispatch/assignment-reconcile.ts +76 -0
- package/src/domains/dispatch/assignment-store.ts +162 -0
- package/src/domains/dispatch/assignment.ts +278 -0
- package/src/domains/dispatch/backoff.ts +59 -0
- package/src/domains/dispatch/batch-store.ts +134 -0
- package/src/domains/dispatch/batch-tracker.ts +66 -0
- package/src/domains/dispatch/capability-match.ts +155 -0
- package/src/domains/dispatch/capacity-lease.ts +413 -0
- package/src/domains/dispatch/code-step-store.ts +52 -0
- package/src/domains/dispatch/code-step.ts +368 -0
- package/src/domains/dispatch/contract.ts +278 -0
- package/src/domains/dispatch/dedup.ts +133 -0
- package/src/domains/dispatch/event-pump.ts +251 -0
- package/src/domains/dispatch/execution-handoff.ts +29 -0
- package/src/domains/dispatch/execution-plan.ts +328 -0
- package/src/domains/dispatch/execution-role.ts +277 -0
- package/src/domains/dispatch/execution-scheduler.ts +583 -0
- package/src/domains/dispatch/extension.ts +5869 -0
- package/src/domains/dispatch/failure-classification.ts +141 -0
- package/src/domains/dispatch/fleet-plan.ts +225 -0
- package/src/domains/dispatch/fleet-preflight.ts +394 -0
- package/src/domains/dispatch/gate-decisions.ts +1040 -0
- package/src/domains/dispatch/gate-role-prompts.ts +58 -0
- package/src/domains/dispatch/heartbeat.ts +27 -0
- package/src/domains/dispatch/index.ts +137 -0
- package/src/domains/dispatch/joint-route-adapter.ts +114 -0
- package/src/domains/dispatch/joint-route-resolver.ts +283 -0
- package/src/domains/dispatch/manifest.ts +6 -0
- package/src/domains/dispatch/orphan-recovery.ts +224 -0
- package/src/domains/dispatch/outcome.ts +109 -0
- package/src/domains/dispatch/phase-timing.ts +47 -0
- package/src/domains/dispatch/placement.ts +202 -0
- package/src/domains/dispatch/receipt-findings.ts +255 -0
- package/src/domains/dispatch/receipt-integrity.ts +378 -0
- package/src/domains/dispatch/recovery-candidates.ts +74 -0
- package/src/domains/dispatch/reproducibility.ts +48 -0
- package/src/domains/dispatch/reservation-store.ts +501 -0
- package/src/domains/dispatch/route-approval.ts +177 -0
- package/src/domains/dispatch/route-candidates.ts +57 -0
- package/src/domains/dispatch/route-decision.ts +1079 -0
- package/src/domains/dispatch/route-facts.ts +187 -0
- package/src/domains/dispatch/route-history.ts +238 -0
- package/src/domains/dispatch/route-observer.ts +488 -0
- package/src/domains/dispatch/route-policy.ts +327 -0
- package/src/domains/dispatch/route-quality.ts +256 -0
- package/src/domains/dispatch/route-readiness.ts +120 -0
- package/src/domains/dispatch/routing-intent.ts +287 -0
- package/src/domains/dispatch/run-identity.ts +56 -0
- package/src/domains/dispatch/scout-transition.ts +130 -0
- package/src/domains/dispatch/state.ts +277 -0
- package/src/domains/dispatch/tool-stats.ts +132 -0
- package/src/domains/dispatch/transport.ts +209 -0
- package/src/domains/dispatch/types.ts +785 -0
- package/src/domains/dispatch/validation-grounding.ts +154 -0
- package/src/domains/dispatch/validation.ts +721 -0
- package/src/domains/dispatch/worker-protocol.ts +275 -0
- package/src/domains/dispatch/worker-spawn.ts +566 -0
- package/src/domains/dispatch/write-boundary-enforcer.ts +97 -0
- package/src/domains/dispatch/write-boundary.ts +426 -0
- package/src/domains/eval/artifacts/redact.ts +42 -0
- package/src/domains/eval/artifacts/store.ts +173 -0
- package/src/domains/eval/compare/compare.ts +33 -0
- package/src/domains/eval/compare/gates.ts +66 -0
- package/src/domains/eval/compare/thresholds.ts +92 -0
- package/src/domains/eval/harness-metrics.ts +62 -0
- package/src/domains/eval/index.ts +66 -0
- package/src/domains/eval/metrics/chaos-stream.ts +93 -0
- package/src/domains/eval/metrics/context.ts +34 -0
- package/src/domains/eval/metrics/coverage.ts +21 -0
- package/src/domains/eval/metrics/evidence.ts +182 -0
- package/src/domains/eval/metrics/fleet-loop-stream.ts +196 -0
- package/src/domains/eval/metrics/invariants.ts +736 -0
- package/src/domains/eval/metrics/latency.ts +4 -0
- package/src/domains/eval/metrics/token-stream.ts +139 -0
- package/src/domains/eval/metrics/tokens.ts +51 -0
- package/src/domains/eval/metrics/tool-calls.ts +7 -0
- package/src/domains/eval/proactive-memory.ts +279 -0
- package/src/domains/eval/provenance.ts +35 -0
- package/src/domains/eval/report.ts +234 -0
- package/src/domains/eval/reports/json.ts +5 -0
- package/src/domains/eval/reports/junit.ts +23 -0
- package/src/domains/eval/reports/markdown.ts +20 -0
- package/src/domains/eval/reports/swe-jsonl.ts +16 -0
- package/src/domains/eval/reports/text.ts +25 -0
- package/src/domains/eval/run-compare.ts +274 -0
- package/src/domains/eval/runner.ts +244 -0
- package/src/domains/eval/runners/clio-run.ts +148 -0
- package/src/domains/eval/runners/context-index.ts +56 -0
- package/src/domains/eval/runners/context-init.ts +203 -0
- package/src/domains/eval/runners/external-command.ts +350 -0
- package/src/domains/eval/runtime-paths.ts +171 -0
- package/src/domains/eval/schema/artifact.ts +75 -0
- package/src/domains/eval/schema/suite.ts +235 -0
- package/src/domains/eval/schema/validate.ts +339 -0
- package/src/domains/eval/store.ts +392 -0
- package/src/domains/eval/suites/load.ts +74 -0
- package/src/domains/eval/suites/matrix.ts +19 -0
- package/src/domains/eval/suites/resolve.ts +44 -0
- package/src/domains/eval/suites/run.ts +325 -0
- package/src/domains/eval/task-file.ts +186 -0
- package/src/domains/eval/types.ts +141 -0
- package/src/domains/eval/verifiers/command.ts +27 -0
- package/src/domains/eval/verifiers/file-exists.ts +6 -0
- package/src/domains/eval/verifiers/patch.ts +22 -0
- package/src/domains/eval/workspaces/git.ts +41 -0
- package/src/domains/eval/workspaces/local.ts +17 -0
- package/src/domains/eval/workspaces/temp-copy.ts +29 -0
- package/src/domains/evidence/build.ts +1714 -0
- package/src/domains/evidence/eval.ts +605 -0
- package/src/domains/evidence/failure-attribution.ts +54 -0
- package/src/domains/evidence/finish-contract-map.ts +42 -0
- package/src/domains/evidence/index.ts +55 -0
- package/src/domains/evidence/provenance.ts +200 -0
- package/src/domains/evidence/redact.ts +81 -0
- package/src/domains/evidence/store.ts +291 -0
- package/src/domains/evidence/types.ts +325 -0
- package/src/domains/evolution/SELF_EDIT_GATE.md +91 -0
- package/src/domains/evolution/index.ts +26 -0
- package/src/domains/evolution/manifest.ts +154 -0
- package/src/domains/evolution/validate.ts +304 -0
- package/src/domains/extensions/contract.ts +22 -0
- package/src/domains/extensions/discovery.ts +193 -0
- package/src/domains/extensions/extension.ts +46 -0
- package/src/domains/extensions/index.ts +34 -0
- package/src/domains/extensions/manager.ts +30 -0
- package/src/domains/extensions/manifest.ts +6 -0
- package/src/domains/extensions/resources.ts +38 -0
- package/src/domains/extensions/state.ts +224 -0
- package/src/domains/extensions/types.ts +83 -0
- package/src/domains/interop/consent.ts +185 -0
- package/src/domains/interop/contract.ts +29 -0
- package/src/domains/interop/detect.ts +173 -0
- package/src/domains/interop/extension.ts +59 -0
- package/src/domains/interop/index.ts +34 -0
- package/src/domains/interop/manifest.ts +6 -0
- package/src/domains/interop/registry.ts +139 -0
- package/src/domains/interop/state.ts +92 -0
- package/src/domains/interop/types.ts +109 -0
- package/src/domains/lifecycle/contract.ts +11 -0
- package/src/domains/lifecycle/doctor.ts +536 -0
- package/src/domains/lifecycle/extension.ts +23 -0
- package/src/domains/lifecycle/index.ts +21 -0
- package/src/domains/lifecycle/install-method.ts +51 -0
- package/src/domains/lifecycle/manifest.ts +6 -0
- package/src/domains/lifecycle/migrations/index.ts +94 -0
- package/src/domains/lifecycle/state.ts +93 -0
- package/src/domains/lifecycle/upgrade-notice.ts +35 -0
- package/src/domains/lifecycle/version.ts +35 -0
- package/src/domains/memory/index.ts +128 -0
- package/src/domains/memory/operations.ts +143 -0
- package/src/domains/memory/prompt-section.ts +86 -0
- package/src/domains/memory/proposal.ts +180 -0
- package/src/domains/memory/store.ts +163 -0
- package/src/domains/memory/task-bank.ts +229 -0
- package/src/domains/memory/task-memory-handoff.ts +222 -0
- package/src/domains/memory/task-memory-policy.ts +602 -0
- package/src/domains/memory/task-memory-status.ts +58 -0
- package/src/domains/memory/task-memory-telemetry.ts +346 -0
- package/src/domains/memory/task-memory-trace.ts +89 -0
- package/src/domains/memory/types.ts +74 -0
- package/src/domains/memory/validate.ts +297 -0
- package/src/domains/middleware/budget.ts +216 -0
- package/src/domains/middleware/contract.ts +35 -0
- package/src/domains/middleware/dispatch-nudge.ts +469 -0
- package/src/domains/middleware/extension.ts +129 -0
- package/src/domains/middleware/hook-receipts.ts +70 -0
- package/src/domains/middleware/hooks-io.ts +174 -0
- package/src/domains/middleware/hooks.ts +432 -0
- package/src/domains/middleware/index.ts +154 -0
- package/src/domains/middleware/manifest.ts +6 -0
- package/src/domains/middleware/memory-intervention.ts +701 -0
- package/src/domains/middleware/rules.ts +42 -0
- package/src/domains/middleware/runtime.ts +333 -0
- package/src/domains/middleware/skills-reminder.ts +187 -0
- package/src/domains/middleware/snapshot.ts +71 -0
- package/src/domains/middleware/stalled-turn.ts +71 -0
- package/src/domains/middleware/task-board-reminder.ts +87 -0
- package/src/domains/middleware/task-nudge.ts +77 -0
- package/src/domains/middleware/tool-choice-control.ts +42 -0
- package/src/domains/middleware/types.ts +131 -0
- package/src/domains/middleware/validate.ts +329 -0
- package/src/domains/observability/accountability.ts +67 -0
- package/src/domains/observability/contract.ts +168 -0
- package/src/domains/observability/cost.ts +206 -0
- package/src/domains/observability/evidence-index.ts +123 -0
- package/src/domains/observability/extension.ts +281 -0
- package/src/domains/observability/index.ts +78 -0
- package/src/domains/observability/manifest.ts +10 -0
- package/src/domains/observability/metrics.ts +37 -0
- package/src/domains/observability/projection.ts +477 -0
- package/src/domains/observability/telemetry.ts +89 -0
- package/src/domains/observability/trace-store.ts +1454 -0
- package/src/domains/prompts/compiler.ts +556 -0
- package/src/domains/prompts/contract.ts +30 -0
- package/src/domains/prompts/extension.ts +304 -0
- package/src/domains/prompts/fragment-loader.ts +139 -0
- package/src/domains/prompts/fragments/identity/clio.md +3 -2
- package/src/domains/prompts/fragments/identity/self-awareness.md +63 -0
- package/src/domains/prompts/fragments/operating/contract.md +10 -1
- package/src/domains/prompts/hash.ts +73 -0
- package/src/domains/prompts/index.ts +32 -0
- package/src/domains/prompts/manifest.ts +8 -0
- package/src/domains/prompts/memory-intervention.ts +57 -0
- package/src/domains/prompts/preload.ts +82 -0
- package/src/domains/providers/auth/api-key.ts +47 -0
- package/src/domains/providers/auth/backend-file.ts +69 -0
- package/src/domains/providers/auth/backend-memory.ts +17 -0
- package/src/domains/providers/auth/index.ts +37 -0
- package/src/domains/providers/auth/oauth.ts +36 -0
- package/src/domains/providers/auth/storage.ts +667 -0
- package/src/domains/providers/capabilities.ts +50 -0
- package/src/domains/providers/catalog.ts +162 -0
- package/src/domains/providers/contract.ts +151 -0
- package/src/domains/providers/credentials.ts +20 -0
- package/src/domains/providers/eligibility.ts +24 -0
- package/src/domains/providers/errors.ts +64 -0
- package/src/domains/providers/extension.ts +536 -0
- package/src/domains/providers/index.ts +142 -0
- package/src/domains/providers/knowledge-base-path.ts +102 -0
- package/src/domains/providers/manifest.ts +6 -0
- package/src/domains/providers/model-capabilities.ts +136 -0
- package/src/domains/providers/model-discovery.ts +144 -0
- package/src/domains/providers/model-family.ts +32 -0
- package/src/domains/providers/model-runtime-capabilities.ts +712 -0
- package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +2 -2
- package/src/domains/providers/plugins.ts +59 -0
- package/src/domains/providers/probe/fingerprint.ts +44 -0
- package/src/domains/providers/probe/http.ts +114 -0
- package/src/domains/providers/probe/reasoning.ts +111 -0
- package/src/domains/providers/registry.ts +166 -0
- package/src/domains/providers/resolver.ts +156 -0
- package/src/domains/providers/runtime-resolution.ts +720 -0
- package/src/domains/providers/runtimes/antigravity/antigravity-code.ts +90 -0
- package/src/domains/providers/runtimes/builtins.ts +78 -0
- package/src/domains/providers/runtimes/claude/claude-code.ts +38 -0
- package/src/domains/providers/runtimes/claude/claude-sdk.ts +38 -0
- package/src/domains/providers/runtimes/claude/common.ts +55 -0
- package/src/domains/providers/runtimes/cloud/alcf.ts +171 -0
- package/src/domains/providers/runtimes/cloud/anthropic-max.ts +71 -0
- package/src/domains/providers/runtimes/cloud/anthropic.ts +43 -0
- package/src/domains/providers/runtimes/cloud/bedrock.ts +35 -0
- package/src/domains/providers/runtimes/cloud/deepseek.ts +50 -0
- package/src/domains/providers/runtimes/cloud/google.ts +41 -0
- package/src/domains/providers/runtimes/cloud/groq.ts +46 -0
- package/src/domains/providers/runtimes/cloud/mistral.ts +46 -0
- package/src/domains/providers/runtimes/cloud/openai-codex.ts +49 -0
- package/src/domains/providers/runtimes/cloud/openai.ts +46 -0
- package/src/domains/providers/runtimes/cloud/openrouter.ts +111 -0
- package/src/domains/providers/runtimes/common/lmstudio-logger.ts +32 -0
- package/src/domains/providers/runtimes/common/local-synth.ts +165 -0
- package/src/domains/providers/runtimes/common/probe-helpers.ts +474 -0
- package/src/domains/providers/runtimes/local-native/lemonade-anthropic.ts +27 -0
- package/src/domains/providers/runtimes/local-native/lemonade-openai.ts +26 -0
- package/src/domains/providers/runtimes/local-native/llamacpp-anthropic.ts +88 -0
- package/src/domains/providers/runtimes/local-native/llamacpp-completion.ts +207 -0
- package/src/domains/providers/runtimes/local-native/llamacpp-embed.ts +180 -0
- package/src/domains/providers/runtimes/local-native/llamacpp-rerank.ts +126 -0
- package/src/domains/providers/runtimes/local-native/llamacpp.ts +98 -0
- package/src/domains/providers/runtimes/local-native/lmstudio-native.ts +491 -0
- package/src/domains/providers/runtimes/local-native/ollama-native.ts +117 -0
- package/src/domains/providers/runtimes/local-native/sglang.ts +26 -0
- package/src/domains/providers/runtimes/local-native/vllm.ts +27 -0
- package/src/domains/providers/runtimes/protocol/anthropic-compat.ts +104 -0
- package/src/domains/providers/runtimes/protocol/anthropic-messages.ts +28 -0
- package/src/domains/providers/runtimes/protocol/bedrock.ts +26 -0
- package/src/domains/providers/runtimes/protocol/google.ts +27 -0
- package/src/domains/providers/runtimes/protocol/openai-compat.ts +145 -0
- package/src/domains/providers/support.ts +238 -0
- package/src/domains/providers/thinking-control-policy.ts +85 -0
- package/src/domains/providers/types/capability-flags.ts +114 -0
- package/src/domains/providers/types/cost-provenance.ts +7 -0
- package/src/domains/providers/types/inference.ts +41 -0
- package/src/domains/providers/types/knowledge-base.ts +156 -0
- package/src/domains/providers/types/local-model-quirks.ts +218 -0
- package/src/domains/providers/types/runtime-descriptor.ts +170 -0
- package/src/domains/providers/types/target-descriptor.ts +38 -0
- package/src/domains/resources/collision.ts +92 -0
- package/src/domains/resources/common-loader.ts +140 -0
- package/src/domains/resources/contract.ts +25 -0
- package/src/domains/resources/extension.ts +61 -0
- package/src/domains/resources/index.ts +74 -0
- package/src/domains/resources/loader.ts +86 -0
- package/src/domains/resources/manifest.ts +6 -0
- package/src/domains/resources/prompts/loader.ts +232 -0
- package/src/domains/resources/prompts/substitute.ts +65 -0
- package/src/domains/resources/skills/content-hash.ts +105 -0
- package/src/domains/resources/skills/evals.ts +175 -0
- package/src/domains/resources/skills/install.ts +410 -0
- package/src/domains/resources/skills/loader.ts +978 -0
- package/src/domains/resources/skills/marketplace.ts +255 -0
- package/src/domains/resources/skills/provenance-pin.ts +115 -0
- package/src/domains/safety/action-classifier.ts +268 -0
- package/src/domains/safety/approval-axis.ts +23 -0
- package/src/domains/safety/audit.ts +487 -0
- package/src/domains/safety/autonomy.ts +174 -0
- package/src/domains/safety/call-target.ts +59 -0
- package/src/domains/safety/contract.ts +65 -0
- package/src/domains/safety/damage-control.ts +44 -0
- package/src/domains/safety/default-path-policy.ts +117 -0
- package/src/domains/safety/extension.ts +383 -0
- package/src/domains/safety/finish-contract-registration.ts +128 -0
- package/src/domains/safety/finish-contract.ts +554 -0
- package/src/domains/safety/index.ts +44 -0
- package/src/domains/safety/loop-detector.ts +143 -0
- package/src/domains/safety/manifest.ts +6 -0
- package/src/domains/safety/path-policy.ts +164 -0
- package/src/domains/safety/policy-engine.ts +994 -0
- package/src/domains/safety/project-policy.ts +391 -0
- package/src/domains/safety/protected-artifacts-registration.ts +189 -0
- package/src/domains/safety/protected-artifacts.ts +993 -0
- package/src/domains/safety/rejection-feedback.ts +102 -0
- package/src/domains/safety/rigor.ts +72 -0
- package/src/domains/safety/rule-compiler.ts +48 -0
- package/src/domains/safety/rule-pack-loader.ts +92 -0
- package/src/domains/safety/run-effects.ts +197 -0
- package/src/domains/safety/scope.ts +61 -0
- package/src/domains/scheduling/budget.ts +35 -0
- package/src/domains/scheduling/cluster.ts +175 -0
- package/src/domains/scheduling/contract.ts +30 -0
- package/src/domains/scheduling/extension.ts +91 -0
- package/src/domains/scheduling/index.ts +12 -0
- package/src/domains/scheduling/manifest.ts +6 -0
- package/src/domains/session/archive-readers.ts +208 -0
- package/src/domains/session/checkpoint.ts +38 -0
- package/src/domains/session/compaction/auto.ts +89 -0
- package/src/domains/session/compaction/branch-summary.ts +186 -0
- package/src/domains/session/compaction/compact.ts +493 -0
- package/src/domains/session/compaction/cut-point.ts +171 -0
- package/src/domains/session/compaction/defaults.ts +34 -0
- package/src/domains/session/compaction/mask-observations.ts +265 -0
- package/src/domains/session/compaction/session-entries.ts +18 -0
- package/src/domains/session/compaction/tokens.ts +147 -0
- package/src/domains/session/context-accounting.ts +583 -0
- package/src/domains/session/context-ledger.ts +348 -0
- package/src/domains/session/contract.ts +144 -0
- package/src/domains/session/cwd-fallback.ts +65 -0
- package/src/domains/session/entries.ts +509 -0
- package/src/domains/session/extension.ts +322 -0
- package/src/domains/session/history.ts +172 -0
- package/src/domains/session/index.ts +84 -0
- package/src/domains/session/manager.ts +128 -0
- package/src/domains/session/manifest.ts +6 -0
- package/src/domains/session/migrations/index.ts +27 -0
- package/src/domains/session/prompt-manifest.ts +173 -0
- package/src/domains/session/protected-artifact-journal.ts +241 -0
- package/src/domains/session/protected-artifacts.ts +90 -0
- package/src/domains/session/retry.ts +238 -0
- package/src/domains/session/task-board.ts +375 -0
- package/src/domains/session/tree/active-path.ts +98 -0
- package/src/domains/session/tree/fork.ts +160 -0
- package/src/domains/session/tree/manager.ts +128 -0
- package/src/domains/session/tree/navigator.ts +177 -0
- package/src/domains/session/tree/preview.ts +259 -0
- package/src/domains/session/usage.ts +148 -0
- package/src/domains/session/workspace/git-probe.ts +161 -0
- package/src/domains/session/workspace/index.ts +4 -0
- package/src/domains/session/workspace/project-type.ts +217 -0
- package/src/domains/session/workspace/snapshot.ts +74 -0
- package/src/domains/share/archive.ts +684 -0
- package/src/domains/share/contract.ts +9 -0
- package/src/domains/share/extension.ts +26 -0
- package/src/domains/share/index.ts +28 -0
- package/src/domains/share/manifest.ts +6 -0
- package/src/engine/acp/adapter.ts +397 -0
- package/src/engine/acp/errors.ts +32 -0
- package/src/engine/acp/event-mapper.ts +154 -0
- package/src/engine/acp/server.ts +656 -0
- package/src/engine/acp/tool-mediator.ts +686 -0
- package/src/engine/acp/transport.ts +654 -0
- package/src/engine/acp/types.ts +170 -0
- package/src/engine/agent.ts +29 -0
- package/src/engine/ai.ts +202 -0
- package/src/engine/alcf-oauth.ts +198 -0
- package/src/engine/antigravity/subprocess-runtime.ts +205 -0
- package/src/engine/apis/degraded-inference.ts +95 -0
- package/src/engine/apis/index.ts +20 -0
- package/src/engine/apis/llamacpp-residency.ts +252 -0
- package/src/engine/apis/lmstudio-native.ts +1438 -0
- package/src/engine/apis/lmstudio-residency.ts +92 -0
- package/src/engine/apis/ollama-native.ts +507 -0
- package/src/engine/apis/openai-completions.ts +716 -0
- package/src/engine/apis/output-budget.ts +99 -0
- package/src/engine/apis/residency-lock.ts +29 -0
- package/src/engine/apis/residency.ts +640 -0
- package/src/engine/apis/resident-models.ts +42 -0
- package/src/engine/apis/sampling-overrides.ts +38 -0
- package/src/engine/apis/thinking-replay.ts +11 -0
- package/src/engine/claude/sdk-runtime.ts +685 -0
- package/src/engine/claude/session-id.ts +5 -0
- package/src/engine/claude/subprocess-runtime.ts +316 -0
- package/src/engine/claude/tool-safety.ts +400 -0
- package/src/engine/external-subprocess.ts +23 -0
- package/src/engine/harmony-response.ts +165 -0
- package/src/engine/loop-guard.ts +1127 -0
- package/src/engine/oauth.ts +192 -0
- package/src/engine/pi-mono-names.ts +13 -0
- package/src/engine/provider-payload.ts +315 -0
- package/src/engine/select-list.ts +53 -0
- package/src/engine/session.ts +834 -0
- package/src/engine/strip-tokenizer-sentinels.ts +125 -0
- package/src/engine/tui.ts +103 -0
- package/src/engine/types.ts +66 -0
- package/src/engine/worker-events.ts +100 -0
- package/src/engine/worker-runtime.ts +1022 -0
- package/src/engine/worker-tools.ts +203 -0
- package/src/entry/orchestrator.ts +1873 -0
- package/src/interactive/application-controller.ts +346 -0
- package/src/interactive/bus-notices.ts +265 -0
- package/src/interactive/chat-loop-messages.ts +626 -0
- package/src/interactive/chat-loop-policy.ts +18 -0
- package/src/interactive/chat-loop.ts +865 -0
- package/src/interactive/chat-panel.ts +1694 -0
- package/src/interactive/chat-renderer.ts +1038 -0
- package/src/interactive/clio-editor.ts +166 -0
- package/src/interactive/command-fallbacks.ts +23 -0
- package/src/interactive/command-output.ts +85 -0
- package/src/interactive/context-activity.ts +203 -0
- package/src/interactive/context-meter.ts +223 -0
- package/src/interactive/context-overlay.ts +220 -0
- package/src/interactive/cost-overlay.ts +279 -0
- package/src/interactive/dispatch-board.ts +1319 -0
- package/src/interactive/dispatch-steering.ts +86 -0
- package/src/interactive/editor-bash.ts +46 -0
- package/src/interactive/editor-steer.ts +51 -0
- package/src/interactive/editor-submit.ts +303 -0
- package/src/interactive/external-editor.ts +67 -0
- package/src/interactive/follow-up-queue-panel.ts +75 -0
- package/src/interactive/footer/dashboard.ts +534 -0
- package/src/interactive/footer/notifications.ts +275 -0
- package/src/interactive/footer/widgets.ts +1246 -0
- package/src/interactive/footer-panel.ts +187 -0
- package/src/interactive/format-time.ts +85 -0
- package/src/interactive/index.ts +11 -0
- package/src/interactive/interactive-application.ts +755 -0
- package/src/interactive/interactive-event-projection.ts +380 -0
- package/src/interactive/interactive-input-runtime.ts +261 -0
- package/src/interactive/interactive-presentation.ts +452 -0
- package/src/interactive/interactive-shell.ts +134 -0
- package/src/interactive/interactive-slash-runtime.ts +492 -0
- package/src/interactive/interactive-subscriptions.ts +111 -0
- package/src/interactive/interactive-tickers.ts +152 -0
- package/src/interactive/keybinding-manager.ts +496 -0
- package/src/interactive/layout.ts +20 -0
- package/src/interactive/leader-key.ts +143 -0
- package/src/interactive/loop-guard-interrupt.ts +101 -0
- package/src/interactive/memory-overlay.ts +280 -0
- package/src/interactive/overlay-ask-user-lifecycle.ts +110 -0
- package/src/interactive/overlay-auth-lifecycle.ts +308 -0
- package/src/interactive/overlay-frame.ts +502 -0
- package/src/interactive/overlay-general-openers.ts +217 -0
- package/src/interactive/overlay-key-routing.ts +213 -0
- package/src/interactive/overlay-lifecycle.ts +413 -0
- package/src/interactive/overlay-model-selectors.ts +127 -0
- package/src/interactive/overlay-permission-lifecycle.ts +344 -0
- package/src/interactive/overlay-resource-openers.ts +112 -0
- package/src/interactive/overlay-session-lifecycle.ts +270 -0
- package/src/interactive/overlay-transitions.ts +57 -0
- package/src/interactive/overlays/agents.ts +69 -0
- package/src/interactive/overlays/ask-user.ts +921 -0
- package/src/interactive/overlays/auth-dialog.ts +221 -0
- package/src/interactive/overlays/context-reset.ts +108 -0
- package/src/interactive/overlays/cwd-fallback.ts +141 -0
- package/src/interactive/overlays/extensions.ts +54 -0
- package/src/interactive/overlays/help-reference.ts +140 -0
- package/src/interactive/overlays/interop.ts +139 -0
- package/src/interactive/overlays/keybinding-detail.ts +90 -0
- package/src/interactive/overlays/list-overlay.ts +606 -0
- package/src/interactive/overlays/message-picker.ts +155 -0
- package/src/interactive/overlays/model-selector.ts +1091 -0
- package/src/interactive/overlays/prompts.ts +72 -0
- package/src/interactive/overlays/session-selector-search.ts +31 -0
- package/src/interactive/overlays/session-selector.ts +266 -0
- package/src/interactive/overlays/settings.ts +3363 -0
- package/src/interactive/overlays/skills-hub.ts +237 -0
- package/src/interactive/overlays/tree-selector.ts +387 -0
- package/src/interactive/permission-overlay.ts +170 -0
- package/src/interactive/render-trace.ts +125 -0
- package/src/interactive/renderers/branch-summary.ts +86 -0
- package/src/interactive/renderers/code-ink.ts +420 -0
- package/src/interactive/renderers/compaction-summary.ts +112 -0
- package/src/interactive/renderers/diff.ts +126 -0
- package/src/interactive/renderers/notice.ts +28 -0
- package/src/interactive/renderers/retry-status.ts +31 -0
- package/src/interactive/renderers/structured.ts +89 -0
- package/src/interactive/renderers/tool-execution.ts +1104 -0
- package/src/interactive/renderers/worker-entry.ts +264 -0
- package/src/interactive/session-last-turn.ts +186 -0
- package/src/interactive/session-transcript.ts +105 -0
- package/src/interactive/session-usage-reseed.ts +74 -0
- package/src/interactive/slash-autocomplete.ts +364 -0
- package/src/interactive/slash-commands.ts +1319 -0
- package/src/interactive/slash-spec.ts +453 -0
- package/src/interactive/status/controller.ts +256 -0
- package/src/interactive/status/index.ts +23 -0
- package/src/interactive/status/state-machine.ts +530 -0
- package/src/interactive/status/summary.ts +170 -0
- package/src/interactive/status/types.ts +121 -0
- package/src/interactive/status/verbs.ts +163 -0
- package/src/interactive/status/watchdog.ts +24 -0
- package/src/interactive/tasks-overlay.ts +186 -0
- package/src/interactive/theme/components.ts +55 -0
- package/src/interactive/theme/glyphs.ts +70 -0
- package/src/interactive/theme/index.ts +15 -0
- package/src/interactive/theme/labels.ts +88 -0
- package/src/interactive/theme/rules.ts +138 -0
- package/src/interactive/theme/segments.ts +88 -0
- package/src/interactive/theme/tokens.ts +121 -0
- package/src/interactive/thinking-level-policy.ts +6 -0
- package/src/interactive/tool-prose-loop.ts +122 -0
- package/src/interactive/tool-prose-registration.ts +48 -0
- package/src/interactive/turn-context.ts +854 -0
- package/src/interactive/turn-middleware.ts +283 -0
- package/src/interactive/turn-persistence.ts +441 -0
- package/src/interactive/turn-queues.ts +169 -0
- package/src/interactive/turn-recovery.ts +306 -0
- package/src/interactive/turn-runtime.ts +802 -0
- package/src/interactive/turn-state.ts +123 -0
- package/src/interactive/view/artifacts.ts +1342 -0
- package/src/interactive/view/view-overlay.ts +732 -0
- package/src/interactive/welcome-dashboard.ts +536 -0
- package/src/interactive/worker-receipts.ts +94 -0
- package/src/interactive/worker-replay.ts +111 -0
- package/src/interactive/worker-share.ts +100 -0
- package/src/interactive/worker-stream.ts +442 -0
- package/src/interactive/workspace-facts.ts +141 -0
- package/src/tools/agent-tools.ts +382 -0
- package/src/tools/artifact.ts +103 -0
- package/src/tools/ask-user.ts +656 -0
- package/src/tools/bash.ts +176 -0
- package/src/tools/bootstrap.ts +474 -0
- package/src/tools/codewiki/code-nav.ts +563 -0
- package/src/tools/codewiki/shared.ts +115 -0
- package/src/tools/compete-worktrees.ts +898 -0
- package/src/tools/context/docs-engine.ts +619 -0
- package/src/tools/context/index.ts +435 -0
- package/src/tools/credential-present.ts +151 -0
- package/src/tools/dispatch-arguments.ts +228 -0
- package/src/tools/dispatch-plan.ts +741 -0
- package/src/tools/dispatch-scout.ts +461 -0
- package/src/tools/dispatch.ts +3338 -0
- package/src/tools/edit-diff.ts +534 -0
- package/src/tools/edit.ts +128 -0
- package/src/tools/executables.ts +22 -0
- package/src/tools/file-mutation-queue.ts +35 -0
- package/src/tools/find.ts +292 -0
- package/src/tools/grep.ts +495 -0
- package/src/tools/ignore-policy.ts +169 -0
- package/src/tools/ledger.ts +206 -0
- package/src/tools/ls.ts +110 -0
- package/src/tools/monitor.ts +797 -0
- package/src/tools/network-policy.ts +29 -0
- package/src/tools/observation.ts +421 -0
- package/src/tools/observers.ts +99 -0
- package/src/tools/path-utils.ts +79 -0
- package/src/tools/policy.ts +154 -0
- package/src/tools/profiles.ts +108 -0
- package/src/tools/read.ts +173 -0
- package/src/tools/registry.ts +1157 -0
- package/src/tools/result-shaping.ts +204 -0
- package/src/tools/safe-exec.ts +128 -0
- package/src/tools/spawn-hygiene.ts +182 -0
- package/src/tools/steer.ts +88 -0
- package/src/tools/string-enum.ts +15 -0
- package/src/tools/tasks.ts +168 -0
- package/src/tools/truncate-utf8.ts +17 -0
- package/src/tools/truncate.ts +202 -0
- package/src/tools/verify/frontend.ts +685 -0
- package/src/tools/verify/index.ts +66 -0
- package/src/tools/verify/scripts.ts +108 -0
- package/src/tools/web-fetch.ts +777 -0
- package/src/tools/worker-evidence.ts +215 -0
- package/src/tools/write.ts +52 -0
- package/src/utils/exif-orientation.ts +192 -0
- package/src/utils/git.ts +24 -0
- package/src/utils/image-resize.ts +148 -0
- package/src/utils/photon.ts +103 -0
- package/src/worker/control-lane.ts +22 -0
- package/src/worker/entry.ts +257 -0
- package/src/worker/event-projection.ts +56 -0
- package/src/worker/heartbeat.ts +24 -0
- package/src/worker/ledger-mirror.ts +95 -0
- package/src/worker/ndjson.ts +40 -0
- package/src/worker/protocol.ts +529 -0
- package/src/worker/resource-facts.ts +79 -0
- package/src/worker/runtime-registry.ts +12 -0
- package/src/worker/spec-contract.ts +654 -0
- package/src/worker/stdin-demux.ts +316 -0
- package/dist/acp-UMLFVA3F.js +0 -92
- package/dist/chunk-262G75JS.js +0 -35
- package/dist/chunk-5LDRNKX2.js +0 -1063
- package/dist/chunk-67MTHP2E.js +0 -135
- package/dist/chunk-GOTUR54M.js +0 -9
- package/dist/chunk-TPOCL34A.js +0 -2876
- package/dist/chunk-XV4ZJ6ZM.js +0 -3177
- package/dist/clio-KIQ5SNDS.js +0 -53
- package/dist/configure-G4E3A2PG.js +0 -27
- package/dist/doctor-M5HJJZOL.js +0 -61
- package/dist/domains/agents/builtins/architect.md +0 -33
- package/dist/domains/agents/builtins/coder.md +0 -31
- package/dist/domains/agents/builtins/context-bootstrap.md +0 -38
- package/dist/domains/agents/builtins/debugger.md +0 -30
- package/dist/domains/agents/builtins/documenter.md +0 -31
- package/dist/domains/agents/builtins/git-master.md +0 -30
- package/dist/domains/agents/builtins/provenance.md +0 -30
- package/dist/domains/agents/builtins/researcher.md +0 -71
- package/dist/domains/agents/builtins/scout.md +0 -42
- package/dist/domains/agents/builtins/tester.md +0 -31
- package/dist/domains/agents/builtins/verifier.md +0 -30
- package/dist/domains/agents/builtins/wiki-writer.md +0 -41
- package/dist/fleet-preflight-CLIAX7YR.js +0 -21
- package/dist/preload-KZVHET2B.js +0 -11
- package/dist/version-ROZ6CZKH.js +0 -16
- package/skills/git/commit-crafting/SKILL.md +0 -79
- package/skills/git/commit-crafting/evals.md +0 -92
- package/skills/git/create-pr/SKILL.md +0 -116
- package/skills/git/create-pr/evals.md +0 -114
- package/skills/git/investigate-issue/SKILL.md +0 -139
- package/skills/git/investigate-issue/evals.md +0 -94
- package/skills/git/review-changes/SKILL.md +0 -103
- package/skills/git/review-changes/evals.md +0 -85
|
@@ -0,0 +1,1714 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { rawDurationMs } from "../../core/timers.js";
|
|
4
|
+
import { isVerificationScriptName } from "../../core/verification-scripts.js";
|
|
5
|
+
import type {
|
|
6
|
+
RunEnvelope,
|
|
7
|
+
RunKind,
|
|
8
|
+
RunReceipt,
|
|
9
|
+
RunReceiptIntegrity,
|
|
10
|
+
RunStatus,
|
|
11
|
+
ToolCallStat,
|
|
12
|
+
} from "../dispatch/index.js";
|
|
13
|
+
import {
|
|
14
|
+
type ExecutionRole,
|
|
15
|
+
isExecutionRole,
|
|
16
|
+
readGateDecisionArtifactsForRunIds,
|
|
17
|
+
verifyReceiptIntegrity,
|
|
18
|
+
} from "../dispatch/index.js";
|
|
19
|
+
import { detectValidationCommand } from "../safety/protected-artifacts.js";
|
|
20
|
+
import {
|
|
21
|
+
type AuditJsonRow,
|
|
22
|
+
type BashExecutionEntry,
|
|
23
|
+
type MessageEntry,
|
|
24
|
+
type ProtectedArtifactEntry,
|
|
25
|
+
protectedArtifactFromSessionEntry,
|
|
26
|
+
protectedArtifactStateFromSessionEntries,
|
|
27
|
+
readAuditRows,
|
|
28
|
+
readSessionEntriesForId,
|
|
29
|
+
type SessionEntry,
|
|
30
|
+
} from "../session/index.js";
|
|
31
|
+
import { attributeEvidenceFailure } from "./failure-attribution.js";
|
|
32
|
+
import { extractRunProvenance, provenanceTranscriptLines } from "./provenance.js";
|
|
33
|
+
import { createRedactionTally, redactSecretsDeep, redactSecretsText } from "./redact.js";
|
|
34
|
+
import { EVIDENCE_FILES, evidenceDirectory, findingsFile } from "./store.js";
|
|
35
|
+
import {
|
|
36
|
+
EVIDENCE_VERSION,
|
|
37
|
+
type EvidenceAuditLinkedRow,
|
|
38
|
+
type EvidenceBuildResult,
|
|
39
|
+
type EvidenceCleanTraceRow,
|
|
40
|
+
type EvidenceFinding,
|
|
41
|
+
type EvidenceGateDecisionsFile,
|
|
42
|
+
type EvidenceLinkConfidence,
|
|
43
|
+
type EvidenceOverview,
|
|
44
|
+
type EvidenceProtectedArtifactsFile,
|
|
45
|
+
type EvidenceRawTraceRow,
|
|
46
|
+
type EvidenceReceiptFile,
|
|
47
|
+
type EvidenceRunLink,
|
|
48
|
+
type EvidenceRunSource,
|
|
49
|
+
type EvidenceTag,
|
|
50
|
+
type EvidenceToolEvent,
|
|
51
|
+
} from "./types.js";
|
|
52
|
+
|
|
53
|
+
const MAX_TASK_CHARS = 500;
|
|
54
|
+
const TRANSCRIPT_TEXT_MAX_CHARS = 500;
|
|
55
|
+
const TOOL_PREVIEW_MAX_CHARS = 240;
|
|
56
|
+
|
|
57
|
+
export interface BuildEvidenceOptions {
|
|
58
|
+
/** Evidence bundles are written under <dataDir>/evidence/. */
|
|
59
|
+
dataDir: string;
|
|
60
|
+
/** Run ledger, receipts, sessions, and audit logs are read from here. */
|
|
61
|
+
stateDir: string;
|
|
62
|
+
runId?: string;
|
|
63
|
+
sessionId?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* One session ledger entry's run attribution: the run it resolved to, plus the
|
|
68
|
+
* EvidenceRunLink provenance the bundle exports for it. An entry that resolved
|
|
69
|
+
* to no run still carries its candidates, so an ambiguous entry is reported
|
|
70
|
+
* rather than silently unattributed or claimed by whichever bundle asks.
|
|
71
|
+
*/
|
|
72
|
+
interface SessionRunLink {
|
|
73
|
+
runId: string | null;
|
|
74
|
+
kind: EvidenceRunLink["kind"];
|
|
75
|
+
confidence: EvidenceLinkConfidence;
|
|
76
|
+
candidateRunIds: string[];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Attribution context: one run's span, without the rest of its envelope. */
|
|
80
|
+
interface RunWindow {
|
|
81
|
+
runId: string;
|
|
82
|
+
startedAt: string;
|
|
83
|
+
endedAt: string | null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface LinkedSessionEntry {
|
|
87
|
+
sessionId: string;
|
|
88
|
+
link: SessionRunLink;
|
|
89
|
+
entry: SessionEntry;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface SessionLinkResult {
|
|
93
|
+
entries: LinkedSessionEntry[];
|
|
94
|
+
attemptedSessionIds: string[];
|
|
95
|
+
missingSessionIds: string[];
|
|
96
|
+
readErrors: string[];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
interface AuditLinkResult {
|
|
100
|
+
rows: EvidenceAuditLinkedRow[];
|
|
101
|
+
readErrors: string[];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Raw runs.json rows, kept unparsed so sibling-run attribution and strict source parsing share one read. */
|
|
105
|
+
interface RunLedgerRows {
|
|
106
|
+
rows: ReadonlyArray<unknown>;
|
|
107
|
+
path: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
type EvidenceSource = { kind: "run"; runId: string } | { kind: "session"; sessionId: string };
|
|
111
|
+
|
|
112
|
+
export async function buildEvidence(options: BuildEvidenceOptions): Promise<EvidenceBuildResult> {
|
|
113
|
+
if (options.runId !== undefined && options.sessionId !== undefined) {
|
|
114
|
+
throw new Error("build evidence accepts either runId or sessionId, not both");
|
|
115
|
+
}
|
|
116
|
+
if (options.runId === undefined && options.sessionId === undefined) {
|
|
117
|
+
throw new Error("build evidence requires runId or sessionId");
|
|
118
|
+
}
|
|
119
|
+
const source: EvidenceSource =
|
|
120
|
+
options.runId !== undefined
|
|
121
|
+
? { kind: "run", runId: options.runId }
|
|
122
|
+
: { kind: "session", sessionId: options.sessionId ?? "" };
|
|
123
|
+
const ledger = await readRunLedger(options.stateDir);
|
|
124
|
+
const envelopes = selectRunEnvelopes(ledger, source);
|
|
125
|
+
if (envelopes.length === 0) {
|
|
126
|
+
throw new Error(source.kind === "run" ? `run not found: ${source.runId}` : `session not found: ${source.sessionId}`);
|
|
127
|
+
}
|
|
128
|
+
const sorted = [...envelopes].sort(compareRuns);
|
|
129
|
+
const runSources: EvidenceRunSource[] = [];
|
|
130
|
+
for (const envelope of sorted) {
|
|
131
|
+
const receiptResult = await readReceipt(options.stateDir, envelope);
|
|
132
|
+
runSources.push({
|
|
133
|
+
envelope,
|
|
134
|
+
receipt: receiptResult.receipt,
|
|
135
|
+
receiptError: receiptResult.error,
|
|
136
|
+
receiptIntegrityFailed: receiptResult.integrityFailed,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const evidenceId =
|
|
141
|
+
source.kind === "run" ? `run-${sanitizeEvidenceId(source.runId)}` : `session-${sanitizeEvidenceId(source.sessionId)}`;
|
|
142
|
+
const gateDecisions: EvidenceGateDecisionsFile = {
|
|
143
|
+
version: EVIDENCE_VERSION,
|
|
144
|
+
evidenceId,
|
|
145
|
+
decisions: readGateDecisionArtifactsForRunIds(
|
|
146
|
+
new Set(runSources.map((item) => item.envelope.id)),
|
|
147
|
+
options.stateDir,
|
|
148
|
+
).map((entry) => entry.artifact),
|
|
149
|
+
};
|
|
150
|
+
const directory = evidenceDirectory(options.dataDir, evidenceId);
|
|
151
|
+
const sessionLinks = await linkSessionEntries(options.stateDir, source, runSources, ledger);
|
|
152
|
+
const auditLinks = await linkAuditRows(options.stateDir, source, runSources, sessionLinks);
|
|
153
|
+
const toolEventRows = toolEvents(runSources, sessionLinks, auditLinks);
|
|
154
|
+
const protectedArtifactsRaw = protectedArtifactsFile(sessionLinks);
|
|
155
|
+
const findings = buildFindings(runSources, sessionLinks, auditLinks, protectedArtifactsRaw);
|
|
156
|
+
// Export-boundary redaction (cold path): secret-shaped values are scrubbed
|
|
157
|
+
// from everything the bundle serializes: envelopes, receipts (including
|
|
158
|
+
// delegation toolCallLog arguments), tool-event previews, audit rows,
|
|
159
|
+
// protected-artifact records, and the rendered transcript. Raw local
|
|
160
|
+
// session files are untouched; the bundle is the boundary, and the
|
|
161
|
+
// overview's redactionCount keeps the bundle honest about its filtering.
|
|
162
|
+
const tally = createRedactionTally();
|
|
163
|
+
const redactedRunSources: EvidenceRunSource[] = runSources.map((item) => ({
|
|
164
|
+
...item,
|
|
165
|
+
envelope: redactSecretsDeep(item.envelope, tally),
|
|
166
|
+
receipt: item.receipt === null ? null : redactSecretsDeep(item.receipt, tally),
|
|
167
|
+
}));
|
|
168
|
+
const redactedToolEvents: EvidenceToolEvent[] = toolEventRows.map((event) => ({
|
|
169
|
+
...event,
|
|
170
|
+
...(event.argsPreview !== undefined ? { argsPreview: redactSecretsText(event.argsPreview, tally) } : {}),
|
|
171
|
+
...(event.resultPreview !== undefined ? { resultPreview: redactSecretsText(event.resultPreview, tally) } : {}),
|
|
172
|
+
}));
|
|
173
|
+
const redactedAuditLinks: AuditLinkResult = {
|
|
174
|
+
rows: auditLinks.rows.map((row) => redactSecretsDeep(row, tally)),
|
|
175
|
+
readErrors: auditLinks.readErrors,
|
|
176
|
+
};
|
|
177
|
+
const protectedArtifacts = redactSecretsDeep(protectedArtifactsRaw, tally);
|
|
178
|
+
const overview = buildOverview(
|
|
179
|
+
evidenceId,
|
|
180
|
+
source,
|
|
181
|
+
redactedRunSources,
|
|
182
|
+
findings,
|
|
183
|
+
sessionLinks,
|
|
184
|
+
redactedAuditLinks,
|
|
185
|
+
redactedToolEvents,
|
|
186
|
+
protectedArtifacts,
|
|
187
|
+
);
|
|
188
|
+
const transcript = redactSecretsText(renderTranscript(overview, redactedRunSources, sessionLinks), tally);
|
|
189
|
+
const finalOverview: EvidenceOverview = { ...overview, redactionCount: tally.count };
|
|
190
|
+
await writeEvidenceFiles(
|
|
191
|
+
directory,
|
|
192
|
+
finalOverview,
|
|
193
|
+
redactedRunSources,
|
|
194
|
+
findings,
|
|
195
|
+
sessionLinks,
|
|
196
|
+
redactedAuditLinks,
|
|
197
|
+
redactedToolEvents,
|
|
198
|
+
gateDecisions,
|
|
199
|
+
protectedArtifacts,
|
|
200
|
+
transcript,
|
|
201
|
+
);
|
|
202
|
+
return { evidenceId, directory, overview: finalOverview, findings };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function buildOverview(
|
|
206
|
+
evidenceId: string,
|
|
207
|
+
source: EvidenceOverview["source"],
|
|
208
|
+
runSources: ReadonlyArray<EvidenceRunSource>,
|
|
209
|
+
findings: ReadonlyArray<EvidenceFinding>,
|
|
210
|
+
sessionLinks: SessionLinkResult,
|
|
211
|
+
auditLinks: AuditLinkResult,
|
|
212
|
+
toolEventRows: ReadonlyArray<EvidenceToolEvent>,
|
|
213
|
+
protectedArtifacts: EvidenceProtectedArtifactsFile,
|
|
214
|
+
): EvidenceOverview {
|
|
215
|
+
const envelopes = runSources.map((item) => item.envelope);
|
|
216
|
+
const receipts = runSources.flatMap((item) => (item.receipt === null ? [] : [item.receipt]));
|
|
217
|
+
const toolStats = receipts.flatMap((receipt) => receipt.toolStats);
|
|
218
|
+
const runIds = envelopes.map((envelope) => envelope.id).sort(compareStrings);
|
|
219
|
+
const sessionIds = uniqueStrings(
|
|
220
|
+
envelopes.flatMap((envelope) => (envelope.sessionId === null ? [] : [envelope.sessionId])),
|
|
221
|
+
);
|
|
222
|
+
const startedAt = earliest(envelopes.map((envelope) => envelope.startedAt));
|
|
223
|
+
const endedAt = latest(envelopes.flatMap((envelope) => (envelope.endedAt === null ? [] : [envelope.endedAt])));
|
|
224
|
+
const generatedAt = latest(
|
|
225
|
+
envelopes.flatMap((envelope) => [envelope.startedAt, ...(envelope.endedAt === null ? [] : [envelope.endedAt])]),
|
|
226
|
+
);
|
|
227
|
+
const wallTimeMs = envelopes.reduce((total, envelope) => total + durationMs(envelope.startedAt, envelope.endedAt), 0);
|
|
228
|
+
return {
|
|
229
|
+
version: EVIDENCE_VERSION,
|
|
230
|
+
evidenceId,
|
|
231
|
+
source,
|
|
232
|
+
generatedAt: generatedAt ?? startedAt ?? "1970-01-01T00:00:00.000Z",
|
|
233
|
+
runIds,
|
|
234
|
+
sessionId: source.kind === "session" ? source.sessionId : (sessionIds[0] ?? null),
|
|
235
|
+
statuses: uniqueStrings(envelopes.map((envelope) => envelope.status)),
|
|
236
|
+
startedAt,
|
|
237
|
+
endedAt,
|
|
238
|
+
tasks: uniqueStrings(envelopes.map((envelope) => truncateText(envelope.task, MAX_TASK_CHARS))),
|
|
239
|
+
cwds: uniqueStrings(envelopes.map((envelope) => envelope.cwd)),
|
|
240
|
+
agentIds: uniqueStrings(envelopes.map((envelope) => envelope.agentId)),
|
|
241
|
+
targetIds: uniqueStrings(envelopes.map((envelope) => envelope.targetId)),
|
|
242
|
+
runtimeIds: uniqueStrings(envelopes.map((envelope) => envelope.runtimeId)),
|
|
243
|
+
modelIds: uniqueStrings(envelopes.map((envelope) => envelope.wireModelId)),
|
|
244
|
+
totals: {
|
|
245
|
+
runs: envelopes.length,
|
|
246
|
+
receipts: receipts.length,
|
|
247
|
+
toolCalls: receipts.reduce((total, receipt) => total + receipt.toolCalls, 0),
|
|
248
|
+
toolErrors: toolStats.reduce((total, stat) => total + stat.errors, 0),
|
|
249
|
+
blockedToolCalls: toolStats.reduce((total, stat) => total + stat.blocked, 0),
|
|
250
|
+
sessionEntries: sessionLinks.entries.length,
|
|
251
|
+
auditRows: auditLinks.rows.length,
|
|
252
|
+
toolEvents: toolEventRows.length,
|
|
253
|
+
linkedToolEvents: toolEventRows.filter((event) => event.source !== "receipt-aggregate").length,
|
|
254
|
+
protectedArtifacts: protectedArtifacts.artifacts.length,
|
|
255
|
+
tokens: receipts.reduce((total, receipt) => total + receipt.tokenCount, 0),
|
|
256
|
+
costUsd: receipts.reduce((total, receipt) => total + receipt.costUsd, 0),
|
|
257
|
+
wallTimeMs,
|
|
258
|
+
},
|
|
259
|
+
tags: uniqueTags(findings.map((finding) => finding.tag)),
|
|
260
|
+
files: [...EVIDENCE_FILES],
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function buildFindings(
|
|
265
|
+
runSources: ReadonlyArray<EvidenceRunSource>,
|
|
266
|
+
sessionLinks: SessionLinkResult,
|
|
267
|
+
auditLinks: AuditLinkResult,
|
|
268
|
+
protectedArtifacts: EvidenceProtectedArtifactsFile,
|
|
269
|
+
): EvidenceFinding[] {
|
|
270
|
+
const findings: EvidenceFinding[] = [];
|
|
271
|
+
const validationRunIds = validationEvidenceRunIds(sessionLinks.entries, runSources, auditLinks.rows);
|
|
272
|
+
for (const source of runSources) {
|
|
273
|
+
if (source.receiptError !== null) {
|
|
274
|
+
const tag = source.receiptIntegrityFailed ? "receipt-integrity" : "unknown";
|
|
275
|
+
findings.push(finding(findings.length, "warn", tag, source.envelope.id, source.receiptError));
|
|
276
|
+
}
|
|
277
|
+
if (isSuccessfulRun(source) && !validationRunIds.has(source.envelope.id)) {
|
|
278
|
+
findings.push(
|
|
279
|
+
finding(
|
|
280
|
+
findings.length,
|
|
281
|
+
"warn",
|
|
282
|
+
"no-validation",
|
|
283
|
+
source.envelope.id,
|
|
284
|
+
"run completed without linked validation evidence",
|
|
285
|
+
),
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
if (source.envelope.cwd.trim().length === 0) {
|
|
289
|
+
findings.push(finding(findings.length, "warn", "cwd-missing", source.envelope.id, "run ledger cwd is empty"));
|
|
290
|
+
}
|
|
291
|
+
const receipt = source.receipt;
|
|
292
|
+
const blocked = receipt?.toolStats.reduce((total, stat) => total + stat.blocked, 0) ?? 0;
|
|
293
|
+
if (blocked > 0) {
|
|
294
|
+
findings.push(
|
|
295
|
+
finding(findings.length, "warn", "blocked-tool", source.envelope.id, `${blocked} blocked tool call(s)`),
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
// An escalation that timed out or was denied is an unresolved permission
|
|
299
|
+
// ask; surface it the way blocked tool calls are surfaced above. Approved
|
|
300
|
+
// escalations resolved cleanly and need no finding.
|
|
301
|
+
const escalation = receipt === null ? undefined : extractRunProvenance(receipt).escalation;
|
|
302
|
+
if (escalation !== undefined && (escalation.timedOut > 0 || escalation.denied > 0)) {
|
|
303
|
+
findings.push(
|
|
304
|
+
finding(
|
|
305
|
+
findings.length,
|
|
306
|
+
"warn",
|
|
307
|
+
"escalation",
|
|
308
|
+
source.envelope.id,
|
|
309
|
+
`escalations unresolved: ${escalation.timedOut} timed out, ${escalation.denied} denied`,
|
|
310
|
+
),
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
const autonomyEnforcement = receipt?.autonomyEnforcement;
|
|
314
|
+
if (autonomyEnforcement?.dangerousBypass === true) {
|
|
315
|
+
const message =
|
|
316
|
+
autonomyEnforcement.externalMode === "agent-managed"
|
|
317
|
+
? "run used external agent-managed governance; Clio safety blocks were not enforced"
|
|
318
|
+
: "run executed with external permission bypass (CLIO_CODER_ALLOW_EXTERNAL_FULL_ACCESS=1); Clio safety blocks were not enforced";
|
|
319
|
+
findings.push(finding(findings.length, "warn", "external-bypass", source.envelope.id, message));
|
|
320
|
+
} else if (autonomyEnforcement?.grade === "approximated") {
|
|
321
|
+
const mode = autonomyEnforcement.externalMode ? ` via ${autonomyEnforcement.externalMode}` : "";
|
|
322
|
+
findings.push(
|
|
323
|
+
finding(
|
|
324
|
+
findings.length,
|
|
325
|
+
"info",
|
|
326
|
+
"external-approximation",
|
|
327
|
+
source.envelope.id,
|
|
328
|
+
`run used approximated external autonomy enforcement${mode}`,
|
|
329
|
+
),
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
if (source.envelope.status === "stale" || source.envelope.status === "dead") {
|
|
333
|
+
findings.push(
|
|
334
|
+
finding(findings.length, "warn", "timeout", source.envelope.id, `run status ${source.envelope.status}`),
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
const exitCode = receipt?.exitCode ?? source.envelope.exitCode;
|
|
338
|
+
if (exitCode !== null && exitCode !== 0) {
|
|
339
|
+
const tag = attributeEvidenceFailure({
|
|
340
|
+
outcome: receipt?.outcome ?? source.envelope.outcome ?? null,
|
|
341
|
+
outcomeCode: receipt?.outcomeCode ?? source.envelope.outcomeCode ?? null,
|
|
342
|
+
outcomeDetail: receipt?.outcomeDetail ?? source.envelope.outcomeDetail ?? null,
|
|
343
|
+
failureMessage: receipt?.failureMessage ?? null,
|
|
344
|
+
});
|
|
345
|
+
findings.push(finding(findings.length, "warn", tag, source.envelope.id, `run exited with code ${exitCode}`));
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (sessionLinks.entries.length > 0) {
|
|
349
|
+
findings.push(
|
|
350
|
+
finding(findings.length, "info", "session-linked", null, `${sessionLinks.entries.length} session entry(s) linked`),
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
for (const sessionId of sessionLinks.missingSessionIds) {
|
|
354
|
+
findings.push(
|
|
355
|
+
finding(findings.length, "warn", "session-missing", null, `session entries not found for ${sessionId}`),
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
for (const error of sessionLinks.readErrors) {
|
|
359
|
+
findings.push(finding(findings.length, "warn", "session-missing", null, error));
|
|
360
|
+
}
|
|
361
|
+
if (auditLinks.rows.length > 0) {
|
|
362
|
+
findings.push(
|
|
363
|
+
finding(findings.length, "info", "audit-linked", null, `${auditLinks.rows.length} audit row(s) linked`),
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
if (protectedArtifacts.artifacts.length > 0) {
|
|
367
|
+
findings.push(
|
|
368
|
+
finding(
|
|
369
|
+
findings.length,
|
|
370
|
+
"info",
|
|
371
|
+
"protected-artifact",
|
|
372
|
+
null,
|
|
373
|
+
`${protectedArtifacts.artifacts.length} protected artifact(s) persisted`,
|
|
374
|
+
),
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
const ambiguousEntries = sessionLinks.entries.filter(
|
|
378
|
+
(linked) => linked.link.kind === "ambiguous-timestamp-window",
|
|
379
|
+
).length;
|
|
380
|
+
if (ambiguousEntries > 0) {
|
|
381
|
+
findings.push(
|
|
382
|
+
finding(
|
|
383
|
+
findings.length,
|
|
384
|
+
"info",
|
|
385
|
+
"best-effort-link",
|
|
386
|
+
null,
|
|
387
|
+
`${ambiguousEntries} session entry(s) fell inside more than one concurrent run window`,
|
|
388
|
+
),
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
const bestEffortAuditRows = auditLinks.rows.filter((row) => row.confidence === "best-effort").length;
|
|
392
|
+
if (bestEffortAuditRows > 0) {
|
|
393
|
+
findings.push(
|
|
394
|
+
finding(
|
|
395
|
+
findings.length,
|
|
396
|
+
"info",
|
|
397
|
+
"best-effort-link",
|
|
398
|
+
null,
|
|
399
|
+
`${bestEffortAuditRows} audit row(s) linked with best-effort metadata`,
|
|
400
|
+
),
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
for (const error of auditLinks.readErrors) {
|
|
404
|
+
findings.push(finding(findings.length, "warn", "audit-missing", null, error));
|
|
405
|
+
}
|
|
406
|
+
return findings;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
interface ValidationToolCallCandidate {
|
|
410
|
+
toolCallId: string;
|
|
411
|
+
runId: string | null;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function validationEvidenceRunIds(
|
|
415
|
+
entries: ReadonlyArray<LinkedSessionEntry>,
|
|
416
|
+
runSources: ReadonlyArray<EvidenceRunSource>,
|
|
417
|
+
auditRows: ReadonlyArray<EvidenceAuditLinkedRow>,
|
|
418
|
+
): Set<string> {
|
|
419
|
+
const runIds = new Set<string>();
|
|
420
|
+
const sourceRunIds = new Set(runSources.map((source) => source.envelope.id));
|
|
421
|
+
const calls = new Map<string, ValidationToolCallCandidate>();
|
|
422
|
+
for (const linked of entries) {
|
|
423
|
+
const runId = validationRunIdFor(linked, runSources);
|
|
424
|
+
const entry = linked.entry;
|
|
425
|
+
if (entry.kind === "bashExecution") {
|
|
426
|
+
if (runId !== null && isSuccessfulValidationBashExecution(entry)) runIds.add(runId);
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
if (entry.kind === "protectedArtifact") {
|
|
430
|
+
if (runId !== null && isValidationProtectedArtifact(entry)) runIds.add(runId);
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
if (entry.kind !== "message") continue;
|
|
434
|
+
if (entry.role === "tool_call") {
|
|
435
|
+
const candidate = validationToolCallCandidate(entry, linked, runSources);
|
|
436
|
+
if (candidate !== null) calls.set(candidate.toolCallId, candidate);
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
if (entry.role !== "tool_result") continue;
|
|
440
|
+
const result = extractSessionToolResult(entry, linked);
|
|
441
|
+
if (result.id === null || !isSuccessfulSessionToolResult(result)) continue;
|
|
442
|
+
const candidate = calls.get(result.id);
|
|
443
|
+
if (candidate?.runId !== null && candidate?.runId !== undefined) runIds.add(candidate.runId);
|
|
444
|
+
}
|
|
445
|
+
for (const linked of auditRows) {
|
|
446
|
+
if (linked.auditKind !== "completion_contract") continue;
|
|
447
|
+
if (linked.runId === null || !sourceRunIds.has(linked.runId)) continue;
|
|
448
|
+
if (readOptionalString(linked.row.decision) !== "ok") continue;
|
|
449
|
+
if (readOptionalString(linked.row.reason) !== "validation_evidence") continue;
|
|
450
|
+
runIds.add(linked.runId);
|
|
451
|
+
}
|
|
452
|
+
return runIds;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function validationRunIdFor(linked: LinkedSessionEntry, runSources: ReadonlyArray<EvidenceRunSource>): string | null {
|
|
456
|
+
if (linked.link.runId !== null) return linked.link.runId;
|
|
457
|
+
// A link that names no run leaves the bundle's scope to decide: a single-run
|
|
458
|
+
// bundle credits its run, a multi-run bundle credits nobody, because an
|
|
459
|
+
// entry two concurrent runs could have produced proves neither validated.
|
|
460
|
+
if (runSources.length !== 1) return null;
|
|
461
|
+
return runSources[0]?.envelope.id ?? null;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function isSuccessfulRun(source: EvidenceRunSource): boolean {
|
|
465
|
+
const outcome = source.receipt?.outcome ?? source.envelope.outcome ?? null;
|
|
466
|
+
if (outcome !== null) return outcome === "succeeded";
|
|
467
|
+
const exitCode = source.receipt?.exitCode ?? source.envelope.exitCode;
|
|
468
|
+
return source.envelope.status === "completed" && exitCode === 0;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function isSuccessfulValidationBashExecution(entry: BashExecutionEntry): boolean {
|
|
472
|
+
if (entry.cancelled || entry.exitCode !== 0) return false;
|
|
473
|
+
return detectValidationCommand(entry.command).kind === "validation";
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function isValidationProtectedArtifact(entry: ProtectedArtifactEntry): boolean {
|
|
477
|
+
const artifact = protectedArtifactFromSessionEntry(entry);
|
|
478
|
+
if (artifact.validationCommand === undefined || artifact.validationExitCode !== 0) return false;
|
|
479
|
+
return detectValidationCommand(artifact.validationCommand).kind === "validation";
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function validationToolCallCandidate(
|
|
483
|
+
entry: MessageEntry,
|
|
484
|
+
linked: LinkedSessionEntry,
|
|
485
|
+
runSources: ReadonlyArray<EvidenceRunSource>,
|
|
486
|
+
): ValidationToolCallCandidate | null {
|
|
487
|
+
const call = extractSessionToolCall(entry, linked);
|
|
488
|
+
if (validationToolCallSummary(call.tool, call.args) === null) return null;
|
|
489
|
+
return {
|
|
490
|
+
toolCallId: call.id,
|
|
491
|
+
runId: validationRunIdFor(linked, runSources),
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function validationToolCallSummary(toolName: string, args: unknown): string | null {
|
|
496
|
+
const parsed = parseMaybeJson(args);
|
|
497
|
+
const argRecord = isRecord(parsed) ? parsed : null;
|
|
498
|
+
if (toolName === "bash") {
|
|
499
|
+
const command = readOptionalString(argRecord?.command);
|
|
500
|
+
if (command === null) return null;
|
|
501
|
+
const detected = detectValidationCommand(command);
|
|
502
|
+
return detected.kind === "validation" ? detected.matched : null;
|
|
503
|
+
}
|
|
504
|
+
if (toolName === "verify") {
|
|
505
|
+
const check = readOptionalString(argRecord?.check);
|
|
506
|
+
if (check === "frontend") {
|
|
507
|
+
const target = readOptionalString(argRecord?.path) ?? "artifact";
|
|
508
|
+
return `verify frontend ${target}`;
|
|
509
|
+
}
|
|
510
|
+
if (check === null || !isVerificationScriptName(check)) return null;
|
|
511
|
+
return `npm run ${check}`;
|
|
512
|
+
}
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function isSuccessfulSessionToolResult(result: SessionToolResult): boolean {
|
|
517
|
+
if (result.isError) return false;
|
|
518
|
+
const obj = isRecord(result.result) ? result.result : null;
|
|
519
|
+
const details = isRecord(obj?.details) ? obj.details : null;
|
|
520
|
+
return details?.kind !== "error";
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function finding(
|
|
524
|
+
index: number,
|
|
525
|
+
severity: EvidenceFinding["severity"],
|
|
526
|
+
tag: EvidenceTag,
|
|
527
|
+
runId: string | null,
|
|
528
|
+
message: string,
|
|
529
|
+
): EvidenceFinding {
|
|
530
|
+
return {
|
|
531
|
+
id: `finding-${String(index + 1).padStart(3, "0")}`,
|
|
532
|
+
severity,
|
|
533
|
+
tag,
|
|
534
|
+
runId,
|
|
535
|
+
message,
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
async function writeEvidenceFiles(
|
|
540
|
+
directory: string,
|
|
541
|
+
overview: EvidenceOverview,
|
|
542
|
+
runSources: ReadonlyArray<EvidenceRunSource>,
|
|
543
|
+
findings: ReadonlyArray<EvidenceFinding>,
|
|
544
|
+
sessionLinks: SessionLinkResult,
|
|
545
|
+
auditLinks: AuditLinkResult,
|
|
546
|
+
toolEventRows: ReadonlyArray<EvidenceToolEvent>,
|
|
547
|
+
gateDecisions: EvidenceGateDecisionsFile,
|
|
548
|
+
protectedArtifacts: EvidenceProtectedArtifactsFile,
|
|
549
|
+
transcript?: string,
|
|
550
|
+
): Promise<void> {
|
|
551
|
+
await mkdir(directory, { recursive: true });
|
|
552
|
+
await writeJson(join(directory, "overview.json"), overview);
|
|
553
|
+
await writeFile(
|
|
554
|
+
join(directory, "transcript.md"),
|
|
555
|
+
transcript ?? renderTranscript(overview, runSources, sessionLinks),
|
|
556
|
+
"utf8",
|
|
557
|
+
);
|
|
558
|
+
await writeJsonl(join(directory, "trace.raw.jsonl"), rawTraceRows(runSources));
|
|
559
|
+
await writeJsonl(join(directory, "trace.cleaned.jsonl"), cleanedTraceRows(runSources, findings, toolEventRows));
|
|
560
|
+
await writeJsonl(join(directory, "tool-events.jsonl"), toolEventRows);
|
|
561
|
+
await writeJsonl(join(directory, "audit-linked.jsonl"), auditLinks.rows);
|
|
562
|
+
await writeJson(join(directory, "receipt.json"), receiptsFile(runSources));
|
|
563
|
+
await writeJson(join(directory, "gate-decisions.json"), gateDecisions);
|
|
564
|
+
await writeJson(join(directory, "protected-artifacts.json"), protectedArtifacts);
|
|
565
|
+
await writeJson(join(directory, "findings.json"), findingsFile(overview.evidenceId, [...findings]));
|
|
566
|
+
await writeFile(join(directory, "findings.md"), renderFindings(findings), "utf8");
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function rawTraceRows(runSources: ReadonlyArray<EvidenceRunSource>): EvidenceRawTraceRow[] {
|
|
570
|
+
const rows: EvidenceRawTraceRow[] = [];
|
|
571
|
+
for (const source of runSources) {
|
|
572
|
+
rows.push({ kind: "run-ledger", runId: source.envelope.id, envelope: source.envelope });
|
|
573
|
+
if (source.receipt !== null) rows.push({ kind: "receipt", runId: source.envelope.id, receipt: source.receipt });
|
|
574
|
+
else if (source.receiptError !== null) {
|
|
575
|
+
rows.push({ kind: "receipt-error", runId: source.envelope.id, error: source.receiptError });
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
return rows;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function cleanedTraceRows(
|
|
582
|
+
runSources: ReadonlyArray<EvidenceRunSource>,
|
|
583
|
+
findings: ReadonlyArray<EvidenceFinding>,
|
|
584
|
+
toolEventRows: ReadonlyArray<EvidenceToolEvent>,
|
|
585
|
+
): EvidenceCleanTraceRow[] {
|
|
586
|
+
const rows: EvidenceCleanTraceRow[] = [];
|
|
587
|
+
for (const source of runSources) {
|
|
588
|
+
const envelope = source.envelope;
|
|
589
|
+
// Clean trace rows project only provenance carried by the sealed receipt.
|
|
590
|
+
// Runs without receipt provenance keep the standard row shape.
|
|
591
|
+
const provenance = source.receipt === null ? {} : extractRunProvenance(source.receipt);
|
|
592
|
+
rows.push({
|
|
593
|
+
kind: "run",
|
|
594
|
+
runId: envelope.id,
|
|
595
|
+
task: truncateText(envelope.task, MAX_TASK_CHARS),
|
|
596
|
+
status: envelope.status,
|
|
597
|
+
exitCode: source.receipt?.exitCode ?? envelope.exitCode,
|
|
598
|
+
startedAt: envelope.startedAt,
|
|
599
|
+
endedAt: envelope.endedAt,
|
|
600
|
+
wallTimeMs: durationMs(envelope.startedAt, envelope.endedAt),
|
|
601
|
+
cwd: envelope.cwd,
|
|
602
|
+
agentId: envelope.agentId,
|
|
603
|
+
targetId: envelope.targetId,
|
|
604
|
+
runtimeId: envelope.runtimeId,
|
|
605
|
+
wireModelId: envelope.wireModelId,
|
|
606
|
+
tokenCount: source.receipt?.tokenCount ?? envelope.tokenCount,
|
|
607
|
+
costUsd: source.receipt?.costUsd ?? envelope.costUsd,
|
|
608
|
+
...(provenance.pipeline !== undefined ? { pipeline: provenance.pipeline } : {}),
|
|
609
|
+
...(provenance.personaOverride !== undefined ? { personaOverride: provenance.personaOverride } : {}),
|
|
610
|
+
...(provenance.escalation !== undefined ? { escalation: provenance.escalation } : {}),
|
|
611
|
+
...(provenance.autonomyEnforcement !== undefined ? { autonomyEnforcement: provenance.autonomyEnforcement } : {}),
|
|
612
|
+
});
|
|
613
|
+
for (const event of toolEventRows.filter((item) => item.runId === source.envelope.id)) {
|
|
614
|
+
rows.push({ kind: "tool-summary", ...event });
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
// Everything the per-run grouping above did not claim, so an event whose run
|
|
618
|
+
// attribution is ambiguous or points outside this bundle still appears once.
|
|
619
|
+
const groupedRunIds = new Set(runSources.map((item) => item.envelope.id));
|
|
620
|
+
for (const event of toolEventRows.filter((item) => item.runId === null || !groupedRunIds.has(item.runId))) {
|
|
621
|
+
rows.push({ kind: "tool-summary", ...event });
|
|
622
|
+
}
|
|
623
|
+
for (const item of findings) rows.push({ kind: "finding", ...item });
|
|
624
|
+
return rows;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function toolEvents(
|
|
628
|
+
runSources: ReadonlyArray<EvidenceRunSource>,
|
|
629
|
+
sessionLinks: SessionLinkResult,
|
|
630
|
+
auditLinks: AuditLinkResult,
|
|
631
|
+
): EvidenceToolEvent[] {
|
|
632
|
+
const fromSession = sessionToolEvents(sessionLinks.entries);
|
|
633
|
+
if (fromSession.length > 0) return fromSession;
|
|
634
|
+
const fromAudit = auditToolEvents(auditLinks.rows);
|
|
635
|
+
if (fromAudit.length > 0) return fromAudit;
|
|
636
|
+
return receiptAggregateToolEvents(runSources);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function receiptAggregateToolEvents(runSources: ReadonlyArray<EvidenceRunSource>): EvidenceToolEvent[] {
|
|
640
|
+
const events: EvidenceToolEvent[] = [];
|
|
641
|
+
for (const source of runSources) {
|
|
642
|
+
const stats = source.receipt?.toolStats ?? [];
|
|
643
|
+
for (const stat of [...stats].sort(compareToolStats)) {
|
|
644
|
+
events.push({
|
|
645
|
+
source: "receipt-aggregate",
|
|
646
|
+
runId: source.envelope.id,
|
|
647
|
+
sessionId: source.envelope.sessionId,
|
|
648
|
+
tool: stat.tool,
|
|
649
|
+
count: stat.count,
|
|
650
|
+
ok: stat.ok,
|
|
651
|
+
errors: stat.errors,
|
|
652
|
+
blocked: stat.blocked,
|
|
653
|
+
totalDurationMs: stat.totalDurationMs,
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
return events;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
async function linkSessionEntries(
|
|
661
|
+
stateDir: string,
|
|
662
|
+
source: EvidenceOverview["source"],
|
|
663
|
+
runSources: ReadonlyArray<EvidenceRunSource>,
|
|
664
|
+
ledger: RunLedgerRows,
|
|
665
|
+
): Promise<SessionLinkResult> {
|
|
666
|
+
const attemptedSessionIds = sourceSessionIds(source, runSources);
|
|
667
|
+
const windows = attributionWindows(ledger, runSources, attemptedSessionIds);
|
|
668
|
+
const result: SessionLinkResult = {
|
|
669
|
+
entries: [],
|
|
670
|
+
attemptedSessionIds,
|
|
671
|
+
missingSessionIds: [],
|
|
672
|
+
readErrors: [],
|
|
673
|
+
};
|
|
674
|
+
for (const sessionId of attemptedSessionIds) {
|
|
675
|
+
const read = await readSessionEntriesForId(stateDir, sessionId);
|
|
676
|
+
if (read.missing) {
|
|
677
|
+
result.missingSessionIds.push(sessionId);
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
result.readErrors.push(...read.errors);
|
|
681
|
+
for (const entry of read.entries) {
|
|
682
|
+
const link = linkSessionEntry(entry, windows);
|
|
683
|
+
if (source.kind === "run" && !linkCoversRun(link, source.runId)) continue;
|
|
684
|
+
result.entries.push({ sessionId, link, entry });
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
result.entries.sort(compareLinkedSessionEntries);
|
|
688
|
+
return result;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* An ambiguous entry belongs in the bundle of every run it may have come from.
|
|
693
|
+
* Requiring an exact match is how coverage disappeared in proportion to how
|
|
694
|
+
* parallel the session was.
|
|
695
|
+
*/
|
|
696
|
+
function linkCoversRun(link: SessionRunLink, runId: string): boolean {
|
|
697
|
+
return link.runId === runId || link.candidateRunIds.includes(runId);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function sourceSessionIds(source: EvidenceOverview["source"], runSources: ReadonlyArray<EvidenceRunSource>): string[] {
|
|
701
|
+
if (source.kind === "session") return [source.sessionId];
|
|
702
|
+
const values: string[] = [];
|
|
703
|
+
for (const item of runSources) {
|
|
704
|
+
if (item.envelope.sessionId !== null) values.push(item.envelope.sessionId);
|
|
705
|
+
if (item.receipt?.sessionId !== null && item.receipt?.sessionId !== undefined) values.push(item.receipt.sessionId);
|
|
706
|
+
}
|
|
707
|
+
return uniqueStrings(values);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function linkSessionEntry(entry: SessionEntry, windows: ReadonlyArray<RunWindow>): SessionRunLink {
|
|
711
|
+
const stamped = writeTimeRunId(entry);
|
|
712
|
+
if (stamped !== null) {
|
|
713
|
+
return { runId: stamped, kind: "entry-run-id", confidence: "exact", candidateRunIds: [stamped] };
|
|
714
|
+
}
|
|
715
|
+
const candidates = windows
|
|
716
|
+
.filter((window) => timestampInRunWindow(entry.timestamp, window))
|
|
717
|
+
.map((window) => window.runId)
|
|
718
|
+
.sort(compareStrings);
|
|
719
|
+
const only = candidates.length === 1 ? candidates[0] : undefined;
|
|
720
|
+
if (only !== undefined) {
|
|
721
|
+
return { runId: only, kind: "timestamp-window", confidence: "best-effort", candidateRunIds: candidates };
|
|
722
|
+
}
|
|
723
|
+
if (candidates.length > 1) {
|
|
724
|
+
return { runId: null, kind: "ambiguous-timestamp-window", confidence: "best-effort", candidateRunIds: candidates };
|
|
725
|
+
}
|
|
726
|
+
return { runId: null, kind: "no-run-window", confidence: "best-effort", candidateRunIds: [] };
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Attribution the producer wrote down. Protected-artifact entries carry the
|
|
731
|
+
* run id of the dispatch that registered them, so they never need the clock.
|
|
732
|
+
* Every other entry kind is written without run context and falls back to
|
|
733
|
+
* timestamp windowing.
|
|
734
|
+
*/
|
|
735
|
+
function writeTimeRunId(entry: SessionEntry): string | null {
|
|
736
|
+
return entry.kind === "protectedArtifact" ? (entry.runId ?? null) : null;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Windows for every run that may have produced an entry in the linked
|
|
741
|
+
* sessions, not just the runs the bundle reports on. A run-scoped bundle that
|
|
742
|
+
* only knows its own window cannot tell an entry it produced from one a
|
|
743
|
+
* concurrent sibling produced, and claims both. These windows are attribution
|
|
744
|
+
* context; they never become run sources.
|
|
745
|
+
*/
|
|
746
|
+
function attributionWindows(
|
|
747
|
+
ledger: RunLedgerRows,
|
|
748
|
+
runSources: ReadonlyArray<EvidenceRunSource>,
|
|
749
|
+
sessionIds: ReadonlyArray<string>,
|
|
750
|
+
): RunWindow[] {
|
|
751
|
+
const windows = new Map<string, RunWindow>();
|
|
752
|
+
for (const source of runSources) {
|
|
753
|
+
windows.set(source.envelope.id, {
|
|
754
|
+
runId: source.envelope.id,
|
|
755
|
+
startedAt: source.envelope.startedAt,
|
|
756
|
+
endedAt: source.envelope.endedAt,
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
const linkedSessions = new Set(sessionIds);
|
|
760
|
+
// Sibling rows are read leniently: they are context, not the bundle's
|
|
761
|
+
// subject, so a retired envelope shape must not fail the build.
|
|
762
|
+
for (const row of ledger.rows) {
|
|
763
|
+
if (!isRecord(row)) continue;
|
|
764
|
+
const id = readOptionalString(row.id);
|
|
765
|
+
const startedAt = readOptionalString(row.startedAt);
|
|
766
|
+
const sessionId = readOptionalString(row.sessionId);
|
|
767
|
+
if (id === null || startedAt === null || sessionId === null) continue;
|
|
768
|
+
if (windows.has(id) || !linkedSessions.has(sessionId)) continue;
|
|
769
|
+
windows.set(id, { runId: id, startedAt, endedAt: readOptionalString(row.endedAt) });
|
|
770
|
+
}
|
|
771
|
+
return [...windows.values()].sort((left, right) => compareStrings(left.runId, right.runId));
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/** Run-attribution provenance as an exported row carries it. */
|
|
775
|
+
function evidenceRunLink(link: SessionRunLink): EvidenceRunLink {
|
|
776
|
+
return {
|
|
777
|
+
kind: link.kind,
|
|
778
|
+
confidence: link.confidence,
|
|
779
|
+
...(link.candidateRunIds.length > 1 ? { candidateRunIds: link.candidateRunIds } : {}),
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
function timestampInRunWindow(timestamp: string, envelope: { startedAt: string; endedAt: string | null }): boolean {
|
|
784
|
+
const value = Date.parse(timestamp);
|
|
785
|
+
const start = Date.parse(envelope.startedAt);
|
|
786
|
+
const end = envelope.endedAt === null ? Number.POSITIVE_INFINITY : Date.parse(envelope.endedAt);
|
|
787
|
+
if (!Number.isFinite(value) || !Number.isFinite(start) || !Number.isFinite(end)) return false;
|
|
788
|
+
return value >= start && value <= end;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
function compareLinkedSessionEntries(a: LinkedSessionEntry, b: LinkedSessionEntry): number {
|
|
792
|
+
const byTimestamp = compareStrings(a.entry.timestamp, b.entry.timestamp);
|
|
793
|
+
if (byTimestamp !== 0) return byTimestamp;
|
|
794
|
+
const bySession = compareStrings(a.sessionId, b.sessionId);
|
|
795
|
+
if (bySession !== 0) return bySession;
|
|
796
|
+
return compareStrings(a.entry.turnId, b.entry.turnId);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
interface SessionToolCall {
|
|
800
|
+
id: string;
|
|
801
|
+
tool: string;
|
|
802
|
+
args: unknown;
|
|
803
|
+
timestamp: string;
|
|
804
|
+
link: SessionRunLink;
|
|
805
|
+
sessionId: string;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
interface SessionToolResult {
|
|
809
|
+
id: string | null;
|
|
810
|
+
tool: string;
|
|
811
|
+
result: unknown;
|
|
812
|
+
isError: boolean;
|
|
813
|
+
timestamp: string;
|
|
814
|
+
link: SessionRunLink;
|
|
815
|
+
sessionId: string;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
function sessionToolEvents(entries: ReadonlyArray<LinkedSessionEntry>): EvidenceToolEvent[] {
|
|
819
|
+
const calls = new Map<string, SessionToolCall>();
|
|
820
|
+
const pendingIds: string[] = [];
|
|
821
|
+
const pairedIds = new Set<string>();
|
|
822
|
+
const events: EvidenceToolEvent[] = [];
|
|
823
|
+
for (const linked of entries) {
|
|
824
|
+
const entry = linked.entry;
|
|
825
|
+
if (entry.kind === "bashExecution") {
|
|
826
|
+
events.push({
|
|
827
|
+
source: "session-entry",
|
|
828
|
+
runId: linked.link.runId,
|
|
829
|
+
runLink: evidenceRunLink(linked.link),
|
|
830
|
+
sessionId: linked.sessionId,
|
|
831
|
+
tool: "bash",
|
|
832
|
+
count: 1,
|
|
833
|
+
ok: entry.cancelled || (entry.exitCode !== null && entry.exitCode !== 0) ? 0 : 1,
|
|
834
|
+
errors: entry.cancelled || (entry.exitCode !== null && entry.exitCode !== 0) ? 1 : 0,
|
|
835
|
+
blocked: 0,
|
|
836
|
+
totalDurationMs: 0,
|
|
837
|
+
timestamp: entry.timestamp,
|
|
838
|
+
linkKind: "session-bash-execution",
|
|
839
|
+
confidence: "exact",
|
|
840
|
+
argsPreview: truncateText(entry.command, TOOL_PREVIEW_MAX_CHARS),
|
|
841
|
+
resultPreview: truncateText(entry.output, TOOL_PREVIEW_MAX_CHARS),
|
|
842
|
+
});
|
|
843
|
+
continue;
|
|
844
|
+
}
|
|
845
|
+
if (entry.kind !== "message") continue;
|
|
846
|
+
if (entry.role === "tool_call") {
|
|
847
|
+
const call = extractSessionToolCall(entry, linked);
|
|
848
|
+
calls.set(call.id, call);
|
|
849
|
+
pendingIds.push(call.id);
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
if (entry.role !== "tool_result") continue;
|
|
853
|
+
const result = extractSessionToolResult(entry, linked);
|
|
854
|
+
const fallbackId = result.id ?? pendingIds.pop() ?? null;
|
|
855
|
+
const call = fallbackId === null ? undefined : calls.get(fallbackId);
|
|
856
|
+
if (fallbackId !== null) {
|
|
857
|
+
const pendingIndex = pendingIds.indexOf(fallbackId);
|
|
858
|
+
if (pendingIndex >= 0) pendingIds.splice(pendingIndex, 1);
|
|
859
|
+
pairedIds.add(fallbackId);
|
|
860
|
+
}
|
|
861
|
+
const tool = call?.tool ?? result.tool;
|
|
862
|
+
const link = call?.link ?? result.link;
|
|
863
|
+
events.push({
|
|
864
|
+
source: "session-entry",
|
|
865
|
+
runId: link.runId,
|
|
866
|
+
runLink: evidenceRunLink(link),
|
|
867
|
+
sessionId: call?.sessionId ?? result.sessionId,
|
|
868
|
+
tool,
|
|
869
|
+
count: 1,
|
|
870
|
+
ok: result.isError ? 0 : 1,
|
|
871
|
+
errors: result.isError ? 1 : 0,
|
|
872
|
+
blocked: 0,
|
|
873
|
+
totalDurationMs: 0,
|
|
874
|
+
timestamp: call?.timestamp ?? result.timestamp,
|
|
875
|
+
...(fallbackId === null ? {} : { toolCallId: fallbackId }),
|
|
876
|
+
linkKind: call === undefined ? "session-tool-result" : "session-tool-call-result",
|
|
877
|
+
confidence: call === undefined ? "best-effort" : "exact",
|
|
878
|
+
...(call === undefined ? {} : { argsPreview: previewUnknown(call.args) }),
|
|
879
|
+
resultPreview: previewUnknown(result.result),
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
for (const call of [...calls.values()].sort(compareSessionToolCalls)) {
|
|
883
|
+
if (pairedIds.has(call.id)) continue;
|
|
884
|
+
events.push({
|
|
885
|
+
source: "session-entry",
|
|
886
|
+
runId: call.link.runId,
|
|
887
|
+
runLink: evidenceRunLink(call.link),
|
|
888
|
+
sessionId: call.sessionId,
|
|
889
|
+
tool: call.tool,
|
|
890
|
+
count: 1,
|
|
891
|
+
ok: 0,
|
|
892
|
+
errors: 0,
|
|
893
|
+
blocked: 0,
|
|
894
|
+
totalDurationMs: 0,
|
|
895
|
+
timestamp: call.timestamp,
|
|
896
|
+
toolCallId: call.id,
|
|
897
|
+
linkKind: "session-tool-call",
|
|
898
|
+
confidence: "best-effort",
|
|
899
|
+
argsPreview: previewUnknown(call.args),
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
return events.sort(compareEvidenceToolEvents);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
function extractSessionToolCall(entry: MessageEntry, linked: LinkedSessionEntry): SessionToolCall {
|
|
906
|
+
const payload = payloadObject(entry.payload);
|
|
907
|
+
const block = firstContentBlock(entry.payload, "toolCall");
|
|
908
|
+
const fn = payloadObject(payload?.function);
|
|
909
|
+
const id =
|
|
910
|
+
readOptionalString(payload?.id) ??
|
|
911
|
+
readOptionalString(payload?.toolCallId) ??
|
|
912
|
+
readOptionalString(payload?.tool_call_id) ??
|
|
913
|
+
readOptionalString(block?.id) ??
|
|
914
|
+
entry.turnId;
|
|
915
|
+
const tool =
|
|
916
|
+
readOptionalString(payload?.name) ??
|
|
917
|
+
readOptionalString(payload?.toolName) ??
|
|
918
|
+
readOptionalString(payload?.tool) ??
|
|
919
|
+
readOptionalString(fn?.name) ??
|
|
920
|
+
readOptionalString(block?.name) ??
|
|
921
|
+
"tool";
|
|
922
|
+
const args =
|
|
923
|
+
payload?.arguments ??
|
|
924
|
+
payload?.args ??
|
|
925
|
+
payload?.input ??
|
|
926
|
+
parseMaybeJson(fn?.arguments) ??
|
|
927
|
+
block?.arguments ??
|
|
928
|
+
block?.args ??
|
|
929
|
+
undefined;
|
|
930
|
+
return { id, tool, args, timestamp: entry.timestamp, link: linked.link, sessionId: linked.sessionId };
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
function extractSessionToolResult(entry: MessageEntry, linked: LinkedSessionEntry): SessionToolResult {
|
|
934
|
+
const payload = payloadObject(entry.payload);
|
|
935
|
+
const contentText = extractTextFromPayload(entry.payload);
|
|
936
|
+
const id =
|
|
937
|
+
readOptionalString(payload?.toolCallId) ??
|
|
938
|
+
readOptionalString(payload?.tool_call_id) ??
|
|
939
|
+
readOptionalString(payload?.id) ??
|
|
940
|
+
null;
|
|
941
|
+
const tool =
|
|
942
|
+
readOptionalString(payload?.toolName) ??
|
|
943
|
+
readOptionalString(payload?.name) ??
|
|
944
|
+
readOptionalString(payload?.tool) ??
|
|
945
|
+
"tool";
|
|
946
|
+
const result =
|
|
947
|
+
payload?.result ??
|
|
948
|
+
payload?.output ??
|
|
949
|
+
payload?.out ??
|
|
950
|
+
payload?.content ??
|
|
951
|
+
(contentText.length > 0 ? contentText : entry.payload);
|
|
952
|
+
return {
|
|
953
|
+
id,
|
|
954
|
+
tool,
|
|
955
|
+
result,
|
|
956
|
+
isError: payload?.isError === true || payload?.error === true,
|
|
957
|
+
timestamp: entry.timestamp,
|
|
958
|
+
link: linked.link,
|
|
959
|
+
sessionId: linked.sessionId,
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
function compareSessionToolCalls(a: SessionToolCall, b: SessionToolCall): number {
|
|
964
|
+
const byTimestamp = compareStrings(a.timestamp, b.timestamp);
|
|
965
|
+
if (byTimestamp !== 0) return byTimestamp;
|
|
966
|
+
return compareStrings(a.id, b.id);
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
async function linkAuditRows(
|
|
970
|
+
stateDir: string,
|
|
971
|
+
source: EvidenceOverview["source"],
|
|
972
|
+
runSources: ReadonlyArray<EvidenceRunSource>,
|
|
973
|
+
sessionLinks: SessionLinkResult,
|
|
974
|
+
): Promise<AuditLinkResult> {
|
|
975
|
+
const auditRows = await readAuditRows(stateDir);
|
|
976
|
+
const rows: EvidenceAuditLinkedRow[] = [];
|
|
977
|
+
const runIds = new Set(runSources.map((item) => item.envelope.id));
|
|
978
|
+
const sessionIds = new Set(sessionLinks.attemptedSessionIds);
|
|
979
|
+
if (source.kind === "session") sessionIds.add(source.sessionId);
|
|
980
|
+
const runSessionIds = new Map(
|
|
981
|
+
runSources.map((item) => [item.envelope.id, item.envelope.sessionId ?? item.receipt?.sessionId ?? null] as const),
|
|
982
|
+
);
|
|
983
|
+
for (const audit of auditRows.rows) {
|
|
984
|
+
const directRunId = readOptionalString(audit.row.runId);
|
|
985
|
+
if (directRunId !== null && runIds.has(directRunId)) {
|
|
986
|
+
rows.push(
|
|
987
|
+
auditLinkedRow(audit, {
|
|
988
|
+
runId: directRunId,
|
|
989
|
+
sessionId: readOptionalString(audit.row.sessionId) ?? runSessionIds.get(directRunId) ?? null,
|
|
990
|
+
linkKind: "run-id",
|
|
991
|
+
confidence: "exact",
|
|
992
|
+
reasons: ["audit runId matched run ledger"],
|
|
993
|
+
}),
|
|
994
|
+
);
|
|
995
|
+
continue;
|
|
996
|
+
}
|
|
997
|
+
const directSessionId = readOptionalString(audit.row.sessionId);
|
|
998
|
+
if (directSessionId !== null && sessionIds.has(directSessionId)) {
|
|
999
|
+
rows.push(
|
|
1000
|
+
auditLinkedRow(audit, {
|
|
1001
|
+
runId: null,
|
|
1002
|
+
sessionId: directSessionId,
|
|
1003
|
+
linkKind: "session-id",
|
|
1004
|
+
confidence: "exact",
|
|
1005
|
+
reasons: ["audit sessionId matched linked session"],
|
|
1006
|
+
}),
|
|
1007
|
+
);
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
1010
|
+
const bestEffort = bestEffortAuditLink(audit, source, runSources, sessionIds);
|
|
1011
|
+
if (bestEffort !== null) rows.push(auditLinkedRow(audit, bestEffort));
|
|
1012
|
+
}
|
|
1013
|
+
rows.sort(compareAuditLinkedRows);
|
|
1014
|
+
return { rows, readErrors: auditRows.errors };
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
interface AuditLinkFields {
|
|
1018
|
+
runId: string | null;
|
|
1019
|
+
sessionId: string | null;
|
|
1020
|
+
linkKind: string;
|
|
1021
|
+
confidence: EvidenceLinkConfidence;
|
|
1022
|
+
reasons: string[];
|
|
1023
|
+
candidateRunIds?: string[];
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
function auditLinkedRow(audit: AuditJsonRow, fields: AuditLinkFields): EvidenceAuditLinkedRow {
|
|
1027
|
+
return {
|
|
1028
|
+
kind: "audit-linked",
|
|
1029
|
+
auditKind: audit.auditKind,
|
|
1030
|
+
ts: audit.ts,
|
|
1031
|
+
runId: fields.runId,
|
|
1032
|
+
sessionId: fields.sessionId,
|
|
1033
|
+
linkKind: fields.linkKind,
|
|
1034
|
+
confidence: fields.confidence,
|
|
1035
|
+
reasons: fields.reasons,
|
|
1036
|
+
...(fields.candidateRunIds === undefined ? {} : { candidateRunIds: fields.candidateRunIds }),
|
|
1037
|
+
row: audit.row,
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
function bestEffortAuditLink(
|
|
1042
|
+
audit: AuditJsonRow,
|
|
1043
|
+
source: EvidenceOverview["source"],
|
|
1044
|
+
runSources: ReadonlyArray<EvidenceRunSource>,
|
|
1045
|
+
sessionIds: ReadonlySet<string>,
|
|
1046
|
+
): AuditLinkFields | null {
|
|
1047
|
+
if (audit.auditKind !== "tool_call") return null;
|
|
1048
|
+
const tool = readOptionalString(audit.row.tool);
|
|
1049
|
+
if (tool === null || audit.ts === null) return null;
|
|
1050
|
+
const candidates = runSources.filter((item) => {
|
|
1051
|
+
if (!timestampInRunWindow(audit.ts ?? "", item.envelope)) return false;
|
|
1052
|
+
const tools = receiptToolNames(item);
|
|
1053
|
+
return tools.size === 0 || tools.has(tool);
|
|
1054
|
+
});
|
|
1055
|
+
if (candidates.length === 1) {
|
|
1056
|
+
const candidate = candidates[0];
|
|
1057
|
+
if (candidate === undefined) return null;
|
|
1058
|
+
return {
|
|
1059
|
+
runId: candidate.envelope.id,
|
|
1060
|
+
sessionId:
|
|
1061
|
+
candidate.envelope.sessionId ??
|
|
1062
|
+
candidate.receipt?.sessionId ??
|
|
1063
|
+
(source.kind === "session" ? source.sessionId : null),
|
|
1064
|
+
linkKind: "timestamp-tool",
|
|
1065
|
+
confidence: "best-effort",
|
|
1066
|
+
reasons: ["audit timestamp fell within one run window", "audit tool matched receipt metadata"],
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
if (source.kind === "session" && candidates.length > 1) {
|
|
1070
|
+
return {
|
|
1071
|
+
runId: null,
|
|
1072
|
+
sessionId: source.sessionId,
|
|
1073
|
+
linkKind: "ambiguous-timestamp-tool",
|
|
1074
|
+
confidence: "best-effort",
|
|
1075
|
+
reasons: ["audit timestamp and tool matched multiple runs in this session"],
|
|
1076
|
+
candidateRunIds: candidates.map((item) => item.envelope.id).sort(compareStrings),
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
if (source.kind === "session" && sessionIds.has(source.sessionId)) {
|
|
1080
|
+
const sessionSpan = sessionTimeSpan(runSources);
|
|
1081
|
+
if (sessionSpan !== null && timestampInSpan(audit.ts, sessionSpan)) {
|
|
1082
|
+
return {
|
|
1083
|
+
runId: null,
|
|
1084
|
+
sessionId: source.sessionId,
|
|
1085
|
+
linkKind: "session-timestamp-tool",
|
|
1086
|
+
confidence: "best-effort",
|
|
1087
|
+
reasons: ["audit timestamp fell within the session run span", "audit tool metadata was present"],
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
return null;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
function receiptToolNames(source: EvidenceRunSource): Set<string> {
|
|
1095
|
+
return new Set((source.receipt?.toolStats ?? []).map((stat) => stat.tool));
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
function sessionTimeSpan(runSources: ReadonlyArray<EvidenceRunSource>): { start: string; end: string | null } | null {
|
|
1099
|
+
const starts = runSources.map((source) => source.envelope.startedAt);
|
|
1100
|
+
if (starts.length === 0) return null;
|
|
1101
|
+
return {
|
|
1102
|
+
start: earliest(starts) ?? starts[0] ?? "1970-01-01T00:00:00.000Z",
|
|
1103
|
+
end: latest(runSources.flatMap((source) => (source.envelope.endedAt === null ? [] : [source.envelope.endedAt]))),
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
function timestampInSpan(timestamp: string, span: { start: string; end: string | null }): boolean {
|
|
1108
|
+
const value = Date.parse(timestamp);
|
|
1109
|
+
const start = Date.parse(span.start);
|
|
1110
|
+
const end = span.end === null ? Number.POSITIVE_INFINITY : Date.parse(span.end);
|
|
1111
|
+
if (!Number.isFinite(value) || !Number.isFinite(start) || !Number.isFinite(end)) return false;
|
|
1112
|
+
return value >= start && value <= end;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
function auditToolEvents(rows: ReadonlyArray<EvidenceAuditLinkedRow>): EvidenceToolEvent[] {
|
|
1116
|
+
const events: EvidenceToolEvent[] = [];
|
|
1117
|
+
for (const row of rows) {
|
|
1118
|
+
if (row.auditKind !== "tool_call") continue;
|
|
1119
|
+
const tool = readOptionalString(row.row.tool);
|
|
1120
|
+
if (tool === null) continue;
|
|
1121
|
+
const decision = readOptionalString(row.row.decision);
|
|
1122
|
+
if (decision === "classified") continue;
|
|
1123
|
+
const event: EvidenceToolEvent = {
|
|
1124
|
+
source: "audit-row",
|
|
1125
|
+
runId: row.runId,
|
|
1126
|
+
sessionId: row.sessionId,
|
|
1127
|
+
tool,
|
|
1128
|
+
count: 1,
|
|
1129
|
+
ok: decision === "allowed" ? 1 : 0,
|
|
1130
|
+
errors: 0,
|
|
1131
|
+
blocked: decision === "blocked" || decision === "denied" ? 1 : 0,
|
|
1132
|
+
totalDurationMs: 0,
|
|
1133
|
+
linkKind: row.linkKind,
|
|
1134
|
+
confidence: row.confidence,
|
|
1135
|
+
};
|
|
1136
|
+
if (row.ts !== null) event.timestamp = row.ts;
|
|
1137
|
+
if (decision !== null) event.decision = decision;
|
|
1138
|
+
const actionClass = readOptionalString(row.row.actionClass);
|
|
1139
|
+
if (actionClass !== null) event.actionClass = actionClass;
|
|
1140
|
+
events.push(event);
|
|
1141
|
+
}
|
|
1142
|
+
return events.sort(compareEvidenceToolEvents);
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
function compareAuditLinkedRows(a: EvidenceAuditLinkedRow, b: EvidenceAuditLinkedRow): number {
|
|
1146
|
+
const byTs = compareNullableStrings(a.ts, b.ts);
|
|
1147
|
+
if (byTs !== 0) return byTs;
|
|
1148
|
+
const byKind = compareStrings(a.auditKind, b.auditKind);
|
|
1149
|
+
if (byKind !== 0) return byKind;
|
|
1150
|
+
const aCorrelation = readOptionalString(a.row.correlationId) ?? "";
|
|
1151
|
+
const bCorrelation = readOptionalString(b.row.correlationId) ?? "";
|
|
1152
|
+
if (aCorrelation !== bCorrelation) return compareStrings(aCorrelation, bCorrelation);
|
|
1153
|
+
return JSON.stringify(a.row).localeCompare(JSON.stringify(b.row));
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
function receiptsFile(runSources: ReadonlyArray<EvidenceRunSource>): EvidenceReceiptFile {
|
|
1157
|
+
return {
|
|
1158
|
+
version: EVIDENCE_VERSION,
|
|
1159
|
+
receipts: runSources.flatMap((source) => (source.receipt === null ? [] : [source.receipt])),
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
function protectedArtifactsFile(sessionLinks: SessionLinkResult): EvidenceProtectedArtifactsFile {
|
|
1164
|
+
const state = protectedArtifactStateFromSessionEntries(sessionLinks.entries.map((linked) => linked.entry));
|
|
1165
|
+
const events = sessionLinks.entries
|
|
1166
|
+
.filter((linked): linked is LinkedSessionEntry & { entry: ProtectedArtifactEntry } => {
|
|
1167
|
+
return linked.entry.kind === "protectedArtifact";
|
|
1168
|
+
})
|
|
1169
|
+
.map((linked) => {
|
|
1170
|
+
const event: EvidenceProtectedArtifactsFile["events"][number] = {
|
|
1171
|
+
kind: "protected-artifact",
|
|
1172
|
+
sessionId: linked.sessionId,
|
|
1173
|
+
runId: linked.link.runId,
|
|
1174
|
+
runLink: evidenceRunLink(linked.link),
|
|
1175
|
+
timestamp: linked.entry.timestamp,
|
|
1176
|
+
turnId: linked.entry.turnId,
|
|
1177
|
+
parentTurnId: linked.entry.parentTurnId,
|
|
1178
|
+
action: linked.entry.action,
|
|
1179
|
+
artifact: protectedArtifactFromSessionEntry(linked.entry),
|
|
1180
|
+
};
|
|
1181
|
+
if (linked.entry.toolName !== undefined) event.toolName = linked.entry.toolName;
|
|
1182
|
+
if (linked.entry.toolCallId !== undefined) event.toolCallId = linked.entry.toolCallId;
|
|
1183
|
+
if (linked.entry.runId !== undefined) event.sourceRunId = linked.entry.runId;
|
|
1184
|
+
if (linked.entry.correlationId !== undefined) event.correlationId = linked.entry.correlationId;
|
|
1185
|
+
return event;
|
|
1186
|
+
})
|
|
1187
|
+
.sort(compareProtectedArtifactEvents);
|
|
1188
|
+
return {
|
|
1189
|
+
version: EVIDENCE_VERSION,
|
|
1190
|
+
artifacts: state.artifacts,
|
|
1191
|
+
events,
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
function compareProtectedArtifactEvents(
|
|
1196
|
+
left: EvidenceProtectedArtifactsFile["events"][number],
|
|
1197
|
+
right: EvidenceProtectedArtifactsFile["events"][number],
|
|
1198
|
+
): number {
|
|
1199
|
+
return (
|
|
1200
|
+
compareStrings(left.timestamp, right.timestamp) ||
|
|
1201
|
+
compareStrings(left.sessionId, right.sessionId) ||
|
|
1202
|
+
compareStrings(left.turnId, right.turnId) ||
|
|
1203
|
+
compareStrings(left.artifact.path, right.artifact.path)
|
|
1204
|
+
);
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
function renderTranscript(
|
|
1208
|
+
overview: EvidenceOverview,
|
|
1209
|
+
runSources: ReadonlyArray<EvidenceRunSource>,
|
|
1210
|
+
sessionLinks: SessionLinkResult,
|
|
1211
|
+
): string {
|
|
1212
|
+
const lines = [
|
|
1213
|
+
`# Evidence ${overview.evidenceId}`,
|
|
1214
|
+
"",
|
|
1215
|
+
`Source: ${formatEvidenceSource(overview.source)}`,
|
|
1216
|
+
`Generated at: ${overview.generatedAt}`,
|
|
1217
|
+
"",
|
|
1218
|
+
"## Runs",
|
|
1219
|
+
];
|
|
1220
|
+
for (const source of runSources) {
|
|
1221
|
+
const envelope = source.envelope;
|
|
1222
|
+
const exitCode = source.receipt?.exitCode ?? envelope.exitCode;
|
|
1223
|
+
lines.push(
|
|
1224
|
+
`- ${envelope.id} status=${envelope.status} exit=${exitCode ?? "?"} agent=${envelope.agentId} target=${envelope.targetId}`,
|
|
1225
|
+
);
|
|
1226
|
+
lines.push(` task: ${truncateText(envelope.task, MAX_TASK_CHARS)}`);
|
|
1227
|
+
// Run section rendering reads provenance from the sealed receipt and adds
|
|
1228
|
+
// sentences only for fields that are present.
|
|
1229
|
+
if (source.receipt !== null) {
|
|
1230
|
+
for (const line of provenanceTranscriptLines(extractRunProvenance(source.receipt))) {
|
|
1231
|
+
lines.push(` ${line}`);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
if (sessionLinks.entries.length > 0) {
|
|
1236
|
+
lines.push("", "## Linked Session Transcript");
|
|
1237
|
+
for (const linked of sessionLinks.entries) {
|
|
1238
|
+
lines.push(...renderSessionTranscriptEntry(linked));
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
lines.push("");
|
|
1242
|
+
return `${lines.join("\n")}\n`;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
function formatEvidenceSource(source: EvidenceOverview["source"]): string {
|
|
1246
|
+
if (source.kind === "run") return `run ${source.runId}`;
|
|
1247
|
+
if (source.kind === "session") return `session ${source.sessionId}`;
|
|
1248
|
+
return `eval ${source.evalId}`;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
/**
|
|
1252
|
+
* Run attribution as the transcript states it. A windowed link says so, and an
|
|
1253
|
+
* ambiguous one names its candidates instead of printing a run id the bundle
|
|
1254
|
+
* cannot stand behind.
|
|
1255
|
+
*/
|
|
1256
|
+
function transcriptRunLink(link: SessionRunLink): string {
|
|
1257
|
+
if (link.kind === "entry-run-id") return ` run=${link.runId}`;
|
|
1258
|
+
if (link.kind === "timestamp-window") return ` run=${link.runId} link=timestamp-window`;
|
|
1259
|
+
if (link.kind === "ambiguous-timestamp-window") {
|
|
1260
|
+
return ` run=? link=ambiguous-timestamp-window candidates=${link.candidateRunIds.join(",")}`;
|
|
1261
|
+
}
|
|
1262
|
+
return "";
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
function renderSessionTranscriptEntry(linked: LinkedSessionEntry): string[] {
|
|
1266
|
+
const entry = linked.entry;
|
|
1267
|
+
const prefix = `- ${entry.timestamp} session=${linked.sessionId}${transcriptRunLink(linked.link)}`;
|
|
1268
|
+
if (entry.kind === "message") {
|
|
1269
|
+
if (entry.role === "tool_call") {
|
|
1270
|
+
const call = extractSessionToolCall(entry, linked);
|
|
1271
|
+
return [`${prefix} tool_call ${call.tool} id=${call.id} args=${previewUnknown(call.args)}`];
|
|
1272
|
+
}
|
|
1273
|
+
if (entry.role === "tool_result") {
|
|
1274
|
+
const result = extractSessionToolResult(entry, linked);
|
|
1275
|
+
const id = result.id === null ? "" : ` id=${result.id}`;
|
|
1276
|
+
const error = result.isError ? " error=true" : "";
|
|
1277
|
+
return [`${prefix} tool_result ${result.tool}${id}${error}: ${previewUnknown(result.result)}`];
|
|
1278
|
+
}
|
|
1279
|
+
const text = collapseWhitespace(extractTextFromPayload(entry.payload));
|
|
1280
|
+
const rendered = text.length > 0 ? truncateText(text, TRANSCRIPT_TEXT_MAX_CHARS) : previewUnknown(entry.payload);
|
|
1281
|
+
return [`${prefix} ${entry.role}: ${rendered}`];
|
|
1282
|
+
}
|
|
1283
|
+
if (entry.kind === "bashExecution") {
|
|
1284
|
+
const status = entry.cancelled ? "cancelled" : entry.exitCode === null ? "exit=?" : `exit=${entry.exitCode}`;
|
|
1285
|
+
return [
|
|
1286
|
+
`${prefix} bash ${status}: $ ${truncateText(entry.command, TRANSCRIPT_TEXT_MAX_CHARS)}`,
|
|
1287
|
+
` output: ${previewUnknown(entry.output)}`,
|
|
1288
|
+
];
|
|
1289
|
+
}
|
|
1290
|
+
if (entry.kind === "branchSummary")
|
|
1291
|
+
return [`${prefix} branchSummary: ${truncateText(entry.summary, TRANSCRIPT_TEXT_MAX_CHARS)}`];
|
|
1292
|
+
if (entry.kind === "compactionSummary")
|
|
1293
|
+
return [`${prefix} compactionSummary: ${truncateText(entry.summary, TRANSCRIPT_TEXT_MAX_CHARS)}`];
|
|
1294
|
+
if (entry.kind === "modelChange") return [`${prefix} modelChange: ${entry.provider}/${entry.modelId}`];
|
|
1295
|
+
if (entry.kind === "thinkingLevelChange") return [`${prefix} thinkingLevelChange: ${entry.thinkingLevel}`];
|
|
1296
|
+
if (entry.kind === "fileEntry") return [`${prefix} file ${entry.operation}: ${entry.path}`];
|
|
1297
|
+
if (entry.kind === "sessionInfo") {
|
|
1298
|
+
const label = entry.name ?? (entry.label === undefined ? "" : entry.label);
|
|
1299
|
+
return label.length === 0 ? [`${prefix} sessionInfo`] : [`${prefix} sessionInfo: ${truncateText(label, 120)}`];
|
|
1300
|
+
}
|
|
1301
|
+
if (entry.kind === "label") {
|
|
1302
|
+
const label = entry.label === undefined ? "" : ` ${truncateText(entry.label, 120)}`;
|
|
1303
|
+
return [`${prefix} label ${entry.targetTurnId}:${label}`];
|
|
1304
|
+
}
|
|
1305
|
+
if (entry.kind === "protectedArtifact") {
|
|
1306
|
+
const artifact = protectedArtifactFromSessionEntry(entry);
|
|
1307
|
+
const tool = entry.toolName === undefined ? "" : ` tool=${entry.toolName}`;
|
|
1308
|
+
const validation =
|
|
1309
|
+
artifact.validationCommand === undefined
|
|
1310
|
+
? ""
|
|
1311
|
+
: ` validation=${artifact.validationCommand}${artifact.validationExitCode === undefined ? "" : ` exit=${artifact.validationExitCode}`}`;
|
|
1312
|
+
return [
|
|
1313
|
+
`${prefix} protectedArtifact protect:${tool} ${artifact.path} source=${artifact.source}${validation} reason=${truncateText(artifact.reason, 120)}`,
|
|
1314
|
+
];
|
|
1315
|
+
}
|
|
1316
|
+
if (entry.kind === "skillActivation") {
|
|
1317
|
+
const activation = entry.activation;
|
|
1318
|
+
const turn = activation.turnId === undefined ? "" : ` turn=${activation.turnId}`;
|
|
1319
|
+
return [
|
|
1320
|
+
`${prefix} skillActivation ${activation.name} source=${activation.source} hash=${activation.hash} triggeredBy=${activation.triggeredBy}${turn} path=${activation.filePath}`,
|
|
1321
|
+
];
|
|
1322
|
+
}
|
|
1323
|
+
if (entry.kind === "taskLedger") {
|
|
1324
|
+
return [
|
|
1325
|
+
`${prefix} taskLedger goals=${entry.goals.length} subgoals=${entry.subgoals.length} activeRuns=${entry.activeRunIds.length} evidence=${entry.requiredValidationEvidence.length}`,
|
|
1326
|
+
];
|
|
1327
|
+
}
|
|
1328
|
+
if (entry.kind === "workerRun") {
|
|
1329
|
+
const parent = entry.parentToolCallId === undefined ? "" : ` parentCall=${entry.parentToolCallId}`;
|
|
1330
|
+
return [
|
|
1331
|
+
`${prefix} workerRun ${entry.agentId} origin=${entry.origin} runtime=${entry.runtime.kind} run=${entry.runId} assignment=${entry.assignmentId}${parent}`,
|
|
1332
|
+
];
|
|
1333
|
+
}
|
|
1334
|
+
if (entry.kind === "custom") return [`${prefix} custom:${entry.customType} ${previewUnknown(entry.data)}`];
|
|
1335
|
+
const _exhaustive: never = entry;
|
|
1336
|
+
return [`${prefix} ${String(_exhaustive)}`];
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
function renderFindings(findings: ReadonlyArray<EvidenceFinding>): string {
|
|
1340
|
+
if (findings.length === 0) return "# Findings\n\nNo findings.\n";
|
|
1341
|
+
const lines = ["# Findings", ""];
|
|
1342
|
+
for (const item of findings) {
|
|
1343
|
+
const run = item.runId === null ? "" : ` run=${item.runId}`;
|
|
1344
|
+
lines.push(`- ${item.id} [${item.severity}] ${item.tag}${run}: ${item.message}`);
|
|
1345
|
+
}
|
|
1346
|
+
lines.push("");
|
|
1347
|
+
return `${lines.join("\n")}\n`;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
async function readRunLedger(stateDir: string): Promise<RunLedgerRows> {
|
|
1351
|
+
const target = join(stateDir, "runs.json");
|
|
1352
|
+
let raw: string;
|
|
1353
|
+
try {
|
|
1354
|
+
raw = await readFile(target, "utf8");
|
|
1355
|
+
} catch (error) {
|
|
1356
|
+
const err = error as NodeJS.ErrnoException;
|
|
1357
|
+
if (err.code === "ENOENT") throw new Error("run ledger not found");
|
|
1358
|
+
throw error;
|
|
1359
|
+
}
|
|
1360
|
+
const parsed = parseJson(raw, target);
|
|
1361
|
+
if (!Array.isArray(parsed)) throw new Error(`${target}: expected array`);
|
|
1362
|
+
return { rows: parsed, path: target };
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
function selectRunEnvelopes(ledger: RunLedgerRows, source: EvidenceSource): RunEnvelope[] {
|
|
1366
|
+
// Strictly validate the selected evidence source, not every retained row:
|
|
1367
|
+
// older rows may use a retired envelope shape. Keep the original index so
|
|
1368
|
+
// corruption in a selected row still fails with an actionable location.
|
|
1369
|
+
return ledger.rows.flatMap((entry, index) => {
|
|
1370
|
+
if (!isRecord(entry)) return [];
|
|
1371
|
+
const selected = source.kind === "run" ? entry.id === source.runId : entry.sessionId === source.sessionId;
|
|
1372
|
+
return selected ? [parseRunEnvelope(entry, `${ledger.path}[${index}]`)] : [];
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
async function readReceipt(
|
|
1377
|
+
stateDir: string,
|
|
1378
|
+
envelope: RunEnvelope,
|
|
1379
|
+
): Promise<{
|
|
1380
|
+
receipt: RunReceipt | null;
|
|
1381
|
+
error: string | null;
|
|
1382
|
+
integrityFailed: boolean;
|
|
1383
|
+
}> {
|
|
1384
|
+
const receiptPath = envelope.receiptPath ?? join(stateDir, "receipts", `${envelope.id}.json`);
|
|
1385
|
+
let raw: string;
|
|
1386
|
+
try {
|
|
1387
|
+
raw = await readFile(receiptPath, "utf8");
|
|
1388
|
+
} catch (error) {
|
|
1389
|
+
const err = error as NodeJS.ErrnoException;
|
|
1390
|
+
if (err.code === "ENOENT") {
|
|
1391
|
+
return { receipt: null, error: "receipt file not found", integrityFailed: false };
|
|
1392
|
+
}
|
|
1393
|
+
return { receipt: null, error: `receipt read error: ${err.message ?? String(err)}`, integrityFailed: false };
|
|
1394
|
+
}
|
|
1395
|
+
const parsed = parseJson(raw, receiptPath);
|
|
1396
|
+
const receipt = parseRunReceipt(parsed, receiptPath);
|
|
1397
|
+
const integrity = verifyReceiptIntegrity(receipt, envelope);
|
|
1398
|
+
if (!integrity.ok) {
|
|
1399
|
+
return { receipt, error: `receipt integrity: ${integrity.reason}`, integrityFailed: true };
|
|
1400
|
+
}
|
|
1401
|
+
return { receipt, error: null, integrityFailed: false };
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
function readExecutionRole(value: Record<string, unknown>, source: string): ExecutionRole {
|
|
1405
|
+
const role = value.executionRole;
|
|
1406
|
+
if (!isExecutionRole(role)) throw new Error(`${source}: executionRole is missing or not a known execution role`);
|
|
1407
|
+
return role;
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
function parseRunEnvelope(value: unknown, source: string): RunEnvelope {
|
|
1411
|
+
if (!isRecord(value)) throw new Error(`${source}: expected object`);
|
|
1412
|
+
const reasoningTokenCount = readOptionalNumber(value, source, "reasoningTokenCount");
|
|
1413
|
+
// Spread the raw row first: integrity verification recomputes digests over
|
|
1414
|
+
// ledger fields this parser does not model (outcome, lineage, identity,
|
|
1415
|
+
// token splits), so dropping them would flag every modern receipt as
|
|
1416
|
+
// tampered. The validated reads below still gate the fields evidence uses.
|
|
1417
|
+
return {
|
|
1418
|
+
...(value as Partial<RunEnvelope>),
|
|
1419
|
+
id: readString(value, source, "id"),
|
|
1420
|
+
agentId: readString(value, source, "agentId"),
|
|
1421
|
+
executionRole: readExecutionRole(value, source),
|
|
1422
|
+
task: readString(value, source, "task"),
|
|
1423
|
+
targetId: readString(value, source, "targetId"),
|
|
1424
|
+
wireModelId: readString(value, source, "wireModelId"),
|
|
1425
|
+
runtimeId: readString(value, source, "runtimeId"),
|
|
1426
|
+
runtimeKind: readRunKind(value, source, "runtimeKind"),
|
|
1427
|
+
startedAt: readString(value, source, "startedAt"),
|
|
1428
|
+
endedAt: readNullableString(value, source, "endedAt"),
|
|
1429
|
+
status: readRunStatus(value, source, "status"),
|
|
1430
|
+
exitCode: readNullableNumber(value, source, "exitCode"),
|
|
1431
|
+
pid: readNullableNumber(value, source, "pid"),
|
|
1432
|
+
heartbeatAt: readNullableString(value, source, "heartbeatAt"),
|
|
1433
|
+
receiptPath: readNullableString(value, source, "receiptPath"),
|
|
1434
|
+
sessionId: readNullableString(value, source, "sessionId"),
|
|
1435
|
+
cwd: readString(value, source, "cwd"),
|
|
1436
|
+
tokenCount: readNumber(value, source, "tokenCount"),
|
|
1437
|
+
...(reasoningTokenCount !== undefined ? { reasoningTokenCount } : {}),
|
|
1438
|
+
costUsd: readNumber(value, source, "costUsd"),
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
function parseRunReceipt(value: unknown, source: string): RunReceipt {
|
|
1443
|
+
if (!isRecord(value)) throw new Error(`${source}: expected object`);
|
|
1444
|
+
const integrity = value.integrity;
|
|
1445
|
+
if (!isRecord(integrity)) throw new Error(`${source}.integrity: expected object`);
|
|
1446
|
+
const reasoningTokenCount = readOptionalNumber(value, source, "reasoningTokenCount");
|
|
1447
|
+
const failureMessage = readOptionalString(value.failureMessage);
|
|
1448
|
+
// Spread the raw receipt first so integrity verification sees the receipt
|
|
1449
|
+
// as written (outcome, safety, reproducibility, token splits, ...); the
|
|
1450
|
+
// write-time digest covered those fields and a lossy re-parse would make
|
|
1451
|
+
// every clean receipt recompute to a different digest.
|
|
1452
|
+
return {
|
|
1453
|
+
...(value as unknown as RunReceipt),
|
|
1454
|
+
runId: readString(value, source, "runId"),
|
|
1455
|
+
agentId: readString(value, source, "agentId"),
|
|
1456
|
+
task: readString(value, source, "task"),
|
|
1457
|
+
targetId: readString(value, source, "targetId"),
|
|
1458
|
+
wireModelId: readString(value, source, "wireModelId"),
|
|
1459
|
+
runtimeId: readString(value, source, "runtimeId"),
|
|
1460
|
+
runtimeKind: readRunKind(value, source, "runtimeKind"),
|
|
1461
|
+
startedAt: readString(value, source, "startedAt"),
|
|
1462
|
+
endedAt: readString(value, source, "endedAt"),
|
|
1463
|
+
exitCode: readNumber(value, source, "exitCode"),
|
|
1464
|
+
...(failureMessage !== null ? { failureMessage } : {}),
|
|
1465
|
+
tokenCount: readNumber(value, source, "tokenCount"),
|
|
1466
|
+
...(reasoningTokenCount !== undefined ? { reasoningTokenCount } : {}),
|
|
1467
|
+
costUsd: readNumber(value, source, "costUsd"),
|
|
1468
|
+
compiledPromptHash: readNullableString(value, source, "compiledPromptHash"),
|
|
1469
|
+
staticCompositionHash: readNullableString(value, source, "staticCompositionHash"),
|
|
1470
|
+
clioVersion: readString(value, source, "clioVersion"),
|
|
1471
|
+
piMonoVersion: readString(value, source, "piMonoVersion"),
|
|
1472
|
+
platform: readString(value, source, "platform"),
|
|
1473
|
+
nodeVersion: readString(value, source, "nodeVersion"),
|
|
1474
|
+
toolCalls: readNumber(value, source, "toolCalls"),
|
|
1475
|
+
toolStats: readToolStats(value, source, "toolStats"),
|
|
1476
|
+
sessionId: readNullableString(value, source, "sessionId"),
|
|
1477
|
+
integrity: {
|
|
1478
|
+
version: readNumber(integrity, `${source}.integrity`, "version") as RunReceiptIntegrity["version"],
|
|
1479
|
+
algorithm: readString(integrity, `${source}.integrity`, "algorithm") as "sha256",
|
|
1480
|
+
digest: readString(integrity, `${source}.integrity`, "digest"),
|
|
1481
|
+
},
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
function readToolStats(record: Record<string, unknown>, source: string, field: string): ToolCallStat[] {
|
|
1486
|
+
const value = record[field];
|
|
1487
|
+
if (!Array.isArray(value)) throw new Error(`${source}.${field}: expected array`);
|
|
1488
|
+
return value.map((entry, index) => {
|
|
1489
|
+
const itemSource = `${source}.${field}[${index}]`;
|
|
1490
|
+
if (!isRecord(entry)) throw new Error(`${itemSource}: expected object`);
|
|
1491
|
+
return {
|
|
1492
|
+
tool: readString(entry, itemSource, "tool"),
|
|
1493
|
+
count: readNumber(entry, itemSource, "count"),
|
|
1494
|
+
ok: readNumber(entry, itemSource, "ok"),
|
|
1495
|
+
errors: readNumber(entry, itemSource, "errors"),
|
|
1496
|
+
blocked: readNumber(entry, itemSource, "blocked"),
|
|
1497
|
+
totalDurationMs: readNumber(entry, itemSource, "totalDurationMs"),
|
|
1498
|
+
};
|
|
1499
|
+
});
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
function readRunKind(record: Record<string, unknown>, source: string, field: string): RunKind {
|
|
1503
|
+
// runtimeKind is digest-covered: returning anything other than the value as
|
|
1504
|
+
// written would make integrity verification recompute a different payload.
|
|
1505
|
+
const value = readString(record, source, field);
|
|
1506
|
+
if (value !== "http" && value !== "sdk" && value !== "subprocess" && value !== "acp-delegation") {
|
|
1507
|
+
throw new Error(`${source}.${field}: unexpected runtime kind`);
|
|
1508
|
+
}
|
|
1509
|
+
return value;
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
function readRunStatus(record: Record<string, unknown>, source: string, field: string): RunStatus {
|
|
1513
|
+
const value = readString(record, source, field);
|
|
1514
|
+
if (
|
|
1515
|
+
value !== "queued" &&
|
|
1516
|
+
value !== "running" &&
|
|
1517
|
+
value !== "completed" &&
|
|
1518
|
+
value !== "failed" &&
|
|
1519
|
+
value !== "interrupted" &&
|
|
1520
|
+
value !== "stale" &&
|
|
1521
|
+
value !== "dead"
|
|
1522
|
+
) {
|
|
1523
|
+
throw new Error(`${source}.${field}: unexpected status`);
|
|
1524
|
+
}
|
|
1525
|
+
return value;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
function readString(record: Record<string, unknown>, source: string, field: string): string {
|
|
1529
|
+
const value = record[field];
|
|
1530
|
+
if (typeof value !== "string" || value.length === 0) throw new Error(`${source}.${field}: expected string`);
|
|
1531
|
+
return value;
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
function readNullableString(record: Record<string, unknown>, source: string, field: string): string | null {
|
|
1535
|
+
const value = record[field];
|
|
1536
|
+
if (value === null) return null;
|
|
1537
|
+
if (typeof value !== "string") throw new Error(`${source}.${field}: expected string or null`);
|
|
1538
|
+
return value;
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
function readNumber(record: Record<string, unknown>, source: string, field: string): number {
|
|
1542
|
+
const value = record[field];
|
|
1543
|
+
if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`${source}.${field}: expected number`);
|
|
1544
|
+
return value;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
function readOptionalNumber(record: Record<string, unknown>, source: string, field: string): number | undefined {
|
|
1548
|
+
if (!(field in record)) return undefined;
|
|
1549
|
+
const value = record[field];
|
|
1550
|
+
if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`${source}.${field}: expected number`);
|
|
1551
|
+
return value;
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
function readNullableNumber(record: Record<string, unknown>, source: string, field: string): number | null {
|
|
1555
|
+
const value = record[field];
|
|
1556
|
+
if (value === null) return null;
|
|
1557
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
1558
|
+
throw new Error(`${source}.${field}: expected number or null`);
|
|
1559
|
+
return value;
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
function readOptionalString(value: unknown): string | null {
|
|
1563
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
function parseJson(raw: string, source: string): unknown {
|
|
1567
|
+
try {
|
|
1568
|
+
return JSON.parse(raw) as unknown;
|
|
1569
|
+
} catch (error) {
|
|
1570
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1571
|
+
throw new Error(`${source}: invalid JSON: ${message}`);
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
1576
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
function payloadObject(payload: unknown): Record<string, unknown> | null {
|
|
1580
|
+
return isRecord(payload) ? payload : null;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
function parseMaybeJson(value: unknown): unknown {
|
|
1584
|
+
if (typeof value !== "string") return value;
|
|
1585
|
+
const trimmed = value.trim();
|
|
1586
|
+
if (trimmed.length === 0) return value;
|
|
1587
|
+
try {
|
|
1588
|
+
return JSON.parse(trimmed) as unknown;
|
|
1589
|
+
} catch {
|
|
1590
|
+
return value;
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
function firstContentBlock(payload: unknown, type: string): Record<string, unknown> | null {
|
|
1595
|
+
const obj = payloadObject(payload);
|
|
1596
|
+
const content = obj?.content;
|
|
1597
|
+
if (!Array.isArray(content)) return null;
|
|
1598
|
+
for (const block of content) {
|
|
1599
|
+
if (!isRecord(block)) continue;
|
|
1600
|
+
if (block.type === type) return block;
|
|
1601
|
+
}
|
|
1602
|
+
return null;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
function extractTextFromPayload(payload: unknown): string {
|
|
1606
|
+
if (typeof payload === "string") return payload;
|
|
1607
|
+
const obj = payloadObject(payload);
|
|
1608
|
+
if (obj === null) return "";
|
|
1609
|
+
if (typeof obj.text === "string") return obj.text;
|
|
1610
|
+
if (Array.isArray(obj.content)) {
|
|
1611
|
+
const parts: string[] = [];
|
|
1612
|
+
for (const block of obj.content) {
|
|
1613
|
+
if (!isRecord(block)) continue;
|
|
1614
|
+
if (block.type === "text" && typeof block.text === "string") parts.push(block.text);
|
|
1615
|
+
}
|
|
1616
|
+
return parts.join("\n");
|
|
1617
|
+
}
|
|
1618
|
+
return "";
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
function previewUnknown(value: unknown): string {
|
|
1622
|
+
if (value === undefined) return "";
|
|
1623
|
+
if (typeof value === "string") return truncateText(collapseWhitespace(value), TOOL_PREVIEW_MAX_CHARS);
|
|
1624
|
+
try {
|
|
1625
|
+
const text = JSON.stringify(value);
|
|
1626
|
+
return text === undefined ? "" : truncateText(collapseWhitespace(text), TOOL_PREVIEW_MAX_CHARS);
|
|
1627
|
+
} catch {
|
|
1628
|
+
return truncateText(collapseWhitespace(String(value)), TOOL_PREVIEW_MAX_CHARS);
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
function collapseWhitespace(text: string): string {
|
|
1633
|
+
return text.replace(/\s+/g, " ").trim();
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
async function writeJson(path: string, value: unknown): Promise<void> {
|
|
1637
|
+
await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
async function writeJsonl(path: string, rows: ReadonlyArray<unknown>): Promise<void> {
|
|
1641
|
+
await writeFile(path, rows.map((row) => JSON.stringify(row)).join("\n") + (rows.length === 0 ? "" : "\n"), "utf8");
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
function durationMs(startedAt: string, endedAt: string | null): number {
|
|
1645
|
+
if (endedAt === null) return 0;
|
|
1646
|
+
const start = Date.parse(startedAt);
|
|
1647
|
+
const end = Date.parse(endedAt);
|
|
1648
|
+
if (!Number.isFinite(start) || !Number.isFinite(end)) return 0;
|
|
1649
|
+
// Clamped for display, but counted first: a receipt whose endedAt precedes
|
|
1650
|
+
// its startedAt is skew, not a zero-length run.
|
|
1651
|
+
return Math.max(0, rawDurationMs(start, end));
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
function earliest(values: ReadonlyArray<string>): string | null {
|
|
1655
|
+
if (values.length === 0) return null;
|
|
1656
|
+
return [...values].sort(compareStrings)[0] ?? null;
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
function latest(values: ReadonlyArray<string>): string | null {
|
|
1660
|
+
if (values.length === 0) return null;
|
|
1661
|
+
return [...values].sort(compareStrings).at(-1) ?? null;
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
function uniqueStrings(values: ReadonlyArray<string>): string[] {
|
|
1665
|
+
return [...new Set(values)].sort(compareStrings);
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
function uniqueTags(values: ReadonlyArray<EvidenceTag>): EvidenceTag[] {
|
|
1669
|
+
return [...new Set(values)].sort(compareStrings);
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
function compareStrings(a: string, b: string): number {
|
|
1673
|
+
return a.localeCompare(b);
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
function compareNullableStrings(a: string | null, b: string | null): number {
|
|
1677
|
+
if (a === b) return 0;
|
|
1678
|
+
if (a === null) return 1;
|
|
1679
|
+
if (b === null) return -1;
|
|
1680
|
+
return compareStrings(a, b);
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
function compareRuns(a: RunEnvelope, b: RunEnvelope): number {
|
|
1684
|
+
const byStart = compareStrings(a.startedAt, b.startedAt);
|
|
1685
|
+
if (byStart !== 0) return byStart;
|
|
1686
|
+
return compareStrings(a.id, b.id);
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
function compareToolStats(a: ToolCallStat, b: ToolCallStat): number {
|
|
1690
|
+
return compareStrings(a.tool, b.tool);
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
function compareEvidenceToolEvents(a: EvidenceToolEvent, b: EvidenceToolEvent): number {
|
|
1694
|
+
const byTimestamp = compareNullableStrings(a.timestamp ?? null, b.timestamp ?? null);
|
|
1695
|
+
if (byTimestamp !== 0) return byTimestamp;
|
|
1696
|
+
const byRun = compareNullableStrings(a.runId, b.runId);
|
|
1697
|
+
if (byRun !== 0) return byRun;
|
|
1698
|
+
const bySession = compareNullableStrings(a.sessionId, b.sessionId);
|
|
1699
|
+
if (bySession !== 0) return bySession;
|
|
1700
|
+
const byTool = compareStrings(a.tool, b.tool);
|
|
1701
|
+
if (byTool !== 0) return byTool;
|
|
1702
|
+
return compareNullableStrings(a.toolCallId ?? null, b.toolCallId ?? null);
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
function truncateText(text: string, maxChars: number): string {
|
|
1706
|
+
if (text.length <= maxChars) return text;
|
|
1707
|
+
if (maxChars <= 3) return text.slice(0, maxChars);
|
|
1708
|
+
return `${text.slice(0, maxChars - 3)}...`;
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
function sanitizeEvidenceId(value: string): string {
|
|
1712
|
+
const clean = value.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1713
|
+
return clean.length === 0 ? "unknown" : clean;
|
|
1714
|
+
}
|