@iowarp/clio-coder 0.3.6 → 0.3.7
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 +28 -0
- package/README.md +16 -5
- package/dist/{acp-2BEHC4DL.js → acp-SK4MD6MM.js} +10 -10
- package/dist/{agents-LNNFTM53.js → agents-2FN2K6ME.js} +30 -25
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-KXXFI2VS.js → auth-QIYZWM5I.js} +13 -13
- package/dist/{chunk-KHSFENX2.js → chunk-3HAPLH5M.js} +10 -10
- package/dist/{chunk-24I7BN55.js → chunk-465YSENW.js} +2 -2
- package/dist/{chunk-4OC57DA6.js → chunk-4DGYLA73.js} +53 -2
- package/dist/{chunk-CYQKWTG3.js → chunk-4DWFMQDR.js} +4 -4
- package/dist/{chunk-E2ER4LJF.js → chunk-5C3AQNDW.js} +25 -1
- package/dist/{chunk-22NAGB7X.js → chunk-5C77SEEY.js} +5 -94
- package/dist/{chunk-43AOLP7E.js → chunk-5FR74PWO.js} +2 -1
- package/dist/{chunk-K7T3E2SR.js → chunk-5UJ6ECTS.js} +10 -9
- package/dist/{chunk-6US73PDB.js → chunk-6M7VS3J3.js} +5 -5
- package/dist/{chunk-CJUB2JJ2.js → chunk-6TUKSZVF.js} +5 -5
- package/dist/{chunk-5JGRAMKL.js → chunk-AB4XIIVB.js} +8 -6
- package/dist/{chunk-R46L2BIR.js → chunk-BMWK7ZIZ.js} +14 -20
- package/dist/{chunk-4BPJXDWC.js → chunk-C4JBQ5SR.js} +30 -14
- package/dist/{chunk-XXQNGV4M.js → chunk-CEYBNUGC.js} +243 -63
- package/dist/{chunk-VEZEGCGW.js → chunk-D4MDIG46.js} +20 -18
- package/dist/chunk-DJNLUABN.js +843 -0
- package/dist/{chunk-RY3LY4J5.js → chunk-DMD2AGVS.js} +5 -4
- package/dist/{chunk-KOHPCX4K.js → chunk-DOOEX22V.js} +2 -2
- package/dist/chunk-DQA7QLMD.js +123 -0
- package/dist/chunk-DR52UMZW.js +21 -0
- package/dist/{chunk-XF5N4U5A.js → chunk-EBEFWSGL.js} +6 -5
- package/dist/{chunk-EYPA3EGJ.js → chunk-EELBMBT6.js} +120 -13
- package/dist/{chunk-CKXWIANG.js → chunk-EOOQZZDE.js} +16 -14
- package/dist/{chunk-WR67VIZY.js → chunk-FOT2FX5J.js} +63 -5
- package/dist/{chunk-FYYLNIL5.js → chunk-GH5622CP.js} +2 -2
- package/dist/chunk-GWS3VEIW.js +195 -0
- package/dist/{chunk-LYF7OHWH.js → chunk-J7PIKKWC.js} +8 -463
- package/dist/{chunk-NILBFAPG.js → chunk-JNXPYBB4.js} +2 -2
- package/dist/{chunk-4VP4KH3K.js → chunk-JRIO5UD2.js} +4 -4
- package/dist/{chunk-6XXKFVSN.js → chunk-JTSEDYVQ.js} +7 -7
- package/dist/{chunk-QKMUKYO7.js → chunk-KCMKRQX4.js} +236 -84
- package/dist/chunk-KZ2H5X4G.js +1026 -0
- package/dist/{chunk-QNQHSOLF.js → chunk-LADCF22A.js} +12 -12
- package/dist/chunk-M4AKACEO.js +382 -0
- package/dist/{chunk-XYDYPRZI.js → chunk-MXI6J5JF.js} +7 -7
- package/dist/{chunk-G7MUEIGA.js → chunk-OB5HIGJY.js} +1 -1
- package/dist/{chunk-EKY57CSP.js → chunk-OBMAI2DP.js} +61 -767
- package/dist/chunk-PD3MESLB.js +242 -0
- package/dist/{chunk-ZRGEBJ4T.js → chunk-QCTRSGHQ.js} +21 -21
- package/dist/chunk-RVG5JXAL.js +41 -0
- package/dist/{chunk-RD5U66HV.js → chunk-SROCI7ZU.js} +7 -7
- package/dist/{chunk-MFFY33HR.js → chunk-THKY7CD7.js} +466 -205
- package/dist/{chunk-PCZJO5TI.js → chunk-UFQ3F4FW.js} +13 -178
- package/dist/{chunk-AD2SYQYC.js → chunk-UHXRNZ2J.js} +121 -3
- package/dist/chunk-UND3GU2L.js +103 -0
- package/dist/{chunk-QM3F2GKX.js → chunk-UUANF5CR.js} +2247 -2096
- package/dist/chunk-UVDSQ6LW.js +472 -0
- package/dist/{chunk-DJVECN66.js → chunk-VQNODYQ4.js} +14 -14
- package/dist/{chunk-3BPUFZDL.js → chunk-VREKEFLL.js} +3 -3
- package/dist/{chunk-PBTHKCPN.js → chunk-WJHBC77E.js} +6 -6
- package/dist/{chunk-XE2VEJHX.js → chunk-X2KV5FXT.js} +2 -2
- package/dist/{chunk-ZXF4XRKW.js → chunk-XEGB6BCN.js} +157 -7
- package/dist/{chunk-E25LMLRW.js → chunk-YD734TPH.js} +2 -2
- package/dist/{verifiers-NCBTHHN2.js → chunk-YTYFXUI3.js} +65 -322
- package/dist/{chunk-OH3TOQTB.js → chunk-ZGH7FGS5.js} +13 -7
- package/dist/cli/index.js +32 -30
- package/dist/{clio-M2KGYUFZ.js → clio-WBVQEBKO.js} +7 -7
- package/dist/{code-nav-GQNL7XA6.js → code-nav-FGGFIE7L.js} +3 -3
- package/dist/{components-5TTYYX6G.js → components-F7OEATSO.js} +4 -4
- package/dist/{config-XUUYQIWO.js → config-TRBL3RCF.js} +34 -29
- package/dist/{configure-IHJ7YOMV.js → configure-OLCVPHNM.js} +15 -15
- package/dist/{context-74JLXAWD.js → context-MJIJ6GOX.js} +11 -11
- package/dist/{context-ZQ7SIFJV.js → context-WFPKQSM6.js} +19 -3
- package/dist/{context-75MIWW3U.js → context-XEWE3MOJ.js} +31 -26
- package/dist/{context-clear-GYKWNUML.js → context-clear-KNOS2JPB.js} +31 -26
- package/dist/{context-working-set-UX5KEP4J.js → context-working-set-EUXAZI6N.js} +8 -8
- package/dist/{dispatch-runner-GIJBHNFL.js → dispatch-runner-B7MTOVKL.js} +313 -53
- package/dist/{docs-6FZSCG5B.js → docs-FLJTIDSE.js} +4 -4
- package/dist/{doctor-SVJ5BZCW.js → doctor-RN4YKO2X.js} +14 -14
- package/dist/{eval-CG6LLBLD.js → eval-RUBJVSNQ.js} +8 -7
- package/dist/{evidence-ZYFIEN42.js → evidence-JZNBUOQZ.js} +30 -25
- package/dist/{evolve-QGEXEMDW.js → evolve-FJVC4KKI.js} +30 -25
- package/dist/{extensions-ADGNCJJD.js → extensions-IQL36S7K.js} +4 -4
- package/dist/{fleet-S5R4ZOQY.js → fleet-BDKYJFCP.js} +214 -360
- package/dist/fleet-commands-ZFIWZSB3.js +70 -0
- package/dist/fleet-graph-Y6HPXIVF.js +125 -0
- package/dist/fleet-new-RDVJLHHH.js +48 -0
- package/dist/fleet-validate-BIYREGIK.js +79 -0
- package/dist/{init-5DRU55YR.js → init-LQUB5COQ.js} +44 -37
- package/dist/library-NJAHIGG4.js +217 -0
- package/dist/{memory-7YKKR6UC.js → memory-OG6HOYKM.js} +31 -26
- package/dist/{models-ZPOLRU2C.js → models-5ZG5XY7J.js} +21 -20
- package/dist/{monitor-US5F5YGZ.js → monitor-TJ7AMTGB.js} +49 -30
- package/dist/{orchestrator-E2AL4T5N.js → orchestrator-WZYB54DM.js} +3827 -581
- package/dist/{paths-E7KYAQWE.js → paths-XUC7GS6E.js} +4 -4
- package/dist/{reset-KZ652EK6.js → reset-PXQT45IY.js} +7 -7
- package/dist/{run-SRNBKDWD.js → run-FQ74YF62.js} +53 -45
- package/dist/{share-CGZE33UP.js → share-FW7SVCL3.js} +33 -9
- package/dist/{skills-S2X4DLY5.js → skills-7E7IRB3R.js} +24 -8
- package/dist/{skills-eval-W2GGIC4R.js → skills-eval-LI75W6OK.js} +34 -27
- package/dist/{targets-54SWINWB.js → targets-4CIFKCTW.js} +23 -22
- package/dist/{terminal-lease-SAIF2OGY.js → terminal-lease-WUZY7ZV5.js} +4 -4
- package/dist/{uninstall-BVLWXKBT.js → uninstall-7FV7IP4E.js} +4 -4
- package/dist/{upgrade-JKAR27XC.js → upgrade-K2HVIVMQ.js} +20 -19
- package/dist/{usage-MSAWCLX4.js → usage-GTZELZQX.js} +116 -49
- package/dist/verifiers-RLAHT27O.js +336 -0
- package/dist/{verify-X5HDROLA.js → verify-BX3BRKH5.js} +7 -6
- package/dist/{wiki-generate-GUSOQ6ZP.js → wiki-generate-ASIFASCN.js} +45 -37
- package/dist/worker/entry.js +38 -35
- package/docs/README.md +3 -2
- package/docs/acp.md +1 -1
- package/docs/alcf-provider.md +1 -1
- package/docs/architecture.md +2 -2
- package/docs/artifact-versions.md +10 -6
- package/docs/built-in-agents.md +26 -2
- package/docs/capacity-and-scheduling.md +1 -1
- package/docs/commands-and-modes.md +82 -2
- package/docs/configuration-and-targets.md +79 -2
- package/docs/context-engine.md +1 -1
- package/docs/development-pipeline.md +1 -1
- package/docs/dispatch-architecture-rationale.md +1 -1
- package/docs/documentation-coverage.md +3 -3
- package/docs/documentation-guide.md +3 -3
- package/docs/eval-runner.md +1 -1
- package/docs/evals-internal.md +1 -1
- package/docs/evidence-and-memory.md +5 -5
- package/docs/evolution.md +1 -1
- package/docs/exit-codes-and-output.md +4 -1
- package/docs/extensions-and-sharing.md +6 -2
- package/docs/fleet-demo-runbook.md +2 -2
- package/docs/fleet-dispatch.md +197 -10
- package/docs/git-commit-provenance.md +2 -2
- package/docs/glossary.md +1 -1
- package/docs/installation-and-lifecycle.md +2 -2
- package/docs/middleware-and-components.md +2 -1
- package/docs/model-catalog.md +1 -1
- package/docs/observability.md +55 -8
- package/docs/proactive-memory.md +1 -1
- package/docs/prompt-envelope-and-tools.md +1 -1
- package/docs/provider-adapter-cookbook.md +1 -1
- package/docs/release-cut-checklist.md +79 -64
- package/docs/resource-library.md +59 -0
- package/docs/safety-model.md +2 -2
- package/docs/scientific-validation.md +3 -3
- package/docs/session-lifecycle.md +37 -1
- package/docs/skills-marketplace.md +16 -3
- package/docs/tool-usage.md +14 -7
- package/docs/trace-store.md +1 -1
- package/docs/troubleshooting.md +1 -1
- package/docs/tui-design.md +1 -1
- package/docs/worker-dispatch-mechanics.md +3 -3
- package/package.json +1 -1
- package/src/cli/fleet-commands.ts +37 -0
- package/src/cli/fleet-graph.ts +102 -0
- package/src/cli/fleet-new.ts +36 -0
- package/src/cli/fleet-preflight.ts +121 -0
- package/src/cli/fleet-validate.ts +30 -0
- package/src/cli/fleet.ts +173 -335
- package/src/cli/index.ts +3 -1
- package/src/cli/library.ts +190 -0
- package/src/cli/share.ts +13 -1
- package/src/cli/usage.ts +111 -19
- package/src/core/bus-events.ts +4 -0
- package/src/core/commit-attribution.ts +4 -4
- package/src/core/config.ts +130 -0
- package/src/core/defaults.ts +81 -0
- package/src/domains/agents/builtins/architect.md +1 -0
- package/src/domains/agents/builtins/oracle.md +33 -0
- package/src/domains/agents/catalog.ts +13 -1
- package/src/domains/agents/fleet-contract.ts +278 -16
- package/src/domains/agents/index.ts +14 -0
- package/src/domains/agents/result-contract.ts +235 -1
- package/src/domains/config/classify.ts +4 -0
- package/src/domains/dispatch/active-route-planner.ts +14 -0
- package/src/domains/dispatch/backoff.ts +2 -1
- package/src/domains/dispatch/capability-match.ts +1 -0
- package/src/domains/dispatch/checkout-writer-lease.ts +175 -0
- package/src/domains/dispatch/contract.ts +34 -0
- package/src/domains/dispatch/delegation-plan.ts +167 -0
- package/src/domains/dispatch/execution-plan.ts +76 -5
- package/src/domains/dispatch/execution-role.ts +3 -1
- package/src/domains/dispatch/execution-scheduler.ts +183 -67
- package/src/domains/dispatch/extension.ts +258 -9
- package/src/domains/dispatch/fleet-gate.ts +14 -0
- package/src/domains/dispatch/fleet-plan.ts +63 -3
- package/src/domains/dispatch/fleet-run.ts +737 -0
- package/src/domains/dispatch/gate-role-prompts.ts +9 -0
- package/src/domains/dispatch/host-verification.ts +178 -0
- package/src/domains/dispatch/index.ts +38 -0
- package/src/domains/dispatch/intent.ts +159 -0
- package/src/domains/dispatch/receipt-integrity.ts +8 -4
- package/src/domains/dispatch/state.ts +36 -3
- package/src/domains/dispatch/types.ts +51 -6
- package/src/domains/dispatch/validation.ts +66 -6
- package/src/domains/evidence/trust-status.ts +10 -1
- package/src/domains/middleware/index.ts +15 -0
- package/src/domains/middleware/watchdog.ts +281 -0
- package/src/domains/observability/contract.ts +3 -1
- package/src/domains/observability/cost.ts +12 -1
- package/src/domains/observability/extension.ts +2 -2
- package/src/domains/observability/index.ts +10 -0
- package/src/domains/observability/out-of-turn-usage.ts +223 -0
- package/src/domains/resources/index.ts +20 -0
- package/src/domains/resources/library.ts +326 -0
- package/src/domains/resources/skills/marketplace.ts +37 -12
- package/src/domains/session/handoff.ts +629 -0
- package/src/domains/share/archive.ts +67 -2
- package/src/entry/orchestrator.ts +37 -0
- package/src/interactive/bus-notices.ts +26 -0
- package/src/interactive/chat-loop.ts +235 -1
- package/src/interactive/chat-renderer.ts +22 -0
- package/src/interactive/cost-overlay.ts +31 -3
- package/src/interactive/council-dispatch.ts +30 -0
- package/src/interactive/council-grid.ts +213 -0
- package/src/interactive/council.ts +99 -0
- package/src/interactive/dispatch-board.ts +260 -16
- package/src/interactive/fleet-run-preview.ts +307 -0
- package/src/interactive/footer/notifications.ts +219 -0
- package/src/interactive/handoff-round.ts +56 -0
- package/src/interactive/interactive-application.ts +43 -1
- package/src/interactive/interactive-event-projection.ts +9 -1
- package/src/interactive/interactive-slash-runtime.ts +52 -2
- package/src/interactive/interactive-subscriptions.ts +14 -2
- package/src/interactive/oracle.ts +179 -0
- package/src/interactive/overlay-ask-user-lifecycle.ts +6 -0
- package/src/interactive/overlay-general-openers.ts +190 -1
- package/src/interactive/overlay-key-routing.ts +17 -1
- package/src/interactive/overlay-lifecycle.ts +41 -1
- package/src/interactive/overlay-permission-lifecycle.ts +10 -0
- package/src/interactive/overlay-resource-openers.ts +11 -3
- package/src/interactive/overlay-session-lifecycle.ts +234 -2
- package/src/interactive/overlays/fleet-run-approval.ts +208 -0
- package/src/interactive/overlays/handoff-review.ts +185 -0
- package/src/interactive/overlays/library-install-confirm.ts +151 -0
- package/src/interactive/overlays/list-overlay.ts +168 -2
- package/src/interactive/overlays/settings.ts +101 -4
- package/src/interactive/overlays/side-question.ts +139 -0
- package/src/interactive/overlays/skills-hub.ts +401 -15
- package/src/interactive/side-question.ts +171 -0
- package/src/interactive/slash-commands.ts +432 -5
- package/src/interactive/slash-spec.ts +19 -6
- package/src/interactive/theme/tokens.ts +30 -0
- package/src/interactive/turn-middleware.ts +15 -1
- package/src/interactive/watchdog-run.ts +75 -0
- package/src/interactive/worker-share.ts +56 -1
- package/src/interactive/worker-stream.ts +7 -0
- package/src/tools/bootstrap.ts +3 -0
- package/src/tools/compete-worktrees.ts +13 -79
- package/src/tools/dispatch-admission.ts +242 -8
- package/src/tools/dispatch-arguments.ts +57 -1
- package/src/tools/dispatch-plan.ts +136 -6
- package/src/tools/dispatch-runner.ts +319 -13
- package/src/tools/dispatch-types.ts +20 -1
- package/src/tools/dispatch.ts +72 -2
- package/src/tools/monitor.ts +16 -0
- package/src/tools/profiles.ts +18 -4
- package/src/tools/task-worktree.ts +238 -0
- package/src/tools/verify/authoring.ts +61 -1
- package/src/tools/verify/scripts.ts +62 -0
- package/src/tools/worker-evidence.ts +2 -1
- package/src/worker/spec-contract.ts +1 -0
- package/dist/chunk-HC4CLZ2Y.js +0 -68
|
@@ -14,6 +14,7 @@ import type { EvidenceTag } from "../evidence/index.js";
|
|
|
14
14
|
import type { CostProvenance, RuntimeTargetSnapshot } from "../providers/index.js";
|
|
15
15
|
import type { RunToolBudgetEnvelope } from "./budget-envelope.js";
|
|
16
16
|
import type { ExecutionRole, GateTopologyRole } from "./execution-role.js";
|
|
17
|
+
import type { DispatchIntent } from "./intent.js";
|
|
17
18
|
import type { RouteDecisionV1 } from "./route-decision.js";
|
|
18
19
|
import type { RoutingIntent } from "./routing-intent.js";
|
|
19
20
|
|
|
@@ -41,7 +42,8 @@ export type RunOutcomeCode =
|
|
|
41
42
|
| "worker_tool_call_cap_exhausted"
|
|
42
43
|
| "loop_guard_tools_disabled_exhausted"
|
|
43
44
|
| "result_contract_exhausted"
|
|
44
|
-
| "worker_final_output_missing"
|
|
45
|
+
| "worker_final_output_missing"
|
|
46
|
+
| "host_verification_rejected";
|
|
45
47
|
|
|
46
48
|
export function isRunOutcomeCode(value: unknown): value is RunOutcomeCode {
|
|
47
49
|
return (
|
|
@@ -49,7 +51,8 @@ export function isRunOutcomeCode(value: unknown): value is RunOutcomeCode {
|
|
|
49
51
|
value === "worker_tool_call_cap_exhausted" ||
|
|
50
52
|
value === "loop_guard_tools_disabled_exhausted" ||
|
|
51
53
|
value === "result_contract_exhausted" ||
|
|
52
|
-
value === "worker_final_output_missing"
|
|
54
|
+
value === "worker_final_output_missing" ||
|
|
55
|
+
value === "host_verification_rejected"
|
|
53
56
|
);
|
|
54
57
|
}
|
|
55
58
|
|
|
@@ -238,6 +241,13 @@ export interface RunGateProvenance {
|
|
|
238
241
|
verdict?: "pass" | "fail" | "revise";
|
|
239
242
|
}
|
|
240
243
|
|
|
244
|
+
export interface RunCouncilProvenance {
|
|
245
|
+
group: string;
|
|
246
|
+
label: string;
|
|
247
|
+
color?: string;
|
|
248
|
+
round: number;
|
|
249
|
+
}
|
|
250
|
+
|
|
241
251
|
/**
|
|
242
252
|
* Plan-approval provenance for multi-task, compete, or remote dispatch.
|
|
243
253
|
* `approval: "operator"` records that a supervised autonomy level parked the
|
|
@@ -248,7 +258,7 @@ export interface RunGateProvenance {
|
|
|
248
258
|
*/
|
|
249
259
|
export interface RunPlanProvenance {
|
|
250
260
|
hash: string;
|
|
251
|
-
topology: "parallel" | "sequential" | "pipeline" | "review" | "compete" | "detached" | "fleet";
|
|
261
|
+
topology: "parallel" | "sequential" | "pipeline" | "review" | "compete" | "council" | "detached" | "fleet";
|
|
252
262
|
taskCount: number;
|
|
253
263
|
approval: "operator" | "full-auto";
|
|
254
264
|
source: null | {
|
|
@@ -311,7 +321,7 @@ export interface RunPhaseDurations {
|
|
|
311
321
|
* bumping one without the other is a compile error.
|
|
312
322
|
*/
|
|
313
323
|
export interface RunReceiptIntegrity {
|
|
314
|
-
version:
|
|
324
|
+
version: 19;
|
|
315
325
|
algorithm: "sha256";
|
|
316
326
|
digest: string;
|
|
317
327
|
}
|
|
@@ -449,6 +459,7 @@ export interface RunEnvelope {
|
|
|
449
459
|
pipeline?: RunPipelineProvenance;
|
|
450
460
|
/** Review/compete gate provenance; present only on runs of a gated dispatch. */
|
|
451
461
|
gate?: RunGateProvenance;
|
|
462
|
+
council?: RunCouncilProvenance;
|
|
452
463
|
/** Plan-approval provenance; present only on runs of an approval-gated plan. */
|
|
453
464
|
plan?: RunPlanProvenance;
|
|
454
465
|
/** Ad-hoc specialist provenance; present only when a persona override composed the stable prompt. */
|
|
@@ -674,6 +685,25 @@ export interface RunReceiptVerification {
|
|
|
674
685
|
| "receipt-unavailable";
|
|
675
686
|
}
|
|
676
687
|
|
|
688
|
+
export interface RunHostVerificationCheck {
|
|
689
|
+
check: string;
|
|
690
|
+
argv: string[];
|
|
691
|
+
cwd: string;
|
|
692
|
+
exitCode: number;
|
|
693
|
+
durationMs: number;
|
|
694
|
+
memo: boolean;
|
|
695
|
+
outputTail: string;
|
|
696
|
+
artifactPath?: string;
|
|
697
|
+
/** Original evidence owner on a memo hit. */
|
|
698
|
+
evidenceRunId?: string;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
export interface RunHostVerification {
|
|
702
|
+
status: "verified" | "rejected" | "skipped";
|
|
703
|
+
reason?: string;
|
|
704
|
+
checks: RunHostVerificationCheck[];
|
|
705
|
+
}
|
|
706
|
+
|
|
677
707
|
export interface RunReceipt {
|
|
678
708
|
runId: string;
|
|
679
709
|
agentId: string;
|
|
@@ -682,6 +712,8 @@ export interface RunReceipt {
|
|
|
682
712
|
agentAudience?: AgentAudience;
|
|
683
713
|
requestOrigin?: DispatchRequestOrigin;
|
|
684
714
|
task: string;
|
|
715
|
+
/** Normalized dispatch intent admitted before worker execution. */
|
|
716
|
+
intent?: DispatchIntent;
|
|
685
717
|
/** Integrity-sealed recipe policy, invocation request, effective phase, and admission reasons. */
|
|
686
718
|
budget?: RunToolBudgetEnvelope;
|
|
687
719
|
/** Proof of briefing content without copying its prose into the receipt. */
|
|
@@ -714,8 +746,11 @@ export interface RunReceipt {
|
|
|
714
746
|
pipeline?: RunPipelineProvenance;
|
|
715
747
|
/** Review/compete gate provenance; present only on runs of a gated dispatch. */
|
|
716
748
|
gate?: RunGateProvenance;
|
|
749
|
+
council?: RunCouncilProvenance;
|
|
717
750
|
/** Plan-approval provenance; present only on runs of an approval-gated plan. */
|
|
718
751
|
plan?: RunPlanProvenance;
|
|
752
|
+
/** Version 5 fleet gate artifact authored by this run. */
|
|
753
|
+
fleetGate?: { path: string; pathHash: string };
|
|
719
754
|
/** Ad-hoc specialist provenance; present only when a persona override composed the stable prompt. */
|
|
720
755
|
personaOverride?: RunPersonaOverride;
|
|
721
756
|
/** Effective project-context tier for this run; absent on receipts written before this field landed. */
|
|
@@ -764,6 +799,17 @@ export interface RunReceipt {
|
|
|
764
799
|
toolActivity?: ToolActivitySummary;
|
|
765
800
|
/** Integrity-sealed evidence confidence. Sealed on every receipt. */
|
|
766
801
|
verification: RunReceiptVerification;
|
|
802
|
+
/** Orchestrator-executed declared checks. Worker self-report never populates this field. */
|
|
803
|
+
hostVerification?: RunHostVerification;
|
|
804
|
+
/** Isolated writer checkout and its collect-time application result. */
|
|
805
|
+
worktree?: {
|
|
806
|
+
path: string;
|
|
807
|
+
branch: string;
|
|
808
|
+
diffHash: string;
|
|
809
|
+
apply: "merge" | "preserve";
|
|
810
|
+
applied: boolean;
|
|
811
|
+
reason?: string;
|
|
812
|
+
};
|
|
767
813
|
/** Required normalized routing request, sealed without task or prompt data. */
|
|
768
814
|
routingIntent: RoutingIntent;
|
|
769
815
|
/** Required routing-quality facts known at receipt finalization. */
|
|
@@ -786,8 +832,7 @@ export interface RunReceipt {
|
|
|
786
832
|
* What this run contributed to its dispatch's agent ledger. Optional and
|
|
787
833
|
* absent unless the run had a ledger, following validationGrounding and
|
|
788
834
|
* capabilityMismatch: receiptDigestFields skips undefined, so a receipt
|
|
789
|
-
* without one
|
|
790
|
-
* RUN_RECEIPT_INTEGRITY_VERSION stays where it is.
|
|
835
|
+
* without one omits the field from canonical serialization.
|
|
791
836
|
*/
|
|
792
837
|
ledgerContribution?: RunLedgerContribution;
|
|
793
838
|
/**
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
} from "./execution-handoff.js";
|
|
20
20
|
import { isExecutionRole } from "./execution-role.js";
|
|
21
21
|
import { COMPETE_STANCES, type CompeteStance } from "./gate-role-prompts.js";
|
|
22
|
+
import { type DispatchIntent, isDispatchIntent } from "./intent.js";
|
|
22
23
|
import { isRoutingIntent, type RoutingIntent } from "./routing-intent.js";
|
|
23
24
|
import type {
|
|
24
25
|
DispatchRequestOrigin,
|
|
@@ -121,6 +122,10 @@ export interface JobSpec {
|
|
|
121
122
|
*/
|
|
122
123
|
denyTools?: ReadonlyArray<string>;
|
|
123
124
|
cwd?: string;
|
|
125
|
+
/** Run this writer in an isolated git worktree. */
|
|
126
|
+
worktree?: true;
|
|
127
|
+
/** How a successful isolated writer is applied to its parent checkout. */
|
|
128
|
+
apply?: "merge" | "preserve";
|
|
124
129
|
memorySection?: string;
|
|
125
130
|
noSkills?: boolean;
|
|
126
131
|
skillPaths?: ReadonlyArray<string>;
|
|
@@ -134,6 +139,8 @@ export interface JobSpec {
|
|
|
134
139
|
* per-tool calls (subprocess).
|
|
135
140
|
*/
|
|
136
141
|
writeRoots?: ReadonlyArray<string>;
|
|
142
|
+
/** Normalized model-declared path, output, and host verification intent. */
|
|
143
|
+
intent?: DispatchIntent;
|
|
137
144
|
requestOrigin?: DispatchRequestOrigin;
|
|
138
145
|
/**
|
|
139
146
|
* Threaded output from the previous pipeline step. Set only by the dispatch
|
|
@@ -159,6 +166,8 @@ export interface JobSpec {
|
|
|
159
166
|
autonomy?: AutonomyLevel;
|
|
160
167
|
/** Review/compete gate provenance sealed into the run's receipt. */
|
|
161
168
|
gate?: RunGateProvenance;
|
|
169
|
+
/** Council grouping projected to receipts and fleet surfaces. */
|
|
170
|
+
council?: { group: string; label: string; color?: string; round: number };
|
|
162
171
|
/** Plan-approval provenance sealed into the run's receipt. */
|
|
163
172
|
plan?: RunPlanProvenance;
|
|
164
173
|
/**
|
|
@@ -195,18 +204,22 @@ const KNOWN_KEYS = new Set([
|
|
|
195
204
|
"toolProfile",
|
|
196
205
|
"denyTools",
|
|
197
206
|
"cwd",
|
|
207
|
+
"worktree",
|
|
208
|
+
"apply",
|
|
198
209
|
"memorySection",
|
|
199
210
|
"noSkills",
|
|
200
211
|
"skillPaths",
|
|
201
212
|
"trustProjectCompatRoots",
|
|
202
213
|
"responseSchema",
|
|
203
214
|
"writeRoots",
|
|
215
|
+
"intent",
|
|
204
216
|
"requestOrigin",
|
|
205
217
|
"pipelineInput",
|
|
206
218
|
"predecessorHandoffs",
|
|
207
219
|
"lineage",
|
|
208
220
|
"autonomy",
|
|
209
221
|
"gate",
|
|
222
|
+
"council",
|
|
210
223
|
"plan",
|
|
211
224
|
"competeStance",
|
|
212
225
|
]);
|
|
@@ -399,7 +412,7 @@ export function validateJobSpec(spec: unknown): Validated {
|
|
|
399
412
|
|
|
400
413
|
if ("toolProfile" in spec && spec.toolProfile !== undefined) {
|
|
401
414
|
if (typeof spec.toolProfile !== "string" || !isToolProfileName(spec.toolProfile)) {
|
|
402
|
-
errors.push("toolProfile must be one of: minimal-local|science-local|full-agent");
|
|
415
|
+
errors.push("toolProfile must be one of: minimal-local|science-local|full-agent|council-read-only");
|
|
403
416
|
}
|
|
404
417
|
}
|
|
405
418
|
|
|
@@ -414,6 +427,13 @@ export function validateJobSpec(spec: unknown): Validated {
|
|
|
414
427
|
errors.push("cwd must be a non-empty string");
|
|
415
428
|
}
|
|
416
429
|
}
|
|
430
|
+
if ("worktree" in spec && spec.worktree !== undefined && spec.worktree !== true) {
|
|
431
|
+
errors.push("worktree must be true when present");
|
|
432
|
+
}
|
|
433
|
+
if ("apply" in spec && spec.apply !== undefined && spec.apply !== "merge" && spec.apply !== "preserve") {
|
|
434
|
+
errors.push("apply must be merge or preserve");
|
|
435
|
+
}
|
|
436
|
+
if (spec.apply !== undefined && spec.worktree !== true) errors.push("apply requires worktree: true");
|
|
417
437
|
|
|
418
438
|
if ("memorySection" in spec && spec.memorySection !== undefined) {
|
|
419
439
|
if (typeof spec.memorySection !== "string") {
|
|
@@ -457,6 +477,15 @@ export function validateJobSpec(spec: unknown): Validated {
|
|
|
457
477
|
errors.push("writeRoots must be a non-empty array of non-empty strings");
|
|
458
478
|
}
|
|
459
479
|
}
|
|
480
|
+
if ("intent" in spec && spec.intent !== undefined && !isDispatchIntent(spec.intent)) {
|
|
481
|
+
errors.push("intent must be a normalized dispatch intent");
|
|
482
|
+
}
|
|
483
|
+
if (isDispatchIntent(spec.intent) && spec.intent.writeRoots.length > 0 && Array.isArray(spec.writeRoots)) {
|
|
484
|
+
const jobCwd = typeof spec.cwd === "string" && spec.cwd.length > 0 ? spec.cwd : process.cwd();
|
|
485
|
+
const legacy = [...new Set(spec.writeRoots.map((root) => path.resolve(jobCwd, String(root))))].sort();
|
|
486
|
+
const declared = [...new Set(spec.intent.writeRoots.map((root) => path.resolve(jobCwd, root)))].sort();
|
|
487
|
+
if (JSON.stringify(legacy) !== JSON.stringify(declared)) errors.push("intent_write_roots_contradiction");
|
|
488
|
+
}
|
|
460
489
|
|
|
461
490
|
if ("requestOrigin" in spec && spec.requestOrigin !== undefined) {
|
|
462
491
|
if (typeof spec.requestOrigin !== "string" || !VALID_REQUEST_ORIGINS.has(spec.requestOrigin)) {
|
|
@@ -550,18 +579,25 @@ export function validateJobSpec(spec: unknown): Validated {
|
|
|
550
579
|
if (typeof spec.toolProfile === "string" && isToolProfileName(spec.toolProfile)) out.toolProfile = spec.toolProfile;
|
|
551
580
|
if (Array.isArray(spec.denyTools)) out.denyTools = spec.denyTools.map((tool) => String(tool));
|
|
552
581
|
if (typeof spec.cwd === "string") out.cwd = spec.cwd;
|
|
582
|
+
if (spec.worktree === true) out.worktree = true;
|
|
583
|
+
if (spec.apply === "merge" || spec.apply === "preserve") out.apply = spec.apply;
|
|
553
584
|
if (typeof spec.memorySection === "string") out.memorySection = spec.memorySection;
|
|
554
585
|
if (typeof spec.noSkills === "boolean") out.noSkills = spec.noSkills;
|
|
555
586
|
if (Array.isArray(spec.skillPaths)) out.skillPaths = spec.skillPaths.map((p) => String(p));
|
|
556
587
|
if (typeof spec.trustProjectCompatRoots === "boolean") out.trustProjectCompatRoots = spec.trustProjectCompatRoots;
|
|
557
588
|
if (responseSchema) out.responseSchema = responseSchema;
|
|
558
|
-
if (
|
|
589
|
+
if (isDispatchIntent(spec.intent)) out.intent = structuredClone(spec.intent);
|
|
590
|
+
const effectiveWriteRoots =
|
|
559
591
|
Array.isArray(spec.writeRoots) &&
|
|
560
592
|
spec.writeRoots.length > 0 &&
|
|
561
593
|
spec.writeRoots.every((root) => typeof root === "string" && root.length > 0)
|
|
562
|
-
|
|
594
|
+
? spec.writeRoots
|
|
595
|
+
: isDispatchIntent(spec.intent) && spec.intent.writeRoots.length > 0
|
|
596
|
+
? spec.intent.writeRoots
|
|
597
|
+
: undefined;
|
|
598
|
+
if (effectiveWriteRoots !== undefined) {
|
|
563
599
|
const jobCwd = typeof spec.cwd === "string" && spec.cwd.length > 0 ? spec.cwd : process.cwd();
|
|
564
|
-
out.writeRoots =
|
|
600
|
+
out.writeRoots = effectiveWriteRoots.map((root) => path.resolve(jobCwd, String(root)));
|
|
565
601
|
}
|
|
566
602
|
if (typeof spec.requestOrigin === "string" && VALID_REQUEST_ORIGINS.has(spec.requestOrigin)) {
|
|
567
603
|
out.requestOrigin = spec.requestOrigin as DispatchRequestOrigin;
|
|
@@ -572,6 +608,7 @@ export function validateJobSpec(spec: unknown): Validated {
|
|
|
572
608
|
if (isValidLineage(spec.lineage)) out.lineage = spec.lineage;
|
|
573
609
|
if (isAutonomyLevel(spec.autonomy)) out.autonomy = spec.autonomy;
|
|
574
610
|
if (isValidGate(spec.gate)) out.gate = cloneGate(spec.gate);
|
|
611
|
+
if (isValidCouncil(spec.council)) out.council = { ...spec.council };
|
|
575
612
|
if (typeof spec.competeStance === "string" && VALID_COMPETE_STANCES.has(spec.competeStance)) {
|
|
576
613
|
out.competeStance = spec.competeStance as CompeteStance;
|
|
577
614
|
}
|
|
@@ -654,9 +691,18 @@ function isValidProtectedArtifactRemap(value: unknown): value is ProtectedArtifa
|
|
|
654
691
|
);
|
|
655
692
|
}
|
|
656
693
|
|
|
657
|
-
const VALID_GATE_ROLES = new Set(["builder", "reviewer", "candidate", "judge"]);
|
|
694
|
+
const VALID_GATE_ROLES = new Set(["builder", "reviewer", "candidate", "judge", "member", "synthesis"]);
|
|
658
695
|
const VALID_GATE_VERDICTS = new Set(["pass", "fail", "revise"]);
|
|
659
|
-
const VALID_PLAN_TOPOLOGIES = new Set([
|
|
696
|
+
const VALID_PLAN_TOPOLOGIES = new Set([
|
|
697
|
+
"parallel",
|
|
698
|
+
"sequential",
|
|
699
|
+
"pipeline",
|
|
700
|
+
"review",
|
|
701
|
+
"compete",
|
|
702
|
+
"council",
|
|
703
|
+
"detached",
|
|
704
|
+
"fleet",
|
|
705
|
+
]);
|
|
660
706
|
const VALID_PLAN_APPROVALS = new Set(["operator", "full-auto"]);
|
|
661
707
|
|
|
662
708
|
function isValidGate(value: unknown): value is RunGateProvenance {
|
|
@@ -688,6 +734,20 @@ function cloneGate(gate: RunGateProvenance): RunGateProvenance {
|
|
|
688
734
|
};
|
|
689
735
|
}
|
|
690
736
|
|
|
737
|
+
function isValidCouncil(value: unknown): value is NonNullable<JobSpec["council"]> {
|
|
738
|
+
return (
|
|
739
|
+
isPlainObject(value) &&
|
|
740
|
+
typeof value.group === "string" &&
|
|
741
|
+
value.group.length > 0 &&
|
|
742
|
+
typeof value.label === "string" &&
|
|
743
|
+
value.label.length > 0 &&
|
|
744
|
+
(value.color === undefined || typeof value.color === "string") &&
|
|
745
|
+
typeof value.round === "number" &&
|
|
746
|
+
Number.isInteger(value.round) &&
|
|
747
|
+
value.round >= 1
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
|
|
691
751
|
function isValidPlan(value: unknown): value is RunPlanProvenance {
|
|
692
752
|
if (!isPlainObject(value)) return false;
|
|
693
753
|
if (typeof value.hash !== "string" || value.hash.length === 0) return false;
|
|
@@ -418,6 +418,7 @@ export type PersistedRunReceiptTrustFacts = Pick<RunReceipt, "runId"> &
|
|
|
418
418
|
RunReceipt,
|
|
419
419
|
| "integrity"
|
|
420
420
|
| "verification"
|
|
421
|
+
| "hostVerification"
|
|
421
422
|
| "quality"
|
|
422
423
|
| "validationGrounding"
|
|
423
424
|
| "briefing"
|
|
@@ -536,6 +537,12 @@ export function adaptRunReceiptValidationStatus(
|
|
|
536
537
|
): ValidationGroundingStatus {
|
|
537
538
|
if (receipt === null || receipt === undefined) return absentTrustStatus("artifact_missing");
|
|
538
539
|
const artifacts = validationArtifacts(receipt);
|
|
540
|
+
if (receipt.hostVerification?.status === "rejected") {
|
|
541
|
+
return attributed("failed", receiptSource(receipt), { kind: "validator", id: "host-verification" }, artifacts);
|
|
542
|
+
}
|
|
543
|
+
if (receipt.hostVerification?.status === "verified") {
|
|
544
|
+
return attributed("validated", receiptSource(receipt), { kind: "validator", id: "host-verification" }, artifacts);
|
|
545
|
+
}
|
|
539
546
|
if (qualityHasFailure(receipt.quality)) {
|
|
540
547
|
return attributed("failed", receiptSource(receipt), { kind: "validator", id: "receipt-quality" }, artifacts);
|
|
541
548
|
}
|
|
@@ -549,7 +556,9 @@ export function adaptRunReceiptValidationStatus(
|
|
|
549
556
|
if (qualityHasValidation(receipt.quality)) {
|
|
550
557
|
return attributed("validated", receiptSource(receipt), { kind: "validator", id: "receipt-quality" }, artifacts);
|
|
551
558
|
}
|
|
552
|
-
if (receipt.verification !== undefined)
|
|
559
|
+
if (receipt.verification !== undefined) {
|
|
560
|
+
return verificationStatus(receipt.verification, receipt, artifacts);
|
|
561
|
+
}
|
|
553
562
|
return attributed("unknown", compatibilitySource(receipt, "verification"), COMPATIBILITY_AUTHORITY, artifacts);
|
|
554
563
|
}
|
|
555
564
|
|
|
@@ -152,3 +152,18 @@ export type {
|
|
|
152
152
|
MiddlewareValidationIssue,
|
|
153
153
|
} from "./validate.js";
|
|
154
154
|
export { validateMiddlewareEffect, validateMiddlewareRule } from "./validate.js";
|
|
155
|
+
export {
|
|
156
|
+
coalesceTurnDiff,
|
|
157
|
+
createWatchdogRegistration,
|
|
158
|
+
WATCHDOG_DIFF_MAX_BYTES,
|
|
159
|
+
WATCHDOG_PATHS_MAX,
|
|
160
|
+
WATCHDOG_REGISTRATION_ID,
|
|
161
|
+
WATCHDOG_TASK,
|
|
162
|
+
WATCHDOG_TRUNCATION_MARKER,
|
|
163
|
+
type WatchdogDeps,
|
|
164
|
+
type WatchdogRegistration,
|
|
165
|
+
type WatchdogSettingsView,
|
|
166
|
+
type WatchdogTrigger,
|
|
167
|
+
type WatchdogTriggerReason,
|
|
168
|
+
watchdogBriefing,
|
|
169
|
+
} from "./watchdog.js";
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The opt-in turn-end watchdog.
|
|
3
|
+
*
|
|
4
|
+
* A long agentic turn can leave the tree in a state nobody looked at: the
|
|
5
|
+
* model reports success, the operator reads the report, and the drift between
|
|
6
|
+
* what the board asked for and what the diff actually did stays invisible
|
|
7
|
+
* until someone reads the diff themselves. When `watchdog.enabled` is on, a
|
|
8
|
+
* turn that changed the tree is handed to one read-only verifier run briefed
|
|
9
|
+
* with the turn's coalesced diff and the board's current scope. Its blockers
|
|
10
|
+
* become one transcript notice and nothing else.
|
|
11
|
+
*
|
|
12
|
+
* What it deliberately does not do: it never auto-follows, never carries the
|
|
13
|
+
* turn onward, never queues a turn, and never mutates. A watchdog that could
|
|
14
|
+
* start work would be a second agent nobody asked for; this one can only tell
|
|
15
|
+
* the operator what it saw.
|
|
16
|
+
*
|
|
17
|
+
* The registration itself emits no middleware effects. It owns the decision of
|
|
18
|
+
* when to fire and the coalesced diff it fires with; the caller owns the
|
|
19
|
+
* dispatch and the notice.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { ToolNames } from "../../core/tool-names.js";
|
|
23
|
+
import type { MiddlewareHookRegistration } from "./runtime.js";
|
|
24
|
+
import type { MiddlewareEffect, MiddlewareHookInput } from "./types.js";
|
|
25
|
+
|
|
26
|
+
export const WATCHDOG_REGISTRATION_ID = "observer.watchdog";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Bound on the coalesced diff a watchdog briefing carries, in UTF-8 bytes. The
|
|
30
|
+
* run is meant to be cheap enough to spend on every mutating turn, so it is
|
|
31
|
+
* given the shape of the change rather than every byte of it.
|
|
32
|
+
*/
|
|
33
|
+
export const WATCHDOG_DIFF_MAX_BYTES = 12 * 1024;
|
|
34
|
+
|
|
35
|
+
/** Bound on how many mutated paths the briefing names before it summarizes the rest. */
|
|
36
|
+
export const WATCHDOG_PATHS_MAX = 40;
|
|
37
|
+
|
|
38
|
+
export const WATCHDOG_TRUNCATION_MARKER = "\n[diff truncated]";
|
|
39
|
+
|
|
40
|
+
/** Tools whose successful results carry a file diff worth coalescing. */
|
|
41
|
+
const MUTATING_TOOL_NAMES = new Set<string>([ToolNames.Write, ToolNames.Edit, ToolNames.Artifact]);
|
|
42
|
+
|
|
43
|
+
const NO_EFFECTS: ReadonlyArray<MiddlewareEffect> = [];
|
|
44
|
+
|
|
45
|
+
export type WatchdogTriggerReason = "turn_end" | "cadence";
|
|
46
|
+
|
|
47
|
+
export interface WatchdogTrigger {
|
|
48
|
+
reason: WatchdogTriggerReason;
|
|
49
|
+
/** The turn's coalesced diff, one block per mutated path in first-touch order. */
|
|
50
|
+
diff: string;
|
|
51
|
+
/** Paths the turn mutated, in first-touch order. */
|
|
52
|
+
paths: ReadonlyArray<string>;
|
|
53
|
+
/** The board's current scope, or null when the session has no task board. */
|
|
54
|
+
scope: string | null;
|
|
55
|
+
/** Tool calls seen in this turn when the trigger fired. */
|
|
56
|
+
toolCalls: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The live settings view the registration reads on every boundary, so a hot reload lands immediately. */
|
|
60
|
+
export interface WatchdogSettingsView {
|
|
61
|
+
enabled: boolean;
|
|
62
|
+
target?: string;
|
|
63
|
+
cadenceToolCalls?: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface WatchdogDeps {
|
|
67
|
+
getSettings: () => Readonly<WatchdogSettingsView>;
|
|
68
|
+
/** The board's current scope line, or null when there is no board. */
|
|
69
|
+
getScope?: () => string | null;
|
|
70
|
+
/**
|
|
71
|
+
* Dispatch the watchdog run and deliver whatever it found. Resolves when the
|
|
72
|
+
* run has settled; the registration awaits only to know the in-flight slot is
|
|
73
|
+
* free again, never to hold the turn.
|
|
74
|
+
*/
|
|
75
|
+
run: (trigger: WatchdogTrigger) => Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* False on a surface that must never fire the watchdog whatever the setting
|
|
78
|
+
* says. Headless and ACP runs pass false: neither has an operator watching a
|
|
79
|
+
* transcript, so a notice they cannot read is a worker run spent on nothing.
|
|
80
|
+
*/
|
|
81
|
+
firesOnThisSurface?: boolean;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface WatchdogRegistration extends MiddlewareHookRegistration {
|
|
85
|
+
/** Triggers dropped because a watchdog run was already in flight. */
|
|
86
|
+
droppedTriggers(): number;
|
|
87
|
+
/** True while a watchdog run is outstanding. */
|
|
88
|
+
runInFlight(): boolean;
|
|
89
|
+
/** Resolves once no watchdog run is outstanding. */
|
|
90
|
+
whenIdle(): Promise<void>;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function stringValue(value: unknown): string | null {
|
|
94
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function firstPath(details: Readonly<Record<string, unknown>> | undefined): string | null {
|
|
98
|
+
const paths = details?.paths;
|
|
99
|
+
if (!Array.isArray(paths)) return null;
|
|
100
|
+
for (const entry of paths) {
|
|
101
|
+
const path = stringValue(entry);
|
|
102
|
+
if (path !== null) return path;
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Coalesce the per-call diffs of one turn into one diff.
|
|
109
|
+
*
|
|
110
|
+
* Per-path last-write-wins, in first-touch order. A turn that edited one file
|
|
111
|
+
* six times produced six diffs against six different base contents; replaying
|
|
112
|
+
* all of them tells a reviewer the history of the edit rather than its result,
|
|
113
|
+
* and the result is what the reviewer is being asked about.
|
|
114
|
+
*/
|
|
115
|
+
export function coalesceTurnDiff(
|
|
116
|
+
byPath: ReadonlyMap<string, string>,
|
|
117
|
+
maxBytes: number = WATCHDOG_DIFF_MAX_BYTES,
|
|
118
|
+
): string {
|
|
119
|
+
const blocks: string[] = [];
|
|
120
|
+
let index = 0;
|
|
121
|
+
for (const [path, diff] of byPath) {
|
|
122
|
+
if (index >= WATCHDOG_PATHS_MAX) {
|
|
123
|
+
blocks.push(`[${byPath.size - WATCHDOG_PATHS_MAX} more changed path(s) omitted]`);
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
blocks.push(`--- ${path}\n${diff}`);
|
|
127
|
+
index += 1;
|
|
128
|
+
}
|
|
129
|
+
const whole = blocks.join("\n\n");
|
|
130
|
+
if (Buffer.byteLength(whole, "utf8") <= maxBytes) return whole;
|
|
131
|
+
const room = Math.max(0, maxBytes - Buffer.byteLength(WATCHDOG_TRUNCATION_MARKER, "utf8"));
|
|
132
|
+
const buffer = Buffer.from(whole, "utf8");
|
|
133
|
+
let cut = Math.min(room, buffer.byteLength);
|
|
134
|
+
while (cut > 0) {
|
|
135
|
+
const next = buffer[cut];
|
|
136
|
+
if (next === undefined || (next & 0xc0) !== 0x80) break;
|
|
137
|
+
cut -= 1;
|
|
138
|
+
}
|
|
139
|
+
return `${buffer.subarray(0, cut).toString("utf8")}${WATCHDOG_TRUNCATION_MARKER}`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** The bounded, fixed task every watchdog run is given. It never varies with the diff. */
|
|
143
|
+
export const WATCHDOG_TASK =
|
|
144
|
+
"Review the coalesced diff in the briefing against the scope it names. Report one check per concern: " +
|
|
145
|
+
"a change outside the stated scope, a change the scope required that the diff does not make, or a defect " +
|
|
146
|
+
"visible in the diff itself. Run no commands and change nothing.";
|
|
147
|
+
|
|
148
|
+
/** The briefing body a watchdog run receives. Data, never instructions. */
|
|
149
|
+
export function watchdogBriefing(trigger: WatchdogTrigger): string {
|
|
150
|
+
const scope = trigger.scope === null ? "(no task board; scope is unstated)" : trigger.scope;
|
|
151
|
+
const paths = trigger.paths.length === 0 ? "(none)" : trigger.paths.join("\n");
|
|
152
|
+
return [
|
|
153
|
+
"## Current scope",
|
|
154
|
+
scope,
|
|
155
|
+
"",
|
|
156
|
+
"## Changed paths",
|
|
157
|
+
paths,
|
|
158
|
+
"",
|
|
159
|
+
"## Coalesced diff for this turn",
|
|
160
|
+
trigger.diff,
|
|
161
|
+
].join("\n");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Build the registration. It is hooked on `after_tool` and `turn_end`:
|
|
166
|
+
* `after_tool` is where the diffs and the tool count come from, and `turn_end`
|
|
167
|
+
* is the boundary a mutating turn fires on.
|
|
168
|
+
*/
|
|
169
|
+
export function createWatchdogRegistration(deps: WatchdogDeps): WatchdogRegistration {
|
|
170
|
+
const diffByPath = new Map<string, string>();
|
|
171
|
+
let toolCalls = 0;
|
|
172
|
+
let dropped = 0;
|
|
173
|
+
let inFlight = false;
|
|
174
|
+
let outstanding: Promise<void> = Promise.resolve();
|
|
175
|
+
|
|
176
|
+
const active = (): WatchdogSettingsView | null => {
|
|
177
|
+
if (deps.firesOnThisSurface === false) return null;
|
|
178
|
+
let settings: Readonly<WatchdogSettingsView>;
|
|
179
|
+
try {
|
|
180
|
+
settings = deps.getSettings();
|
|
181
|
+
} catch {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
return settings.enabled ? settings : null;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const resetTurn = (): void => {
|
|
188
|
+
diffByPath.clear();
|
|
189
|
+
toolCalls = 0;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const fire = (reason: WatchdogTriggerReason): void => {
|
|
193
|
+
if (diffByPath.size === 0) return;
|
|
194
|
+
// One run at a time. A watchdog slower than the turns that trigger it must
|
|
195
|
+
// not queue: a queued review describes a tree that has already moved on,
|
|
196
|
+
// and a backlog of them would outlive the session. The drop is counted so
|
|
197
|
+
// a starved cadence and a quiet one stay distinguishable.
|
|
198
|
+
if (inFlight) {
|
|
199
|
+
dropped += 1;
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const trigger: WatchdogTrigger = {
|
|
203
|
+
reason,
|
|
204
|
+
diff: coalesceTurnDiff(diffByPath),
|
|
205
|
+
paths: [...diffByPath.keys()].slice(0, WATCHDOG_PATHS_MAX),
|
|
206
|
+
scope: scopeNow(),
|
|
207
|
+
toolCalls,
|
|
208
|
+
};
|
|
209
|
+
inFlight = true;
|
|
210
|
+
// Started synchronously, so the in-flight slot is genuinely taken before
|
|
211
|
+
// this hook returns. Deferring the start to a microtask would let two
|
|
212
|
+
// triggers in the same tool batch both find the slot free.
|
|
213
|
+
let started: Promise<void>;
|
|
214
|
+
try {
|
|
215
|
+
started = deps.run(trigger);
|
|
216
|
+
} catch {
|
|
217
|
+
inFlight = false;
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
outstanding = started
|
|
221
|
+
.catch(() => {
|
|
222
|
+
// A failed watchdog run is not a failed turn. The run's own reporting
|
|
223
|
+
// path already told the operator whatever it could.
|
|
224
|
+
})
|
|
225
|
+
.finally(() => {
|
|
226
|
+
inFlight = false;
|
|
227
|
+
});
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const scopeNow = (): string | null => {
|
|
231
|
+
try {
|
|
232
|
+
return deps.getScope?.() ?? null;
|
|
233
|
+
} catch {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
id: WATCHDOG_REGISTRATION_ID,
|
|
240
|
+
description: "review a mutating turn's coalesced diff against the board scope with a read-only verifier run",
|
|
241
|
+
hooks: ["after_tool", "turn_end"],
|
|
242
|
+
evaluate(input: MiddlewareHookInput): ReadonlyArray<MiddlewareEffect> {
|
|
243
|
+
const settings = active();
|
|
244
|
+
if (settings === null) {
|
|
245
|
+
// Still clear turn state so a mid-session disable cannot leave a diff
|
|
246
|
+
// behind that a later enable would fire on.
|
|
247
|
+
if (input.hook === "turn_end") resetTurn();
|
|
248
|
+
return NO_EFFECTS;
|
|
249
|
+
}
|
|
250
|
+
if (input.hook === "after_tool") {
|
|
251
|
+
toolCalls += 1;
|
|
252
|
+
recordMutation(input);
|
|
253
|
+
const cadence = settings.cadenceToolCalls;
|
|
254
|
+
// Mid-turn cadence keeps accumulating: turn end still reviews the whole
|
|
255
|
+
// turn, not just what landed after the last cadence firing.
|
|
256
|
+
if (cadence !== undefined && cadence >= 1 && toolCalls % cadence === 0) fire("cadence");
|
|
257
|
+
return NO_EFFECTS;
|
|
258
|
+
}
|
|
259
|
+
if (input.hook !== "turn_end") return NO_EFFECTS;
|
|
260
|
+
// A turn with no file mutations never fires. A middleware continuation
|
|
261
|
+
// re-evaluates turn_end with the accumulator already cleared, so it
|
|
262
|
+
// cannot fire a second run for the same change either.
|
|
263
|
+
fire("turn_end");
|
|
264
|
+
resetTurn();
|
|
265
|
+
return NO_EFFECTS;
|
|
266
|
+
},
|
|
267
|
+
droppedTriggers: () => dropped,
|
|
268
|
+
runInFlight: () => inFlight,
|
|
269
|
+
whenIdle: () => outstanding,
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
function recordMutation(input: MiddlewareHookInput): void {
|
|
273
|
+
if (input.metadata?.resultKind !== "ok") return;
|
|
274
|
+
if (input.toolName === undefined || !MUTATING_TOOL_NAMES.has(input.toolName)) return;
|
|
275
|
+
const diff = stringValue(input.toolResultDetails?.diff);
|
|
276
|
+
if (diff === null) return;
|
|
277
|
+
// Map.set on an existing key keeps its insertion position, so the newest
|
|
278
|
+
// diff for a path wins while the path keeps its first-touch order.
|
|
279
|
+
diffByPath.set(firstPath(input.toolResultDetails) ?? input.toolName, diff);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
@@ -2,7 +2,7 @@ import type { ResponseModelIdObservationCounts } from "../../core/response-model
|
|
|
2
2
|
import type { TargetStatus } from "../providers/contract.js";
|
|
3
3
|
import type { CostProvenance } from "../providers/index.js";
|
|
4
4
|
import type { AccountabilitySummary } from "./accountability.js";
|
|
5
|
-
import type { CostAggregate, CostEntry, UsageBreakdown } from "./cost.js";
|
|
5
|
+
import type { CostAggregate, CostEntry, CostEntryLabel, UsageBreakdown } from "./cost.js";
|
|
6
6
|
import type { MetricsView } from "./metrics.js";
|
|
7
7
|
import type { TelemetrySnapshot } from "./telemetry.js";
|
|
8
8
|
import type { SessionTurnTrace } from "./trace-store.js";
|
|
@@ -142,6 +142,8 @@ export interface ObservabilityContract {
|
|
|
142
142
|
requestedModelIds: ReadonlyArray<string>;
|
|
143
143
|
responseModelIdObservationCounts: Readonly<ResponseModelIdObservationCounts>;
|
|
144
144
|
},
|
|
145
|
+
/** Marks a priced call that was not an ordinary turn, such as a `/btw` side question. */
|
|
146
|
+
label?: CostEntryLabel,
|
|
145
147
|
): void;
|
|
146
148
|
/** Record final output token throughput for one completed assistant stream. */
|
|
147
149
|
recordTokenThroughput(snapshot: TokenThroughputSnapshot): void;
|