@iowarp/clio-coder 0.3.4 → 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 +65 -2
- package/CONTRIBUTING.md +6 -6
- package/README.md +16 -5
- package/dist/{acp-S5R4RR5B.js → acp-SK4MD6MM.js} +11 -11
- package/dist/{agents-P6DMMVZY.js → agents-2FN2K6ME.js} +33 -26
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-2XCZLPKS.js → auth-QIYZWM5I.js} +15 -15
- package/dist/{chunk-EKMEHE4H.js → chunk-33YXPOE3.js} +2 -3
- package/dist/{chunk-VAWWTKDP.js → chunk-3HAPLH5M.js} +11 -11
- package/dist/{chunk-YCWGATWI.js → chunk-465YSENW.js} +3 -3
- package/dist/{chunk-4OC57DA6.js → chunk-4DGYLA73.js} +53 -2
- package/dist/{chunk-UZHIZC5S.js → chunk-4DWFMQDR.js} +61 -76
- package/dist/{chunk-ZWMF7253.js → chunk-5C3AQNDW.js} +328 -9
- package/dist/{chunk-22NAGB7X.js → chunk-5C77SEEY.js} +5 -94
- package/dist/{chunk-WPQLXFOZ.js → chunk-5FR74PWO.js} +3 -2
- package/dist/{chunk-35MKKU5R.js → chunk-5UJ6ECTS.js} +18 -10
- package/dist/{chunk-BRXQQJFP.js → chunk-6M7VS3J3.js} +571 -50
- package/dist/{chunk-QQK64KLB.js → chunk-6TUKSZVF.js} +141 -23
- package/dist/{chunk-N4CZJQRK.js → chunk-AB4XIIVB.js} +8 -6
- package/dist/{chunk-KRPY7NTG.js → chunk-BMWK7ZIZ.js} +14 -20
- package/dist/{chunk-4BPJXDWC.js → chunk-C4JBQ5SR.js} +30 -14
- package/dist/{chunk-ZYKPLLNQ.js → chunk-CEYBNUGC.js} +821 -83
- package/dist/{chunk-VEZEGCGW.js → chunk-D4MDIG46.js} +20 -18
- package/dist/chunk-DJNLUABN.js +843 -0
- package/dist/{chunk-BP4OYD6A.js → chunk-DMD2AGVS.js} +21 -2
- 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-3HZ5RWN2.js → chunk-EBEFWSGL.js} +9 -7
- package/dist/{chunk-EDRHSCIE.js → chunk-EELBMBT6.js} +128 -13
- package/dist/{chunk-HV5X7OR2.js → chunk-EOOQZZDE.js} +16 -14
- package/dist/{chunk-WR67VIZY.js → chunk-FOT2FX5J.js} +63 -5
- package/dist/{chunk-BPGS2WCQ.js → chunk-GEYXPTRF.js} +2 -1
- package/dist/{chunk-FYYLNIL5.js → chunk-GH5622CP.js} +2 -2
- package/dist/{chunk-BEY543CS.js → chunk-GOXNB3AO.js} +5 -2
- package/dist/chunk-GWS3VEIW.js +195 -0
- package/dist/{chunk-G4BMMOKF.js → chunk-HVDIIIQW.js} +2 -2
- package/dist/chunk-HWUFFB6L.js +83 -0
- package/dist/{chunk-X6COSD2O.js → chunk-J7PIKKWC.js} +8 -436
- package/dist/{chunk-NILBFAPG.js → chunk-JNXPYBB4.js} +2 -2
- package/dist/{chunk-4VP4KH3K.js → chunk-JRIO5UD2.js} +4 -4
- package/dist/{chunk-K6WL7QZT.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-A2GZF7DC.js → chunk-LADCF22A.js} +13 -13
- package/dist/chunk-LCGCVYZ4.js +57 -0
- package/dist/chunk-M4AKACEO.js +382 -0
- package/dist/{chunk-POHLU5DW.js → chunk-M6L6IDJG.js} +3 -3
- package/dist/{chunk-4JUF2NNX.js → chunk-MXI6J5JF.js} +7 -7
- package/dist/{chunk-X4RCMKVQ.js → chunk-NDINPTJ4.js} +2 -2
- package/dist/{chunk-TTNYS3EA.js → chunk-OB5HIGJY.js} +1 -1
- package/dist/{chunk-7RXG6QRZ.js → chunk-OBMAI2DP.js} +61 -840
- package/dist/{chunk-5M54SPOL.js → chunk-ODFEOB4F.js} +161 -5
- package/dist/chunk-PD3MESLB.js +242 -0
- package/dist/{chunk-ED4KHGC3.js → chunk-PPAMZ32Z.js} +9 -2
- package/dist/{chunk-VMNQ6OZA.js → chunk-QCTRSGHQ.js} +963 -786
- 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-34475P3I.js → chunk-TSHXZTOQ.js} +5 -4
- 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-QQL5RT5M.js → chunk-UUANF5CR.js} +2323 -2114
- package/dist/{chunk-VJWL6YS5.js → chunk-UUVG37B4.js} +2 -2
- package/dist/chunk-UVDSQ6LW.js +472 -0
- package/dist/{chunk-QWU7ZBO7.js → chunk-VQNODYQ4.js} +215 -56
- package/dist/chunk-VREKEFLL.js +37 -0
- package/dist/{chunk-2TZWSW76.js → chunk-WHGPSPT5.js} +2 -2
- package/dist/{chunk-TW3WDMVS.js → chunk-WHJYKASB.js} +2 -2
- package/dist/{chunk-MEQ45TQ4.js → chunk-WJHBC77E.js} +21 -7
- package/dist/{chunk-HXG4IURW.js → chunk-X2KV5FXT.js} +2 -2
- package/dist/{chunk-YHZX5GEU.js → chunk-XAKHZX5N.js} +2 -2
- package/dist/{chunk-2LZI5CAG.js → chunk-XEGB6BCN.js} +228 -36
- package/dist/{chunk-E25LMLRW.js → chunk-YD734TPH.js} +2 -2
- package/dist/{verifiers-4UUM6TEE.js → chunk-YTYFXUI3.js} +121 -372
- package/dist/{chunk-3JLKSKD7.js → chunk-ZGH7FGS5.js} +17 -7
- package/dist/{chunk-VSNATDE6.js → chunk-ZZMN5OM4.js} +2 -2
- package/dist/cli/index.js +34 -32
- package/dist/{clio-J5JIOIDS.js → clio-WBVQEBKO.js} +7 -7
- package/dist/{code-nav-AXCXSBHX.js → code-nav-FGGFIE7L.js} +7 -7
- package/dist/codewiki/build-worker.js +4 -4
- package/dist/{components-KELWS457.js → components-F7OEATSO.js} +5 -5
- package/dist/{config-OEBMIN2U.js → config-TRBL3RCF.js} +48 -41
- package/dist/{configure-PUQOSIXQ.js → configure-OLCVPHNM.js} +17 -17
- package/dist/{context-URSXPBCK.js → context-MJIJ6GOX.js} +12 -12
- package/dist/{context-EKDCKUUZ.js → context-WFPKQSM6.js} +26 -9
- package/dist/{context-MGSE4Z2T.js → context-XEWE3MOJ.js} +44 -37
- package/dist/{context-clear-KDAJRNUK.js → context-clear-KNOS2JPB.js} +44 -37
- package/dist/{context-index-BZ4UYMTC.js → context-index-SSR5ECNE.js} +3 -3
- package/dist/{context-working-set-SBKMPPI2.js → context-working-set-EUXAZI6N.js} +14 -13
- package/dist/{dispatch-runner-MSWN72NK.js → dispatch-runner-B7MTOVKL.js} +321 -60
- package/dist/{docs-2C2LTVT2.js → docs-FLJTIDSE.js} +5 -5
- package/dist/{doctor-7BSE27PJ.js → doctor-RN4YKO2X.js} +15 -15
- package/dist/{eval-IZGDOO4H.js → eval-RUBJVSNQ.js} +52 -236
- package/dist/{evidence-SR7WXB5B.js → evidence-JZNBUOQZ.js} +39 -33
- package/dist/{evolve-K7VE2CBX.js → evolve-FJVC4KKI.js} +39 -33
- package/dist/{extensions-QVDOHDGJ.js → extensions-IQL36S7K.js} +5 -5
- package/dist/{fleet-7XMJNQNF.js → fleet-BDKYJFCP.js} +243 -370
- 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-preflight-AQNAH644.js → fleet-preflight-BHSNPBMH.js} +2 -2
- package/dist/fleet-validate-BIYREGIK.js +79 -0
- package/dist/{init-JGNPAYXT.js → init-LQUB5COQ.js} +57 -48
- package/dist/library-NJAHIGG4.js +217 -0
- package/dist/memory-OG6HOYKM.js +472 -0
- package/dist/{models-ZMMLFJNN.js → models-5ZG5XY7J.js} +23 -22
- package/dist/{monitor-2F3T5KHP.js → monitor-TJ7AMTGB.js} +69 -35
- package/dist/{orchestrator-ORHT43JB.js → orchestrator-WZYB54DM.js} +4868 -1189
- package/dist/{paths-UXLN5YYZ.js → paths-XUC7GS6E.js} +5 -5
- package/dist/{reset-NXGTYNUO.js → reset-PXQT45IY.js} +8 -8
- package/dist/{run-RF4WJGMT.js → run-FQ74YF62.js} +82 -62
- package/dist/{share-UT3W6E4M.js → share-FW7SVCL3.js} +34 -10
- package/dist/{skills-PSACKC5Q.js → skills-7E7IRB3R.js} +25 -9
- package/dist/{skills-eval-WJSI55RZ.js → skills-eval-LI75W6OK.js} +43 -35
- package/dist/{targets-PIIRAOYS.js → targets-4CIFKCTW.js} +27 -24
- package/dist/{terminal-lease-ULWXWNVY.js → terminal-lease-WUZY7ZV5.js} +5 -4
- package/dist/{uninstall-FZCQCDKC.js → uninstall-7FV7IP4E.js} +5 -5
- package/dist/{upgrade-346TZ6AV.js → upgrade-K2HVIVMQ.js} +21 -20
- package/dist/{usage-6KKXR32N.js → usage-GTZELZQX.js} +159 -59
- package/dist/verifiers-RLAHT27O.js +336 -0
- package/dist/{verify-X5HDROLA.js → verify-BX3BRKH5.js} +7 -6
- package/dist/{wiki-generate-7STOCIFZ.js → wiki-generate-ASIFASCN.js} +58 -48
- package/dist/worker/entry.js +98 -84
- package/dist/{workspace-G4ZWUIPR.js → workspace-ZJ6BFM3Q.js} +4 -4
- package/docs/README.md +4 -3
- package/docs/acp.md +1 -1
- package/docs/alcf-provider.md +1 -1
- package/docs/architecture.md +2 -2
- package/docs/artifact-placement.md +1 -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 +90 -8
- package/docs/configuration-and-targets.md +90 -2
- package/docs/context-engine.md +4 -2
- package/docs/context-working-set.md +4 -4
- package/docs/development-pipeline.md +1 -1
- package/docs/dispatch-architecture-rationale.md +1 -1
- package/docs/documentation-coverage.md +4 -4
- package/docs/documentation-guide.md +4 -4
- package/docs/eval-runner.md +1 -1
- package/docs/evals-internal.md +4 -45
- package/docs/evidence-and-memory.md +70 -10
- 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 +224 -11
- 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 +20 -2
- package/docs/model-catalog.md +1 -1
- package/docs/observability.md +55 -8
- package/docs/proactive-memory.md +26 -16
- package/docs/prompt-envelope-and-tools.md +4 -2
- package/docs/provider-adapter-cookbook.md +1 -1
- package/docs/release-cut-checklist.md +83 -65
- package/docs/resource-library.md +59 -0
- package/docs/safety-model.md +29 -7
- 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 +38 -4
- package/docs/worker-dispatch-mechanics.md +3 -3
- package/package.json +7 -4
- package/src/cli/agents.ts +2 -3
- package/src/cli/argv.ts +14 -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 +188 -335
- package/src/cli/index.ts +4 -2
- package/src/cli/library.ts +190 -0
- package/src/cli/memory.ts +272 -10
- package/src/cli/modes/json-stream.ts +2 -2
- package/src/cli/modes/print.ts +12 -1
- package/src/cli/run.ts +22 -2
- package/src/cli/share.ts +13 -1
- package/src/cli/targets.ts +12 -3
- package/src/cli/usage.ts +160 -20
- package/src/core/bus-events.ts +7 -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/core/response-model-id.ts +134 -0
- package/src/core/toml.ts +62 -0
- package/src/core/workspace-files.ts +0 -1
- package/src/domains/agents/builtins/architect.md +2 -1
- package/src/domains/agents/builtins/oracle.md +33 -0
- package/src/domains/agents/catalog.ts +18 -5
- package/src/domains/agents/fleet-contract.ts +278 -16
- package/src/domains/agents/index.ts +14 -0
- package/src/domains/agents/recipe.ts +54 -14
- package/src/domains/agents/result-contract.ts +242 -5
- package/src/domains/config/classify.ts +4 -0
- package/src/domains/context/bootstrap.ts +36 -27
- package/src/domains/context/project-metadata.ts +19 -63
- package/src/domains/context/prompt-context.ts +8 -0
- package/src/domains/context/working-set/policies/index.ts +3 -4
- package/src/domains/dispatch/active-route-planner.ts +14 -0
- package/src/domains/dispatch/backoff.ts +2 -1
- package/src/domains/dispatch/budget-envelope.ts +396 -0
- 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 +36 -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 +339 -36
- 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/orphan-recovery.ts +1 -0
- package/src/domains/dispatch/receipt-integrity.ts +12 -4
- package/src/domains/dispatch/state.ts +37 -3
- package/src/domains/dispatch/types.ts +61 -9
- package/src/domains/dispatch/validation.ts +80 -6
- package/src/domains/dispatch/worker-spawn.ts +14 -3
- package/src/domains/eval/metrics/evidence.ts +0 -116
- package/src/domains/eval/metrics/invariants.ts +1 -1
- package/src/domains/eval/runners/clio-run.ts +1 -10
- package/src/domains/eval/runners/external-command.ts +2 -29
- package/src/domains/eval/schema/suite.ts +0 -7
- package/src/domains/eval/suites/run.ts +1 -7
- package/src/domains/evidence/trust-status.ts +10 -1
- package/src/domains/memory/index.ts +22 -0
- package/src/domains/memory/operations.ts +58 -1
- package/src/domains/memory/promotion.ts +281 -0
- package/src/domains/memory/prompt-section.ts +25 -5
- package/src/domains/memory/proposal.ts +51 -7
- package/src/domains/memory/task-bank.ts +3 -2
- package/src/domains/memory/task-memory-handoff.ts +181 -24
- package/src/domains/memory/task-memory-policy.ts +3 -1
- package/src/domains/memory/types.ts +37 -0
- package/src/domains/memory/validate.ts +178 -0
- package/src/domains/middleware/index.ts +15 -0
- package/src/domains/middleware/memory-intervention.ts +35 -25
- package/src/domains/middleware/runtime.ts +6 -0
- package/src/domains/middleware/skills-reminder.ts +19 -4
- package/src/domains/middleware/stalled-turn.ts +43 -1
- package/src/domains/middleware/types.ts +10 -0
- package/src/domains/middleware/watchdog.ts +281 -0
- package/src/domains/observability/contract.ts +9 -2
- package/src/domains/observability/cost.ts +31 -4
- 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/providers/index.ts +3 -0
- package/src/domains/providers/model-discovery.ts +9 -0
- package/src/domains/providers/runtime-resolution.ts +38 -1
- package/src/domains/providers/runtimes/common/probe-helpers.ts +97 -16
- package/src/domains/providers/types/context-window-slots.ts +18 -0
- package/src/domains/providers/types/runtime-descriptor.ts +3 -1
- 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/safety/call-target.ts +211 -14
- package/src/domains/safety/decision-presentation.ts +268 -0
- package/src/domains/safety/redaction.ts +73 -0
- package/src/domains/session/context-ledger.ts +10 -1
- package/src/domains/session/decision-board.ts +4 -0
- package/src/domains/session/entries.ts +3 -0
- package/src/domains/session/handoff.ts +629 -0
- package/src/domains/session/history.ts +68 -19
- package/src/domains/session/usage.ts +24 -7
- package/src/domains/share/archive.ts +67 -2
- package/src/engine/acp/event-mapper.ts +7 -0
- package/src/engine/acp/server.ts +29 -2
- package/src/engine/apis/lmstudio.ts +25 -4
- package/src/engine/apis/openai-completions.ts +147 -22
- package/src/engine/claude/sdk-runtime.ts +8 -2
- package/src/engine/claude/tool-safety.ts +13 -0
- package/src/engine/loop-guard.ts +27 -3
- package/src/engine/worker-events.ts +4 -3
- package/src/engine/worker-runtime.ts +59 -54
- package/src/entry/orchestrator.ts +55 -1
- package/src/interactive/bus-notices.ts +26 -0
- package/src/interactive/chat-loop-messages.ts +22 -0
- package/src/interactive/chat-loop.ts +248 -1
- package/src/interactive/chat-renderer.ts +41 -3
- package/src/interactive/clio-editor.ts +44 -7
- package/src/interactive/context-overlay.ts +43 -5
- package/src/interactive/cost-overlay.ts +70 -11
- 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 +471 -50
- package/src/interactive/fleet-run-preview.ts +307 -0
- package/src/interactive/footer/notifications.ts +219 -0
- package/src/interactive/footer/widgets.ts +13 -0
- package/src/interactive/handoff-round.ts +56 -0
- package/src/interactive/interactive-application.ts +49 -2
- package/src/interactive/interactive-event-projection.ts +9 -1
- package/src/interactive/interactive-input-runtime.ts +11 -1
- package/src/interactive/interactive-presentation.ts +11 -1
- package/src/interactive/interactive-slash-runtime.ts +52 -2
- package/src/interactive/interactive-subscriptions.ts +14 -2
- package/src/interactive/memory-overlay.ts +89 -4
- package/src/interactive/oracle.ts +179 -0
- package/src/interactive/overlay-ask-user-lifecycle.ts +7 -1
- package/src/interactive/overlay-frame.ts +5 -2
- package/src/interactive/overlay-general-openers.ts +230 -2
- package/src/interactive/overlay-key-routing.ts +58 -2
- package/src/interactive/overlay-lifecycle.ts +52 -5
- package/src/interactive/overlay-permission-lifecycle.ts +33 -8
- package/src/interactive/overlay-resource-openers.ts +11 -3
- package/src/interactive/overlay-session-lifecycle.ts +234 -2
- package/src/interactive/overlay-transitions.ts +11 -0
- package/src/interactive/overlays/ask-user.ts +74 -30
- package/src/interactive/overlays/decisions.ts +3 -1
- 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/permission-hint.ts +35 -0
- package/src/interactive/permission-overlay.ts +95 -45
- package/src/interactive/renderers/tool-execution.ts +19 -49
- package/src/interactive/session-last-turn.ts +8 -1
- package/src/interactive/session-usage-reseed.ts +36 -10
- package/src/interactive/side-question.ts +171 -0
- package/src/interactive/slash-commands.ts +434 -7
- package/src/interactive/slash-spec.ts +19 -6
- package/src/interactive/status/summary.ts +5 -0
- package/src/interactive/status/types.ts +5 -0
- package/src/interactive/terminal-lease.ts +1 -0
- package/src/interactive/theme/tokens.ts +30 -0
- package/src/interactive/turn-context.ts +96 -23
- package/src/interactive/turn-middleware.ts +16 -1
- package/src/interactive/turn-runtime.ts +37 -8
- package/src/interactive/turn-state.ts +3 -0
- package/src/interactive/watchdog-run.ts +75 -0
- package/src/interactive/worker-progress.ts +440 -0
- package/src/interactive/worker-share.ts +56 -1
- package/src/interactive/worker-stream.ts +58 -110
- package/src/tools/agent-tools.ts +28 -3
- package/src/tools/ask-user.ts +21 -1
- package/src/tools/bootstrap.ts +3 -0
- package/src/tools/compete-worktrees.ts +13 -79
- package/src/tools/context/index.ts +2 -2
- package/src/tools/dispatch-admission.ts +242 -8
- package/src/tools/dispatch-arguments.ts +65 -1
- package/src/tools/dispatch-event-text.ts +19 -0
- 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 +96 -3
- package/src/tools/monitor.ts +31 -0
- package/src/tools/profiles.ts +18 -4
- package/src/tools/registry.ts +15 -5
- package/src/tools/result-disposition.ts +156 -0
- package/src/tools/result-shaping.ts +59 -1
- package/src/tools/task-worktree.ts +238 -0
- package/src/tools/verify/authoring.ts +116 -55
- package/src/tools/verify/scripts.ts +62 -0
- package/src/tools/worker-evidence.ts +21 -1
- package/src/worker/spec-contract.ts +44 -3
- package/dist/chunk-EFADSJET.js +0 -18
- package/dist/chunk-HC4CLZ2Y.js +0 -68
- package/dist/memory-4ALKDJ4Q.js +0 -246
- package/src/domains/eval/metrics/chaos-stream.ts +0 -93
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TextDecoder } from "node:util";
|
|
1
2
|
import {
|
|
2
3
|
type Api,
|
|
3
4
|
type AssistantMessage,
|
|
@@ -15,6 +16,7 @@ import {
|
|
|
15
16
|
type Usage,
|
|
16
17
|
} from "@earendil-works/pi-ai";
|
|
17
18
|
import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
|
|
19
|
+
import type { ResponseModelIdObservation } from "../../core/response-model-id.js";
|
|
18
20
|
import {
|
|
19
21
|
type AppliedThinking,
|
|
20
22
|
type ResolvedModelRuntimeCapabilities,
|
|
@@ -35,6 +37,13 @@ import { residencyManagedFor } from "./residency.js";
|
|
|
35
37
|
import { mergeSamplingOverride } from "./sampling-overrides.js";
|
|
36
38
|
import type { EngineApiProvider } from "./types.js";
|
|
37
39
|
|
|
40
|
+
declare module "@earendil-works/pi-ai" {
|
|
41
|
+
interface AssistantMessage {
|
|
42
|
+
/** Direct observation of model-id presence in an OpenAI-compatible response. */
|
|
43
|
+
responseModelIdObservation?: ResponseModelIdObservation;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
38
47
|
const piOpenAICompletions = openAICompletionsApi();
|
|
39
48
|
|
|
40
49
|
/**
|
|
@@ -83,6 +92,124 @@ function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
|
|
83
92
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
84
93
|
}
|
|
85
94
|
|
|
95
|
+
interface ResponseModelIdCapture {
|
|
96
|
+
reportedModelId: string | null;
|
|
97
|
+
observed: boolean;
|
|
98
|
+
done: boolean;
|
|
99
|
+
buffer: string;
|
|
100
|
+
decoder: TextDecoder | null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function observeResponseModelIdLine(line: string, capture: ResponseModelIdCapture): void {
|
|
104
|
+
if (capture.done) return;
|
|
105
|
+
const normalized = line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
106
|
+
if (!normalized.startsWith("data:")) return;
|
|
107
|
+
const data = normalized.slice("data:".length).trimStart();
|
|
108
|
+
if (data.length === 0 || data === "[DONE]") return;
|
|
109
|
+
try {
|
|
110
|
+
const payload = JSON.parse(data) as unknown;
|
|
111
|
+
if (!isPlainRecord(payload)) return;
|
|
112
|
+
const model = payload.model;
|
|
113
|
+
if (typeof model === "string" && model.trim().length > 0) {
|
|
114
|
+
capture.reportedModelId = model.trim();
|
|
115
|
+
capture.done = true;
|
|
116
|
+
}
|
|
117
|
+
} catch {
|
|
118
|
+
// A partial or vendor-specific event is pi-ai's parsing concern. This
|
|
119
|
+
// observer records only complete OpenAI-compatible JSON data lines.
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function observeResponseModelIdBytes(
|
|
124
|
+
chunk: Uint8Array | undefined,
|
|
125
|
+
capture: ResponseModelIdCapture,
|
|
126
|
+
flush = false,
|
|
127
|
+
): void {
|
|
128
|
+
if (capture.done || !capture.decoder) return;
|
|
129
|
+
capture.buffer += chunk ? capture.decoder.decode(chunk, { stream: !flush }) : capture.decoder.decode();
|
|
130
|
+
let newline = capture.buffer.indexOf("\n");
|
|
131
|
+
while (newline >= 0) {
|
|
132
|
+
const line = capture.buffer.slice(0, newline);
|
|
133
|
+
capture.buffer = capture.buffer.slice(newline + 1);
|
|
134
|
+
observeResponseModelIdLine(line, capture);
|
|
135
|
+
if (capture.done) {
|
|
136
|
+
capture.buffer = "";
|
|
137
|
+
capture.decoder = null;
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
newline = capture.buffer.indexOf("\n");
|
|
141
|
+
}
|
|
142
|
+
if (flush && capture.buffer.length > 0) {
|
|
143
|
+
observeResponseModelIdLine(capture.buffer, capture);
|
|
144
|
+
capture.buffer = "";
|
|
145
|
+
}
|
|
146
|
+
if (capture.done || flush) capture.decoder = null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function captureResponseModelId(response: Response, capture: ResponseModelIdCapture): Response {
|
|
150
|
+
if (!response.body || !response.headers.get("content-type")?.toLowerCase().includes("text/event-stream")) {
|
|
151
|
+
return response;
|
|
152
|
+
}
|
|
153
|
+
const body = response.body.pipeThrough(
|
|
154
|
+
new TransformStream<Uint8Array, Uint8Array>({
|
|
155
|
+
transform(chunk, controller) {
|
|
156
|
+
capture.observed = true;
|
|
157
|
+
if (!capture.done) observeResponseModelIdBytes(chunk, capture);
|
|
158
|
+
controller.enqueue(chunk);
|
|
159
|
+
},
|
|
160
|
+
flush() {
|
|
161
|
+
capture.observed = true;
|
|
162
|
+
if (!capture.done) observeResponseModelIdBytes(undefined, capture, true);
|
|
163
|
+
},
|
|
164
|
+
}),
|
|
165
|
+
);
|
|
166
|
+
return new Response(body, {
|
|
167
|
+
status: response.status,
|
|
168
|
+
statusText: response.statusText,
|
|
169
|
+
headers: response.headers,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function withResponseModelIdCapture<TOptions extends StreamOptions>(
|
|
174
|
+
options: TOptions,
|
|
175
|
+
sourceFactory: (capturedOptions: TOptions) => AssistantMessageEventStream,
|
|
176
|
+
): AssistantMessageEventStream {
|
|
177
|
+
const capture: ResponseModelIdCapture = {
|
|
178
|
+
reportedModelId: null,
|
|
179
|
+
observed: false,
|
|
180
|
+
done: false,
|
|
181
|
+
buffer: "",
|
|
182
|
+
decoder: new TextDecoder(),
|
|
183
|
+
};
|
|
184
|
+
const fetchImpl: NonNullable<StreamOptions["fetch"]> =
|
|
185
|
+
options.fetch ?? ((input, init) => globalThis.fetch(input, init));
|
|
186
|
+
const capturedOptions = {
|
|
187
|
+
...options,
|
|
188
|
+
fetch: async (input, init) => captureResponseModelId(await fetchImpl(input, init), capture),
|
|
189
|
+
} as TOptions;
|
|
190
|
+
const source = sourceFactory(capturedOptions);
|
|
191
|
+
const annotated = createAssistantMessageEventStream();
|
|
192
|
+
(async () => {
|
|
193
|
+
try {
|
|
194
|
+
for await (const event of source) {
|
|
195
|
+
const observation: ResponseModelIdObservation = capture.observed
|
|
196
|
+
? capture.reportedModelId === null
|
|
197
|
+
? { state: "not-reported" }
|
|
198
|
+
: { state: "reported", reportedModelId: capture.reportedModelId }
|
|
199
|
+
: { state: "not-observed" };
|
|
200
|
+
if (event.type === "done") event.message.responseModelIdObservation = observation;
|
|
201
|
+
else if (event.type === "error") event.error.responseModelIdObservation = observation;
|
|
202
|
+
annotated.push(event as AssistantMessageEvent);
|
|
203
|
+
}
|
|
204
|
+
annotated.end();
|
|
205
|
+
} catch (err) {
|
|
206
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
207
|
+
throw new Error(message);
|
|
208
|
+
}
|
|
209
|
+
})();
|
|
210
|
+
return annotated;
|
|
211
|
+
}
|
|
212
|
+
|
|
86
213
|
function samplingParamsFromProfile(profile: SamplingProfile): Record<string, unknown> {
|
|
87
214
|
return {
|
|
88
215
|
...(profile.topP !== undefined ? { top_p: profile.topP } : {}),
|
|
@@ -735,17 +862,16 @@ export const openAICompletionsApiProvider: EngineApiProvider<"openai-completions
|
|
|
735
862
|
stripSentinelsFromStream(
|
|
736
863
|
stripNeverReasoningFromStream(
|
|
737
864
|
filterGemmaChannelStream(
|
|
738
|
-
|
|
739
|
-
model,
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
effectiveContext,
|
|
748
|
-
withRemainingContextBudget(model, effectiveContext, withSamplers),
|
|
865
|
+
withResponseModelIdCapture(
|
|
866
|
+
withRemainingContextBudget(model, effectiveContext, withSamplers),
|
|
867
|
+
(capturedOptions) =>
|
|
868
|
+
withLocalResidency(
|
|
869
|
+
model,
|
|
870
|
+
{
|
|
871
|
+
...(options?.apiKey !== undefined ? { apiKey: options.apiKey } : {}),
|
|
872
|
+
...(options?.signal !== undefined ? { signal: options.signal } : {}),
|
|
873
|
+
},
|
|
874
|
+
(requestModel) => piOpenAICompletions.stream(requestModel, effectiveContext, capturedOptions),
|
|
749
875
|
),
|
|
750
876
|
),
|
|
751
877
|
resolved.family === "gemma-4",
|
|
@@ -768,17 +894,16 @@ export const openAICompletionsApiProvider: EngineApiProvider<"openai-completions
|
|
|
768
894
|
stripSentinelsFromStream(
|
|
769
895
|
stripNeverReasoningFromStream(
|
|
770
896
|
filterGemmaChannelStream(
|
|
771
|
-
|
|
772
|
-
model,
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
effectiveContext,
|
|
781
|
-
withRemainingContextBudget(model, effectiveContext, withSamplers),
|
|
897
|
+
withResponseModelIdCapture(
|
|
898
|
+
withRemainingContextBudget(model, effectiveContext, withSamplers),
|
|
899
|
+
(capturedOptions) =>
|
|
900
|
+
withLocalResidency(
|
|
901
|
+
model,
|
|
902
|
+
{
|
|
903
|
+
...(options?.apiKey !== undefined ? { apiKey: options.apiKey } : {}),
|
|
904
|
+
...(options?.signal !== undefined ? { signal: options.signal } : {}),
|
|
905
|
+
},
|
|
906
|
+
(requestModel) => piOpenAICompletions.streamSimple(requestModel, effectiveContext, capturedOptions),
|
|
782
907
|
),
|
|
783
908
|
),
|
|
784
909
|
resolved.family === "gemma-4",
|
|
@@ -413,9 +413,15 @@ function permissionResultForDecision(
|
|
|
413
413
|
return result;
|
|
414
414
|
}
|
|
415
415
|
|
|
416
|
-
function decideToolUse(
|
|
416
|
+
function decideToolUse(
|
|
417
|
+
input: PermissionGateInput,
|
|
418
|
+
toolCallId: string | undefined,
|
|
419
|
+
toolName: string,
|
|
420
|
+
toolInput: unknown,
|
|
421
|
+
): ClaudeToolPermissionDecision {
|
|
417
422
|
return emitClaudeToolPermissionDecision({
|
|
418
423
|
toolName,
|
|
424
|
+
...(toolCallId !== undefined ? { toolCallId } : {}),
|
|
419
425
|
input: coerceToolInput(toolInput),
|
|
420
426
|
safety: input.safety,
|
|
421
427
|
cwd: input.cwd,
|
|
@@ -447,7 +453,7 @@ function decideToolUseOnce(
|
|
|
447
453
|
toolInput: unknown,
|
|
448
454
|
): ClaudeToolPermissionDecision {
|
|
449
455
|
return decideClaudeSdkToolUseOnce(input.handledToolDecisions, toolUseID, () =>
|
|
450
|
-
decideToolUse(input, toolName, toolInput),
|
|
456
|
+
decideToolUse(input, toolUseID, toolName, toolInput),
|
|
451
457
|
);
|
|
452
458
|
}
|
|
453
459
|
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
DEFAULT_AUTONOMY_LEVEL,
|
|
9
9
|
mapAutonomy,
|
|
10
10
|
} from "../../domains/safety/autonomy.js";
|
|
11
|
+
import { describeCallAction } from "../../domains/safety/call-target.js";
|
|
11
12
|
import type { SafetyContract, SafetyDecision } from "../../domains/safety/contract.js";
|
|
12
13
|
import type { RejectionMessage } from "../../domains/safety/rejection-feedback.js";
|
|
13
14
|
import type { ToolFinishEvent, ToolStartEvent } from "../../tools/agent-tools.js";
|
|
@@ -68,6 +69,8 @@ export interface EvaluateClaudeToolPermissionInput {
|
|
|
68
69
|
export interface EmitClaudeToolPermissionInput extends EvaluateClaudeToolPermissionInput {
|
|
69
70
|
emit(event: ClioWorkerEvent): void;
|
|
70
71
|
onPermission?: "deny" | "fail";
|
|
72
|
+
/** SDK tool-use id shared by the start and finish telemetry events. */
|
|
73
|
+
toolCallId?: string;
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
@@ -324,9 +327,11 @@ function emitToolFinish(
|
|
|
324
327
|
outcome: ToolFinishEvent["outcome"],
|
|
325
328
|
reason: string,
|
|
326
329
|
reasonCode?: string,
|
|
330
|
+
toolCallId?: string,
|
|
327
331
|
): void {
|
|
328
332
|
const event: ToolFinishEvent = {
|
|
329
333
|
tool: mapped.clioToolName,
|
|
334
|
+
...(toolCallId !== undefined ? { toolCallId } : {}),
|
|
330
335
|
posture: "operating",
|
|
331
336
|
durationMs: Math.round(performance.now() - startedAtClock),
|
|
332
337
|
outcome,
|
|
@@ -350,10 +355,16 @@ export function emitClaudeToolPermissionDecision(input: EmitClaudeToolPermission
|
|
|
350
355
|
const startedAt = Date.now();
|
|
351
356
|
const startedAtClock = performance.now();
|
|
352
357
|
const decision = evaluateClaudeToolPermission(input);
|
|
358
|
+
const toolCallId = input.toolCallId?.trim() ? input.toolCallId : undefined;
|
|
359
|
+
// Mapped args are this side's own translation of the subprocess call, so the
|
|
360
|
+
// descriptor is composed from them here rather than downstream of the seam.
|
|
361
|
+
const action = describeCallAction(decision.mapped.clioToolName, decision.mapped.args);
|
|
353
362
|
const start: ToolStartEvent = {
|
|
354
363
|
tool: decision.mapped.clioToolName,
|
|
364
|
+
...(toolCallId !== undefined ? { toolCallId } : {}),
|
|
355
365
|
posture: "operating",
|
|
356
366
|
startedAt,
|
|
367
|
+
...(action !== null ? { action } : {}),
|
|
357
368
|
};
|
|
358
369
|
input.emit({ type: "clio_tool_start", payload: start });
|
|
359
370
|
if (decision.kind === "allow") {
|
|
@@ -365,6 +376,7 @@ export function emitClaudeToolPermissionDecision(input: EmitClaudeToolPermission
|
|
|
365
376
|
"ok",
|
|
366
377
|
decision.reason,
|
|
367
378
|
decision.reasonCode,
|
|
379
|
+
toolCallId,
|
|
368
380
|
);
|
|
369
381
|
return decision;
|
|
370
382
|
}
|
|
@@ -391,6 +403,7 @@ export function emitClaudeToolPermissionDecision(input: EmitClaudeToolPermission
|
|
|
391
403
|
"blocked",
|
|
392
404
|
decision.reason,
|
|
393
405
|
decision.reasonCode,
|
|
406
|
+
toolCallId,
|
|
394
407
|
);
|
|
395
408
|
return decision;
|
|
396
409
|
}
|
package/src/engine/loop-guard.ts
CHANGED
|
@@ -458,12 +458,14 @@ export interface CreateLoopGuardRegistrationOptions {
|
|
|
458
458
|
export interface LoopGuardRegistration extends MiddlewareHookRegistration {
|
|
459
459
|
/** Read-only attempt counter for tests and telemetry. */
|
|
460
460
|
callCount(): number;
|
|
461
|
+
/** One-way worker phase growth admitted before launch for a result-contract revision. */
|
|
462
|
+
extendWorkerToolCallPhase(phase: { toolCalls: number; readReserve: number }): boolean;
|
|
461
463
|
}
|
|
462
464
|
|
|
463
465
|
export function createLoopGuardRegistration(options: CreateLoopGuardRegistrationOptions): LoopGuardRegistration {
|
|
464
466
|
const budget = options.turnBlockBudget ?? INTERACTIVE_LOOP_BLOCK_BUDGET;
|
|
465
467
|
const cap = options.toolCallCap;
|
|
466
|
-
|
|
468
|
+
let softLimit =
|
|
467
469
|
options.toolCallSoftLimit !== undefined &&
|
|
468
470
|
Number.isSafeInteger(options.toolCallSoftLimit) &&
|
|
469
471
|
options.toolCallSoftLimit > 0 &&
|
|
@@ -472,8 +474,8 @@ export function createLoopGuardRegistration(options: CreateLoopGuardRegistration
|
|
|
472
474
|
: undefined;
|
|
473
475
|
const turnBudget = options.turnToolCallBudget;
|
|
474
476
|
const synthesisLockout = options.turnSynthesisLockout === true;
|
|
475
|
-
|
|
476
|
-
|
|
477
|
+
let softReadReserve = options.toolCallSoftReadReserve ?? 0;
|
|
478
|
+
let softReadReserveThreshold =
|
|
477
479
|
softLimit !== undefined && softReadReserve > 0 && softLimit > softReadReserve ? softLimit - softReadReserve : null;
|
|
478
480
|
const deliveryTools = [...new Set(options.deliveryTools ?? [])];
|
|
479
481
|
const reserveAdmits = (tool: string | undefined): boolean => isReserveAdmittedTool(tool, deliveryTools);
|
|
@@ -850,6 +852,28 @@ export function createLoopGuardRegistration(options: CreateLoopGuardRegistration
|
|
|
850
852
|
"blocks repeated tool calls, enforces tool-call caps, and annotates substantial identical read results across distinct arguments",
|
|
851
853
|
hooks: ["before_tool", "after_tool"],
|
|
852
854
|
callCount: () => count,
|
|
855
|
+
extendWorkerToolCallPhase(phase): boolean {
|
|
856
|
+
if (
|
|
857
|
+
softLimit === undefined ||
|
|
858
|
+
!Number.isSafeInteger(phase.toolCalls) ||
|
|
859
|
+
!Number.isSafeInteger(phase.readReserve) ||
|
|
860
|
+
phase.toolCalls <= 0 ||
|
|
861
|
+
phase.readReserve < 0 ||
|
|
862
|
+
phase.readReserve >= phase.toolCalls ||
|
|
863
|
+
(cap !== undefined && phase.toolCalls > cap) ||
|
|
864
|
+
phase.toolCalls < softLimit ||
|
|
865
|
+
(phase.toolCalls === softLimit && phase.readReserve <= softReadReserve)
|
|
866
|
+
) {
|
|
867
|
+
return false;
|
|
868
|
+
}
|
|
869
|
+
softLimit = phase.toolCalls;
|
|
870
|
+
softReadReserve = phase.readReserve;
|
|
871
|
+
softReadReserveThreshold = softReadReserve > 0 && softLimit > softReadReserve ? softLimit - softReadReserve : null;
|
|
872
|
+
softReadReserveEntered = false;
|
|
873
|
+
softLockout = null;
|
|
874
|
+
reserveDenials = { denials: 0 };
|
|
875
|
+
return true;
|
|
876
|
+
},
|
|
853
877
|
evaluate(input): ReadonlyArray<MiddlewareEffect> {
|
|
854
878
|
if (input.hook === "after_tool") {
|
|
855
879
|
recordSuccessfulResult(input);
|
|
@@ -53,9 +53,10 @@ export interface ClioPermissionEscalatedEvent {
|
|
|
53
53
|
tool: string;
|
|
54
54
|
summary: string;
|
|
55
55
|
/**
|
|
56
|
-
* Sanitized one-line preview of the call's object
|
|
57
|
-
*
|
|
58
|
-
*
|
|
56
|
+
* Sanitized one-line preview of the call's allowlisted object fields, so
|
|
57
|
+
* the operator's approval overlay can show what the call will touch.
|
|
58
|
+
* Unlisted fields carry only type-and-size summaries, and the args
|
|
59
|
+
* themselves never cross the stdout seam.
|
|
59
60
|
*/
|
|
60
61
|
target?: string;
|
|
61
62
|
axis?: string;
|
|
@@ -443,6 +443,42 @@ export function startWorkerRun(input: WorkerRunInput, emit: WorkerEventEmit): Wo
|
|
|
443
443
|
// loop guard counts synthesis-lock noncompliance by model round, preventing
|
|
444
444
|
// one wide parallel batch from consuming the entire denial backstop.
|
|
445
445
|
let workerModelRound = 0;
|
|
446
|
+
const loopGuardRegistration = createLoopGuardRegistration({
|
|
447
|
+
safety,
|
|
448
|
+
toolCallCap: workerBudget.hardCap,
|
|
449
|
+
toolCallSoftLimit: workerBudget.toolCalls,
|
|
450
|
+
// A worker's blocks all land in one run-long bucket, so the bound on
|
|
451
|
+
// them is a statement about this run's length, not about a turn.
|
|
452
|
+
turnBlockBudget: workerLoopBlockBudget(workerBudget.revision?.toolCalls ?? workerBudget.toolCalls),
|
|
453
|
+
toolCallSoftReadReserve: readReserve,
|
|
454
|
+
...(deliveryTools.length > 0 ? { deliveryTools } : {}),
|
|
455
|
+
turnSynthesisLockout: workerBudget.synthesis,
|
|
456
|
+
// Once locked, the next model round is forced text-only at the
|
|
457
|
+
// request level. The lockout directive alone relies on model compliance.
|
|
458
|
+
onSynthesisLockout: () => {
|
|
459
|
+
if (workerBudget.synthesis) synthesisToolLock = true;
|
|
460
|
+
},
|
|
461
|
+
...(!workerBudget.synthesis
|
|
462
|
+
? {
|
|
463
|
+
onSoftLimitFinalCallAdmitted: (toolCallId: string | undefined) => {
|
|
464
|
+
if (workerBoundFailure === null) {
|
|
465
|
+
workerBoundFailure = `worker agent budget reached (${workerBudget.toolCalls}); synthesis is disabled`;
|
|
466
|
+
}
|
|
467
|
+
// Native agent tool calls carry a stable provider id. The wildcard
|
|
468
|
+
// keeps even an invariant violation on the post-result path.
|
|
469
|
+
stopAfterToolResultCallId = toolCallId ?? STOP_AFTER_ANY_TOOL_RESULT;
|
|
470
|
+
},
|
|
471
|
+
}
|
|
472
|
+
: {}),
|
|
473
|
+
// Requiring read is correct only when reading is the whole reserve.
|
|
474
|
+
...(readReserve > 0 && deliveryTools.length === 0
|
|
475
|
+
? {
|
|
476
|
+
onSoftReadReserve: () => {
|
|
477
|
+
middlewareToolChoice.apply([{ kind: "require_tool", toolName: ToolNames.Read }]);
|
|
478
|
+
},
|
|
479
|
+
}
|
|
480
|
+
: {}),
|
|
481
|
+
});
|
|
446
482
|
const registry = createWorkerToolRegistry(
|
|
447
483
|
input.middlewareSnapshot,
|
|
448
484
|
safety,
|
|
@@ -461,52 +497,7 @@ export function startWorkerRun(input: WorkerRunInput, emit: WorkerEventEmit): Wo
|
|
|
461
497
|
// and has no persistence sink. It can still absorb worker-local
|
|
462
498
|
// protect_path effects from snapshot rules for the rest of this run.
|
|
463
499
|
[
|
|
464
|
-
|
|
465
|
-
safety,
|
|
466
|
-
toolCallCap: workerBudget.hardCap,
|
|
467
|
-
toolCallSoftLimit: workerBudget.toolCalls,
|
|
468
|
-
// A worker's blocks all land in one run-long bucket, so the bound on
|
|
469
|
-
// them is a statement about this run's length, not about a turn.
|
|
470
|
-
turnBlockBudget: workerLoopBlockBudget(workerBudget.toolCalls),
|
|
471
|
-
toolCallSoftReadReserve: readReserve,
|
|
472
|
-
...(deliveryTools.length > 0 ? { deliveryTools } : {}),
|
|
473
|
-
turnSynthesisLockout: workerBudget.synthesis,
|
|
474
|
-
// Once locked, the next model round is forced text-only at the
|
|
475
|
-
// request level (the tool surface is removed in onPayload below):
|
|
476
|
-
// the lockout directive alone relies on model compliance, and
|
|
477
|
-
// measured local models kept calling tools until the backstop
|
|
478
|
-
// aborted the run, or answered the forced round with tool-call
|
|
479
|
-
// markup that the loop guard then removed.
|
|
480
|
-
onSynthesisLockout: () => {
|
|
481
|
-
if (workerBudget.synthesis) {
|
|
482
|
-
synthesisToolLock = true;
|
|
483
|
-
}
|
|
484
|
-
},
|
|
485
|
-
...(!workerBudget.synthesis
|
|
486
|
-
? {
|
|
487
|
-
onSoftLimitFinalCallAdmitted: (toolCallId: string | undefined) => {
|
|
488
|
-
if (workerBoundFailure === null) {
|
|
489
|
-
workerBoundFailure = `worker agent budget reached (${workerBudget.toolCalls}); synthesis is disabled`;
|
|
490
|
-
}
|
|
491
|
-
// Native agent tool calls carry a stable provider id. The wildcard
|
|
492
|
-
// keeps even an invariant violation on the post-result path
|
|
493
|
-
// instead of reintroducing a before_tool abort.
|
|
494
|
-
stopAfterToolResultCallId = toolCallId ?? STOP_AFTER_ANY_TOOL_RESULT;
|
|
495
|
-
},
|
|
496
|
-
}
|
|
497
|
-
: {}),
|
|
498
|
-
// Forcing the next round to `read` is only correct when reading is
|
|
499
|
-
// the whole reserve. An agent with delivery tools must be able to
|
|
500
|
-
// write in its own reserve window, so it gets the steering directive
|
|
501
|
-
// without the request-level lock.
|
|
502
|
-
...(readReserve > 0 && deliveryTools.length === 0
|
|
503
|
-
? {
|
|
504
|
-
onSoftReadReserve: () => {
|
|
505
|
-
middlewareToolChoice.apply([{ kind: "require_tool", toolName: ToolNames.Read }]);
|
|
506
|
-
},
|
|
507
|
-
}
|
|
508
|
-
: {}),
|
|
509
|
-
}),
|
|
500
|
+
loopGuardRegistration,
|
|
510
501
|
createProtectedArtifactsRegistration({
|
|
511
502
|
...(input.protectedArtifactState !== undefined
|
|
512
503
|
? { initialState: { artifacts: [...input.protectedArtifactState.artifacts] } }
|
|
@@ -519,6 +510,7 @@ export function startWorkerRun(input: WorkerRunInput, emit: WorkerEventEmit): Wo
|
|
|
519
510
|
);
|
|
520
511
|
const contractCwd = input.cwd ?? process.cwd();
|
|
521
512
|
let resultContractRepairsQueued = 0;
|
|
513
|
+
let resultContractRevisionActive = false;
|
|
522
514
|
/** Read tool call id -> what was asked for, pending that call's result. */
|
|
523
515
|
const pendingReadCitations = new Map<string, { path: string; offset: number | null; tail: boolean }>();
|
|
524
516
|
const observedReadRanges = new Map<string, Array<readonly [number, number]>>();
|
|
@@ -702,10 +694,17 @@ export function startWorkerRun(input: WorkerRunInput, emit: WorkerEventEmit): Wo
|
|
|
702
694
|
if (violation !== null) {
|
|
703
695
|
if (resultContractRepairsQueued < RESULT_CONTRACT_REPAIR_LIMIT) {
|
|
704
696
|
resultContractRepairsQueued += 1;
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
697
|
+
if (!resultContractRevisionActive && workerBudget.revision !== undefined) {
|
|
698
|
+
resultContractRevisionActive = loopGuardRegistration.extendWorkerToolCallPhase(workerBudget.revision);
|
|
699
|
+
if (resultContractRevisionActive) {
|
|
700
|
+
synthesisToolLock = false;
|
|
701
|
+
middlewareToolChoice.reset();
|
|
702
|
+
stopAfterToolResultCallId = null;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
const revisionToolsAvailable = resultContractRevisionActive && !synthesisToolLock;
|
|
706
|
+
// A repair without preauthorized growth remains terminal and text-only.
|
|
707
|
+
if (!revisionToolsAvailable) synthesisToolLock = true;
|
|
709
708
|
// Delivered as a paired tool exchange, never a user turn. Templates
|
|
710
709
|
// that key history rendering off the last `user` message re-render
|
|
711
710
|
// every earlier assistant turn when one is appended mid-run, which
|
|
@@ -714,7 +713,13 @@ export function startWorkerRun(input: WorkerRunInput, emit: WorkerEventEmit): Wo
|
|
|
714
713
|
// bytes as a tool result (#55). The synthetic assistant call gives
|
|
715
714
|
// the result a real tool_call_id for strict endpoints (#62).
|
|
716
715
|
const repair = resultContractRepairMessages(
|
|
717
|
-
{
|
|
716
|
+
{
|
|
717
|
+
contract,
|
|
718
|
+
reason: violation,
|
|
719
|
+
attempt: resultContractRepairsQueued,
|
|
720
|
+
anchors: observedReadAnchors(),
|
|
721
|
+
...(revisionToolsAvailable ? { toolsAvailable: true } : {}),
|
|
722
|
+
},
|
|
718
723
|
{ provider: model.provider, api: model.api, model: model.id },
|
|
719
724
|
);
|
|
720
725
|
for (const message of repair) agent.followUp(message as unknown as AgentMessage);
|
|
@@ -915,9 +920,9 @@ export function startWorkerRun(input: WorkerRunInput, emit: WorkerEventEmit): Wo
|
|
|
915
920
|
const timer = setTimeout(() => resolveEscalation(requestId, "deny", "timeout"), escalationConfig.timeoutMs);
|
|
916
921
|
activeEscalation = { requestId, tool: call.tool, actionClass, callKey, timer };
|
|
917
922
|
// The operator decides on this exact call, so the escalation carries a
|
|
918
|
-
// sanitized preview of its object
|
|
919
|
-
//
|
|
920
|
-
const target = describeCallTarget(call.
|
|
923
|
+
// sanitized allowlisted preview of its object. Unlisted fields cross only
|
|
924
|
+
// as type-and-size summaries, and the args stay inside the worker.
|
|
925
|
+
const target = describeCallTarget(call.tool, call.args);
|
|
921
926
|
emit({
|
|
922
927
|
type: "clio_permission_escalated",
|
|
923
928
|
payload: {
|
|
@@ -90,6 +90,7 @@ import {
|
|
|
90
90
|
import { createMemoryInterventionRegistration } from "../domains/middleware/memory-intervention.js";
|
|
91
91
|
import { createTaskBoardReminderRegistration } from "../domains/middleware/task-board-reminder.js";
|
|
92
92
|
import { createTaskNudgeRegistration } from "../domains/middleware/task-nudge.js";
|
|
93
|
+
import { createWatchdogRegistration } from "../domains/middleware/watchdog.js";
|
|
93
94
|
import type { ObservabilityContract } from "../domains/observability/index.js";
|
|
94
95
|
import { ObservabilityDomainModule } from "../domains/observability/index.js";
|
|
95
96
|
import type { PromptsContract } from "../domains/prompts/contract.js";
|
|
@@ -177,12 +178,14 @@ import {
|
|
|
177
178
|
import { subscribeLoopGuardStop } from "../interactive/loop-guard-interrupt.js";
|
|
178
179
|
import { BUILTIN_SLASH_COMMANDS } from "../interactive/slash-commands.js";
|
|
179
180
|
import { createToolProseRegistration } from "../interactive/tool-prose-registration.js";
|
|
181
|
+
import { runWatchdogReview } from "../interactive/watchdog-run.js";
|
|
180
182
|
import { type AskUserHandler, cancelledAskUserResult } from "../tools/ask-user.js";
|
|
181
183
|
import { registerAllTools } from "../tools/bootstrap.js";
|
|
182
184
|
import { isGitRepository, recoverCleanupReadyCompeteGroups } from "../tools/compete-worktrees.js";
|
|
183
185
|
import { createDispatchBackgroundRegistry } from "../tools/dispatch-background.js";
|
|
184
186
|
import { createFileMutationObserver, createSkillActivationObserver } from "../tools/observers.js";
|
|
185
187
|
import { createRegistry } from "../tools/registry.js";
|
|
188
|
+
import { sweepExpiredToolOffloads } from "../tools/result-shaping.js";
|
|
186
189
|
|
|
187
190
|
export interface BootResult {
|
|
188
191
|
exitCode: number;
|
|
@@ -726,6 +729,7 @@ export async function bootOrchestrator(options: BootOptions = {}): Promise<BootR
|
|
|
726
729
|
termination.installSignalHandlers();
|
|
727
730
|
|
|
728
731
|
ensureClioState();
|
|
732
|
+
sweepExpiredToolOffloads();
|
|
729
733
|
timer.mark("install check");
|
|
730
734
|
|
|
731
735
|
// A hard-killed coordinator cannot run domain drains. Reconcile its compete
|
|
@@ -1067,6 +1071,10 @@ export async function bootOrchestrator(options: BootOptions = {}): Promise<BootR
|
|
|
1067
1071
|
// Bound late: the registration is built here, but the buffer a deferred
|
|
1068
1072
|
// reminder lands in belongs to the chat loop that has not been composed yet.
|
|
1069
1073
|
let deferredMemoryReminderSink: ((message: string) => void) | null = null;
|
|
1074
|
+
// The watchdog's findings are for the operator, not the model, so they take
|
|
1075
|
+
// the transcript-notice path rather than the reminder buffer. Bound late for
|
|
1076
|
+
// the same reason: the chat loop that owns the transcript is composed below.
|
|
1077
|
+
let deferredWatchdogNoticeSink: ((text: string) => void) | null = null;
|
|
1070
1078
|
// Content-bearing, so it exists only when the operator named a file. The
|
|
1071
1079
|
// telemetry row says which silence happened; this says what the model wrote.
|
|
1072
1080
|
const memoryTracePath = taskMemoryTracePath();
|
|
@@ -1259,6 +1267,7 @@ export async function bootOrchestrator(options: BootOptions = {}): Promise<BootR
|
|
|
1259
1267
|
getAutonomy: resolveEffectiveAutonomy,
|
|
1260
1268
|
...(interactive ? { dispatchBackground } : {}),
|
|
1261
1269
|
getCostCeilingUsd: () => result.getContract<SchedulingContract>("scheduling")?.ceilingUsd() ?? 0,
|
|
1270
|
+
...(config ? { getWorkerRosters: () => config.get().workers.rosters } : {}),
|
|
1262
1271
|
getSkillLoaderOptions: () => ({
|
|
1263
1272
|
trustProjectCompatRoots: config?.get().skills.trustProjectCompatRoots === true,
|
|
1264
1273
|
disableDiscovery: options.noSkills === true || options.headless?.noSkills === true,
|
|
@@ -1519,6 +1528,29 @@ export async function bootOrchestrator(options: BootOptions = {}): Promise<BootR
|
|
|
1519
1528
|
middleware.registerHook(
|
|
1520
1529
|
createDetachedDispatchNudgeRegistration({ getOpenBatches: () => openDetachedBatchViews(dispatch) }),
|
|
1521
1530
|
);
|
|
1531
|
+
// The opt-in turn-end watchdog. Headless and ACP runs pass `false` for the
|
|
1532
|
+
// surface: neither has an operator reading a transcript, so a notice they
|
|
1533
|
+
// cannot see would be a worker run spent on nothing whatever the setting says.
|
|
1534
|
+
middleware.registerHook(
|
|
1535
|
+
createWatchdogRegistration({
|
|
1536
|
+
firesOnThisSurface: interactive,
|
|
1537
|
+
getSettings: () => (effectiveSettingsForDispatch?.() ?? getCurrentSettings()).watchdog,
|
|
1538
|
+
getScope: () => {
|
|
1539
|
+
const board = taskBoard.snapshot();
|
|
1540
|
+
if (board === null) return null;
|
|
1541
|
+
const active = board.tasks.find((task) => task.status === "active");
|
|
1542
|
+
return active ? `${board.title}: ${active.id} ${active.title}` : board.title;
|
|
1543
|
+
},
|
|
1544
|
+
run: (trigger) =>
|
|
1545
|
+
runWatchdogReview(trigger, {
|
|
1546
|
+
dispatch,
|
|
1547
|
+
bus,
|
|
1548
|
+
...(agents ? { getAgentRoleFacts: agentRoleFactsResolver((id: string) => agents.getSpec(id)) } : {}),
|
|
1549
|
+
target: (effectiveSettingsForDispatch?.() ?? getCurrentSettings()).watchdog.target,
|
|
1550
|
+
...(deferredWatchdogNoticeSink ? { emitNotice: deferredWatchdogNoticeSink } : {}),
|
|
1551
|
+
}),
|
|
1552
|
+
}),
|
|
1553
|
+
);
|
|
1522
1554
|
if (session) {
|
|
1523
1555
|
middleware.registerHook(
|
|
1524
1556
|
createFinishContractRegistration({
|
|
@@ -1553,8 +1585,13 @@ export async function bootOrchestrator(options: BootOptions = {}): Promise<BootR
|
|
|
1553
1585
|
getMemorySection: () => {
|
|
1554
1586
|
try {
|
|
1555
1587
|
const records = loadMemoryRecordsSync(clioDataDir());
|
|
1588
|
+
const settings = getCurrentSettings();
|
|
1589
|
+
const targetId = session?.current()?.target ?? settings.orchestrator?.target;
|
|
1590
|
+
const runtimeId = targetId ? providers.getTarget(targetId)?.runtime : undefined;
|
|
1556
1591
|
return buildMemoryPromptSection(records, {
|
|
1592
|
+
scopes: ["global", "repo", "runtime"],
|
|
1557
1593
|
activeRepository: canonicalMemoryRepositoryIdentity(process.cwd()),
|
|
1594
|
+
activeRuntime: runtimeId === undefined ? null : { kind: "runtime", key: runtimeId },
|
|
1558
1595
|
}).section;
|
|
1559
1596
|
} catch {
|
|
1560
1597
|
return "";
|
|
@@ -1562,11 +1599,24 @@ export async function bootOrchestrator(options: BootOptions = {}): Promise<BootR
|
|
|
1562
1599
|
},
|
|
1563
1600
|
getTaskMemoryHandoffSource: () => {
|
|
1564
1601
|
ensureTaskMemorySession();
|
|
1565
|
-
|
|
1602
|
+
const meta = session?.current();
|
|
1603
|
+
if (!meta) throw new Error("task memory handoff requires an active session");
|
|
1604
|
+
const settings = getCurrentSettings();
|
|
1605
|
+
const targetId = meta.target ?? settings.orchestrator?.target;
|
|
1606
|
+
const runtimeId = targetId ? providers.getTarget(targetId)?.runtime : undefined;
|
|
1607
|
+
return renderTaskMemoryHandoffSource(taskMemoryBank.snapshot(), {
|
|
1608
|
+
sessionId: meta.id,
|
|
1609
|
+
evidenceRefs: [`session-${meta.id}`],
|
|
1610
|
+
runtimeIds: runtimeId === undefined ? [] : [runtimeId],
|
|
1611
|
+
agentIds: [],
|
|
1612
|
+
});
|
|
1566
1613
|
},
|
|
1567
1614
|
registerDeferredReminderSink: (sink) => {
|
|
1568
1615
|
deferredMemoryReminderSink = sink;
|
|
1569
1616
|
},
|
|
1617
|
+
registerDeferredNoticeSink: (sink) => {
|
|
1618
|
+
deferredWatchdogNoticeSink = sink;
|
|
1619
|
+
},
|
|
1570
1620
|
onAskUserFinalized: (policy) => {
|
|
1571
1621
|
decisionBoard.recordFinalizedInterview(policy);
|
|
1572
1622
|
},
|
|
@@ -1805,6 +1855,10 @@ export async function bootOrchestrator(options: BootOptions = {}): Promise<BootR
|
|
|
1805
1855
|
providers,
|
|
1806
1856
|
dispatch,
|
|
1807
1857
|
...(agents ? { agents } : {}),
|
|
1858
|
+
...(() => {
|
|
1859
|
+
const scheduling = result.getContract<SchedulingContract>("scheduling");
|
|
1860
|
+
return scheduling ? { scheduling } : {};
|
|
1861
|
+
})(),
|
|
1808
1862
|
observability,
|
|
1809
1863
|
chat,
|
|
1810
1864
|
...(options.terminalLease
|