@iowarp/clio-coder 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +269 -458
- package/CONTRIBUTING.md +1 -1
- package/README.md +3 -3
- package/dist/{acp-BIYHVZIM.js → acp-S5R4RR5B.js} +7 -6
- package/dist/{agents-YT6SSRIT.js → agents-P6DMMVZY.js} +24 -21
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-5TWEIYDN.js → auth-2XCZLPKS.js} +12 -8
- package/dist/{chunk-GGXXDWE4.js → chunk-22NAGB7X.js} +2 -2
- package/dist/{chunk-WMSVI4G2.js → chunk-2LZI5CAG.js} +133 -13
- package/dist/{chunk-OAO4GE4M.js → chunk-2TZWSW76.js} +2 -2
- package/dist/{chunk-OOJYHWRB.js → chunk-34475P3I.js} +2 -2
- package/dist/{chunk-WVO7V2QY.js → chunk-35MKKU5R.js} +4 -4
- package/dist/{chunk-LBNRH5WM.js → chunk-3HZ5RWN2.js} +5 -5
- package/dist/{chunk-AGYYIBLL.js → chunk-3JLKSKD7.js} +2 -2
- package/dist/{chunk-MBS4V7ZP.js → chunk-4JUF2NNX.js} +7 -7
- package/dist/{chunk-ZDOOVTXZ.js → chunk-4OC57DA6.js} +27 -4
- package/dist/chunk-5M54SPOL.js +926 -0
- package/dist/{chunk-STBPMHSX.js → chunk-7RXG6QRZ.js} +51 -11
- package/dist/{chunk-77VKQEHF.js → chunk-A2GZF7DC.js} +5 -5
- package/dist/{chunk-A3CYT5EX.js → chunk-AD2SYQYC.js} +55 -2
- package/dist/chunk-AOCYTWAV.js +449 -0
- package/dist/chunk-BEY543CS.js +258 -0
- package/dist/{chunk-6N5PTWMY.js → chunk-BP4OYD6A.js} +32 -13
- package/dist/chunk-BPGS2WCQ.js +612 -0
- package/dist/{chunk-J5HN4RYU.js → chunk-BRXQQJFP.js} +8 -8
- package/dist/chunk-CFGTUFWB.js +67 -0
- package/dist/{chunk-CBCAPZAA.js → chunk-E25LMLRW.js} +2 -2
- package/dist/{chunk-G2DE3C7R.js → chunk-EDRHSCIE.js} +4 -4
- package/dist/{chunk-4KLWL3UC.js → chunk-EFADSJET.js} +2 -2
- package/dist/{chunk-M6SHUN7Q.js → chunk-FO5ZOVUY.js} +2 -2
- package/dist/chunk-FYYLNIL5.js +313 -0
- package/dist/{chunk-EPVUXGXG.js → chunk-HV5X7OR2.js} +14 -12
- package/dist/{chunk-TZTZS7QK.js → chunk-HXG4IURW.js} +5 -3
- package/dist/{chunk-IGLFWIYI.js → chunk-K6WL7QZT.js} +3 -3
- package/dist/chunk-K7VKOLQQ.js +15 -0
- package/dist/{chunk-BMEMKKIT.js → chunk-KOHPCX4K.js} +2 -2
- package/dist/{chunk-V4RXGQ5Q.js → chunk-KRPY7NTG.js} +10 -7
- package/dist/chunk-LL4KHSZI.js +22 -0
- package/dist/{chunk-KJ5LWLOE.js → chunk-MEQ45TQ4.js} +15 -9
- package/dist/{chunk-5UUP6MWO.js → chunk-MV3K5QF2.js} +5 -436
- package/dist/{chunk-AO4RKG4M.js → chunk-N4CZJQRK.js} +5 -5
- package/dist/{chunk-ARBGF5F7.js → chunk-NILBFAPG.js} +14 -8
- package/dist/chunk-OZNBF4L3.js +23 -0
- package/dist/{verify-G6V4D2G7.js → chunk-PCZJO5TI.js} +127 -42
- package/dist/chunk-QQK64KLB.js +1360 -0
- package/dist/{chunk-6EJV5X2W.js → chunk-QQL5RT5M.js} +979 -1619
- package/dist/{chunk-LZSJBIVT.js → chunk-QWU7ZBO7.js} +70 -720
- package/dist/{chunk-2EHAIA3X.js → chunk-RD5U66HV.js} +3 -3
- package/dist/{chunk-OKGUZO2U.js → chunk-SPULKLCF.js} +4 -3
- package/dist/{chunk-OQ33BKR3.js → chunk-TTNYS3EA.js} +3 -60
- package/dist/chunk-TW3WDMVS.js +677 -0
- package/dist/chunk-TZSKNMZG.js +434 -0
- package/dist/{chunk-7MNJORFF.js → chunk-UL3WSD3F.js} +6 -1
- package/dist/{chunk-7EYHLWU7.js → chunk-UZHIZC5S.js} +7 -7
- package/dist/{chunk-QTYWRVRA.js → chunk-VAWWTKDP.js} +8 -8
- package/dist/{chunk-X75S7HFS.js → chunk-VEZEGCGW.js} +214 -20
- package/dist/{chunk-OHHN2SO4.js → chunk-VMNQ6OZA.js} +98 -202
- package/dist/chunk-VSNATDE6.js +122 -0
- package/dist/chunk-W6GROXXM.js +69 -0
- package/dist/chunk-WPQLXFOZ.js +375 -0
- package/dist/{chunk-ORBHGJC5.js → chunk-WR67VIZY.js} +3 -3
- package/dist/{chunk-3ZXDFGR5.js → chunk-X6COSD2O.js} +5 -5
- package/dist/chunk-ZGVHUX3M.js +66 -0
- package/dist/{chunk-MAW544W2.js → chunk-ZWMF7253.js} +4 -4
- package/dist/{chunk-MQSRRFWA.js → chunk-ZYKPLLNQ.js} +563 -546
- package/dist/cli/index.js +27 -23
- package/dist/{clio-4LY5K2AC.js → clio-J5JIOIDS.js} +7 -6
- package/dist/{code-nav-7AX6FYE6.js → code-nav-AXCXSBHX.js} +5 -3
- package/dist/{config-GTLUW2PR.js → config-OEBMIN2U.js} +37 -27
- package/dist/{configure-R6A64DHX.js → configure-PUQOSIXQ.js} +16 -13
- package/dist/{context-5VKGUVJJ.js → context-EKDCKUUZ.js} +82 -7
- package/dist/{context-RW5HC47S.js → context-MGSE4Z2T.js} +33 -23
- package/dist/{context-JFZEJ7W5.js → context-URSXPBCK.js} +17 -9
- package/dist/{context-clear-6ZHBAZZT.js → context-clear-KDAJRNUK.js} +33 -23
- package/dist/context-working-set-SBKMPPI2.js +1552 -0
- package/dist/{dispatch-runner-VKBRCWQC.js → dispatch-runner-MSWN72NK.js} +43 -29
- package/dist/{doctor-KI767GSN.js → doctor-7BSE27PJ.js} +10 -10
- package/dist/{eval-XSSNATB4.js → eval-IZGDOO4H.js} +9 -8
- package/dist/{evidence-UA6AWDQQ.js → evidence-SR7WXB5B.js} +51 -23
- package/dist/{evolve-QNTFGV6Z.js → evolve-K7VE2CBX.js} +30 -20
- package/dist/{fleet-Q7UOMUSG.js → fleet-7XMJNQNF.js} +48 -38
- package/dist/{fleet-preflight-DDN536IT.js → fleet-preflight-AQNAH644.js} +3 -3
- package/dist/{init-WBB65ZHQ.js → init-JGNPAYXT.js} +41 -31
- package/dist/{memory-MD3O64RI.js → memory-4ALKDJ4Q.js} +32 -22
- package/dist/{models-BZU34YWD.js → models-ZMMLFJNN.js} +22 -19
- package/dist/{monitor-MEQA5C3I.js → monitor-2F3T5KHP.js} +55 -43
- package/dist/{orchestrator-CGFKEP27.js → orchestrator-ORHT43JB.js} +2507 -1896
- package/dist/{reset-L2FQEE3E.js → reset-NXGTYNUO.js} +4 -3
- package/dist/{run-IV4Q6RLN.js → run-RF4WJGMT.js} +51 -41
- package/dist/{share-S5BZQC5I.js → share-UT3W6E4M.js} +5 -4
- package/dist/{skills-LQEKRDTN.js → skills-PSACKC5Q.js} +2 -2
- package/dist/{skills-eval-3DC4HEWS.js → skills-eval-WJSI55RZ.js} +34 -24
- package/dist/{targets-C4SSGQOB.js → targets-PIIRAOYS.js} +23 -20
- package/dist/{terminal-lease-IT5JW2NR.js → terminal-lease-ULWXWNVY.js} +5 -4
- package/dist/{upgrade-7TT7SQ3G.js → upgrade-346TZ6AV.js} +18 -17
- package/dist/{usage-GV4PKT3M.js → usage-6KKXR32N.js} +34 -24
- package/dist/verifiers-4UUM6TEE.js +1214 -0
- package/dist/verify-X5HDROLA.js +25 -0
- package/dist/{wiki-generate-DQF6Z66B.js → wiki-generate-7STOCIFZ.js} +42 -31
- package/dist/worker/entry.js +33 -24
- package/docs/README.md +8 -7
- package/docs/acp.md +1 -1
- package/docs/alcf-provider.md +1 -1
- package/docs/architecture.md +2 -2
- package/docs/artifact-versions.md +1 -1
- package/docs/built-in-agents.md +1 -1
- package/docs/capacity-and-scheduling.md +1 -1
- package/docs/commands-and-modes.md +53 -21
- package/docs/config-knobs-audit.md +1 -2
- package/docs/configuration-and-targets.md +15 -1
- package/docs/context-engine.md +64 -12
- package/docs/context-working-set.md +194 -0
- package/docs/development-pipeline.md +1 -1
- package/docs/documentation-coverage.md +5 -5
- package/docs/documentation-guide.md +6 -5
- package/docs/environment-variables.md +2 -1
- package/docs/eval-runner.md +1 -1
- package/docs/evals-internal.md +14 -1
- package/docs/evidence-and-memory.md +74 -2
- package/docs/evolution.md +1 -1
- package/docs/exit-codes-and-output.md +1 -1
- package/docs/extensions-and-sharing.md +2 -2
- package/docs/fleet-dispatch.md +22 -7
- package/docs/glossary.md +21 -1
- package/docs/installation-and-lifecycle.md +6 -6
- package/docs/middleware-and-components.md +1 -1
- package/docs/model-catalog.md +7 -9
- package/docs/observability.md +4 -4
- package/docs/performance-methodology.md +2 -2
- package/docs/proactive-memory.md +1 -1
- package/docs/prompt-envelope-and-tools.md +4 -4
- package/docs/provider-adapter-cookbook.md +1 -1
- package/docs/release-cut-checklist.md +35 -35
- package/docs/safety-model.md +23 -4
- package/docs/scientific-validation.md +21 -3
- package/docs/session-lifecycle.md +3 -3
- package/docs/skills-marketplace.md +1 -1
- package/docs/tool-usage.md +79 -12
- package/docs/trace-store.md +1 -1
- package/docs/troubleshooting.md +1 -1
- package/docs/tui-design.md +2 -2
- package/docs/worker-dispatch-mechanics.md +11 -1
- package/package.json +8 -11
- package/skills/meta/clio-test/SKILL.md +20 -17
- package/skills/meta/clio-test/evals.md +3 -3
- package/skills/meta/clio-test/references/harness.md +35 -6
- package/skills/meta/clio-test/references/test-map.md +20 -10
- package/skills/registry.yaml +2 -2
- package/skills/skill-marketplace.json +1 -1
- package/src/cli/context-working-set.ts +513 -0
- package/src/cli/context.ts +8 -0
- package/src/cli/evidence.ts +20 -2
- package/src/cli/index.ts +4 -0
- package/src/cli/verifiers.ts +325 -0
- package/src/core/bash-exec.ts +39 -14
- package/src/core/bus-events.ts +19 -4
- package/src/core/config.ts +54 -0
- package/src/core/defaults.ts +50 -3
- package/src/core/git-commit-attribution.ts +46 -21
- package/src/core/verification-scripts.ts +6 -0
- package/src/domains/agents/builtins/verifier.md +3 -0
- package/src/domains/config/classify.ts +1 -0
- package/src/domains/config/keybindings.ts +3 -3
- package/src/domains/context/working-set/contract.ts +161 -0
- package/src/domains/context/working-set/defaults.ts +28 -0
- package/src/domains/context/working-set/engine.ts +203 -0
- package/src/domains/context/working-set/fold.ts +62 -0
- package/src/domains/context/working-set/horizon.ts +38 -0
- package/src/domains/context/working-set/marker.ts +103 -0
- package/src/domains/context/working-set/path-index.ts +436 -0
- package/src/domains/context/working-set/payload.ts +152 -0
- package/src/domains/context/working-set/policies/age-horizon.ts +55 -0
- package/src/domains/context/working-set/policies/index.ts +21 -0
- package/src/domains/context/working-set/policies/structural.ts +160 -0
- package/src/domains/context/working-set/project.ts +132 -0
- package/src/domains/context/working-set/protect.ts +109 -0
- package/src/domains/context/working-set/recall.ts +177 -0
- package/src/domains/context/working-set/replay/controls.ts +112 -0
- package/src/domains/context/working-set/replay/load-clio.ts +199 -0
- package/src/domains/context/working-set/replay/metrics.ts +185 -0
- package/src/domains/context/working-set/replay/reference-graph.ts +79 -0
- package/src/domains/context/working-set/replay/report.ts +139 -0
- package/src/domains/context/working-set/replay/runner.ts +325 -0
- package/src/domains/context/working-set/replay/synthetic.ts +422 -0
- package/src/domains/context/working-set/replay/trace.ts +21 -0
- package/src/domains/context/working-set/visible.ts +54 -0
- package/src/domains/evidence/build.ts +112 -45
- package/src/domains/evidence/eval.ts +24 -7
- package/src/domains/evidence/index.ts +53 -0
- package/src/domains/evidence/ordering.ts +12 -0
- package/src/domains/evidence/run-trust.ts +221 -0
- package/src/domains/evidence/store.ts +46 -6
- package/src/domains/evidence/trust-status.ts +854 -0
- package/src/domains/evidence/types.ts +26 -0
- package/src/domains/middleware/memory-intervention.ts +3 -0
- package/src/domains/middleware/stalled-turn.ts +165 -4
- package/src/domains/safety/autonomy.ts +1 -1
- package/src/domains/safety/default-path-policy.ts +8 -0
- package/src/domains/safety/finish-contract.ts +4 -3
- package/src/domains/safety/policy-engine.ts +48 -6
- package/src/domains/session/compaction/compact.ts +23 -1
- package/src/domains/session/compaction/cut-point.ts +2 -0
- package/src/domains/session/compaction/tokens.ts +16 -1
- package/src/domains/session/context-ledger.ts +2 -0
- package/src/domains/session/entries.ts +107 -1
- package/src/domains/session/manager.ts +9 -2
- package/src/domains/session/migrations/index.ts +22 -3
- package/src/engine/acp/server.ts +3 -0
- package/src/engine/agent.ts +18 -1
- package/src/engine/session.ts +9 -3
- package/src/entry/orchestrator.ts +16 -4
- package/src/interactive/chat-loop-messages.ts +18 -6
- package/src/interactive/chat-panel.ts +571 -244
- package/src/interactive/chat-renderer.ts +79 -39
- package/src/interactive/context-meter.ts +10 -0
- package/src/interactive/context-overlay.ts +81 -6
- package/src/interactive/context-recall-command.ts +110 -0
- package/src/interactive/editor-submit.ts +26 -1
- package/src/interactive/footer/widgets.ts +22 -20
- package/src/interactive/footer-panel.ts +6 -1
- package/src/interactive/interactive-application.ts +2 -0
- package/src/interactive/interactive-event-projection.ts +12 -0
- package/src/interactive/interactive-slash-runtime.ts +49 -8
- package/src/interactive/model-session-replay.ts +21 -0
- package/src/interactive/overlay-general-openers.ts +6 -0
- package/src/interactive/overlay-session-lifecycle.ts +8 -4
- package/src/interactive/overlays/ask-user.ts +146 -24
- package/src/interactive/renderers/tool-execution.ts +167 -56
- package/src/interactive/session-transcript.ts +2 -2
- package/src/interactive/slash-commands.ts +29 -2
- package/src/interactive/status/index.ts +12 -1
- package/src/interactive/status/reasoning.ts +87 -0
- package/src/interactive/status/summary.ts +13 -2
- package/src/interactive/transcript-detail.ts +120 -0
- package/src/interactive/turn-context.ts +238 -88
- package/src/interactive/turn-middleware.ts +6 -6
- package/src/tools/agent-tools.ts +11 -4
- package/src/tools/bash.ts +144 -82
- package/src/tools/builtin-tool-catalog.ts +18 -6
- package/src/tools/context/index.ts +105 -3
- package/src/tools/context/surface.ts +3 -2
- package/src/tools/core-bootstrap.ts +21 -0
- package/src/tools/dispatch-runner.ts +9 -7
- package/src/tools/monitor.ts +28 -20
- package/src/tools/presentation.ts +107 -0
- package/src/tools/registry.ts +65 -7
- package/src/tools/result-disposition.ts +550 -0
- package/src/tools/result-shaping.ts +262 -19
- package/src/tools/safe-exec.ts +2 -0
- package/src/tools/verify/authoring.ts +1119 -0
- package/src/tools/verify/catalog.ts +346 -0
- package/src/tools/verify/index.ts +13 -3
- package/src/tools/verify/scripts.ts +135 -37
- package/src/tools/verify/surface.ts +9 -5
- package/src/tools/worker-evidence.ts +35 -12
- package/dist/chunk-MNA4JGU4.js +0 -255
- package/dist/chunk-SRF2PJNW.js +0 -184
- package/dist/chunk-T6YILFSB.js +0 -80
- package/dist/chunk-VAKQQHWR.js +0 -434
package/src/cli/evidence.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { InvalidIdError } from "../core/safe-id.js";
|
|
2
2
|
import { clioDataDir, clioStateDir } from "../core/xdg.js";
|
|
3
|
-
import type { EvidenceOverview, EvidenceRunProvenance } from "../domains/evidence/index.js";
|
|
3
|
+
import type { CanonicalTrustStatus, EvidenceOverview, EvidenceRunProvenance } from "../domains/evidence/index.js";
|
|
4
4
|
import {
|
|
5
5
|
buildEvalEvidence,
|
|
6
6
|
buildEvidence,
|
|
@@ -178,7 +178,7 @@ function renderEvidence(
|
|
|
178
178
|
input: Awaited<ReturnType<typeof inspectEvidence>>,
|
|
179
179
|
provenance: ReadonlyArray<EvidenceRunProvenance> = [],
|
|
180
180
|
): void {
|
|
181
|
-
const { overview, findings } = input;
|
|
181
|
+
const { overview, findings, trustStatus } = input;
|
|
182
182
|
process.stdout.write(`evidence: ${overview.evidenceId}\n`);
|
|
183
183
|
process.stdout.write(`source: ${formatSource(overview)}\n`);
|
|
184
184
|
process.stdout.write(`generated: ${overview.generatedAt}\n`);
|
|
@@ -188,6 +188,13 @@ function renderEvidence(
|
|
|
188
188
|
process.stdout.write(`blocked tools: ${overview.totals.blockedToolCalls}\n`);
|
|
189
189
|
process.stdout.write(`tags: ${formatList(overview.tags)}\n`);
|
|
190
190
|
process.stdout.write(`findings: ${findings.length}\n`);
|
|
191
|
+
if (trustStatus.projection === "historical_format") {
|
|
192
|
+
process.stdout.write("trust status: unavailable (historical bundle without canonical projection)\n");
|
|
193
|
+
} else {
|
|
194
|
+
for (const run of trustStatus.runs) {
|
|
195
|
+
process.stdout.write(`trust ${run.runId}: ${formatTrustStatus(run.status)}\n`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
191
198
|
// Provenance is printed only for runs whose receipts carry it, so a legacy
|
|
192
199
|
// bundle prints nothing new here.
|
|
193
200
|
for (const { runId, view } of provenance) {
|
|
@@ -197,6 +204,17 @@ function renderEvidence(
|
|
|
197
204
|
process.stdout.write(`files: ${formatList(overview.files)}\n`);
|
|
198
205
|
}
|
|
199
206
|
|
|
207
|
+
function formatTrustStatus(status: CanonicalTrustStatus): string {
|
|
208
|
+
return [
|
|
209
|
+
`artifactIntegrity=${status.artifactIntegrity.state}`,
|
|
210
|
+
`validationGrounding=${status.validationGrounding.state}`,
|
|
211
|
+
`independentReview=${status.independentReview.state}`,
|
|
212
|
+
`contextProvenance=${status.contextProvenance.state}`,
|
|
213
|
+
`autonomyEnforcement=${status.autonomyEnforcement.state}`,
|
|
214
|
+
`completionEvidence=${status.completionEvidence.state}`,
|
|
215
|
+
].join(" ");
|
|
216
|
+
}
|
|
217
|
+
|
|
200
218
|
function renderEvidenceList(overviews: ReadonlyArray<EvidenceOverview>): void {
|
|
201
219
|
process.stdout.write(`${overviews.length} evidence artifacts\n`);
|
|
202
220
|
if (overviews.length === 0) return;
|
package/src/cli/index.ts
CHANGED
|
@@ -52,6 +52,8 @@ Usage:
|
|
|
52
52
|
clio-coder context wiki [--update] [--status] [--depth auto|simple|medium|detailed] [--target <id>] [--model <id>]
|
|
53
53
|
clio-coder context reset [--all] [--yes] clear accumulated project context artifacts
|
|
54
54
|
clio-coder context index [--json] build the codewiki index without model calls
|
|
55
|
+
clio-coder context replay --sessions <path>... compare working-set policies over Clio ledgers
|
|
56
|
+
clio-coder context working-set --session <id|path> inspect one session's working-set state
|
|
55
57
|
clio-coder uninstall remove all Clio Coder state; --remove-binary also unlinks the launcher
|
|
56
58
|
clio-coder upgrade upgrade Clio Coder and run pending migrations
|
|
57
59
|
clio-coder agents list discovered agent recipes
|
|
@@ -63,6 +65,7 @@ Usage:
|
|
|
63
65
|
clio-coder trace query or view the durable dispatch trace mirror
|
|
64
66
|
clio-coder extensions install, list, enable, disable, or remove extension packages
|
|
65
67
|
clio-coder skills list, inspect, validate, or install skills
|
|
68
|
+
clio-coder verifiers discover, author, validate, edit, or dry-run project checks
|
|
66
69
|
clio-coder docs [topic] serve the HTML docs of a source checkout on 127.0.0.1 (--no-open to skip browser)
|
|
67
70
|
clio-coder dev <command> harness instruments; run 'clio-coder dev' for the list
|
|
68
71
|
clio-coder --help, -h this message
|
|
@@ -243,6 +246,7 @@ const COMMAND_HANDLERS = new Map<string, CommandHandler>([
|
|
|
243
246
|
["ext", extensionsCommand],
|
|
244
247
|
["fleet", async (subArgs) => (await import("./fleet.js")).runFleetCommand(subArgs)],
|
|
245
248
|
["skills", async (subArgs) => (await import("./skills.js")).runSkillsCommand(subArgs)],
|
|
249
|
+
["verifiers", async (subArgs) => (await import("./verifiers.js")).runVerifiersCommand(subArgs)],
|
|
246
250
|
["docs", async (subArgs) => (await import("./docs.js")).runDocsCommand(subArgs)],
|
|
247
251
|
["share", async (subArgs) => (await import("./share.js")).runShareCommand(subArgs)],
|
|
248
252
|
["export", async (subArgs) => (await import("./share.js")).runExportCommand(subArgs)],
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { SAFE_EXEC_DEFAULT_TIMEOUT_MS } from "../core/safe-exec.js";
|
|
2
|
+
import {
|
|
3
|
+
createVerifierDraft,
|
|
4
|
+
discoverVerifierAuthoring,
|
|
5
|
+
previewVerifierDraft,
|
|
6
|
+
runVerifierAuthoringWorkflow,
|
|
7
|
+
type VerifierRevision,
|
|
8
|
+
} from "../tools/verify/authoring.js";
|
|
9
|
+
import { loadProjectVerifierCatalog, PROJECT_VERIFIER_CATALOG_RELATIVE_PATH } from "../tools/verify/catalog.js";
|
|
10
|
+
import { verifyTool } from "../tools/verify/index.js";
|
|
11
|
+
import { printError, printOk } from "./argv.js";
|
|
12
|
+
|
|
13
|
+
const HELP = `Project verifier authoring
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
clio-coder verifiers discover
|
|
17
|
+
clio-coder verifiers author [--exclude <id>] [--rename <old>=<new>] [--dry-run <id>] [--yes]
|
|
18
|
+
clio-coder verifiers validate
|
|
19
|
+
clio-coder verifiers dry-run <id>
|
|
20
|
+
clio-coder verifiers add --id <id> --description <text> --command <json-argv> [fields] [--yes]
|
|
21
|
+
clio-coder verifiers edit <id> [--description <text>] [--command <json-argv>] [fields] [--yes]
|
|
22
|
+
clio-coder verifiers rename <old> <new> [--yes]
|
|
23
|
+
clio-coder verifiers remove <id> [--yes]
|
|
24
|
+
|
|
25
|
+
Fields:
|
|
26
|
+
--cwd <path> repository-relative working directory (default .)
|
|
27
|
+
--timeout-ms <number> bounded timeout in milliseconds (default 120000)
|
|
28
|
+
--tags <a,b> comma-separated catalog tags
|
|
29
|
+
|
|
30
|
+
Discovery and every preview are read-only. Mutating commands write only with
|
|
31
|
+
--yes after printing the exact argv, cwd, timeout, tags, source provenance, and
|
|
32
|
+
effective execution authority. dry-run invokes the production verify path.
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
const VALUE_OPTIONS = new Set([
|
|
36
|
+
"--id",
|
|
37
|
+
"--description",
|
|
38
|
+
"--command",
|
|
39
|
+
"--cwd",
|
|
40
|
+
"--timeout-ms",
|
|
41
|
+
"--tags",
|
|
42
|
+
"--exclude",
|
|
43
|
+
"--rename",
|
|
44
|
+
"--dry-run",
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
interface ParsedAuthoringArgs {
|
|
48
|
+
yes: boolean;
|
|
49
|
+
help: boolean;
|
|
50
|
+
positional: string[];
|
|
51
|
+
values: Map<string, string[]>;
|
|
52
|
+
error?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function parseAuthoringArgs(argv: ReadonlyArray<string>): ParsedAuthoringArgs {
|
|
56
|
+
const positional: string[] = [];
|
|
57
|
+
const values = new Map<string, string[]>();
|
|
58
|
+
let yes = false;
|
|
59
|
+
let help = false;
|
|
60
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
61
|
+
const arg = argv[index];
|
|
62
|
+
if (arg === "--yes") {
|
|
63
|
+
yes = true;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (arg === "--help" || arg === "-h") {
|
|
67
|
+
help = true;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (arg?.startsWith("--") === true) {
|
|
71
|
+
if (!VALUE_OPTIONS.has(arg)) return { yes, help, positional, values, error: `unknown option: ${arg}` };
|
|
72
|
+
const value = argv[index + 1];
|
|
73
|
+
if (value === undefined || value.startsWith("--")) {
|
|
74
|
+
return { yes, help, positional, values, error: `${arg} requires a value` };
|
|
75
|
+
}
|
|
76
|
+
const prior = values.get(arg) ?? [];
|
|
77
|
+
prior.push(value);
|
|
78
|
+
values.set(arg, prior);
|
|
79
|
+
index += 1;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (arg !== undefined) positional.push(arg);
|
|
83
|
+
}
|
|
84
|
+
return { yes, help, positional, values };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function oneValue(parsed: ParsedAuthoringArgs, option: string): string | undefined {
|
|
88
|
+
return parsed.values.get(option)?.at(-1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function parseCommand(value: string | undefined): string[] | Error {
|
|
92
|
+
if (value === undefined) return new Error("--command is required and must be a JSON argv array");
|
|
93
|
+
let parsed: unknown;
|
|
94
|
+
try {
|
|
95
|
+
parsed = JSON.parse(value) as unknown;
|
|
96
|
+
} catch (error) {
|
|
97
|
+
return new Error(`--command must be valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
98
|
+
}
|
|
99
|
+
if (!Array.isArray(parsed) || parsed.length === 0 || parsed.some((entry) => typeof entry !== "string")) {
|
|
100
|
+
return new Error("--command must be a non-empty JSON string array");
|
|
101
|
+
}
|
|
102
|
+
return parsed as string[];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function parseTimeout(value: string | undefined): number | Error {
|
|
106
|
+
if (value === undefined) return SAFE_EXEC_DEFAULT_TIMEOUT_MS;
|
|
107
|
+
const parsed = Number(value);
|
|
108
|
+
if (!Number.isInteger(parsed) || parsed <= 0) return new Error("--timeout-ms must be a positive integer");
|
|
109
|
+
return parsed;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function tags(value: string | undefined): string[] {
|
|
113
|
+
return value === undefined
|
|
114
|
+
? []
|
|
115
|
+
: value
|
|
116
|
+
.split(",")
|
|
117
|
+
.map((tag) => tag.trim())
|
|
118
|
+
.filter((tag) => tag.length > 0);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function requiredValue(parsed: ParsedAuthoringArgs, option: string): string | Error {
|
|
122
|
+
const value = oneValue(parsed, option);
|
|
123
|
+
return value === undefined || value.length === 0 ? new Error(`${option} is required`) : value;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function authorRevisions(parsed: ParsedAuthoringArgs): VerifierRevision[] | Error {
|
|
127
|
+
const revisions: VerifierRevision[] = [];
|
|
128
|
+
for (const id of parsed.values.get("--exclude") ?? []) revisions.push({ kind: "remove", id });
|
|
129
|
+
for (const rename of parsed.values.get("--rename") ?? []) {
|
|
130
|
+
const separator = rename.indexOf("=");
|
|
131
|
+
if (separator <= 0 || separator === rename.length - 1) {
|
|
132
|
+
return new Error("--rename must use <old>=<new>");
|
|
133
|
+
}
|
|
134
|
+
revisions.push({ kind: "rename", id: rename.slice(0, separator), newId: rename.slice(separator + 1) });
|
|
135
|
+
}
|
|
136
|
+
return revisions;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function addRevision(parsed: ParsedAuthoringArgs): VerifierRevision | Error {
|
|
140
|
+
const id = requiredValue(parsed, "--id");
|
|
141
|
+
if (id instanceof Error) return id;
|
|
142
|
+
const description = requiredValue(parsed, "--description");
|
|
143
|
+
if (description instanceof Error) return description;
|
|
144
|
+
const command = parseCommand(oneValue(parsed, "--command"));
|
|
145
|
+
if (command instanceof Error) return command;
|
|
146
|
+
const timeoutMs = parseTimeout(oneValue(parsed, "--timeout-ms"));
|
|
147
|
+
if (timeoutMs instanceof Error) return timeoutMs;
|
|
148
|
+
return {
|
|
149
|
+
kind: "add",
|
|
150
|
+
check: {
|
|
151
|
+
id,
|
|
152
|
+
description,
|
|
153
|
+
command,
|
|
154
|
+
cwd: oneValue(parsed, "--cwd") ?? ".",
|
|
155
|
+
timeoutMs,
|
|
156
|
+
tags: tags(oneValue(parsed, "--tags")),
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function editRevision(parsed: ParsedAuthoringArgs): VerifierRevision | Error {
|
|
162
|
+
const id = parsed.positional[1];
|
|
163
|
+
if (id === undefined) return new Error("edit requires a check ID");
|
|
164
|
+
const changes: Extract<VerifierRevision, { kind: "edit" }>["changes"] = {};
|
|
165
|
+
const description = oneValue(parsed, "--description");
|
|
166
|
+
if (description !== undefined) changes.description = description;
|
|
167
|
+
const commandValue = oneValue(parsed, "--command");
|
|
168
|
+
if (commandValue !== undefined) {
|
|
169
|
+
const command = parseCommand(commandValue);
|
|
170
|
+
if (command instanceof Error) return command;
|
|
171
|
+
changes.command = command;
|
|
172
|
+
}
|
|
173
|
+
const cwd = oneValue(parsed, "--cwd");
|
|
174
|
+
if (cwd !== undefined) changes.cwd = cwd;
|
|
175
|
+
const timeoutValue = oneValue(parsed, "--timeout-ms");
|
|
176
|
+
if (timeoutValue !== undefined) {
|
|
177
|
+
const timeoutMs = parseTimeout(timeoutValue);
|
|
178
|
+
if (timeoutMs instanceof Error) return timeoutMs;
|
|
179
|
+
changes.timeoutMs = timeoutMs;
|
|
180
|
+
}
|
|
181
|
+
const tagValue = oneValue(parsed, "--tags");
|
|
182
|
+
if (tagValue !== undefined) changes.tags = tags(tagValue);
|
|
183
|
+
if (Object.keys(changes).length === 0) return new Error("edit requires at least one changed field");
|
|
184
|
+
return { kind: "edit", id, changes };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function mutationRevision(parsed: ParsedAuthoringArgs): VerifierRevision | Error {
|
|
188
|
+
const command = parsed.positional[0];
|
|
189
|
+
if (command === "add") return addRevision(parsed);
|
|
190
|
+
if (command === "edit") return editRevision(parsed);
|
|
191
|
+
if (command === "rename") {
|
|
192
|
+
const id = parsed.positional[1];
|
|
193
|
+
const newId = parsed.positional[2];
|
|
194
|
+
if (id === undefined || newId === undefined) return new Error("rename requires <old> and <new> IDs");
|
|
195
|
+
return { kind: "rename", id, newId };
|
|
196
|
+
}
|
|
197
|
+
if (command === "remove") {
|
|
198
|
+
const id = parsed.positional[1];
|
|
199
|
+
if (id === undefined) return new Error("remove requires a check ID");
|
|
200
|
+
return { kind: "remove", id };
|
|
201
|
+
}
|
|
202
|
+
return new Error(`unknown verifiers command: ${command ?? "(none)"}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function printDryRun(id: string, result: Awaited<ReturnType<typeof verifyTool.run>>): number {
|
|
206
|
+
if (result.kind === "error") {
|
|
207
|
+
printError(`dry-run '${id}' failed: ${result.message}`);
|
|
208
|
+
return 1;
|
|
209
|
+
}
|
|
210
|
+
process.stdout.write(`Dry run '${id}':\n${result.output}${result.output.endsWith("\n") ? "" : "\n"}`);
|
|
211
|
+
return 0;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function executeWorkflow(
|
|
215
|
+
parsed: ParsedAuthoringArgs,
|
|
216
|
+
options: { includeProposals: boolean; revisions: VerifierRevision[] },
|
|
217
|
+
): Promise<number> {
|
|
218
|
+
let printedPreview = false;
|
|
219
|
+
const dryRunCheckIds = parsed.values.get("--dry-run") ?? [];
|
|
220
|
+
const result = await runVerifierAuthoringWorkflow({
|
|
221
|
+
includeProposals: options.includeProposals,
|
|
222
|
+
initialRevisions: options.revisions,
|
|
223
|
+
confirmed: parsed.yes,
|
|
224
|
+
decide(context) {
|
|
225
|
+
process.stdout.write(`${context.preview}\n`);
|
|
226
|
+
printedPreview = true;
|
|
227
|
+
if (!parsed.yes || (context.draft.checks.length === 0 && options.revisions.length === 0)) {
|
|
228
|
+
return { kind: "reject" };
|
|
229
|
+
}
|
|
230
|
+
return { kind: "confirm", dryRunCheckIds };
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
if (result.status === "invalid") {
|
|
234
|
+
if (!printedPreview && result.preview !== undefined) process.stdout.write(`${result.preview}\n`);
|
|
235
|
+
printError(result.reason);
|
|
236
|
+
if (result.wrote && result.path !== undefined) {
|
|
237
|
+
process.stderr.write(` ${result.path} was written; repair or remove it before running a check.\n`);
|
|
238
|
+
}
|
|
239
|
+
return 1;
|
|
240
|
+
}
|
|
241
|
+
if (result.status === "rejected") {
|
|
242
|
+
if (!parsed.yes) {
|
|
243
|
+
process.stdout.write("Review complete. Nothing changed; rerun the same command with --yes to write this preview.\n");
|
|
244
|
+
} else {
|
|
245
|
+
process.stdout.write(
|
|
246
|
+
"Nothing changed because the preview contains no catalog checks. Package checks remain active; unsupported commands can be added with `clio-coder verifiers add`.\n",
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
return 0;
|
|
250
|
+
}
|
|
251
|
+
printOk(`wrote ${result.path}; production parser accepted the catalog`);
|
|
252
|
+
let code = 0;
|
|
253
|
+
for (const dryRun of result.dryRuns) code = Math.max(code, printDryRun(dryRun.id, dryRun.result));
|
|
254
|
+
return code;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async function discoverCommand(): Promise<number> {
|
|
258
|
+
const discovery = discoverVerifierAuthoring();
|
|
259
|
+
if (!discovery.ok) {
|
|
260
|
+
printError(discovery.reason);
|
|
261
|
+
process.stderr.write(`${discovery.manualEntry}\n`);
|
|
262
|
+
return 1;
|
|
263
|
+
}
|
|
264
|
+
process.stdout.write(`${previewVerifierDraft(createVerifierDraft(discovery))}\n`);
|
|
265
|
+
return 0;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function validateCommand(): number {
|
|
269
|
+
const loaded = loadProjectVerifierCatalog(process.cwd());
|
|
270
|
+
if (!loaded.ok) {
|
|
271
|
+
printError(`production catalog parser rejected ${loaded.reason}`);
|
|
272
|
+
return 1;
|
|
273
|
+
}
|
|
274
|
+
if (loaded.source === null) {
|
|
275
|
+
process.stdout.write(`No ${PROJECT_VERIFIER_CATALOG_RELATIVE_PATH} exists. Run \`clio-coder verifiers author\`.\n`);
|
|
276
|
+
return 0;
|
|
277
|
+
}
|
|
278
|
+
printOk(
|
|
279
|
+
`production catalog parser accepted ${PROJECT_VERIFIER_CATALOG_RELATIVE_PATH} (${loaded.source.checks.length} check${loaded.source.checks.length === 1 ? "" : "s"})`,
|
|
280
|
+
);
|
|
281
|
+
return 0;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export async function runVerifiersCommand(argv: string[]): Promise<number> {
|
|
285
|
+
const parsed = parseAuthoringArgs(argv);
|
|
286
|
+
if (parsed.error !== undefined) {
|
|
287
|
+
printError(parsed.error);
|
|
288
|
+
process.stderr.write(HELP);
|
|
289
|
+
return 2;
|
|
290
|
+
}
|
|
291
|
+
if (parsed.help || parsed.positional.length === 0) {
|
|
292
|
+
process.stdout.write(HELP);
|
|
293
|
+
return 0;
|
|
294
|
+
}
|
|
295
|
+
const command = parsed.positional[0];
|
|
296
|
+
if (command === "discover") return discoverCommand();
|
|
297
|
+
if (command === "validate") return validateCommand();
|
|
298
|
+
if (command === "dry-run") {
|
|
299
|
+
const id = parsed.positional[1];
|
|
300
|
+
if (id === undefined) {
|
|
301
|
+
printError("dry-run requires a check ID");
|
|
302
|
+
return 2;
|
|
303
|
+
}
|
|
304
|
+
return printDryRun(id, await verifyTool.run({ check: id }));
|
|
305
|
+
}
|
|
306
|
+
if (command === "author") {
|
|
307
|
+
const revisions = authorRevisions(parsed);
|
|
308
|
+
if (revisions instanceof Error) {
|
|
309
|
+
printError(revisions.message);
|
|
310
|
+
return 2;
|
|
311
|
+
}
|
|
312
|
+
return executeWorkflow(parsed, { includeProposals: true, revisions });
|
|
313
|
+
}
|
|
314
|
+
if (["add", "edit", "rename", "remove"].includes(command ?? "")) {
|
|
315
|
+
const revision = mutationRevision(parsed);
|
|
316
|
+
if (revision instanceof Error) {
|
|
317
|
+
printError(revision.message);
|
|
318
|
+
return 2;
|
|
319
|
+
}
|
|
320
|
+
return executeWorkflow(parsed, { includeProposals: false, revisions: [revision] });
|
|
321
|
+
}
|
|
322
|
+
printError(`unknown verifiers command: ${command}`);
|
|
323
|
+
process.stderr.write(HELP);
|
|
324
|
+
return 2;
|
|
325
|
+
}
|
package/src/core/bash-exec.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { StringDecoder } from "node:string_decoder";
|
|
2
3
|
import { AI_AGENT_NAME } from "./agent-environment.js";
|
|
3
4
|
import {
|
|
4
5
|
gitCommitAttributionEnabled,
|
|
@@ -28,6 +29,8 @@ export interface BashCommandResult {
|
|
|
28
29
|
aborted: boolean;
|
|
29
30
|
timedOut: boolean;
|
|
30
31
|
outputCapped: boolean;
|
|
32
|
+
/** Exact bytes retained from stdout and stderr, never above the hard cap. */
|
|
33
|
+
outputBytes: number;
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
export interface BashCommandProgress {
|
|
@@ -151,6 +154,9 @@ export async function runBashCommand(command: string, options: RunBashCommandOpt
|
|
|
151
154
|
let killSent = false;
|
|
152
155
|
let stdout = "";
|
|
153
156
|
let stderr = "";
|
|
157
|
+
const stdoutDecoder = new StringDecoder("utf8");
|
|
158
|
+
const stderrDecoder = new StringDecoder("utf8");
|
|
159
|
+
let decodersFinished = false;
|
|
154
160
|
let outputBytes = 0;
|
|
155
161
|
let outputCapped = false;
|
|
156
162
|
let updateTimer: ReturnType<typeof setTimeout> | null = null;
|
|
@@ -164,6 +170,16 @@ export async function runBashCommand(command: string, options: RunBashCommandOpt
|
|
|
164
170
|
stdio: ["ignore", "pipe", "pipe"],
|
|
165
171
|
});
|
|
166
172
|
|
|
173
|
+
const finishDecoders = (): void => {
|
|
174
|
+
if (decodersFinished) return;
|
|
175
|
+
decodersFinished = true;
|
|
176
|
+
// A hard byte cap may bisect a multibyte code point. Discard the
|
|
177
|
+
// decoder's incomplete suffix instead of manufacturing U+FFFD.
|
|
178
|
+
if (outputCapped) return;
|
|
179
|
+
stdout += stdoutDecoder.end();
|
|
180
|
+
stderr += stderrDecoder.end();
|
|
181
|
+
};
|
|
182
|
+
|
|
167
183
|
const emitUpdate = (): void => {
|
|
168
184
|
if (options.onUpdate === undefined || !updateDirty) return;
|
|
169
185
|
updateDirty = false;
|
|
@@ -250,24 +266,20 @@ export async function runBashCommand(command: string, options: RunBashCommandOpt
|
|
|
250
266
|
|
|
251
267
|
const appendChunk = (target: "stdout" | "stderr", chunk: Buffer): void => {
|
|
252
268
|
if (outputCapped) return;
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
else stderr += slice;
|
|
263
|
-
}
|
|
269
|
+
const remaining = BASH_HARD_CAP_BYTES - outputBytes;
|
|
270
|
+
const accepted = chunk.byteLength <= remaining ? chunk : chunk.subarray(0, Math.max(0, remaining));
|
|
271
|
+
outputBytes += accepted.byteLength;
|
|
272
|
+
if (accepted.byteLength > 0) {
|
|
273
|
+
const decoded = target === "stdout" ? stdoutDecoder.write(accepted) : stderrDecoder.write(accepted);
|
|
274
|
+
if (target === "stdout") stdout += decoded;
|
|
275
|
+
else stderr += decoded;
|
|
276
|
+
}
|
|
277
|
+
if (accepted.byteLength < chunk.byteLength) {
|
|
264
278
|
outputCapped = true;
|
|
265
279
|
scheduleUpdate();
|
|
266
280
|
killChild();
|
|
267
281
|
return;
|
|
268
282
|
}
|
|
269
|
-
if (target === "stdout") stdout += chunk.toString("utf8");
|
|
270
|
-
else stderr += chunk.toString("utf8");
|
|
271
283
|
scheduleUpdate();
|
|
272
284
|
};
|
|
273
285
|
|
|
@@ -278,6 +290,7 @@ export async function runBashCommand(command: string, options: RunBashCommandOpt
|
|
|
278
290
|
settled = true;
|
|
279
291
|
if (timeoutId) clearTimeout(timeoutId);
|
|
280
292
|
clearKillGraceTimer();
|
|
293
|
+
finishDecoders();
|
|
281
294
|
finishUpdates();
|
|
282
295
|
options.signal?.removeEventListener("abort", onAbort);
|
|
283
296
|
resolve({
|
|
@@ -289,6 +302,7 @@ export async function runBashCommand(command: string, options: RunBashCommandOpt
|
|
|
289
302
|
aborted,
|
|
290
303
|
timedOut,
|
|
291
304
|
outputCapped,
|
|
305
|
+
outputBytes,
|
|
292
306
|
});
|
|
293
307
|
});
|
|
294
308
|
child.on("close", (code, signalName) => {
|
|
@@ -296,6 +310,7 @@ export async function runBashCommand(command: string, options: RunBashCommandOpt
|
|
|
296
310
|
settled = true;
|
|
297
311
|
if (timeoutId) clearTimeout(timeoutId);
|
|
298
312
|
clearKillGraceTimer();
|
|
313
|
+
finishDecoders();
|
|
299
314
|
finishUpdates();
|
|
300
315
|
options.signal?.removeEventListener("abort", onAbort);
|
|
301
316
|
const error =
|
|
@@ -307,7 +322,17 @@ export async function runBashCommand(command: string, options: RunBashCommandOpt
|
|
|
307
322
|
code: code ?? undefined,
|
|
308
323
|
signal: signalName ?? undefined,
|
|
309
324
|
} as NodeJS.ErrnoException);
|
|
310
|
-
resolve({
|
|
325
|
+
resolve({
|
|
326
|
+
error,
|
|
327
|
+
stdout,
|
|
328
|
+
stderr,
|
|
329
|
+
exitCode: code,
|
|
330
|
+
signal: signalName,
|
|
331
|
+
aborted,
|
|
332
|
+
timedOut,
|
|
333
|
+
outputCapped,
|
|
334
|
+
outputBytes,
|
|
335
|
+
});
|
|
311
336
|
});
|
|
312
337
|
});
|
|
313
338
|
}
|
package/src/core/bus-events.ts
CHANGED
|
@@ -61,6 +61,7 @@ export const BusChannels = {
|
|
|
61
61
|
ContextActivity: "context.activity",
|
|
62
62
|
ContextWarning: "context.warning",
|
|
63
63
|
ContextPruned: "context.pruned",
|
|
64
|
+
ContextRecalled: "context.recalled",
|
|
64
65
|
AgentStatusChanged: "agent.status.changed",
|
|
65
66
|
RunAborted: "run.aborted",
|
|
66
67
|
BudgetAlert: "budget.alert",
|
|
@@ -253,21 +254,34 @@ export function isRunAbortedPayload(value: unknown): value is RunAbortedPayload
|
|
|
253
254
|
return true;
|
|
254
255
|
}
|
|
255
256
|
|
|
256
|
-
/** Payload published on {@link BusChannels.ContextPruned} after
|
|
257
|
+
/** Payload published on {@link BusChannels.ContextPruned} after projected or summarized context shrinks. */
|
|
257
258
|
export interface ContextPrunedPayload {
|
|
258
|
-
stage: "mask_observations" | "llm_summary";
|
|
259
|
+
stage: "mask_observations" | "working_set" | "llm_summary";
|
|
259
260
|
tokensBefore: number;
|
|
260
261
|
tokensAfter: number;
|
|
261
262
|
trigger: string;
|
|
262
263
|
snapshotIdBefore: string | null;
|
|
263
264
|
snapshotIdAfter: string;
|
|
264
265
|
at: number;
|
|
265
|
-
/** Used/window ratio at trigger time; mask
|
|
266
|
+
/** Used/window ratio at trigger time; working-set and legacy-mask stages only. */
|
|
266
267
|
pressure?: number | null;
|
|
268
|
+
/** Legacy destructive-mask count. */
|
|
267
269
|
maskedObservations?: number;
|
|
268
|
-
/** Thinking blocks stripped from stale assistant messages; mask
|
|
270
|
+
/** Thinking blocks stripped from stale assistant messages; legacy mask only. */
|
|
269
271
|
maskedThinkingBlocks?: number;
|
|
270
272
|
maskedThinkingChars?: number;
|
|
273
|
+
/** Working-set policy that selected an applied eviction event. */
|
|
274
|
+
policyId?: string;
|
|
275
|
+
/** Number of working-set units evicted by the event. */
|
|
276
|
+
evictedItems?: number;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Payload published on {@link BusChannels.ContextRecalled} after an exact working-set recall. */
|
|
280
|
+
export interface ContextRecalledPayload {
|
|
281
|
+
ref: string;
|
|
282
|
+
trigger: "tool" | "operator";
|
|
283
|
+
tokensReadmitted: number;
|
|
284
|
+
at: number;
|
|
271
285
|
}
|
|
272
286
|
|
|
273
287
|
// ---------------------------------------------------------------------------
|
|
@@ -727,6 +741,7 @@ export type BusPayloadMap = {
|
|
|
727
741
|
[BusChannels.ContextActivity]: ContextActivityPayload;
|
|
728
742
|
[BusChannels.ContextWarning]: ContextWarningPayload;
|
|
729
743
|
[BusChannels.ContextPruned]: ContextPrunedPayload;
|
|
744
|
+
[BusChannels.ContextRecalled]: ContextRecalledPayload;
|
|
730
745
|
[BusChannels.AgentStatusChanged]: AgentStatusChangedPayload;
|
|
731
746
|
[BusChannels.RunAborted]: RunAbortedPayload;
|
|
732
747
|
[BusChannels.BudgetAlert]: BudgetAlertPayload;
|
package/src/core/config.ts
CHANGED
|
@@ -968,6 +968,7 @@ const TOP_LEVEL_KEYS = [
|
|
|
968
968
|
"delegation",
|
|
969
969
|
"keybindings",
|
|
970
970
|
"compaction",
|
|
971
|
+
"context",
|
|
971
972
|
"retry",
|
|
972
973
|
"guardrails",
|
|
973
974
|
] as const;
|
|
@@ -1405,6 +1406,59 @@ export function validateSettings(raw: unknown): SettingsValidationResult {
|
|
|
1405
1406
|
}
|
|
1406
1407
|
}
|
|
1407
1408
|
|
|
1409
|
+
if ("context" in raw) {
|
|
1410
|
+
if (!isPlainObject(raw.context)) {
|
|
1411
|
+
issues.add("context", `expected a map, got ${describe(raw.context)}`);
|
|
1412
|
+
} else {
|
|
1413
|
+
issues.unknownKeys("context", raw.context, ["workingSet"]);
|
|
1414
|
+
if ("workingSet" in raw.context) {
|
|
1415
|
+
if (!isPlainObject(raw.context.workingSet)) {
|
|
1416
|
+
issues.add("context.workingSet", `expected a map, got ${describe(raw.context.workingSet)}`);
|
|
1417
|
+
} else {
|
|
1418
|
+
const workingSet = raw.context.workingSet;
|
|
1419
|
+
issues.unknownKeys("context.workingSet", workingSet, [
|
|
1420
|
+
"enabled",
|
|
1421
|
+
"policy",
|
|
1422
|
+
"target",
|
|
1423
|
+
"protectLastTurns",
|
|
1424
|
+
"minEvictableTokens",
|
|
1425
|
+
]);
|
|
1426
|
+
if ("enabled" in workingSet) {
|
|
1427
|
+
const v = expectBoolean(issues, "context.workingSet.enabled", workingSet.enabled);
|
|
1428
|
+
if (v !== undefined) settings.context.workingSet.enabled = v;
|
|
1429
|
+
}
|
|
1430
|
+
if ("policy" in workingSet) {
|
|
1431
|
+
const v = expectEnum(issues, "context.workingSet.policy", workingSet.policy, [
|
|
1432
|
+
"age-horizon",
|
|
1433
|
+
"structural-v1",
|
|
1434
|
+
] as const);
|
|
1435
|
+
if (v !== undefined) settings.context.workingSet.policy = v;
|
|
1436
|
+
}
|
|
1437
|
+
if ("target" in workingSet) {
|
|
1438
|
+
const v = expectNumber(issues, "context.workingSet.target", workingSet.target);
|
|
1439
|
+
if (v !== undefined && (v <= 0 || v >= 1)) {
|
|
1440
|
+
issues.add("context.workingSet.target", `expected a number > 0 and < 1, got ${v}`);
|
|
1441
|
+
} else if (v !== undefined) {
|
|
1442
|
+
settings.context.workingSet.target = v;
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
if ("protectLastTurns" in workingSet) {
|
|
1446
|
+
const v = expectInteger(issues, "context.workingSet.protectLastTurns", workingSet.protectLastTurns, {
|
|
1447
|
+
min: 1,
|
|
1448
|
+
});
|
|
1449
|
+
if (v !== undefined) settings.context.workingSet.protectLastTurns = v;
|
|
1450
|
+
}
|
|
1451
|
+
if ("minEvictableTokens" in workingSet) {
|
|
1452
|
+
const v = expectInteger(issues, "context.workingSet.minEvictableTokens", workingSet.minEvictableTokens, {
|
|
1453
|
+
min: 0,
|
|
1454
|
+
});
|
|
1455
|
+
if (v !== undefined) settings.context.workingSet.minEvictableTokens = v;
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1408
1462
|
if ("retry" in raw) {
|
|
1409
1463
|
if (!isPlainObject(raw.retry)) {
|
|
1410
1464
|
issues.add("retry", `expected a map, got ${describe(raw.retry)}`);
|