@iowarp/clio-coder 0.3.4 → 0.3.6
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 +37 -2
- package/CONTRIBUTING.md +6 -6
- package/README.md +2 -2
- package/dist/{acp-S5R4RR5B.js → acp-2BEHC4DL.js} +4 -4
- package/dist/{agents-P6DMMVZY.js → agents-LNNFTM53.js} +13 -11
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-2XCZLPKS.js → auth-KXXFI2VS.js} +6 -6
- package/dist/{chunk-YCWGATWI.js → chunk-24I7BN55.js} +2 -2
- package/dist/{chunk-EKMEHE4H.js → chunk-33YXPOE3.js} +2 -3
- package/dist/chunk-3BPUFZDL.js +37 -0
- package/dist/{chunk-WPQLXFOZ.js → chunk-43AOLP7E.js} +2 -2
- package/dist/{chunk-N4CZJQRK.js → chunk-5JGRAMKL.js} +4 -4
- package/dist/{chunk-BRXQQJFP.js → chunk-6US73PDB.js} +568 -47
- package/dist/{chunk-K6WL7QZT.js → chunk-6XXKFVSN.js} +2 -2
- package/dist/{chunk-QQK64KLB.js → chunk-CJUB2JJ2.js} +138 -20
- package/dist/{chunk-HV5X7OR2.js → chunk-CKXWIANG.js} +12 -12
- package/dist/{chunk-UZHIZC5S.js → chunk-CYQKWTG3.js} +61 -76
- package/dist/{chunk-QWU7ZBO7.js → chunk-DJVECN66.js} +204 -45
- package/dist/{chunk-ZWMF7253.js → chunk-E2ER4LJF.js} +304 -9
- package/dist/{chunk-7RXG6QRZ.js → chunk-EKY57CSP.js} +2 -75
- package/dist/{chunk-EDRHSCIE.js → chunk-EYPA3EGJ.js} +10 -2
- package/dist/{chunk-TTNYS3EA.js → chunk-G7MUEIGA.js} +1 -1
- package/dist/{chunk-BPGS2WCQ.js → chunk-GEYXPTRF.js} +2 -1
- package/dist/{chunk-BEY543CS.js → chunk-GOXNB3AO.js} +5 -2
- package/dist/{chunk-G4BMMOKF.js → chunk-HVDIIIQW.js} +2 -2
- package/dist/chunk-HWUFFB6L.js +83 -0
- package/dist/{chunk-35MKKU5R.js → chunk-K7T3E2SR.js} +15 -8
- package/dist/{chunk-VAWWTKDP.js → chunk-KHSFENX2.js} +2 -2
- package/dist/chunk-LCGCVYZ4.js +57 -0
- package/dist/{chunk-X6COSD2O.js → chunk-LYF7OHWH.js} +41 -14
- package/dist/{chunk-POHLU5DW.js → chunk-M6L6IDJG.js} +3 -3
- package/dist/{chunk-X4RCMKVQ.js → chunk-NDINPTJ4.js} +2 -2
- package/dist/{chunk-5M54SPOL.js → chunk-ODFEOB4F.js} +161 -5
- package/dist/{chunk-3JLKSKD7.js → chunk-OH3TOQTB.js} +5 -1
- package/dist/{chunk-MEQ45TQ4.js → chunk-PBTHKCPN.js} +18 -4
- package/dist/{chunk-ED4KHGC3.js → chunk-PPAMZ32Z.js} +9 -2
- package/dist/{chunk-QQL5RT5M.js → chunk-QM3F2GKX.js} +94 -36
- package/dist/{chunk-A2GZF7DC.js → chunk-QNQHSOLF.js} +4 -4
- package/dist/{chunk-KRPY7NTG.js → chunk-R46L2BIR.js} +3 -3
- package/dist/{chunk-BP4OYD6A.js → chunk-RY3LY4J5.js} +20 -2
- package/dist/{chunk-34475P3I.js → chunk-TSHXZTOQ.js} +5 -4
- package/dist/{chunk-VJWL6YS5.js → chunk-UUVG37B4.js} +2 -2
- package/dist/{chunk-2TZWSW76.js → chunk-WHGPSPT5.js} +2 -2
- package/dist/{chunk-TW3WDMVS.js → chunk-WHJYKASB.js} +2 -2
- package/dist/{chunk-YHZX5GEU.js → chunk-XAKHZX5N.js} +2 -2
- package/dist/{chunk-HXG4IURW.js → chunk-XE2VEJHX.js} +2 -2
- package/dist/{chunk-3HZ5RWN2.js → chunk-XF5N4U5A.js} +7 -6
- package/dist/{chunk-ZYKPLLNQ.js → chunk-XXQNGV4M.js} +590 -32
- package/dist/{chunk-4JUF2NNX.js → chunk-XYDYPRZI.js} +4 -4
- package/dist/{chunk-VMNQ6OZA.js → chunk-ZRGEBJ4T.js} +971 -794
- package/dist/{chunk-2LZI5CAG.js → chunk-ZXF4XRKW.js} +75 -33
- package/dist/{chunk-VSNATDE6.js → chunk-ZZMN5OM4.js} +2 -2
- package/dist/cli/index.js +31 -31
- package/dist/{clio-J5JIOIDS.js → clio-M2KGYUFZ.js} +2 -2
- package/dist/{code-nav-AXCXSBHX.js → code-nav-GQNL7XA6.js} +5 -5
- package/dist/codewiki/build-worker.js +4 -4
- package/dist/{components-KELWS457.js → components-5TTYYX6G.js} +3 -3
- package/dist/{config-OEBMIN2U.js → config-XUUYQIWO.js} +27 -25
- package/dist/{configure-PUQOSIXQ.js → configure-IHJ7YOMV.js} +7 -7
- package/dist/{context-URSXPBCK.js → context-74JLXAWD.js} +12 -12
- package/dist/{context-MGSE4Z2T.js → context-75MIWW3U.js} +24 -22
- package/dist/{context-EKDCKUUZ.js → context-ZQ7SIFJV.js} +8 -7
- package/dist/{context-clear-KDAJRNUK.js → context-clear-GYKWNUML.js} +24 -22
- package/dist/{context-index-BZ4UYMTC.js → context-index-SSR5ECNE.js} +3 -3
- package/dist/{context-working-set-SBKMPPI2.js → context-working-set-UX5KEP4J.js} +11 -10
- package/dist/{dispatch-runner-MSWN72NK.js → dispatch-runner-GIJBHNFL.js} +21 -20
- package/dist/{docs-2C2LTVT2.js → docs-6FZSCG5B.js} +3 -3
- package/dist/{doctor-7BSE27PJ.js → doctor-SVJ5BZCW.js} +4 -4
- package/dist/{eval-IZGDOO4H.js → eval-CG6LLBLD.js} +47 -232
- package/dist/{evidence-SR7WXB5B.js → evidence-ZYFIEN42.js} +19 -18
- package/dist/{evolve-K7VE2CBX.js → evolve-QGEXEMDW.js} +19 -18
- package/dist/{extensions-QVDOHDGJ.js → extensions-ADGNCJJD.js} +3 -3
- package/dist/{fleet-7XMJNQNF.js → fleet-S5R4ZOQY.js} +49 -30
- package/dist/{fleet-preflight-AQNAH644.js → fleet-preflight-BHSNPBMH.js} +2 -2
- package/dist/{init-JGNPAYXT.js → init-5DRU55YR.js} +31 -29
- package/dist/memory-7YKKR6UC.js +467 -0
- package/dist/{models-ZMMLFJNN.js → models-ZPOLRU2C.js} +10 -10
- package/dist/{monitor-2F3T5KHP.js → monitor-US5F5YGZ.js} +33 -18
- package/dist/{orchestrator-ORHT43JB.js → orchestrator-E2AL4T5N.js} +1092 -659
- package/dist/{paths-UXLN5YYZ.js → paths-E7KYAQWE.js} +3 -3
- package/dist/{reset-NXGTYNUO.js → reset-KZ652EK6.js} +3 -3
- package/dist/{run-RF4WJGMT.js → run-SRNBKDWD.js} +52 -40
- package/dist/{share-UT3W6E4M.js → share-CGZE33UP.js} +3 -3
- package/dist/{skills-PSACKC5Q.js → skills-S2X4DLY5.js} +4 -4
- package/dist/{skills-eval-WJSI55RZ.js → skills-eval-W2GGIC4R.js} +19 -18
- package/dist/{targets-PIIRAOYS.js → targets-54SWINWB.js} +14 -12
- package/dist/{terminal-lease-ULWXWNVY.js → terminal-lease-SAIF2OGY.js} +5 -4
- package/dist/{uninstall-FZCQCDKC.js → uninstall-BVLWXKBT.js} +3 -3
- package/dist/{upgrade-346TZ6AV.js → upgrade-JKAR27XC.js} +8 -8
- package/dist/{usage-6KKXR32N.js → usage-MSAWCLX4.js} +60 -27
- package/dist/{verifiers-4UUM6TEE.js → verifiers-NCBTHHN2.js} +60 -54
- package/dist/{wiki-generate-7STOCIFZ.js → wiki-generate-GUSOQ6ZP.js} +30 -28
- package/dist/worker/entry.js +69 -58
- package/dist/{workspace-G4ZWUIPR.js → workspace-ZJ6BFM3Q.js} +4 -4
- package/docs/README.md +3 -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 +1 -1
- package/docs/built-in-agents.md +1 -1
- package/docs/capacity-and-scheduling.md +1 -1
- package/docs/commands-and-modes.md +9 -7
- package/docs/configuration-and-targets.md +12 -1
- package/docs/context-engine.md +4 -2
- package/docs/context-working-set.md +4 -4
- package/docs/development-pipeline.md +1 -1
- package/docs/documentation-coverage.md +3 -3
- package/docs/documentation-guide.md +2 -2
- package/docs/eval-runner.md +1 -1
- package/docs/evals-internal.md +4 -45
- package/docs/evidence-and-memory.md +67 -7
- package/docs/evolution.md +1 -1
- package/docs/exit-codes-and-output.md +1 -1
- package/docs/extensions-and-sharing.md +2 -2
- package/docs/fleet-dispatch.md +28 -2
- package/docs/installation-and-lifecycle.md +2 -2
- package/docs/middleware-and-components.md +19 -2
- package/docs/model-catalog.md +1 -1
- package/docs/observability.md +3 -3
- 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 +38 -35
- package/docs/safety-model.md +29 -7
- package/docs/scientific-validation.md +3 -3
- package/docs/session-lifecycle.md +1 -1
- package/docs/skills-marketplace.md +1 -1
- package/docs/tool-usage.md +2 -2
- 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 +1 -1
- package/package.json +7 -4
- package/src/cli/agents.ts +2 -3
- package/src/cli/argv.ts +14 -1
- package/src/cli/fleet.ts +15 -0
- package/src/cli/index.ts +1 -1
- 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/targets.ts +12 -3
- package/src/cli/usage.ts +55 -7
- package/src/core/bus-events.ts +3 -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 +1 -1
- package/src/domains/agents/catalog.ts +5 -4
- package/src/domains/agents/recipe.ts +54 -14
- package/src/domains/agents/result-contract.ts +7 -4
- 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/budget-envelope.ts +396 -0
- package/src/domains/dispatch/contract.ts +2 -0
- package/src/domains/dispatch/extension.ts +81 -27
- package/src/domains/dispatch/orphan-recovery.ts +1 -0
- package/src/domains/dispatch/receipt-integrity.ts +4 -0
- package/src/domains/dispatch/state.ts +1 -0
- package/src/domains/dispatch/types.ts +10 -3
- package/src/domains/dispatch/validation.ts +14 -0
- 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/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/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/observability/contract.ts +6 -1
- package/src/domains/observability/cost.ts +20 -4
- package/src/domains/observability/extension.ts +2 -2
- 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/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/history.ts +68 -19
- package/src/domains/session/usage.ts +24 -7
- 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 +18 -1
- package/src/interactive/chat-loop-messages.ts +22 -0
- package/src/interactive/chat-loop.ts +13 -0
- package/src/interactive/chat-renderer.ts +19 -3
- package/src/interactive/clio-editor.ts +44 -7
- package/src/interactive/context-overlay.ts +43 -5
- package/src/interactive/cost-overlay.ts +39 -8
- package/src/interactive/dispatch-board.ts +212 -35
- package/src/interactive/footer/widgets.ts +13 -0
- package/src/interactive/interactive-application.ts +6 -1
- package/src/interactive/interactive-input-runtime.ts +11 -1
- package/src/interactive/interactive-presentation.ts +11 -1
- package/src/interactive/memory-overlay.ts +89 -4
- package/src/interactive/overlay-ask-user-lifecycle.ts +1 -1
- package/src/interactive/overlay-frame.ts +5 -2
- package/src/interactive/overlay-general-openers.ts +40 -1
- package/src/interactive/overlay-key-routing.ts +41 -1
- package/src/interactive/overlay-lifecycle.ts +11 -4
- package/src/interactive/overlay-permission-lifecycle.ts +23 -8
- 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/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/slash-commands.ts +2 -2
- 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/turn-context.ts +96 -23
- package/src/interactive/turn-middleware.ts +1 -0
- package/src/interactive/turn-runtime.ts +37 -8
- package/src/interactive/turn-state.ts +3 -0
- package/src/interactive/worker-progress.ts +440 -0
- package/src/interactive/worker-stream.ts +51 -110
- package/src/tools/agent-tools.ts +28 -3
- package/src/tools/ask-user.ts +21 -1
- package/src/tools/context/index.ts +2 -2
- package/src/tools/dispatch-arguments.ts +8 -0
- package/src/tools/dispatch-event-text.ts +19 -0
- package/src/tools/dispatch.ts +24 -1
- package/src/tools/monitor.ts +15 -0
- 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/verify/authoring.ts +55 -54
- package/src/tools/worker-evidence.ts +19 -0
- package/src/worker/spec-contract.ts +43 -3
- package/dist/chunk-EFADSJET.js +0 -18
- package/dist/memory-4ALKDJ4Q.js +0 -246
- package/src/domains/eval/metrics/chaos-stream.ts +0 -93
package/src/engine/acp/server.ts
CHANGED
|
@@ -5,12 +5,18 @@ import { BusChannels, type LoopBlockedPayload } from "../../core/bus-events.js";
|
|
|
5
5
|
import { DEFAULT_DELEGATION_PERMISSION_TIMEOUT_MS } from "../../core/defaults.js";
|
|
6
6
|
import type { SafeEventBus } from "../../core/event-bus.js";
|
|
7
7
|
import { MAX_TIMER_DELAY_MS } from "../../core/timers.js";
|
|
8
|
+
import { ToolNames } from "../../core/tool-names.js";
|
|
8
9
|
import type { ProvidersContract } from "../../domains/providers/contract.js";
|
|
9
10
|
import { isOrchestratorEligibleRuntime } from "../../domains/providers/eligibility.js";
|
|
10
11
|
import { type AutonomyLevel, DEFAULT_AUTONOMY_LEVEL } from "../../domains/safety/autonomy.js";
|
|
12
|
+
import {
|
|
13
|
+
classifyDecisionPresentation,
|
|
14
|
+
decisionFactsForPermission,
|
|
15
|
+
} from "../../domains/safety/decision-presentation.js";
|
|
11
16
|
import type { SessionContract, SessionMeta } from "../../domains/session/contract.js";
|
|
12
17
|
import type { MessageEntry, SessionEntry } from "../../domains/session/entries.js";
|
|
13
18
|
import { filterEntriesToActivePath } from "../../domains/session/tree/active-path.js";
|
|
19
|
+
import { askUserExposure } from "../../tools/ask-user.js";
|
|
14
20
|
import type { ToolRegistry } from "../../tools/registry.js";
|
|
15
21
|
import { toolResultPresentationText } from "../../tools/result-disposition.js";
|
|
16
22
|
import type { AgentMessage } from "../types.js";
|
|
@@ -1265,6 +1271,19 @@ function installPermissionBridge(input: {
|
|
|
1265
1271
|
const queuedRequestDetails = new Map<string, { tool: string; actionClass: string }>();
|
|
1266
1272
|
const unregister = input.toolRegistry.onPermissionRequired((call, decision, meta) => {
|
|
1267
1273
|
if (queuedRequestIds.has(meta.requestId)) return;
|
|
1274
|
+
const presentation = classifyDecisionPresentation(
|
|
1275
|
+
decisionFactsForPermission({
|
|
1276
|
+
tool: call.tool,
|
|
1277
|
+
actionClass: decision.classification.actionClass,
|
|
1278
|
+
axis: meta.axis.startsWith("net:")
|
|
1279
|
+
? { kind: "safety-net", ruleId: meta.axis.slice("net:".length) || "unknown" }
|
|
1280
|
+
: { kind: "autonomy", level: meta.axis.slice("autonomy:".length) || DEFAULT_AUTONOMY_LEVEL },
|
|
1281
|
+
origin: { kind: "main" },
|
|
1282
|
+
...(call.tool === ToolNames.AskUser ? { exposure: askUserExposure(call.args) } : {}),
|
|
1283
|
+
}),
|
|
1284
|
+
);
|
|
1285
|
+
const approveAction = presentation.requiredActions.find((action) => action.id === "approve-once");
|
|
1286
|
+
const denyAction = presentation.requiredActions.find((action) => action.id === "deny");
|
|
1268
1287
|
queuedRequestIds.add(meta.requestId);
|
|
1269
1288
|
queuedRequestDetails.set(meta.requestId, {
|
|
1270
1289
|
tool: call.tool,
|
|
@@ -1381,8 +1400,16 @@ function installPermissionBridge(input: {
|
|
|
1381
1400
|
...(snapshot.locations !== undefined ? { locations: snapshot.locations } : {}),
|
|
1382
1401
|
},
|
|
1383
1402
|
options: [
|
|
1384
|
-
{
|
|
1385
|
-
|
|
1403
|
+
{
|
|
1404
|
+
optionId: "allow-once",
|
|
1405
|
+
name: approveAction?.label ?? "Approve once",
|
|
1406
|
+
kind: "allow_once",
|
|
1407
|
+
},
|
|
1408
|
+
{
|
|
1409
|
+
optionId: "reject-once",
|
|
1410
|
+
name: denyAction?.label ?? "Deny this request",
|
|
1411
|
+
kind: "reject_once",
|
|
1412
|
+
},
|
|
1386
1413
|
],
|
|
1387
1414
|
},
|
|
1388
1415
|
input.permissionTimeoutMs,
|
|
@@ -130,6 +130,25 @@ async function loadOwnedInstance(
|
|
|
130
130
|
return instanceId;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Residency runs before every request, and the same co-residency facts held
|
|
135
|
+
* every turn, so the peer warning printed once per turn (issue #185). Each
|
|
136
|
+
* distinct fact is said once per process; a new target, model, or peer set is
|
|
137
|
+
* a new fact.
|
|
138
|
+
*/
|
|
139
|
+
const announcedResidencyFacts = new Set<string>();
|
|
140
|
+
|
|
141
|
+
function emitResidencyNoticeOnce(key: string, notice: Parameters<typeof emitResidencyNotice>[0]): void {
|
|
142
|
+
if (announcedResidencyFacts.has(key)) return;
|
|
143
|
+
announcedResidencyFacts.add(key);
|
|
144
|
+
emitResidencyNotice(notice);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Test seam: forget which residency facts were announced. */
|
|
148
|
+
export function resetAnnouncedResidencyFacts(): void {
|
|
149
|
+
announcedResidencyFacts.clear();
|
|
150
|
+
}
|
|
151
|
+
|
|
133
152
|
export async function ensureLmStudioResidency(
|
|
134
153
|
model: Model<"openai-completions">,
|
|
135
154
|
options: { apiKey?: string; signal?: AbortSignal } = {},
|
|
@@ -155,7 +174,7 @@ export async function ensureLmStudioResidency(
|
|
|
155
174
|
);
|
|
156
175
|
}
|
|
157
176
|
if (resolution.wireModelId !== model.id) {
|
|
158
|
-
|
|
177
|
+
emitResidencyNoticeOnce(`resolved|${info.targetId}|${model.id}|${resolution.wireModelId}`, {
|
|
159
178
|
kind: "co-resident",
|
|
160
179
|
level: "info",
|
|
161
180
|
targetId: info.targetId,
|
|
@@ -166,14 +185,16 @@ export async function ensureLmStudioResidency(
|
|
|
166
185
|
});
|
|
167
186
|
}
|
|
168
187
|
if (resolution.peerTargets.length > 0) {
|
|
169
|
-
|
|
188
|
+
const peers = resolution.peerTargets.join(", ");
|
|
189
|
+
const requested = resolution.wireModelId === model.id ? "" : ` (requested '${model.id}')`;
|
|
190
|
+
emitResidencyNoticeOnce(`peer|${info.targetId}|${model.id}|${resolution.wireModelId}|${peers}`, {
|
|
170
191
|
kind: "co-resident",
|
|
171
192
|
level: "warning",
|
|
172
193
|
targetId: info.targetId,
|
|
173
194
|
runtimeId: "lmstudio",
|
|
174
195
|
model: resolution.wireModelId,
|
|
175
|
-
message: `LM Studio instance '${resolution.wireModelId}' is also loaded on ${
|
|
176
|
-
detail: {
|
|
196
|
+
message: `LM Studio instance '${resolution.wireModelId}'${requested} is also loaded on ${peers}; a request may be served by that LM Link peer, and the footer and usage ledger name the id that answered when it differs.`,
|
|
197
|
+
detail: { requestedModel: model.id, wireModel: resolution.wireModelId, peerTargets: peers },
|
|
177
198
|
});
|
|
178
199
|
}
|
|
179
200
|
if (!load || Object.keys(load).length === 0 || resolution.instance) return resolution.wireModelId;
|
|
@@ -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: {
|