@iowarp/clio-coder 0.3.7 → 0.3.8
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 +31 -0
- package/README.md +7 -2
- package/dist/{acp-SK4MD6MM.js → acp-U67UHUK2.js} +13 -13
- package/dist/{agents-2FN2K6ME.js → agents-YU6SGALZ.js} +35 -33
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-QIYZWM5I.js → auth-5ZPJOIVG.js} +32 -24
- package/dist/builtins-C6JMZVV6.js +17 -0
- package/dist/{chunk-YD734TPH.js → chunk-26LEYJZH.js} +2 -2
- package/dist/{chunk-TSHXZTOQ.js → chunk-2HEJ2F35.js} +5 -5
- package/dist/{chunk-WJHBC77E.js → chunk-2HFZQUHL.js} +5 -5
- package/dist/{chunk-FO5ZOVUY.js → chunk-3BINW3FP.js} +5 -5
- package/dist/{chunk-EOOQZZDE.js → chunk-4SPRNWDE.js} +18 -18
- package/dist/{chunk-EBEFWSGL.js → chunk-5DHKRSMQ.js} +7 -7
- package/dist/{chunk-OBMAI2DP.js → chunk-5H3GB5BO.js} +11 -9
- package/dist/{chunk-JTSEDYVQ.js → chunk-5Q2VVUKB.js} +7 -7
- package/dist/{chunk-VQNODYQ4.js → chunk-7RFXX52T.js} +294 -3704
- package/dist/{chunk-XEGB6BCN.js → chunk-7RGZWPB6.js} +5 -5
- package/dist/{chunk-GH5622CP.js → chunk-A2NJGIB3.js} +2 -2
- package/dist/{chunk-CEYBNUGC.js → chunk-A3WNZD3P.js} +323 -27
- package/dist/{chunk-D4MDIG46.js → chunk-B5CSFE7B.js} +7 -7
- package/dist/{chunk-2SFS6XQE.js → chunk-DGSYXYMX.js} +3 -2
- package/dist/{chunk-ZWLZP4ZT.js → chunk-DYIM5TJT.js} +98 -12
- package/dist/{chunk-KCMKRQX4.js → chunk-E77JEWSD.js} +42 -49
- package/dist/{chunk-J7PIKKWC.js → chunk-EMYUUSFG.js} +7 -7
- package/dist/{chunk-JRIO5UD2.js → chunk-EQ63NRB7.js} +5 -5
- package/dist/{chunk-UVDSQ6LW.js → chunk-FBVTI2TJ.js} +74 -28
- package/dist/{chunk-BMWK7ZIZ.js → chunk-FHJEP5SW.js} +13 -13
- package/dist/{chunk-FOT2FX5J.js → chunk-GN57SG4G.js} +7 -7
- package/dist/{chunk-SROCI7ZU.js → chunk-GPIEI3LY.js} +5 -5
- package/dist/{chunk-UFQ3F4FW.js → chunk-GU2UIAFZ.js} +4 -4
- package/dist/{chunk-UND3GU2L.js → chunk-H7IXIC72.js} +2 -2
- package/dist/{chunk-VREKEFLL.js → chunk-HLE42MG7.js} +3 -3
- package/dist/{chunk-465YSENW.js → chunk-IFBNV6H6.js} +3 -3
- package/dist/{chunk-LADCF22A.js → chunk-IGWKHNIQ.js} +113 -54
- package/dist/chunk-IIZWH4XA.js +172 -0
- package/dist/{chunk-UHXRNZ2J.js → chunk-IJ7RPIYJ.js} +4 -4
- package/dist/{chunk-M4AKACEO.js → chunk-J3YUBZWY.js} +2 -2
- package/dist/{chunk-AB4XIIVB.js → chunk-JOZYP4GM.js} +6 -6
- package/dist/{chunk-ZGH7FGS5.js → chunk-K4XHGFR5.js} +742 -12
- package/dist/{chunk-EELBMBT6.js → chunk-KTYTFRMB.js} +62 -5
- package/dist/chunk-LU7P4LHA.js +33 -0
- package/dist/{chunk-DQA7QLMD.js → chunk-ME6CCNFO.js} +9 -24
- package/dist/{chunk-KZ2H5X4G.js → chunk-MXKJU4JB.js} +88 -14
- package/dist/{chunk-DMD2AGVS.js → chunk-N22QMJKY.js} +20 -18
- package/dist/chunk-NMPKI6XL.js +3006 -0
- package/dist/chunk-NUGM5KR6.js +165 -0
- package/dist/{chunk-5C77SEEY.js → chunk-P43ETTHK.js} +2 -2
- package/dist/{chunk-JNXPYBB4.js → chunk-PMDBGQSJ.js} +2 -2
- package/dist/chunk-PT7HYKEM.js +165 -0
- package/dist/{chunk-GEYXPTRF.js → chunk-RWSI4YD7.js} +2 -2
- package/dist/{chunk-C4JBQ5SR.js → chunk-TANS5ZJS.js} +6 -6
- package/dist/{chunk-YTYFXUI3.js → chunk-TB5666IT.js} +9 -9
- package/dist/{chunk-UUANF5CR.js → chunk-TLQJPP24.js} +375 -687
- package/dist/{chunk-OB5HIGJY.js → chunk-TT36MB5S.js} +2 -1
- package/dist/{chunk-DJNLUABN.js → chunk-TTHACPOM.js} +135 -17
- package/dist/{chunk-6M7VS3J3.js → chunk-TYPGUK6W.js} +4 -4
- package/dist/{chunk-THKY7CD7.js → chunk-U6MBIEMB.js} +108 -24
- package/dist/{chunk-PD3MESLB.js → chunk-VAWNZU7Z.js} +4 -4
- package/dist/{chunk-IR4CFBFN.js → chunk-VCBR6CU7.js} +12 -12
- package/dist/{chunk-WHJYKASB.js → chunk-VHN4MY6O.js} +2 -2
- package/dist/{chunk-MXI6J5JF.js → chunk-VWZOAB7K.js} +10 -10
- package/dist/{chunk-QCTRSGHQ.js → chunk-WLFILSD5.js} +36 -36
- package/dist/{chunk-5UJ6ECTS.js → chunk-WNIJTQQK.js} +6 -6
- package/dist/{chunk-4DWFMQDR.js → chunk-WSB3FPX7.js} +68 -20
- package/dist/{chunk-3HAPLH5M.js → chunk-WWCZ5F23.js} +112 -10
- package/dist/{chunk-X2KV5FXT.js → chunk-WXY7KU3G.js} +2 -2
- package/dist/{chunk-PPAMZ32Z.js → chunk-XK56QHLX.js} +6 -1
- package/dist/{chunk-DOOEX22V.js → chunk-XWSF374K.js} +4 -4
- package/dist/{chunk-6TUKSZVF.js → chunk-YS5VLNH5.js} +8 -8
- package/dist/{chunk-ZZMN5OM4.js → chunk-ZNLWCMVZ.js} +2 -2
- package/dist/{chunk-WHGPSPT5.js → chunk-ZVJ5BLO2.js} +2 -2
- package/dist/cli/index.js +31 -31
- package/dist/{clio-WBVQEBKO.js → clio-QVTYJ57A.js} +8 -8
- package/dist/code-nav-FGGFIE7L.js +2 -2
- package/dist/codewiki/build-worker.js +4 -4
- package/dist/{components-F7OEATSO.js → components-ZFA3SAER.js} +8 -8
- package/dist/{config-TRBL3RCF.js → config-LW5IJFQN.js} +58 -53
- package/dist/{configure-OLCVPHNM.js → configure-7XIZCOU4.js} +26 -21
- package/dist/{context-XEWE3MOJ.js → context-L3WL3X7K.js} +47 -41
- package/dist/{context-WFPKQSM6.js → context-N52ZA626.js} +22 -22
- package/dist/{context-MJIJ6GOX.js → context-Y6Y7QPR6.js} +12 -12
- package/dist/{context-clear-KNOS2JPB.js → context-clear-MBQRLSDQ.js} +47 -41
- package/dist/{context-index-SSR5ECNE.js → context-index-HVMFQHK3.js} +5 -5
- package/dist/{context-working-set-EUXAZI6N.js → context-working-set-GS6DSO7F.js} +14 -14
- package/dist/{dispatch-runner-B7MTOVKL.js → dispatch-runner-22ZCNOM3.js} +83 -55
- package/dist/{docs-FLJTIDSE.js → docs-7LQ23DLM.js} +8 -8
- package/dist/{doctor-RN4YKO2X.js → doctor-M7YEDGAE.js} +24 -20
- package/dist/{eval-RUBJVSNQ.js → eval-BEC2WHDA.js} +71 -19
- package/dist/{evidence-JZNBUOQZ.js → evidence-REJUMSKM.js} +62 -56
- package/dist/{evolve-FJVC4KKI.js → evolve-PY5ZBA5K.js} +41 -35
- package/dist/{extensions-IQL36S7K.js → extensions-HVKU65YU.js} +6 -6
- package/dist/{fleet-BDKYJFCP.js → fleet-7WZEWRFA.js} +72 -67
- package/dist/{fleet-commands-ZFIWZSB3.js → fleet-commands-UVHWM76J.js} +10 -10
- package/dist/{fleet-graph-Y6HPXIVF.js → fleet-graph-6ULH7PES.js} +13 -13
- package/dist/{fleet-preflight-BHSNPBMH.js → fleet-preflight-J53T6CCE.js} +5 -5
- package/dist/{fleet-validate-BIYREGIK.js → fleet-validate-72PC4SLA.js} +17 -17
- package/dist/{init-LQUB5COQ.js → init-OG3TPGQG.js} +61 -56
- package/dist/{library-NJAHIGG4.js → library-CNTMPLRF.js} +18 -18
- package/dist/{memory-OG6HOYKM.js → memory-6IS7F275.js} +43 -37
- package/dist/{models-5ZG5XY7J.js → models-ENRJDA5W.js} +33 -28
- package/dist/{monitor-TJ7AMTGB.js → monitor-XLDVO7TN.js} +56 -38
- package/dist/{orchestrator-WZYB54DM.js → orchestrator-6KSPYRHA.js} +421 -303
- package/dist/{paths-XUC7GS6E.js → paths-DBXMZMDU.js} +5 -5
- package/dist/registry-LG64LTF4.js +11 -0
- package/dist/{reset-PXQT45IY.js → reset-RZ4ER727.js} +10 -10
- package/dist/{run-FQ74YF62.js → run-Y2CNK5RU.js} +79 -74
- package/dist/{share-FW7SVCL3.js → share-A55GYP6Z.js} +16 -16
- package/dist/{skills-7E7IRB3R.js → skills-ALC5J6AT.js} +19 -19
- package/dist/{skills-eval-LI75W6OK.js → skills-eval-JPBEBYQU.js} +52 -47
- package/dist/support-MIETYA5E.js +38 -0
- package/dist/{targets-4CIFKCTW.js → targets-VGNXIR3S.js} +42 -33
- package/dist/{terminal-lease-WUZY7ZV5.js → terminal-lease-WOBR64YA.js} +3 -3
- package/dist/{uninstall-7FV7IP4E.js → uninstall-ZJF5H5ZN.js} +8 -8
- package/dist/{upgrade-K2HVIVMQ.js → upgrade-FUSUAGHR.js} +27 -25
- package/dist/{usage-GTZELZQX.js → usage-N4MKVHKD.js} +51 -46
- package/dist/{verifiers-RLAHT27O.js → verifiers-YAWOJ3H2.js} +13 -13
- package/dist/{verify-BX3BRKH5.js → verify-LTDHYBGY.js} +9 -9
- package/dist/{wiki-generate-ASIFASCN.js → wiki-generate-6M7GHTBJ.js} +66 -61
- package/dist/worker/entry.js +68 -61
- package/docs/alcf-provider.md +1 -1
- package/docs/architecture.md +1 -1
- package/docs/artifact-versions.md +6 -5
- package/docs/built-in-agents.md +1 -1
- package/docs/commands-and-modes.md +2 -2
- package/docs/configuration-and-targets.md +8 -3
- package/docs/context-engine.md +1 -1
- package/docs/documentation-guide.md +1 -1
- package/docs/eval-runner.md +1 -1
- package/docs/evals-internal.md +1 -1
- package/docs/evidence-and-memory.md +70 -6
- package/docs/evolution.md +1 -1
- package/docs/extensions-and-sharing.md +1 -1
- package/docs/fleet-dispatch.md +34 -9
- package/docs/glossary.md +21 -1
- package/docs/installation-and-lifecycle.md +1 -1
- package/docs/middleware-and-components.md +1 -1
- package/docs/model-catalog.md +1 -1
- package/docs/observability.md +2 -2
- package/docs/proactive-memory.md +1 -1
- package/docs/prompt-envelope-and-tools.md +4 -2
- package/docs/provider-adapter-cookbook.md +1 -1
- package/docs/release-cut-checklist.md +41 -38
- package/docs/safety-model.md +1 -1
- package/docs/scientific-validation.md +1 -1
- package/docs/skills-marketplace.md +1 -1
- package/docs/tool-usage.md +1 -1
- package/docs/trace-store.md +1 -1
- package/docs/tui-design.md +1 -1
- package/docs/worker-dispatch-mechanics.md +1 -1
- package/package.json +1 -2
- package/src/cli/argv.ts +5 -0
- package/src/cli/configure.ts +107 -23
- package/src/cli/doctor.ts +5 -1
- package/src/cli/evidence.ts +30 -25
- package/src/cli/fleet-preflight.ts +2 -12
- package/src/cli/shared.ts +1 -0
- package/src/cli/targets.ts +4 -1
- package/src/cli/validate-model.ts +60 -5
- package/src/core/bus-events.ts +25 -0
- package/src/core/commit-attribution.ts +4 -4
- package/src/core/path-boundary.ts +100 -0
- package/src/domains/agents/extension.ts +2 -11
- package/src/domains/agents/fleet-contract.ts +30 -12
- package/src/domains/agents/recipe.ts +7 -1
- package/src/domains/agents/registry.ts +73 -5
- package/src/domains/agents/result-contract.ts +128 -17
- package/src/domains/agents/write-boundary.ts +15 -50
- package/src/domains/context/project-rules.ts +51 -1
- package/src/domains/dispatch/assignment-reconcile.ts +22 -5
- package/src/domains/dispatch/assignment-store.ts +151 -14
- package/src/domains/dispatch/contract.ts +15 -1
- package/src/domains/dispatch/delegation-plan.ts +2 -5
- package/src/domains/dispatch/execution-role.ts +9 -1
- package/src/domains/dispatch/extension.ts +156 -67
- package/src/domains/dispatch/fleet-run.ts +57 -3
- package/src/domains/dispatch/gate-role-prompts.ts +38 -0
- package/src/domains/dispatch/index.ts +3 -1
- package/src/domains/dispatch/intent-requirements.ts +40 -0
- package/src/domains/dispatch/intent.ts +84 -8
- package/src/domains/dispatch/path-scope.ts +370 -0
- package/src/domains/dispatch/receipt-integrity.ts +2 -1
- package/src/domains/dispatch/types.ts +14 -7
- package/src/domains/dispatch/validation.ts +6 -3
- package/src/domains/dispatch/write-boundary-enforcer.ts +45 -0
- package/src/domains/dispatch/write-boundary.ts +201 -22
- package/src/domains/eval/metrics/evidence.ts +79 -2
- package/src/domains/eval/runners/clio-run.ts +12 -2
- package/src/domains/evidence/build.ts +69 -11
- package/src/domains/evidence/index.ts +21 -0
- package/src/domains/evidence/provenance.ts +46 -11
- package/src/domains/evidence/trust-projection.ts +274 -0
- package/src/domains/evidence/trust-status.ts +145 -17
- package/src/domains/evidence/types.ts +4 -0
- package/src/domains/extensions/discovery.ts +88 -1
- package/src/domains/extensions/resources.ts +20 -8
- package/src/domains/extensions/state.ts +6 -2
- package/src/domains/extensions/types.ts +4 -1
- package/src/domains/lifecycle/doctor.ts +140 -1
- package/src/domains/prompts/contract.ts +3 -5
- package/src/domains/providers/extension.ts +30 -2
- package/src/domains/resources/common-loader.ts +3 -0
- package/src/domains/resources/prompts/loader.ts +119 -14
- package/src/domains/safety/policy-engine.ts +5 -5
- package/src/domains/safety/run-effects.ts +64 -1
- package/src/domains/safety/scope.ts +7 -12
- package/src/engine/acp/server.ts +4 -1
- package/src/engine/prompt-templates.ts +18 -1
- package/src/engine/worker-runtime.ts +6 -3
- package/src/interactive/dispatch-board.ts +53 -3
- package/src/interactive/interactive-event-projection.ts +14 -0
- package/src/interactive/overlays/settings.ts +141 -47
- package/src/interactive/slash-commands.ts +7 -2
- package/src/interactive/view/artifacts.ts +42 -9
- package/src/interactive/view/view-overlay.ts +15 -3
- package/src/interactive/worker-receipts.ts +14 -2
- package/src/interactive/worker-stream.ts +8 -0
- package/src/tools/dispatch-admission.ts +12 -13
- package/src/tools/dispatch-arguments.ts +27 -0
- package/src/tools/dispatch-plan.ts +29 -0
- package/src/tools/dispatch-runner.ts +48 -13
- package/src/tools/monitor.ts +13 -0
- package/src/tools/worker-evidence.ts +19 -13
- package/src/worker/spec-contract.ts +2 -1
|
@@ -593,6 +593,13 @@ function delegationAgentsAffordance(
|
|
|
593
593
|
type SubmenuTitle = string | ((width: number) => string);
|
|
594
594
|
|
|
595
595
|
export class SubmenuWrapper implements Component {
|
|
596
|
+
/**
|
|
597
|
+
* Why the last submission was refused, or null while nothing is wrong. The
|
|
598
|
+
* editor sets it instead of closing, so the operator corrects the value with
|
|
599
|
+
* the reason on screen rather than back at the row list with no reason.
|
|
600
|
+
*/
|
|
601
|
+
private problem: string | null = null;
|
|
602
|
+
|
|
596
603
|
constructor(
|
|
597
604
|
private readonly title: SubmenuTitle,
|
|
598
605
|
private readonly child: Component,
|
|
@@ -600,6 +607,10 @@ export class SubmenuWrapper implements Component {
|
|
|
600
607
|
private readonly note?: string,
|
|
601
608
|
) {}
|
|
602
609
|
|
|
610
|
+
setProblem(problem: string | null): void {
|
|
611
|
+
this.problem = problem;
|
|
612
|
+
}
|
|
613
|
+
|
|
603
614
|
render(width: number): string[] {
|
|
604
615
|
const theme = clioTheme();
|
|
605
616
|
const lines: string[] = [];
|
|
@@ -619,6 +630,11 @@ export class SubmenuWrapper implements Component {
|
|
|
619
630
|
// two columns off each one and marked the cut, so every entry wore a
|
|
620
631
|
// trailing … it had not earned.
|
|
621
632
|
lines.push(...this.child.render(Math.max(1, width - 2)).map((line) => ` ${line}`));
|
|
633
|
+
if (this.problem !== null) {
|
|
634
|
+
for (const line of wrapTextWithAnsi(this.problem, Math.max(1, width - 2))) {
|
|
635
|
+
lines.push(theme.fg("error", ` ${line}`));
|
|
636
|
+
}
|
|
637
|
+
}
|
|
622
638
|
lines.push("");
|
|
623
639
|
lines.push(theme.fg("dim", ` ${this.hint}`));
|
|
624
640
|
return lines;
|
|
@@ -693,33 +709,108 @@ function editTextSubmenu(title: string, note?: string): SettingSubmenuBuilder {
|
|
|
693
709
|
return textInputSubmenu(title, note);
|
|
694
710
|
}
|
|
695
711
|
|
|
696
|
-
|
|
697
|
-
|
|
712
|
+
/**
|
|
713
|
+
* The bound a number row enforces, shared by the editor and the apply path so
|
|
714
|
+
* the two cannot drift: whatever the editor forwards, the apply path stores.
|
|
715
|
+
* The bounds mirror the config validator's, so a value the Settings Center
|
|
716
|
+
* refuses is one settings.yaml would have refused too, in the same words.
|
|
717
|
+
*/
|
|
718
|
+
interface NumberSettingRule {
|
|
719
|
+
readonly min: number;
|
|
720
|
+
readonly max?: number;
|
|
721
|
+
readonly integer: boolean;
|
|
722
|
+
/** A blank submission clears the key instead of being a parse failure. */
|
|
723
|
+
readonly allowBlank?: boolean;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const NUMBER_SETTING_RULES = {
|
|
727
|
+
"budget.sessionCeilingUsd": { min: 0, integer: false },
|
|
728
|
+
"watchdog.cadenceToolCalls": { min: 1, integer: true, allowBlank: true },
|
|
729
|
+
"delegation.defaults.connectTimeoutMs": { min: 1, max: MAX_TIMER_DELAY_MS, integer: true },
|
|
730
|
+
"delegation.defaults.turnTimeoutMs": { min: 1, max: MAX_TIMER_DELAY_MS, integer: true },
|
|
731
|
+
"delegation.defaults.permissionTimeoutMs": { min: 1, max: MAX_TIMER_DELAY_MS, integer: true },
|
|
732
|
+
} as const satisfies Partial<Record<EditableSettingId, NumberSettingRule>>;
|
|
733
|
+
|
|
734
|
+
export type NumberSettingId = keyof typeof NUMBER_SETTING_RULES;
|
|
735
|
+
|
|
736
|
+
export const NUMBER_SETTING_IDS = Object.keys(NUMBER_SETTING_RULES) as readonly NumberSettingId[];
|
|
737
|
+
|
|
738
|
+
type NumberSettingOutcome = { readonly value: number | null } | { readonly refusal: string };
|
|
739
|
+
|
|
740
|
+
function describeSubmittedText(value: string): string {
|
|
741
|
+
const trimmed = value.trim();
|
|
742
|
+
return trimmed.length === 0 ? "an empty string" : JSON.stringify(trimmed);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Parse one submitted number-row value under its rule. A null value means the
|
|
747
|
+
* operator cleared an optional key. The refusal is worded like the config
|
|
748
|
+
* validator's issue for the same key ("expected an integer >= 1, got 0"), with
|
|
749
|
+
* a "Not applied:" prefix so it reads as the outcome of this submission.
|
|
750
|
+
*/
|
|
751
|
+
function parseNumberSetting(value: string, rule: NumberSettingRule): NumberSettingOutcome {
|
|
752
|
+
const trimmed = value.trim();
|
|
753
|
+
const noun = rule.integer ? "an integer" : "a number";
|
|
754
|
+
if (trimmed.length === 0 && rule.allowBlank === true) return { value: null };
|
|
755
|
+
const parsed = trimmed.length === 0 ? Number.NaN : Number(trimmed);
|
|
756
|
+
if (!Number.isFinite(parsed) || (rule.integer && !Number.isInteger(parsed))) {
|
|
757
|
+
const shown = Number.isFinite(parsed) ? String(parsed) : describeSubmittedText(value);
|
|
758
|
+
return { refusal: `Not applied: expected ${noun}, got ${shown}.` };
|
|
759
|
+
}
|
|
760
|
+
if (parsed < rule.min) return { refusal: `Not applied: expected ${noun} >= ${rule.min}, got ${parsed}.` };
|
|
761
|
+
if (rule.max !== undefined && parsed > rule.max) {
|
|
762
|
+
return { refusal: `Not applied: expected ${noun} <= ${rule.max}, got ${parsed}.` };
|
|
763
|
+
}
|
|
764
|
+
return { value: parsed };
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* The reason a number row would refuse `value`, or null when it would apply.
|
|
769
|
+
* This is the same check the editor and `applySettingChange` run, exposed so a
|
|
770
|
+
* caller that bypasses the editor can still name the bound it hit.
|
|
771
|
+
*/
|
|
772
|
+
export function describeNumberSettingRefusal(id: NumberSettingId, value: string): string | null {
|
|
773
|
+
const outcome = parseNumberSetting(value, NUMBER_SETTING_RULES[id]);
|
|
774
|
+
return "refusal" in outcome ? outcome.refusal : null;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
function numberSettingNote(rule: NumberSettingRule): string {
|
|
778
|
+
const noun = rule.integer ? "a whole number" : "a number";
|
|
779
|
+
const clears = rule.allowBlank === true ? "; blank clears it" : "";
|
|
780
|
+
if (rule.min > 0 && rule.max !== undefined) return `Use ${noun} from ${rule.min} to ${rule.max}${clears}.`;
|
|
781
|
+
if (rule.min > 0) return `Use ${noun} of at least ${rule.min}${clears}.`;
|
|
782
|
+
return `Use a non-negative ${rule.integer ? "whole number" : "number"}${clears}.`;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* A number editor that stays open on a refused submission. Closing with no
|
|
787
|
+
* value looked like a successful edit that changed nothing, so the reason is
|
|
788
|
+
* rendered under the input and the operator corrects the text in place; Esc
|
|
789
|
+
* still leaves without applying.
|
|
790
|
+
*/
|
|
791
|
+
function editNumberSubmenu(title: string, id: NumberSettingId): SettingSubmenuBuilder {
|
|
792
|
+
const rule = NUMBER_SETTING_RULES[id];
|
|
698
793
|
return (currentValue: string, done: (val?: string) => void) => {
|
|
699
794
|
const input = new Input();
|
|
700
795
|
input.setValue(currentValue);
|
|
701
796
|
input.focused = true;
|
|
797
|
+
const wrapper = new SubmenuWrapper(
|
|
798
|
+
title,
|
|
799
|
+
input,
|
|
800
|
+
buildHint([{ key: "Enter", verb: "confirm" }], "back"),
|
|
801
|
+
numberSettingNote(rule),
|
|
802
|
+
);
|
|
702
803
|
input.onSubmit = (val) => {
|
|
703
|
-
|
|
704
|
-
|
|
804
|
+
const outcome = parseNumberSetting(val, rule);
|
|
805
|
+
if ("refusal" in outcome) {
|
|
806
|
+
wrapper.setProblem(outcome.refusal);
|
|
705
807
|
return;
|
|
706
808
|
}
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
done(val);
|
|
710
|
-
} else {
|
|
711
|
-
done();
|
|
712
|
-
}
|
|
809
|
+
wrapper.setProblem(null);
|
|
810
|
+
done(outcome.value === null ? "" : val.trim());
|
|
713
811
|
};
|
|
714
812
|
input.onEscape = () => done();
|
|
715
|
-
return
|
|
716
|
-
title,
|
|
717
|
-
input,
|
|
718
|
-
buildHint([{ key: "Enter", verb: "confirm" }], "back"),
|
|
719
|
-
min > 0
|
|
720
|
-
? `Use a whole number of at least ${min}${options.allowBlank === true ? "; blank clears it" : ""}.`
|
|
721
|
-
: "Use a non-negative number.",
|
|
722
|
-
);
|
|
813
|
+
return wrapper;
|
|
723
814
|
};
|
|
724
815
|
}
|
|
725
816
|
|
|
@@ -1412,7 +1503,7 @@ export function buildSettingItems(
|
|
|
1412
1503
|
readOnly: true,
|
|
1413
1504
|
}),
|
|
1414
1505
|
settingItem("budget.sessionCeilingUsd", String(settings.budget.sessionCeilingUsd), {
|
|
1415
|
-
submenu: editNumberSubmenu("Edit session cost ceiling USD"),
|
|
1506
|
+
submenu: editNumberSubmenu("Edit session cost ceiling USD", "budget.sessionCeilingUsd"),
|
|
1416
1507
|
affordance: "free text",
|
|
1417
1508
|
}),
|
|
1418
1509
|
settingItem("defaults.maxTokens", String(settings.defaults.maxTokens), {
|
|
@@ -1479,10 +1570,10 @@ export function buildSettingItems(
|
|
|
1479
1570
|
"watchdog.cadenceToolCalls",
|
|
1480
1571
|
watchdog.cadenceToolCalls === undefined ? "(turn end only)" : String(watchdog.cadenceToolCalls),
|
|
1481
1572
|
{
|
|
1482
|
-
submenu: editNumberSubmenu(
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1573
|
+
submenu: editNumberSubmenu(
|
|
1574
|
+
"Edit watchdog cadence in tool calls; blank fires at turn end only",
|
|
1575
|
+
"watchdog.cadenceToolCalls",
|
|
1576
|
+
),
|
|
1486
1577
|
affordance: "free text",
|
|
1487
1578
|
editValue: watchdog.cadenceToolCalls === undefined ? "" : String(watchdog.cadenceToolCalls),
|
|
1488
1579
|
},
|
|
@@ -1503,15 +1594,15 @@ export function buildSettingItems(
|
|
|
1503
1594
|
affordance: "free text",
|
|
1504
1595
|
}),
|
|
1505
1596
|
settingItem("delegation.defaults.connectTimeoutMs", String(settings.delegation.defaults.connectTimeoutMs), {
|
|
1506
|
-
submenu: editNumberSubmenu("Edit delegate connect timeout (ms)"),
|
|
1597
|
+
submenu: editNumberSubmenu("Edit delegate connect timeout (ms)", "delegation.defaults.connectTimeoutMs"),
|
|
1507
1598
|
affordance: "free text",
|
|
1508
1599
|
}),
|
|
1509
1600
|
settingItem("delegation.defaults.turnTimeoutMs", String(settings.delegation.defaults.turnTimeoutMs), {
|
|
1510
|
-
submenu: editNumberSubmenu("Edit delegate turn timeout (ms)"),
|
|
1601
|
+
submenu: editNumberSubmenu("Edit delegate turn timeout (ms)", "delegation.defaults.turnTimeoutMs"),
|
|
1511
1602
|
affordance: "free text",
|
|
1512
1603
|
}),
|
|
1513
1604
|
settingItem("delegation.defaults.permissionTimeoutMs", String(settings.delegation.defaults.permissionTimeoutMs), {
|
|
1514
|
-
submenu: editNumberSubmenu("Edit delegate permission timeout (ms)"),
|
|
1605
|
+
submenu: editNumberSubmenu("Edit delegate permission timeout (ms)", "delegation.defaults.permissionTimeoutMs"),
|
|
1515
1606
|
affordance: "free text",
|
|
1516
1607
|
}),
|
|
1517
1608
|
settingItem("keybindings", keybindingCount > 0 ? `${keybindingCount} override(s)` : "(defaults)", {
|
|
@@ -1819,9 +1910,15 @@ function applyNonNegativeInteger(value: string, set: (next: number) => void): vo
|
|
|
1819
1910
|
if (Number.isFinite(parsed) && parsed >= 0) set(Math.floor(parsed));
|
|
1820
1911
|
}
|
|
1821
1912
|
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1913
|
+
/**
|
|
1914
|
+
* Store a number-row submission under the row's shared rule. A refused value
|
|
1915
|
+
* leaves the setting alone; the editor has already shown the operator why, and
|
|
1916
|
+
* `describeNumberSettingRefusal` names the reason for any other caller.
|
|
1917
|
+
*/
|
|
1918
|
+
function applyNumberSetting(id: NumberSettingId, value: string, set: (next: number | null) => void): void {
|
|
1919
|
+
const outcome = parseNumberSetting(value, NUMBER_SETTING_RULES[id]);
|
|
1920
|
+
if ("refusal" in outcome) return;
|
|
1921
|
+
set(outcome.value);
|
|
1825
1922
|
}
|
|
1826
1923
|
|
|
1827
1924
|
/**
|
|
@@ -1986,15 +2083,12 @@ export function applySettingChange(settings: ClioSettings, id: string, value: st
|
|
|
1986
2083
|
else delete settings.watchdog.target;
|
|
1987
2084
|
return;
|
|
1988
2085
|
}
|
|
1989
|
-
case "watchdog.cadenceToolCalls":
|
|
1990
|
-
|
|
1991
|
-
delete settings.watchdog.cadenceToolCalls;
|
|
1992
|
-
|
|
1993
|
-
}
|
|
1994
|
-
const parsed = Number(value);
|
|
1995
|
-
if (Number.isFinite(parsed) && Math.floor(parsed) >= 1) settings.watchdog.cadenceToolCalls = Math.floor(parsed);
|
|
2086
|
+
case "watchdog.cadenceToolCalls":
|
|
2087
|
+
applyNumberSetting("watchdog.cadenceToolCalls", value, (next) => {
|
|
2088
|
+
if (next === null) delete settings.watchdog.cadenceToolCalls;
|
|
2089
|
+
else settings.watchdog.cadenceToolCalls = next;
|
|
2090
|
+
});
|
|
1996
2091
|
return;
|
|
1997
|
-
}
|
|
1998
2092
|
case "terminal.smoothStreaming":
|
|
1999
2093
|
if (value === "off" || value === "auto" || value === "on") settings.terminal.smoothStreaming = value;
|
|
2000
2094
|
return;
|
|
@@ -2005,18 +2099,18 @@ export function applySettingChange(settings: ClioSettings, id: string, value: st
|
|
|
2005
2099
|
.filter(Boolean);
|
|
2006
2100
|
return;
|
|
2007
2101
|
case "delegation.defaults.connectTimeoutMs":
|
|
2008
|
-
|
|
2009
|
-
settings.delegation.defaults.connectTimeoutMs = next;
|
|
2102
|
+
applyNumberSetting("delegation.defaults.connectTimeoutMs", value, (next) => {
|
|
2103
|
+
if (next !== null) settings.delegation.defaults.connectTimeoutMs = next;
|
|
2010
2104
|
});
|
|
2011
2105
|
return;
|
|
2012
2106
|
case "delegation.defaults.turnTimeoutMs":
|
|
2013
|
-
|
|
2014
|
-
settings.delegation.defaults.turnTimeoutMs = next;
|
|
2107
|
+
applyNumberSetting("delegation.defaults.turnTimeoutMs", value, (next) => {
|
|
2108
|
+
if (next !== null) settings.delegation.defaults.turnTimeoutMs = next;
|
|
2015
2109
|
});
|
|
2016
2110
|
return;
|
|
2017
2111
|
case "delegation.defaults.permissionTimeoutMs":
|
|
2018
|
-
|
|
2019
|
-
settings.delegation.defaults.permissionTimeoutMs = next;
|
|
2112
|
+
applyNumberSetting("delegation.defaults.permissionTimeoutMs", value, (next) => {
|
|
2113
|
+
if (next !== null) settings.delegation.defaults.permissionTimeoutMs = next;
|
|
2020
2114
|
});
|
|
2021
2115
|
return;
|
|
2022
2116
|
case "orchestrator.target": {
|
|
@@ -2067,11 +2161,11 @@ export function applySettingChange(settings: ClioSettings, id: string, value: st
|
|
|
2067
2161
|
.map((v) => v.trim())
|
|
2068
2162
|
.filter(Boolean);
|
|
2069
2163
|
return;
|
|
2070
|
-
case "budget.sessionCeilingUsd":
|
|
2071
|
-
|
|
2072
|
-
|
|
2164
|
+
case "budget.sessionCeilingUsd":
|
|
2165
|
+
applyNumberSetting("budget.sessionCeilingUsd", value, (next) => {
|
|
2166
|
+
if (next !== null) settings.budget.sessionCeilingUsd = next;
|
|
2167
|
+
});
|
|
2073
2168
|
return;
|
|
2074
|
-
}
|
|
2075
2169
|
}
|
|
2076
2170
|
}
|
|
2077
2171
|
|
|
@@ -6,9 +6,9 @@ import type { AgentSpec } from "../domains/agents/spec.js";
|
|
|
6
6
|
import type { ContextInitOptions } from "../domains/context/init-options.js";
|
|
7
7
|
import type { DispatchContract, DispatchRequest } from "../domains/dispatch/contract.js";
|
|
8
8
|
import { type AgentRoleFactsResolver, requestExecutionRole } from "../domains/dispatch/execution-role.js";
|
|
9
|
-
import type { ReceiptIntegrityResult } from "../domains/dispatch/receipt-integrity.js";
|
|
10
9
|
import type { RunReceipt } from "../domains/dispatch/types.js";
|
|
11
10
|
import type { JobThinkingLevel } from "../domains/dispatch/validation.js";
|
|
11
|
+
import type { ReceiptIntegrityOutcome } from "../domains/evidence/trust-status.js";
|
|
12
12
|
import type { InstalledExtension } from "../domains/extensions/index.js";
|
|
13
13
|
import type { InteropAgentId, InteropProposal, InteropReport } from "../domains/interop/index.js";
|
|
14
14
|
import type { ProvidersContract, ResolvedModelRef } from "../domains/providers/index.js";
|
|
@@ -665,7 +665,7 @@ export interface SlashCommandContext {
|
|
|
665
665
|
* and emit a single status line. Kept on the context so the registry does
|
|
666
666
|
* not import the overlay module.
|
|
667
667
|
*/
|
|
668
|
-
verifyReceipt: (runId: string) =>
|
|
668
|
+
verifyReceipt: (runId: string) => ReceiptIntegrityOutcome;
|
|
669
669
|
/**
|
|
670
670
|
* Handles the "unknown" case: append the text to the chat panel as a user
|
|
671
671
|
* turn, submit to the chat loop, and schedule a re-render. Handlers for
|
|
@@ -1507,6 +1507,11 @@ export const BUILTIN_SLASH_COMMANDS: ReadonlyArray<BuiltinSlashCommand> = [
|
|
|
1507
1507
|
const result = ctx.verifyReceipt(command.runId);
|
|
1508
1508
|
if (result.ok) {
|
|
1509
1509
|
ctx.notice("success", `verify ok ${command.runId}`);
|
|
1510
|
+
} else if (result.retired !== undefined) {
|
|
1511
|
+
// A retired seal is the expected state of a run from the previous
|
|
1512
|
+
// release, so it is a warning that names the versions, never the
|
|
1513
|
+
// failure a tampered receipt gets.
|
|
1514
|
+
ctx.notice("warn", `verify retired ${command.runId} ${result.reason}`);
|
|
1510
1515
|
} else {
|
|
1511
1516
|
ctx.notice("error", `verify fail ${command.runId} ${result.reason}`);
|
|
1512
1517
|
}
|
|
@@ -2,13 +2,16 @@ import { createReadStream, readFileSync, statSync } from "node:fs";
|
|
|
2
2
|
import { readFile, realpath, stat } from "node:fs/promises";
|
|
3
3
|
import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import type { DispatchContract } from "../../domains/dispatch/contract.js";
|
|
5
|
-
import {
|
|
6
|
-
isReceiptIntegrity,
|
|
7
|
-
type ReceiptIntegrityResult,
|
|
8
|
-
verifyReceiptIntegrity,
|
|
9
|
-
} from "../../domains/dispatch/receipt-integrity.js";
|
|
5
|
+
import { isReceiptIntegrity, verifyReceiptIntegrity } from "../../domains/dispatch/receipt-integrity.js";
|
|
10
6
|
import type { RunEnvelope, RunReceipt } from "../../domains/dispatch/types.js";
|
|
11
7
|
import { evidenceDirectory, inspectEvidence, listEvidenceOverviews } from "../../domains/evidence/store.js";
|
|
8
|
+
import { formatTrustSummaryLine } from "../../domains/evidence/trust-projection.js";
|
|
9
|
+
import {
|
|
10
|
+
inspectRunReceiptTrustStatus,
|
|
11
|
+
type ReceiptIntegrityOutcome,
|
|
12
|
+
retiredReceiptIntegrity,
|
|
13
|
+
retiredReceiptIntegrityReason,
|
|
14
|
+
} from "../../domains/evidence/trust-status.js";
|
|
12
15
|
import type { EvidenceFinding, EvidenceOverview, EvidenceSource } from "../../domains/evidence/types.js";
|
|
13
16
|
import { type AccountabilitySummary, readAccountabilitySummary } from "../../domains/observability/index.js";
|
|
14
17
|
import type {
|
|
@@ -72,7 +75,7 @@ export interface ViewArtifact {
|
|
|
72
75
|
toolName?: string;
|
|
73
76
|
searchText?: readonly string[];
|
|
74
77
|
load(): Promise<ViewArtifactLoadResult>;
|
|
75
|
-
verify?(): Promise<
|
|
78
|
+
verify?(): Promise<ViewArtifactVerification>;
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
export interface ArtifactProvider {
|
|
@@ -108,7 +111,14 @@ const ACCOUNTABILITY_TOP_CAUSES = 8;
|
|
|
108
111
|
export const VIEW_ARTIFACT_LINE_CAP = 50_000;
|
|
109
112
|
const JSON_PRETTY_MAX_BYTES = 10 * 1024 * 1024;
|
|
110
113
|
|
|
111
|
-
export type ReceiptVerifyResult =
|
|
114
|
+
export type ReceiptVerifyResult = ReceiptIntegrityOutcome;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* What a verify action reports. `retired` marks a seal this build does not
|
|
118
|
+
* verify because its integrity version was retired: not a pass, and not the
|
|
119
|
+
* failure a tampered receipt reports.
|
|
120
|
+
*/
|
|
121
|
+
export type ViewArtifactVerification = { ok: true; detail: string } | { ok: false; detail: string; retired?: true };
|
|
112
122
|
|
|
113
123
|
const RECEIPT_REQUIRED_KEYS = [
|
|
114
124
|
"runId",
|
|
@@ -286,6 +296,21 @@ function validateToolStats(value: unknown): ReceiptVerifyResult {
|
|
|
286
296
|
|
|
287
297
|
type ReadLedgerResult = { ok: true; envelope: RunEnvelope } | { ok: false; reason: string };
|
|
288
298
|
|
|
299
|
+
/**
|
|
300
|
+
* The canonical trust line for a receipt the integrity verifier just accepted:
|
|
301
|
+
* the same file and ledger row, projected through the shared boundary so the
|
|
302
|
+
* receipt view spells its verdict exactly as the board and the CLI do.
|
|
303
|
+
*/
|
|
304
|
+
export function receiptTrustDetail(stateDir: string, runId: string): string {
|
|
305
|
+
try {
|
|
306
|
+
const receipt = JSON.parse(readFileSync(receiptFilePath(stateDir, runId), "utf8")) as RunReceipt;
|
|
307
|
+
const ledger = readRunEnvelope(stateDir, runId);
|
|
308
|
+
return formatTrustSummaryLine(inspectRunReceiptTrustStatus(receipt, ledger.ok ? ledger.envelope : null).status);
|
|
309
|
+
} catch {
|
|
310
|
+
return "integrity verified; trust status unreadable";
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
289
314
|
function readRunEnvelope(stateDir: string, runId: string): ReadLedgerResult {
|
|
290
315
|
const runs = readRunLedger(stateDir);
|
|
291
316
|
for (const entry of runs) {
|
|
@@ -398,6 +423,10 @@ export function verifyReceiptFile(stateDir: string, runId: string): ReceiptVerif
|
|
|
398
423
|
if (!isNullableString(r.sessionId)) {
|
|
399
424
|
return { ok: false, reason: `sessionId invalid: ${String(r.sessionId)}` };
|
|
400
425
|
}
|
|
426
|
+
// A retired seal is diagnosed ahead of the shape check, which would
|
|
427
|
+
// otherwise report the older version as an invalid integrity block.
|
|
428
|
+
const retired = retiredReceiptIntegrity(r.integrity);
|
|
429
|
+
if (retired !== null) return { ok: false, reason: retiredReceiptIntegrityReason(retired), retired };
|
|
401
430
|
if (!isReceiptIntegrity(r.integrity)) {
|
|
402
431
|
return { ok: false, reason: "integrity invalid" };
|
|
403
432
|
}
|
|
@@ -673,8 +702,12 @@ export class ReceiptArtifactProvider implements ArtifactProvider {
|
|
|
673
702
|
load: () => loadJsonFileLines(path),
|
|
674
703
|
verify: async () => {
|
|
675
704
|
const result = verifyReceiptFile(this.deps.stateDir, env.id);
|
|
676
|
-
if (!result.ok)
|
|
677
|
-
|
|
705
|
+
if (!result.ok) {
|
|
706
|
+
return { ok: false, detail: result.reason, ...(result.retired !== undefined ? { retired: true } : {}) };
|
|
707
|
+
}
|
|
708
|
+
// "integrity verified" alone read as a verified result. The
|
|
709
|
+
// canonical line says what the seal proves and what it does not.
|
|
710
|
+
return { ok: true, detail: receiptTrustDetail(this.deps.stateDir, env.id) };
|
|
678
711
|
},
|
|
679
712
|
};
|
|
680
713
|
});
|
|
@@ -51,7 +51,9 @@ export type ViewVerificationState =
|
|
|
51
51
|
| { status: "idle" }
|
|
52
52
|
| { status: "running" }
|
|
53
53
|
| { status: "ok"; detail: string }
|
|
54
|
-
| { status: "fail"; detail: string }
|
|
54
|
+
| { status: "fail"; detail: string }
|
|
55
|
+
/** A seal this build does not verify because its version was retired: neither a pass nor a tampered-receipt failure. */
|
|
56
|
+
| { status: "retired"; detail: string };
|
|
55
57
|
|
|
56
58
|
export type ViewScrollAction =
|
|
57
59
|
| "line-up"
|
|
@@ -327,6 +329,7 @@ function verificationText(state: ViewVerificationState | undefined): string {
|
|
|
327
329
|
if (!state || state.status === "idle") return "";
|
|
328
330
|
if (state.status === "running") return `${GLYPH.running} verify running`;
|
|
329
331
|
if (state.status === "ok") return `${GLYPH.ok} verify ok ${state.detail}`;
|
|
332
|
+
if (state.status === "retired") return `${GLYPH.warn} verify retired ${state.detail}`;
|
|
330
333
|
return `${GLYPH.error} verify fail ${state.detail}`;
|
|
331
334
|
}
|
|
332
335
|
|
|
@@ -343,7 +346,14 @@ export function buildArtifactHeader(
|
|
|
343
346
|
if (size.length > 0) parts.push(theme.fg("dim", size));
|
|
344
347
|
const verify = verificationText(verification);
|
|
345
348
|
if (verify.length > 0) {
|
|
346
|
-
const token =
|
|
349
|
+
const token =
|
|
350
|
+
verification?.status === "ok"
|
|
351
|
+
? "success"
|
|
352
|
+
: verification?.status === "fail"
|
|
353
|
+
? "error"
|
|
354
|
+
: verification?.status === "retired"
|
|
355
|
+
? "warning"
|
|
356
|
+
: "info";
|
|
347
357
|
parts.push(theme.fg(token, verify));
|
|
348
358
|
}
|
|
349
359
|
return padAnsi(parts.join(" "), width, ELLIPSIS);
|
|
@@ -689,7 +699,9 @@ export class ViewOverlayView implements Component {
|
|
|
689
699
|
.then((result) => {
|
|
690
700
|
this.verifications.set(
|
|
691
701
|
key,
|
|
692
|
-
result.ok
|
|
702
|
+
result.ok
|
|
703
|
+
? { status: "ok", detail: result.detail }
|
|
704
|
+
: { status: result.retired === true ? "retired" : "fail", detail: result.detail },
|
|
693
705
|
);
|
|
694
706
|
})
|
|
695
707
|
.catch((err) => {
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
import { existsSync, readFileSync } from "node:fs";
|
|
17
17
|
import { join } from "node:path";
|
|
18
18
|
import { clioStateDir } from "../core/xdg.js";
|
|
19
|
+
import type { RunEnvelope, RunReceipt } from "../domains/dispatch/types.js";
|
|
20
|
+
import { inspectRunReceiptTrustStatus } from "../domains/evidence/trust-status.js";
|
|
19
21
|
import { receiptFilePath } from "./view/artifacts.js";
|
|
20
22
|
import type { WorkerReceiptFacts, WorkerResultContract } from "./worker-stream.js";
|
|
21
23
|
|
|
@@ -86,12 +88,22 @@ export function workerReceiptFacts(receipt: Record<string, unknown>): WorkerRece
|
|
|
86
88
|
|
|
87
89
|
/** Read and project `<state>/receipts/<runId>.json` alone; null when it is absent or unreadable. */
|
|
88
90
|
function readReceiptFileFacts(runId: string, stateDir: string): WorkerReceiptFacts | null {
|
|
91
|
+
let parsed: unknown;
|
|
89
92
|
try {
|
|
90
|
-
|
|
91
|
-
return isRecord(parsed) ? workerReceiptFacts(parsed) : null;
|
|
93
|
+
parsed = JSON.parse(readFileSync(receiptFilePath(stateDir, runId), "utf8"));
|
|
92
94
|
} catch {
|
|
93
95
|
return null;
|
|
94
96
|
}
|
|
97
|
+
if (!isRecord(parsed)) return null;
|
|
98
|
+
const facts = workerReceiptFacts(parsed);
|
|
99
|
+
if (facts === null) return null;
|
|
100
|
+
// The trust verdict is an authenticated read-back: the receipt file
|
|
101
|
+
// against the ledger row it was sealed from. Without the row there is no
|
|
102
|
+
// authentication, and no verdict is better than a guessed one.
|
|
103
|
+
const row = findRunRow(runId, stateDir);
|
|
104
|
+
if (row === null) return facts;
|
|
105
|
+
const trust = inspectRunReceiptTrustStatus(parsed as unknown as RunReceipt, row as unknown as RunEnvelope).status;
|
|
106
|
+
return { ...facts, trust };
|
|
95
107
|
}
|
|
96
108
|
|
|
97
109
|
/** Read and project `<state>/receipts/<runId>.json`; null when it is absent or unreadable. */
|
|
@@ -42,6 +42,7 @@ import type {
|
|
|
42
42
|
RunAbortedPayload,
|
|
43
43
|
} from "../core/bus-events.js";
|
|
44
44
|
import type { RunKind } from "../domains/dispatch/types.js";
|
|
45
|
+
import type { CanonicalTrustStatus } from "../domains/evidence/trust-status.js";
|
|
45
46
|
import type { WorkerRunOrigin, WorkerRunRuntime, WorkerRunRuntimeKind } from "../domains/session/index.js";
|
|
46
47
|
import { createWorkerProgressFold, type WorkerProgressFold, type WorkerProgressSnapshot } from "./worker-progress.js";
|
|
47
48
|
|
|
@@ -88,6 +89,13 @@ export interface WorkerReceiptSummary {
|
|
|
88
89
|
/** A receipt's projection: the summary plus the answer it sealed. */
|
|
89
90
|
export interface WorkerReceiptFacts extends WorkerReceiptSummary {
|
|
90
91
|
text?: string;
|
|
92
|
+
/**
|
|
93
|
+
* The canonical trust status of the persisted receipt, authenticated
|
|
94
|
+
* against the persisted ledger row it was sealed from. Present only when
|
|
95
|
+
* both could be read back, so a surface that shows it shows an
|
|
96
|
+
* authenticated verdict and never the sealing process's own claim.
|
|
97
|
+
*/
|
|
98
|
+
trust?: CanonicalTrustStatus;
|
|
91
99
|
}
|
|
92
100
|
|
|
93
101
|
/** Reads `receipts/<runId>.json` and projects it; null when it is absent or unreadable. */
|
|
@@ -6,8 +6,10 @@ import {
|
|
|
6
6
|
COUNCIL_JUDGE_PROMPT,
|
|
7
7
|
JUDGE_GATE_PROMPT,
|
|
8
8
|
REVIEWER_GATE_PROMPT,
|
|
9
|
+
renderCouncilVoteMemberTask,
|
|
9
10
|
} from "../domains/dispatch/gate-role-prompts.js";
|
|
10
11
|
import { normalizeDispatchIntent } from "../domains/dispatch/intent.js";
|
|
12
|
+
import { renderDispatchReviewerTask } from "../domains/dispatch/intent-requirements.js";
|
|
11
13
|
import { approvedRouteCandidates } from "../domains/dispatch/route-approval.js";
|
|
12
14
|
import { defaultRoutingIntent } from "../domains/dispatch/routing-intent.js";
|
|
13
15
|
import type { DispatchFailoverMode } from "../domains/dispatch/validation.js";
|
|
@@ -139,15 +141,6 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
139
141
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
140
142
|
}
|
|
141
143
|
|
|
142
|
-
function reviewerTask(originalTask: string, builderRunId: string, cycle: number): string {
|
|
143
|
-
return [
|
|
144
|
-
`Review the work of builder run ${builderRunId} (review cycle ${cycle}).`,
|
|
145
|
-
"The builder's final answer is provided as input data; verify it against the workspace, do not trust it blindly.",
|
|
146
|
-
"Original task the builder was given:",
|
|
147
|
-
originalTask,
|
|
148
|
-
].join("\n\n");
|
|
149
|
-
}
|
|
150
|
-
|
|
151
144
|
function reviewSettingsFromArgs(
|
|
152
145
|
args: Record<string, unknown>,
|
|
153
146
|
): { ok: true; review: DispatchReviewSettings | undefined } | { ok: false; message: string } {
|
|
@@ -534,9 +527,6 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
|
|
|
534
527
|
if (parsed.requests.length !== 1 || parsed.requests[0] === undefined) {
|
|
535
528
|
return shapeRejection(args, REVIEW_SINGLE_TASK_MESSAGE);
|
|
536
529
|
}
|
|
537
|
-
if (parsed.requests.some((request) => (request.resolvedVerification?.length ?? 0) > 0)) {
|
|
538
|
-
return shapeRejection(args, "verification_unsupported_for_mode: review requests cannot run host verification");
|
|
539
|
-
}
|
|
540
530
|
}
|
|
541
531
|
if (mode === "compete" && (parsed.requests.length !== 1 || parsed.requests[0] === undefined)) {
|
|
542
532
|
return shapeRejection(args, COMPETE_SINGLE_TASK_MESSAGE);
|
|
@@ -587,7 +577,7 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
|
|
|
587
577
|
{
|
|
588
578
|
agentId: gateDeciderAgentId(reviewResult.review.reviewer),
|
|
589
579
|
executionRole: "reviewer",
|
|
590
|
-
task:
|
|
580
|
+
task: renderDispatchReviewerTask(base.task, subject.runId, cycle, base.intent),
|
|
591
581
|
systemPrompt: REVIEWER_GATE_PROMPT,
|
|
592
582
|
autonomy: "read-only",
|
|
593
583
|
gate: { role: "reviewer", group: "plan-preview", cycle, subjects: [subject] },
|
|
@@ -648,6 +638,15 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
|
|
|
648
638
|
executionRole: "researcher",
|
|
649
639
|
autonomy: "read-only",
|
|
650
640
|
toolProfile: "council-read-only",
|
|
641
|
+
// The runner reuses this composed task from the resolved plan, so
|
|
642
|
+
// the approval artifact the plan hash binds shows the operator the
|
|
643
|
+
// exact ballot the member is asked to cast.
|
|
644
|
+
...(council.synthesis === "vote"
|
|
645
|
+
? {
|
|
646
|
+
task: renderCouncilVoteMemberTask(base.task),
|
|
647
|
+
resultContractOverride: { kind: "council-ballot" as const },
|
|
648
|
+
}
|
|
649
|
+
: {}),
|
|
651
650
|
target: member.target,
|
|
652
651
|
...(member.model ? { model: member.model } : {}),
|
|
653
652
|
...(member.thinking ? { thinkingLevel: member.thinking as NonNullable<DispatchRequest["thinkingLevel"]> } : {}),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Pure model-argument parsing; every returned DispatchRequest has a concrete agent id. */
|
|
2
2
|
|
|
3
|
+
import { pathBoundaryCovers } from "../core/path-boundary.js";
|
|
3
4
|
import type { AgentAutomationAuthority } from "../domains/agents/spec.js";
|
|
4
5
|
import { type AgentTaskType, classifyAgentTask } from "../domains/dispatch/agent-candidates.js";
|
|
5
6
|
import { cloneDispatchBudgetRequest } from "../domains/dispatch/budget-envelope.js";
|
|
@@ -261,8 +262,34 @@ export function dispatchRequestsFromArgs(
|
|
|
261
262
|
for (let index = 0; index < tasks.length; index += 1) {
|
|
262
263
|
const item = tasks[index];
|
|
263
264
|
const itemArgs: Record<string, unknown> = isRecord(item) ? { ...shared, ...item } : { ...shared, task: item };
|
|
265
|
+
const sharedIntent = isRecord(shared.intent) ? shared.intent : null;
|
|
266
|
+
const itemIntent = isRecord(item) && Object.hasOwn(item, "intent") && isRecord(item.intent) ? item.intent : null;
|
|
267
|
+
if (sharedIntent !== null && itemIntent !== null) itemArgs.intent = { ...sharedIntent, ...itemIntent };
|
|
264
268
|
const parsed = dispatchRequestFromArgs(itemArgs, options);
|
|
265
269
|
if (!parsed.ok) return { ok: false, message: `dispatch: task ${index + 1}: ${parsed.message}` };
|
|
270
|
+
if (sharedIntent !== null && itemIntent !== null) {
|
|
271
|
+
if (options.resolveIntent === undefined) {
|
|
272
|
+
return { ok: false, message: `dispatch: task ${index + 1}: intent resolver is unavailable` };
|
|
273
|
+
}
|
|
274
|
+
const ceiling = options.resolveIntent(sharedIntent, parsed.request.cwd);
|
|
275
|
+
if (!ceiling.ok) return { ok: false, message: `dispatch: task ${index + 1}: ${ceiling.message}` };
|
|
276
|
+
const pathCeilings = {
|
|
277
|
+
readRoots: [...ceiling.intent.readRoots, ...ceiling.intent.writeRoots],
|
|
278
|
+
writeRoots: ceiling.intent.writeRoots,
|
|
279
|
+
relevantPaths: [...ceiling.intent.readRoots, ...ceiling.intent.writeRoots, ...ceiling.intent.relevantPaths],
|
|
280
|
+
};
|
|
281
|
+
for (const field of ["readRoots", "writeRoots", "relevantPaths"] as const) {
|
|
282
|
+
const outside = parsed.request.intent?.[field].find(
|
|
283
|
+
(candidate) => !pathBoundaryCovers(pathCeilings[field], candidate),
|
|
284
|
+
);
|
|
285
|
+
if (outside !== undefined) {
|
|
286
|
+
return {
|
|
287
|
+
ok: false,
|
|
288
|
+
message: `dispatch: task ${index + 1}: intent_scope_widening: ${field} entry '${outside}' is outside the top-level intent ceiling`,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
266
293
|
requests.push(parsed.request);
|
|
267
294
|
}
|
|
268
295
|
return { ok: true, requests };
|