@rulvar/core 1.63.0 → 1.65.0
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/dist/index.d.ts +95 -4
- package/dist/index.js +2345 -2262
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -12246,6 +12246,20 @@ const DEFAULT_MAX_DEPTH = 1;
|
|
|
12246
12246
|
const MAX_DEPTH_CEILING = 4;
|
|
12247
12247
|
const DEFAULT_MAX_CHILDREN_PER_NODE = 16;
|
|
12248
12248
|
const DEFAULT_CHILD_BUDGET_FRACTION = .3;
|
|
12249
|
+
/**
|
|
12250
|
+
* The ONE dispatch-projection reserve formula (the 1.63.0 experiment
|
|
12251
|
+
* review, P0.3): the spawn's declared estimate (a spawn tool has no
|
|
12252
|
+
* per-call estCost channel, so the estimate is the agentType profile's)
|
|
12253
|
+
* or the flat default, clamped by the explicit child budget when one
|
|
12254
|
+
* exists. This is the reserve the embedded layer-2 gate evaluates a
|
|
12255
|
+
* spawn_agent call against BEFORE dispatch, and the number
|
|
12256
|
+
* preflightEstimate projects for the same gate, so the linter and the
|
|
12257
|
+
* runtime cannot drift: both call this function.
|
|
12258
|
+
*/
|
|
12259
|
+
function dispatchProjectionReserveUsd(spec, flatReserveUsd) {
|
|
12260
|
+
const base = spec.estCostUsd ?? flatReserveUsd;
|
|
12261
|
+
return spec.budgetUsd === void 0 ? base : Math.min(base, spec.budgetUsd);
|
|
12262
|
+
}
|
|
12249
12263
|
/** Nesting depth of a child scope: its workflow, agent, and plan-node segments. */
|
|
12250
12264
|
function spawnDepthOf(childScope) {
|
|
12251
12265
|
return parseScopePath(childScope).filter((segment) => segment.kind === "workflow" || segment.kind === "agent" || segment.kind === "plan-node").length;
|
|
@@ -12390,11 +12404,13 @@ var AdmissionController = class {
|
|
|
12390
12404
|
* never materializes as an account and must not shrink the
|
|
12391
12405
|
* projection. The token-count-priced estimate of ctx.agent is
|
|
12392
12406
|
* unreachable here (async); a divergence there lands as a journaled
|
|
12393
|
-
* dispatch rejection instead of a strand.
|
|
12407
|
+
* dispatch rejection instead of a strand. Delegates to the exported
|
|
12408
|
+
* {@link dispatchProjectionReserveUsd} so the live gate and
|
|
12409
|
+
* preflightEstimate share ONE formula (the 1.63.0 experiment review,
|
|
12410
|
+
* P0.3).
|
|
12394
12411
|
*/
|
|
12395
12412
|
projectedDispatchReserveUsd(spec) {
|
|
12396
|
-
|
|
12397
|
-
return spec.budgetUsd === void 0 ? base : Math.min(base, spec.budgetUsd);
|
|
12413
|
+
return dispatchProjectionReserveUsd(spec, this.flatReserveUsd);
|
|
12398
12414
|
}
|
|
12399
12415
|
admit(spec, options) {
|
|
12400
12416
|
const commitReserve = options?.commitReserve ?? true;
|
|
@@ -12565,1996 +12581,924 @@ var AdmissionController = class {
|
|
|
12565
12581
|
}
|
|
12566
12582
|
};
|
|
12567
12583
|
//#endregion
|
|
12568
|
-
//#region src/
|
|
12584
|
+
//#region src/model/profile-card.ts
|
|
12585
|
+
function toolNamesOf(profile) {
|
|
12586
|
+
return (profile.tools ?? []).map((entry) => {
|
|
12587
|
+
if (typeof entry === "string") return `${entry} (registered toolset)`;
|
|
12588
|
+
if ("kind" in entry && entry.kind === "tool") return entry.name;
|
|
12589
|
+
return `${entry.id}:* (tool source)`;
|
|
12590
|
+
});
|
|
12591
|
+
}
|
|
12569
12592
|
/**
|
|
12570
|
-
*
|
|
12571
|
-
*
|
|
12572
|
-
*
|
|
12573
|
-
*
|
|
12574
|
-
*
|
|
12593
|
+
* Renders the registry into the shared agent vocabulary card. Sorted,
|
|
12594
|
+
* deterministic, byte-stable; an empty registry renders explicitly so
|
|
12595
|
+
* the planner never guesses at unregistered agentTypes. When the engine
|
|
12596
|
+
* registers toolsets, their names render as a closing line (v1.17.0
|
|
12597
|
+
* review P1-3): those are the ONLY values valid as string entries of a
|
|
12598
|
+
* tools option, so the planner never invents a registry name.
|
|
12575
12599
|
*/
|
|
12576
|
-
|
|
12577
|
-
const
|
|
12578
|
-
|
|
12579
|
-
|
|
12580
|
-
|
|
12581
|
-
|
|
12582
|
-
|
|
12583
|
-
* `limit` must be a positive integer: anything else (NaN included) is
|
|
12584
|
-
* a typed ConfigError. Before this gate a NaN limit made
|
|
12585
|
-
* `active < limit` permanently false, so the first acquire queued
|
|
12586
|
-
* forever and the run could not settle, not even through cancel()
|
|
12587
|
-
* (v1.34.0 review P2-4). Unlimited is expressed by not constructing a
|
|
12588
|
-
* semaphore, never by a sentinel limit.
|
|
12589
|
-
*/
|
|
12590
|
-
constructor(limit) {
|
|
12591
|
-
requirePositiveInteger(limit, "Semaphore limit");
|
|
12592
|
-
this.limit = limit;
|
|
12593
|
-
}
|
|
12594
|
-
get pending() {
|
|
12595
|
-
return this.waiters.length;
|
|
12596
|
-
}
|
|
12597
|
-
/**
|
|
12598
|
-
* Acquires a slot, resolving in FIFO order. `onQueued` fires only when
|
|
12599
|
-
* the caller actually has to wait (feeds the agent:queued event).
|
|
12600
|
-
* An aborted `signal` releases the caller from the queue without a
|
|
12601
|
-
* slot: the returned release is a no-op, the remaining waiters keep
|
|
12602
|
-
* their FIFO positions, and the caller proceeds to observe its own
|
|
12603
|
-
* aborted signal (the model layers refuse dispatch under an aborted
|
|
12604
|
-
* signal, so no provider call follows). Cancellation can therefore
|
|
12605
|
-
* always drain a queued run (v1.34.0 review P2-4).
|
|
12606
|
-
*/
|
|
12607
|
-
async acquire(onQueued, signal) {
|
|
12608
|
-
if (this.active < this.limit) {
|
|
12609
|
-
this.active += 1;
|
|
12610
|
-
return () => this.release();
|
|
12611
|
-
}
|
|
12612
|
-
if (signal?.aborted === true) return () => void 0;
|
|
12613
|
-
onQueued?.();
|
|
12614
|
-
const waiter = {
|
|
12615
|
-
resolve: () => void 0,
|
|
12616
|
-
aborted: false
|
|
12617
|
-
};
|
|
12618
|
-
const wait = new Promise((resolve) => {
|
|
12619
|
-
waiter.resolve = resolve;
|
|
12620
|
-
});
|
|
12621
|
-
this.waiters.push(waiter);
|
|
12622
|
-
let onAbort;
|
|
12623
|
-
if (signal !== void 0) {
|
|
12624
|
-
onAbort = () => {
|
|
12625
|
-
const index = this.waiters.indexOf(waiter);
|
|
12626
|
-
if (index === -1) return;
|
|
12627
|
-
this.waiters.splice(index, 1);
|
|
12628
|
-
waiter.aborted = true;
|
|
12629
|
-
waiter.resolve();
|
|
12630
|
-
};
|
|
12631
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
12632
|
-
}
|
|
12633
|
-
try {
|
|
12634
|
-
await wait;
|
|
12635
|
-
} finally {
|
|
12636
|
-
if (signal !== void 0 && onAbort !== void 0) signal.removeEventListener("abort", onAbort);
|
|
12637
|
-
}
|
|
12638
|
-
if (waiter.aborted) return () => void 0;
|
|
12639
|
-
this.active += 1;
|
|
12640
|
-
return () => this.release();
|
|
12641
|
-
}
|
|
12642
|
-
async withSlot(fn, onQueued, signal) {
|
|
12643
|
-
const release = await this.acquire(onQueued, signal);
|
|
12644
|
-
try {
|
|
12645
|
-
return await fn();
|
|
12646
|
-
} finally {
|
|
12647
|
-
release();
|
|
12648
|
-
}
|
|
12600
|
+
function profileCard(profiles, toolsets) {
|
|
12601
|
+
const toolsetNames = Object.keys(toolsets ?? {}).sort();
|
|
12602
|
+
const toolsetsLine = toolsetNames.length === 0 ? void 0 : `Registered toolsets (valid string entries of a tools option): ${toolsetNames.join(", ")}.`;
|
|
12603
|
+
const names = Object.keys(profiles ?? {}).sort();
|
|
12604
|
+
if (profiles === void 0 || names.length === 0) {
|
|
12605
|
+
const empty = "Agent profiles: none registered. Calls take no agentType.";
|
|
12606
|
+
return toolsetsLine === void 0 ? empty : `${empty}\n${toolsetsLine}`;
|
|
12649
12607
|
}
|
|
12650
|
-
|
|
12651
|
-
|
|
12652
|
-
const
|
|
12653
|
-
|
|
12608
|
+
const lines = ["Agent profiles (agentType values):"];
|
|
12609
|
+
for (const name of names) {
|
|
12610
|
+
const profile = profiles[name];
|
|
12611
|
+
const description = profile.description ?? "no description";
|
|
12612
|
+
lines.push(`- ${name}: ${description}`);
|
|
12613
|
+
const toolNames = toolNamesOf(profile);
|
|
12614
|
+
if (toolNames.length > 0) lines.push(` tools: ${toolNames.join(", ")}`);
|
|
12615
|
+
if (profile.taskClass !== void 0) lines.push(` taskClass: ${profile.taskClass}`);
|
|
12616
|
+
if (profile.estCost !== void 0) lines.push(` estCost: ${profile.estCost.toFixed(2)} USD`);
|
|
12617
|
+
if (profile.escalation !== void 0) lines.push(` escalation: flavor ${profile.escalation.flavor ?? "A"} (opt-in)`);
|
|
12654
12618
|
}
|
|
12655
|
-
|
|
12656
|
-
|
|
12657
|
-
//#region src/engine/preflight.ts
|
|
12658
|
-
const ANY_TOOL = "(any)";
|
|
12659
|
-
function resolveServing(spec) {
|
|
12660
|
-
if (spec === void 0) return;
|
|
12661
|
-
if (typeof spec === "string") return spec;
|
|
12662
|
-
if ("model" in spec) return spec.model;
|
|
12663
|
-
return spec.ladder.rungs[spec.ladder.startTier]?.model;
|
|
12619
|
+
if (toolsetsLine !== void 0) lines.push(toolsetsLine);
|
|
12620
|
+
return lines.join("\n");
|
|
12664
12621
|
}
|
|
12622
|
+
//#endregion
|
|
12623
|
+
//#region src/engine/internal.ts
|
|
12624
|
+
/** Registered by createCtx; keyed by the ctx object identity. */
|
|
12625
|
+
const ctxRuntimes = /* @__PURE__ */ new WeakMap();
|
|
12665
12626
|
/**
|
|
12666
|
-
*
|
|
12667
|
-
*
|
|
12668
|
-
*
|
|
12669
|
-
*
|
|
12670
|
-
*
|
|
12627
|
+
* Internal AgentOpts channel (M6-T07): agentImpl reports the agent
|
|
12628
|
+
* dispatch seq (the spawn handle) through this symbol-keyed callback on
|
|
12629
|
+
* the running append, on a dangling redispatch, AND on the replay
|
|
12630
|
+
* branch, so the orchestrator learns handles that are stable across
|
|
12631
|
+
* resume. Never part of the public AgentOpts surface.
|
|
12671
12632
|
*/
|
|
12672
|
-
|
|
12673
|
-
|
|
12674
|
-
|
|
12675
|
-
|
|
12676
|
-
|
|
12677
|
-
|
|
12678
|
-
|
|
12679
|
-
|
|
12680
|
-
|
|
12681
|
-
|
|
12682
|
-
|
|
12683
|
-
|
|
12684
|
-
|
|
12685
|
-
|
|
12686
|
-
|
|
12687
|
-
|
|
12688
|
-
|
|
12689
|
-
|
|
12690
|
-
|
|
12691
|
-
|
|
12692
|
-
|
|
12693
|
-
|
|
12694
|
-
|
|
12695
|
-
|
|
12696
|
-
|
|
12697
|
-
|
|
12698
|
-
|
|
12699
|
-
});
|
|
12700
|
-
continue;
|
|
12701
|
-
}
|
|
12702
|
-
const min = terms.reduce((best, term) => term.ceiling < best.ceiling ? term : best);
|
|
12703
|
-
rows.push({
|
|
12704
|
-
tool,
|
|
12705
|
-
ceiling: min.ceiling,
|
|
12706
|
-
boundBy: min.boundBy
|
|
12707
|
-
});
|
|
12708
|
-
}
|
|
12709
|
-
return rows;
|
|
12633
|
+
const kOnRunning = Symbol("rulvar.onRunning");
|
|
12634
|
+
/**
|
|
12635
|
+
* Internal AgentOpts channel (M6-T07): names the terminal tool whose
|
|
12636
|
+
* accepted call ends the loop with status ok (the orchestrator finish
|
|
12637
|
+
* tool), plus the optional host validation hook over the accepted call
|
|
12638
|
+
* (the RV-204 finish validators). Never part of the public AgentOpts
|
|
12639
|
+
* surface.
|
|
12640
|
+
*/
|
|
12641
|
+
const kTerminalTool = Symbol("rulvar.terminalTool");
|
|
12642
|
+
/**
|
|
12643
|
+
* Internal AgentOpts channel (M7-T08): a transcript checkpoint ref the
|
|
12644
|
+
* fresh dispatch boots from (park/unpark continuation and the DEF-5
|
|
12645
|
+
* graft boot). Dangling redispatch checkpoints take precedence.
|
|
12646
|
+
*/
|
|
12647
|
+
const kBootCheckpoint = Symbol("rulvar.bootCheckpoint");
|
|
12648
|
+
/**
|
|
12649
|
+
* Internal AgentOpts channel: marks the orchestrator forced-finish
|
|
12650
|
+
* dispatch, whose spend draws from the released finalize reserve
|
|
12651
|
+
* (DEF-7). Settlement stamps the flag into the terminal's cost
|
|
12652
|
+
* attribution so the journal fold reproduces reserveUsedUsd.
|
|
12653
|
+
*/
|
|
12654
|
+
const kFinalizeReserve = Symbol("rulvar.finalizeReserve");
|
|
12655
|
+
/** Typed accessor used by the in-package consumers. */
|
|
12656
|
+
function runtimeOf(ctx) {
|
|
12657
|
+
const runtime = ctxRuntimes.get(ctx);
|
|
12658
|
+
if (runtime === void 0) throw new Error("ctx runtime missing: the ctx value was not created by createCtx (engine run context)");
|
|
12659
|
+
return runtime;
|
|
12710
12660
|
}
|
|
12711
|
-
|
|
12712
|
-
|
|
12713
|
-
|
|
12714
|
-
|
|
12715
|
-
|
|
12716
|
-
|
|
12661
|
+
//#endregion
|
|
12662
|
+
//#region src/engine/spawn-events.ts
|
|
12663
|
+
function emitSpawnAdmitted(events, input) {
|
|
12664
|
+
events.emit({
|
|
12665
|
+
type: "spawn:admitted",
|
|
12666
|
+
entryRef: input.entryRef,
|
|
12667
|
+
verdict: input.verdict,
|
|
12668
|
+
agentType: input.agentType,
|
|
12669
|
+
logicalTaskId: input.logicalTaskId,
|
|
12670
|
+
...input.spawnUnitsAfter === void 0 ? {} : { spawnUnitsAfter: input.spawnUnitsAfter }
|
|
12671
|
+
}, input.spanId, input.replayed);
|
|
12717
12672
|
}
|
|
12673
|
+
function emitSpawnRejected(events, input) {
|
|
12674
|
+
events.emit({
|
|
12675
|
+
type: "spawn:rejected",
|
|
12676
|
+
...input.entryRef === void 0 ? {} : { entryRef: input.entryRef },
|
|
12677
|
+
code: input.code,
|
|
12678
|
+
agentType: input.agentType
|
|
12679
|
+
}, input.spanId, input.replayed);
|
|
12680
|
+
}
|
|
12681
|
+
//#endregion
|
|
12682
|
+
//#region src/l0/long-timer.ts
|
|
12718
12683
|
/**
|
|
12719
|
-
*
|
|
12720
|
-
*
|
|
12721
|
-
*
|
|
12722
|
-
*
|
|
12723
|
-
*
|
|
12724
|
-
*
|
|
12684
|
+
* Sliced timers for absolute wall-clock deadlines (v1.34.0 review P2-2).
|
|
12685
|
+
*
|
|
12686
|
+
* Node clamps a setTimeout delay above 2147483647 ms (about 24.8 days)
|
|
12687
|
+
* to 1 ms, so a naive timer for a far-future deadline fires immediately.
|
|
12688
|
+
* setLongTimeout never hands Node more than one MAX_TIMER_DELAY_MS
|
|
12689
|
+
* slice, re-checks the wall clock when a slice fires, and re-arms until
|
|
12690
|
+
* the clock actually reaches the deadline: firing a slice is never taken
|
|
12691
|
+
* as proof the deadline arrived. A deadline already in the past fires on
|
|
12692
|
+
* the next macrotask (delay 0), matching the plain setTimeout behavior
|
|
12693
|
+
* the callers had for near deadlines.
|
|
12725
12694
|
*/
|
|
12726
|
-
|
|
12727
|
-
|
|
12728
|
-
|
|
12729
|
-
|
|
12730
|
-
|
|
12731
|
-
|
|
12732
|
-
|
|
12733
|
-
|
|
12734
|
-
|
|
12695
|
+
/**
|
|
12696
|
+
* Schedules `onDue` for the absolute wall-clock instant `dueAtMs` as
|
|
12697
|
+
* reported by `now` (default Date.now), slicing delays beyond the Node
|
|
12698
|
+
* timer maximum.
|
|
12699
|
+
*/
|
|
12700
|
+
function setLongTimeout(onDue, dueAtMs, now = Date.now) {
|
|
12701
|
+
let handle;
|
|
12702
|
+
let cancelled = false;
|
|
12703
|
+
const arm = () => {
|
|
12704
|
+
const remaining = Math.max(0, dueAtMs - now());
|
|
12705
|
+
handle = setTimeout(() => {
|
|
12706
|
+
if (cancelled) return;
|
|
12707
|
+
if (now() >= dueAtMs) {
|
|
12708
|
+
onDue();
|
|
12709
|
+
return;
|
|
12710
|
+
}
|
|
12711
|
+
arm();
|
|
12712
|
+
}, Math.min(remaining, MAX_TIMER_DELAY_MS));
|
|
12735
12713
|
};
|
|
12736
|
-
|
|
12737
|
-
|
|
12738
|
-
|
|
12739
|
-
|
|
12714
|
+
arm();
|
|
12715
|
+
return { cancel: () => {
|
|
12716
|
+
cancelled = true;
|
|
12717
|
+
if (handle !== void 0) clearTimeout(handle);
|
|
12718
|
+
} };
|
|
12719
|
+
}
|
|
12720
|
+
//#endregion
|
|
12721
|
+
//#region src/runtime/permission-chain.ts
|
|
12722
|
+
/**
|
|
12723
|
+
* The layered permission chain (M3-T03): the single approval surface for
|
|
12724
|
+
* every tool dispatch, regardless of tool origin. The order is fixed and
|
|
12725
|
+
* normative: hooks -> deny rules -> ask rules -> canUseTool -> terminal
|
|
12726
|
+
* default (allow unless needsApproval, then ask). Evaluation is
|
|
12727
|
+
* short-circuit; unconfigured layers are skipped. Rules never yield
|
|
12728
|
+
* allow: allow is only ever falling through to canUseTool or the
|
|
12729
|
+
* terminal default.
|
|
12730
|
+
*
|
|
12731
|
+
* Full contract: https://docs.rulvar.com/guide/tools.
|
|
12732
|
+
* Risk presets, the argv shell matcher, domain rules, and the
|
|
12733
|
+
* audit/dry-run surface land in M5.
|
|
12734
|
+
*/
|
|
12735
|
+
/**
|
|
12736
|
+
* Merges the engine-wide config and the profile config into one chain.
|
|
12737
|
+
* Layers concatenate engine-first; since rules only deny or ask, ordering
|
|
12738
|
+
* within a layer cannot change the verdict. The
|
|
12739
|
+
* profile's canUseTool wins over the engine's (a single slot by
|
|
12740
|
+
* construction). A declared preset compiles INTO the same layers, after
|
|
12741
|
+
* the host-authored rules, never as a fifth layer (M5-T05).
|
|
12742
|
+
*/
|
|
12743
|
+
function compilePermissionChain(engine, profile) {
|
|
12744
|
+
const preset = profile?.preset === void 0 ? {
|
|
12745
|
+
deny: [],
|
|
12746
|
+
ask: []
|
|
12747
|
+
} : compilePermissionPreset(profile.preset);
|
|
12748
|
+
const deny = [
|
|
12749
|
+
...engine?.deny ?? [],
|
|
12750
|
+
...profile?.deny ?? [],
|
|
12751
|
+
...preset.deny
|
|
12752
|
+
];
|
|
12753
|
+
const ask = [
|
|
12754
|
+
...engine?.ask ?? [],
|
|
12755
|
+
...profile?.ask ?? [],
|
|
12756
|
+
...preset.ask
|
|
12757
|
+
];
|
|
12758
|
+
const canUseTool = profile?.canUseTool ?? engine?.canUseTool;
|
|
12759
|
+
return {
|
|
12760
|
+
hooks: [...engine?.hooks ?? [], ...profile?.hooks ?? []],
|
|
12761
|
+
deny,
|
|
12762
|
+
ask,
|
|
12763
|
+
...canUseTool === void 0 ? {} : { canUseTool }
|
|
12740
12764
|
};
|
|
12741
|
-
|
|
12742
|
-
|
|
12743
|
-
|
|
12744
|
-
|
|
12745
|
-
|
|
12746
|
-
|
|
12747
|
-
|
|
12748
|
-
|
|
12749
|
-
|
|
12750
|
-
|
|
12751
|
-
|
|
12752
|
-
|
|
12753
|
-
|
|
12754
|
-
|
|
12755
|
-
|
|
12756
|
-
|
|
12757
|
-
|
|
12758
|
-
|
|
12759
|
-
const
|
|
12760
|
-
|
|
12761
|
-
|
|
12762
|
-
|
|
12763
|
-
|
|
12764
|
-
|
|
12765
|
-
|
|
12766
|
-
|
|
12767
|
-
|
|
12768
|
-
|
|
12769
|
-
|
|
12770
|
-
|
|
12771
|
-
|
|
12772
|
-
|
|
12773
|
-
|
|
12774
|
-
|
|
12775
|
-
|
|
12776
|
-
|
|
12765
|
+
}
|
|
12766
|
+
/** The command text an argv rule matches against. */
|
|
12767
|
+
function commandOf(input) {
|
|
12768
|
+
if (typeof input === "string") return input;
|
|
12769
|
+
if (typeof input === "object" && input !== null) {
|
|
12770
|
+
const command = input.command;
|
|
12771
|
+
if (typeof command === "string") return command;
|
|
12772
|
+
}
|
|
12773
|
+
}
|
|
12774
|
+
function ruleMatches(rule, toolName, risk, input) {
|
|
12775
|
+
if ("risk" in rule) {
|
|
12776
|
+
const risks = Array.isArray(rule.risk) ? rule.risk : [rule.risk];
|
|
12777
|
+
if (risks.includes("undeclared") && risk === void 0) return true;
|
|
12778
|
+
return risk !== void 0 && risks.includes(risk);
|
|
12779
|
+
}
|
|
12780
|
+
if ("domains" in rule) return false;
|
|
12781
|
+
if (!(Array.isArray(rule.tool) ? rule.tool : [rule.tool]).includes(toolName)) return false;
|
|
12782
|
+
if ("argv" in rule) {
|
|
12783
|
+
const command = commandOf(input);
|
|
12784
|
+
if (command === void 0) return false;
|
|
12785
|
+
const patterns = Array.isArray(rule.argv) ? rule.argv : [rule.argv];
|
|
12786
|
+
return lexShellCommand(command).some((segment) => !segment.unmatchable && patterns.some((pattern) => matchArgvPattern(pattern, segment.argv)));
|
|
12787
|
+
}
|
|
12788
|
+
return true;
|
|
12789
|
+
}
|
|
12790
|
+
/**
|
|
12791
|
+
* Advisory domain-rule matches for the audit payload:
|
|
12792
|
+
* reported, never enforced in the current release.
|
|
12793
|
+
*/
|
|
12794
|
+
function advisoryMatches(chain, toolName) {
|
|
12795
|
+
return [...chain.deny, ...chain.ask].filter((rule) => "domains" in rule && rule.tool === toolName);
|
|
12796
|
+
}
|
|
12797
|
+
/**
|
|
12798
|
+
* Unmatchable segments (command/process substitution, here-docs) yield
|
|
12799
|
+
* ask, ALWAYS, for any tool that has argv rules.
|
|
12800
|
+
*/
|
|
12801
|
+
function argvUnmatchableAsk(chain, toolName, input) {
|
|
12802
|
+
if (![...chain.deny, ...chain.ask].some((rule) => "argv" in rule && (Array.isArray(rule.tool) ? rule.tool : [rule.tool]).includes(toolName))) return false;
|
|
12803
|
+
const command = commandOf(input);
|
|
12804
|
+
if (command === void 0) return true;
|
|
12805
|
+
return lexShellCommand(command).some((segment) => segment.unmatchable);
|
|
12806
|
+
}
|
|
12807
|
+
/** A stub ToolContext for offline (dry-run) evaluations. */
|
|
12808
|
+
function offlineContext(toolName) {
|
|
12809
|
+
return {
|
|
12810
|
+
runId: "dry-run",
|
|
12811
|
+
spanId: `dry-run-${toolName}`,
|
|
12812
|
+
agent: { agentType: "" },
|
|
12813
|
+
cwd: process.cwd(),
|
|
12814
|
+
isolation: "none",
|
|
12815
|
+
signal: new AbortController().signal,
|
|
12816
|
+
log: () => void 0
|
|
12817
|
+
};
|
|
12818
|
+
}
|
|
12819
|
+
/**
|
|
12820
|
+
* Evaluates the chain for one dispatch, or OFFLINE against a
|
|
12821
|
+
* hypothetical call by tool name (the dry-run API: nothing executes;
|
|
12822
|
+
* shells and tests read the verdict, the
|
|
12823
|
+
* deciding layer, and the matched rule). Hooks run in deterministic
|
|
12824
|
+
* registration order; { modifiedInput } substitutes the input and
|
|
12825
|
+
* continues; the first decisive verdict wins. The returned input is what
|
|
12826
|
+
* execute receives and what the approval identity hashes (post hook
|
|
12827
|
+
* modification). Advisory domain-rule matches
|
|
12828
|
+
* ride every verdict for the audit payload.
|
|
12829
|
+
*/
|
|
12830
|
+
async function evaluatePermission(chain, tool, input, ctx) {
|
|
12831
|
+
const def = typeof tool === "string" ? {
|
|
12832
|
+
name: tool,
|
|
12833
|
+
needsApproval: false
|
|
12834
|
+
} : tool;
|
|
12835
|
+
const risk = typeof tool === "string" ? void 0 : tool.risk;
|
|
12836
|
+
const context = ctx ?? offlineContext(def.name);
|
|
12837
|
+
const advisory = advisoryMatches(chain, def.name);
|
|
12838
|
+
const withAdvisory = (verdict) => advisory.length === 0 ? verdict : {
|
|
12839
|
+
...verdict,
|
|
12840
|
+
advisory
|
|
12841
|
+
};
|
|
12842
|
+
let effective = input;
|
|
12843
|
+
for (const hook of chain.hooks) {
|
|
12844
|
+
const verdict = await hook(def.name, effective, context);
|
|
12845
|
+
if (verdict === void 0) continue;
|
|
12846
|
+
if (verdict === "allow" || verdict === "deny" || verdict === "ask") return withAdvisory({
|
|
12847
|
+
verdict,
|
|
12848
|
+
decidedBy: "hook",
|
|
12849
|
+
input: effective
|
|
12777
12850
|
});
|
|
12851
|
+
effective = verdict.modifiedInput;
|
|
12778
12852
|
}
|
|
12779
|
-
const
|
|
12780
|
-
|
|
12781
|
-
|
|
12782
|
-
|
|
12783
|
-
|
|
12784
|
-
|
|
12785
|
-
|
|
12786
|
-
|
|
12787
|
-
|
|
12788
|
-
|
|
12789
|
-
|
|
12790
|
-
|
|
12791
|
-
|
|
12792
|
-
|
|
12853
|
+
for (const rule of chain.deny) if (ruleMatches(rule, def.name, risk, effective)) return withAdvisory({
|
|
12854
|
+
verdict: "deny",
|
|
12855
|
+
decidedBy: "deny-rule",
|
|
12856
|
+
rule,
|
|
12857
|
+
input: effective
|
|
12858
|
+
});
|
|
12859
|
+
for (const rule of chain.ask) if (ruleMatches(rule, def.name, risk, effective)) return withAdvisory({
|
|
12860
|
+
verdict: "ask",
|
|
12861
|
+
decidedBy: "ask-rule",
|
|
12862
|
+
rule,
|
|
12863
|
+
input: effective
|
|
12864
|
+
});
|
|
12865
|
+
if (argvUnmatchableAsk(chain, def.name, effective)) return withAdvisory({
|
|
12866
|
+
verdict: "ask",
|
|
12867
|
+
decidedBy: "ask-rule",
|
|
12868
|
+
input: effective
|
|
12869
|
+
});
|
|
12870
|
+
if (chain.canUseTool !== void 0) {
|
|
12871
|
+
const verdict = await chain.canUseTool(def.name, effective, context);
|
|
12872
|
+
if (verdict === "allow") return withAdvisory({
|
|
12873
|
+
verdict: "allow",
|
|
12874
|
+
decidedBy: "canUseTool",
|
|
12875
|
+
input: effective
|
|
12793
12876
|
});
|
|
12794
|
-
|
|
12795
|
-
|
|
12796
|
-
|
|
12797
|
-
|
|
12798
|
-
code: "unrouted-role",
|
|
12799
|
-
message: `spawn '${label}' resolves no model for role '${role}': the run would fail with a ConfigError at spawn time; set a model, a profile model, or defaults.routing.${role}`,
|
|
12800
|
-
spawn: label
|
|
12877
|
+
if (verdict === "deny") return withAdvisory({
|
|
12878
|
+
verdict: "deny",
|
|
12879
|
+
decidedBy: "canUseTool",
|
|
12880
|
+
input: effective
|
|
12801
12881
|
});
|
|
12802
|
-
|
|
12803
|
-
|
|
12804
|
-
|
|
12805
|
-
|
|
12806
|
-
|
|
12807
|
-
|
|
12808
|
-
|
|
12809
|
-
|
|
12810
|
-
|
|
12811
|
-
|
|
12812
|
-
|
|
12813
|
-
|
|
12814
|
-
|
|
12815
|
-
|
|
12816
|
-
|
|
12817
|
-
|
|
12818
|
-
|
|
12819
|
-
|
|
12820
|
-
|
|
12821
|
-
|
|
12822
|
-
|
|
12823
|
-
|
|
12824
|
-
|
|
12825
|
-
|
|
12826
|
-
|
|
12827
|
-
|
|
12828
|
-
|
|
12829
|
-
|
|
12830
|
-
|
|
12831
|
-
|
|
12832
|
-
|
|
12833
|
-
|
|
12834
|
-
|
|
12835
|
-
|
|
12836
|
-
|
|
12837
|
-
|
|
12838
|
-
|
|
12839
|
-
|
|
12840
|
-
|
|
12841
|
-
|
|
12842
|
-
|
|
12843
|
-
|
|
12844
|
-
|
|
12845
|
-
|
|
12846
|
-
|
|
12847
|
-
|
|
12848
|
-
|
|
12849
|
-
|
|
12850
|
-
|
|
12851
|
-
|
|
12852
|
-
|
|
12853
|
-
|
|
12854
|
-
|
|
12855
|
-
|
|
12856
|
-
|
|
12857
|
-
|
|
12858
|
-
|
|
12859
|
-
|
|
12860
|
-
|
|
12861
|
-
|
|
12862
|
-
|
|
12863
|
-
|
|
12864
|
-
|
|
12865
|
-
|
|
12882
|
+
effective = verdict.modifiedInput;
|
|
12883
|
+
}
|
|
12884
|
+
if (def.needsApproval) return withAdvisory({
|
|
12885
|
+
verdict: "ask",
|
|
12886
|
+
decidedBy: "default",
|
|
12887
|
+
input: effective
|
|
12888
|
+
});
|
|
12889
|
+
return withAdvisory({
|
|
12890
|
+
verdict: "allow",
|
|
12891
|
+
decidedBy: "default",
|
|
12892
|
+
input: effective
|
|
12893
|
+
});
|
|
12894
|
+
}
|
|
12895
|
+
//#endregion
|
|
12896
|
+
//#region src/runtime/executor.ts
|
|
12897
|
+
/**
|
|
12898
|
+
* Isolated-executor dispatch helpers (RV-216). The engine routes a
|
|
12899
|
+
* non-inprocess tool call through the registered ToolExecutorProvider;
|
|
12900
|
+
* this module derives the stable per-call idempotency key the provider
|
|
12901
|
+
* receives, so an at-least-once retry of a side-effecting tool can be
|
|
12902
|
+
* folded into effectively-once.
|
|
12903
|
+
*
|
|
12904
|
+
* Public contract: https://docs.rulvar.com/guide/isolated-executor.
|
|
12905
|
+
*/
|
|
12906
|
+
/**
|
|
12907
|
+
* Derives the idempotency key for one isolated tool dispatch. The key is
|
|
12908
|
+
* a pure function of the run, the LOGICAL INVOCATION (the seq of the
|
|
12909
|
+
* containing agent's journal entry plus that call's ordinal within the
|
|
12910
|
+
* agent's tool loop), the tool name, and the JCS-canonical arguments.
|
|
12911
|
+
*
|
|
12912
|
+
* The logical-invocation component is what makes the key both stable and
|
|
12913
|
+
* distinguishing (v1.59.x review P0.4): the agent-entry seq and the
|
|
12914
|
+
* per-agent tool-call ordinal are journal- and checkpoint-stable, so a
|
|
12915
|
+
* crash-and-resume re-dispatch of the SAME logical call (the at-least-
|
|
12916
|
+
* once window between execution and the turn checkpoint) reuses the same
|
|
12917
|
+
* dispatch entry and the restored ordinal, and therefore the same key;
|
|
12918
|
+
* while two SEPARATE calls in one run, even with byte-identical
|
|
12919
|
+
* arguments, occupy different ordinals and never collide. Without it two
|
|
12920
|
+
* intended effects sharing arguments would fold into one under external
|
|
12921
|
+
* deduplication.
|
|
12922
|
+
*
|
|
12923
|
+
* The key never enters run identity (it is absent from every content key
|
|
12924
|
+
* and toolset hash); it exists only for the provider's own side-effect
|
|
12925
|
+
* deduplication.
|
|
12926
|
+
*/
|
|
12927
|
+
function deriveExecIdempotencyKey(runId, agentSeq, ordinal, tool, args) {
|
|
12928
|
+
const canonical = jcsSerialize({
|
|
12929
|
+
runId,
|
|
12930
|
+
agentSeq,
|
|
12931
|
+
ordinal,
|
|
12932
|
+
tool,
|
|
12933
|
+
args
|
|
12934
|
+
});
|
|
12935
|
+
return createHash("sha256").update(canonical, "utf8").digest("hex");
|
|
12936
|
+
}
|
|
12937
|
+
//#endregion
|
|
12938
|
+
//#region src/engine/scheduler.ts
|
|
12939
|
+
/**
|
|
12940
|
+
* Scheduler and concurrency (M1-T08): the per-run semaphore with a FIFO
|
|
12941
|
+
* queue (default 12 concurrent model calls). The engine lifetime spawn cap
|
|
12942
|
+
* is enforced by the budget layer at admission; parallel/pipeline
|
|
12943
|
+
* composition semantics live with ctx.
|
|
12944
|
+
* Per-provider concurrency keys land with M4.
|
|
12945
|
+
*/
|
|
12946
|
+
/** FIFO semaphore; default per-run width is 12. */
|
|
12947
|
+
const DEFAULT_PER_RUN_CONCURRENCY = 12;
|
|
12948
|
+
var Semaphore = class {
|
|
12949
|
+
limit;
|
|
12950
|
+
active = 0;
|
|
12951
|
+
waiters = [];
|
|
12952
|
+
/**
|
|
12953
|
+
* `limit` must be a positive integer: anything else (NaN included) is
|
|
12954
|
+
* a typed ConfigError. Before this gate a NaN limit made
|
|
12955
|
+
* `active < limit` permanently false, so the first acquire queued
|
|
12956
|
+
* forever and the run could not settle, not even through cancel()
|
|
12957
|
+
* (v1.34.0 review P2-4). Unlimited is expressed by not constructing a
|
|
12958
|
+
* semaphore, never by a sentinel limit.
|
|
12959
|
+
*/
|
|
12960
|
+
constructor(limit) {
|
|
12961
|
+
requirePositiveInteger(limit, "Semaphore limit");
|
|
12962
|
+
this.limit = limit;
|
|
12963
|
+
}
|
|
12964
|
+
get pending() {
|
|
12965
|
+
return this.waiters.length;
|
|
12966
|
+
}
|
|
12967
|
+
/**
|
|
12968
|
+
* Acquires a slot, resolving in FIFO order. `onQueued` fires only when
|
|
12969
|
+
* the caller actually has to wait (feeds the agent:queued event).
|
|
12970
|
+
* An aborted `signal` releases the caller from the queue without a
|
|
12971
|
+
* slot: the returned release is a no-op, the remaining waiters keep
|
|
12972
|
+
* their FIFO positions, and the caller proceeds to observe its own
|
|
12973
|
+
* aborted signal (the model layers refuse dispatch under an aborted
|
|
12974
|
+
* signal, so no provider call follows). Cancellation can therefore
|
|
12975
|
+
* always drain a queued run (v1.34.0 review P2-4).
|
|
12976
|
+
*/
|
|
12977
|
+
async acquire(onQueued, signal) {
|
|
12978
|
+
if (this.active < this.limit) {
|
|
12979
|
+
this.active += 1;
|
|
12980
|
+
return () => this.release();
|
|
12866
12981
|
}
|
|
12867
|
-
if (
|
|
12868
|
-
|
|
12869
|
-
|
|
12870
|
-
|
|
12871
|
-
|
|
12872
|
-
}
|
|
12873
|
-
|
|
12874
|
-
|
|
12875
|
-
code: "inert-tool-budget-notices",
|
|
12876
|
-
message: `spawn '${label}' sets toolBudgetNotices without maxToolCalls: the notices never fire`,
|
|
12877
|
-
spawn: label
|
|
12878
|
-
});
|
|
12879
|
-
if (unpriced && ceilingUsd !== void 0) say({
|
|
12880
|
-
severity: "warning",
|
|
12881
|
-
code: "unpriced-under-ceiling",
|
|
12882
|
-
message: `spawn '${label}' is served by '${servedBy ?? ""}' with no price row: the ${ceilingUsd.toFixed(4)} USD run ceiling does NOT bound it and its admission reserve is zero`,
|
|
12883
|
-
spawn: label
|
|
12884
|
-
});
|
|
12885
|
-
spawnReports.push({
|
|
12886
|
-
label,
|
|
12887
|
-
role,
|
|
12888
|
-
count,
|
|
12889
|
-
...servedBy === void 0 ? {} : { servedBy },
|
|
12890
|
-
...unpriced ? { unpriced: true } : {},
|
|
12891
|
-
limits,
|
|
12892
|
-
admissionReserveUsd: reserveUsd,
|
|
12893
|
-
reserveSource,
|
|
12894
|
-
...outputBound === void 0 ? {} : { maxOutputTokensPerTurn: outputBound },
|
|
12895
|
-
...turnFloorUsd === void 0 ? {} : { turnFloorUsd },
|
|
12896
|
-
executedToolCallCeiling,
|
|
12897
|
-
toolCeilings
|
|
12982
|
+
if (signal?.aborted === true) return () => void 0;
|
|
12983
|
+
onQueued?.();
|
|
12984
|
+
const waiter = {
|
|
12985
|
+
resolve: () => void 0,
|
|
12986
|
+
aborted: false
|
|
12987
|
+
};
|
|
12988
|
+
const wait = new Promise((resolve) => {
|
|
12989
|
+
waiter.resolve = resolve;
|
|
12898
12990
|
});
|
|
12899
|
-
|
|
12900
|
-
|
|
12901
|
-
|
|
12902
|
-
|
|
12991
|
+
this.waiters.push(waiter);
|
|
12992
|
+
let onAbort;
|
|
12993
|
+
if (signal !== void 0) {
|
|
12994
|
+
onAbort = () => {
|
|
12995
|
+
const index = this.waiters.indexOf(waiter);
|
|
12996
|
+
if (index === -1) return;
|
|
12997
|
+
this.waiters.splice(index, 1);
|
|
12998
|
+
waiter.aborted = true;
|
|
12999
|
+
waiter.resolve();
|
|
12903
13000
|
};
|
|
12904
|
-
|
|
12905
|
-
|
|
12906
|
-
|
|
12907
|
-
|
|
12908
|
-
|
|
12909
|
-
if (
|
|
12910
|
-
units.push(unit);
|
|
13001
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
13002
|
+
}
|
|
13003
|
+
try {
|
|
13004
|
+
await wait;
|
|
13005
|
+
} finally {
|
|
13006
|
+
if (signal !== void 0 && onAbort !== void 0) signal.removeEventListener("abort", onAbort);
|
|
12911
13007
|
}
|
|
13008
|
+
if (waiter.aborted) return () => void 0;
|
|
13009
|
+
this.active += 1;
|
|
13010
|
+
return () => this.release();
|
|
12912
13011
|
}
|
|
12913
|
-
|
|
12914
|
-
const
|
|
12915
|
-
|
|
12916
|
-
|
|
12917
|
-
|
|
12918
|
-
|
|
12919
|
-
spawn: "orchestrator"
|
|
12920
|
-
});
|
|
12921
|
-
else {
|
|
12922
|
-
const caps = capsOf(servedBy);
|
|
12923
|
-
const pricing = pricingOf(servedBy);
|
|
12924
|
-
const orchLimits = mergeUsageLimits(input.orchestrator.limits, void 0, defaults.limits);
|
|
12925
|
-
const outputBound = caps === void 0 ? orchLimits.maxOutputTokensPerTurn : orchLimits.maxOutputTokensPerTurn === void 0 ? caps.maxOutputTokens : Math.min(caps.maxOutputTokens, orchLimits.maxOutputTokensPerTurn);
|
|
12926
|
-
const { adapterId, model } = parseModelRef(servedBy);
|
|
12927
|
-
const unit = {
|
|
12928
|
-
label: "orchestrator",
|
|
12929
|
-
provider: adapterId,
|
|
12930
|
-
model,
|
|
12931
|
-
tokensFloor: outputBound ?? 0
|
|
12932
|
-
};
|
|
12933
|
-
if (pricing !== void 0 && outputBound !== void 0) unit.turnFloorUsd = priceUsdOf(pricing, {
|
|
12934
|
-
inputTokens: 0,
|
|
12935
|
-
outputTokens: outputBound,
|
|
12936
|
-
cacheReadTokens: 0,
|
|
12937
|
-
cacheWriteTokens: 0
|
|
12938
|
-
});
|
|
12939
|
-
units.push(unit);
|
|
13012
|
+
async withSlot(fn, onQueued, signal) {
|
|
13013
|
+
const release = await this.acquire(onQueued, signal);
|
|
13014
|
+
try {
|
|
13015
|
+
return await fn();
|
|
13016
|
+
} finally {
|
|
13017
|
+
release();
|
|
12940
13018
|
}
|
|
12941
13019
|
}
|
|
12942
|
-
|
|
12943
|
-
|
|
12944
|
-
|
|
12945
|
-
|
|
12946
|
-
const admitAgainstRoot = (reserveUsd) => {
|
|
12947
|
-
if (ceilingUsd === void 0) return true;
|
|
12948
|
-
const held = committed + reservedForFinalizationUsd;
|
|
12949
|
-
return !(held >= ceilingUsd || held + reserveUsd > ceilingUsd);
|
|
12950
|
-
};
|
|
12951
|
-
if (input.orchestrator !== void 0) {
|
|
12952
|
-
const reserveUsd = flatReserveUsd;
|
|
12953
|
-
let deniedBy;
|
|
12954
|
-
if (spawned >= lifetimeSpawnCap) deniedBy = "spawn-cap";
|
|
12955
|
-
else if (effectiveCapUsd !== void 0 && reserveUsd > effectiveCapUsd) deniedBy = "orchestrator-cap";
|
|
12956
|
-
else if (!admitAgainstRoot(reserveUsd)) deniedBy = "budget";
|
|
12957
|
-
wave.push({
|
|
12958
|
-
label: "orchestrator",
|
|
12959
|
-
reserveUsd,
|
|
12960
|
-
admitted: deniedBy === void 0,
|
|
12961
|
-
...deniedBy === void 0 ? {} : { deniedBy }
|
|
12962
|
-
});
|
|
12963
|
-
if (deniedBy === void 0) {
|
|
12964
|
-
committed += reserveUsd;
|
|
12965
|
-
spawned += 1;
|
|
12966
|
-
} else if (deniedBy === "orchestrator-cap") say({
|
|
12967
|
-
severity: "error",
|
|
12968
|
-
code: "orchestrator-cap-below-reserve",
|
|
12969
|
-
message: `the orchestrator's own admission reserve ${reserveUsd.toFixed(4)} USD does not fit its effective cap ${(effectiveCapUsd ?? 0).toFixed(4)} USD: the run cannot start`
|
|
12970
|
-
});
|
|
12971
|
-
}
|
|
12972
|
-
const maxSpawns = input.orchestrator?.maxSpawns;
|
|
12973
|
-
for (const report of spawnReports) for (let i = 0; i < report.count; i += 1) {
|
|
12974
|
-
const label = report.count === 1 ? report.label : `${report.label}#${String(i + 1)}`;
|
|
12975
|
-
const reserveUsd = report.admissionReserveUsd;
|
|
12976
|
-
let deniedBy;
|
|
12977
|
-
if (spawned >= lifetimeSpawnCap) deniedBy = "spawn-cap";
|
|
12978
|
-
else if (maxSpawns !== void 0 && children >= maxSpawns) deniedBy = "orchestrator-max-spawns";
|
|
12979
|
-
else if (!admitAgainstRoot(reserveUsd)) deniedBy = "budget";
|
|
12980
|
-
wave.push({
|
|
12981
|
-
label,
|
|
12982
|
-
reserveUsd,
|
|
12983
|
-
admitted: deniedBy === void 0,
|
|
12984
|
-
...deniedBy === void 0 ? {} : { deniedBy }
|
|
12985
|
-
});
|
|
12986
|
-
if (deniedBy === void 0) {
|
|
12987
|
-
committed += reserveUsd;
|
|
12988
|
-
spawned += 1;
|
|
12989
|
-
children += 1;
|
|
12990
|
-
}
|
|
12991
|
-
}
|
|
12992
|
-
const admitted = wave.filter((row) => row.admitted).length;
|
|
12993
|
-
const denied = wave.length - admitted;
|
|
12994
|
-
if (wave.length > 0 && denied > 0) {
|
|
12995
|
-
const deniedLabels = wave.filter((row) => !row.admitted).map((row) => row.label);
|
|
12996
|
-
if (admitted === 0) say({
|
|
12997
|
-
severity: "error",
|
|
12998
|
-
code: "nothing-admitted",
|
|
12999
|
-
message: `the declared wave admits NOTHING: every spawn is denied (${deniedLabels.join(", ")}); no paid work can start`
|
|
13000
|
-
});
|
|
13001
|
-
else say({
|
|
13002
|
-
severity: "warning",
|
|
13003
|
-
code: "partial-admission",
|
|
13004
|
-
message: `the declared wave admits ${String(admitted)} of ${String(wave.length)} spawns; denied before any work: ${deniedLabels.join(", ")}`
|
|
13005
|
-
});
|
|
13006
|
-
}
|
|
13007
|
-
if (ceilingUsd === void 0 && wave.length > 0) say({
|
|
13008
|
-
severity: "info",
|
|
13009
|
-
code: "no-usd-ceiling",
|
|
13010
|
-
message: "the run has no budgetUsd ceiling: only turn, tool, and time limits bound spend, and the whole declared wave admits"
|
|
13011
|
-
});
|
|
13012
|
-
const declaredUnits = units.length;
|
|
13013
|
-
const maxInFlight = declaredUnits === 0 ? perRun : Math.min(perRun, declaredUnits);
|
|
13014
|
-
const perProviderCaps = engine.concurrency?.perProvider;
|
|
13015
|
-
const perProvider = {};
|
|
13016
|
-
const byProvider = /* @__PURE__ */ new Map();
|
|
13017
|
-
for (const unit of units) {
|
|
13018
|
-
if (unit.provider === void 0) continue;
|
|
13019
|
-
const list = byProvider.get(unit.provider) ?? [];
|
|
13020
|
-
list.push(unit);
|
|
13021
|
-
byProvider.set(unit.provider, list);
|
|
13020
|
+
release() {
|
|
13021
|
+
this.active -= 1;
|
|
13022
|
+
const next = this.waiters.shift();
|
|
13023
|
+
if (next !== void 0) next.resolve();
|
|
13022
13024
|
}
|
|
13023
|
-
|
|
13024
|
-
|
|
13025
|
-
|
|
13026
|
-
|
|
13027
|
-
|
|
13028
|
-
|
|
13029
|
-
|
|
13025
|
+
};
|
|
13026
|
+
//#endregion
|
|
13027
|
+
//#region src/engine/ctx.ts
|
|
13028
|
+
/**
|
|
13029
|
+
* Ctx primitives (M1-T07) plus the parallel/pipeline composition semantics
|
|
13030
|
+
* of the scheduler (M1-T08): the canonical authoring surface bound to the
|
|
13031
|
+
* journal write path, the model router, the agent runtime, and the
|
|
13032
|
+
* three-layer budget. M1 ships agent, parallel, pipeline, step, phase,
|
|
13033
|
+
* log, budget, and the deterministic shims; workflow/orchestrate/
|
|
13034
|
+
* awaitExternal/brief land with their milestones (M2/M6).
|
|
13035
|
+
*
|
|
13036
|
+
* Public contract: https://docs.rulvar.com/guide/workflows.
|
|
13037
|
+
*/
|
|
13038
|
+
/**
|
|
13039
|
+
* The rejection carrier of ctx.agent value-form calls: a real Error that
|
|
13040
|
+
* structurally satisfies the typed AgentError and carries the full
|
|
13041
|
+
* AgentResult for Settled mapping. Deliberately not a RulvarError:
|
|
13042
|
+
* AgentError is not in the closed code registry.
|
|
13043
|
+
*/
|
|
13044
|
+
var AgentCallError = class extends Error {
|
|
13045
|
+
kind;
|
|
13046
|
+
retryable;
|
|
13047
|
+
retryAfterMs;
|
|
13048
|
+
issues;
|
|
13049
|
+
result;
|
|
13050
|
+
scope;
|
|
13051
|
+
entryRef;
|
|
13052
|
+
constructor(message, result, scope, entryRef) {
|
|
13053
|
+
super(message);
|
|
13054
|
+
this.name = "AgentCallError";
|
|
13055
|
+
const error = result.error ?? {
|
|
13056
|
+
kind: "terminal",
|
|
13057
|
+
retryable: false
|
|
13030
13058
|
};
|
|
13059
|
+
this.kind = error.kind;
|
|
13060
|
+
this.retryable = error.retryable;
|
|
13061
|
+
if (error.retryAfterMs !== void 0) this.retryAfterMs = error.retryAfterMs;
|
|
13062
|
+
if (error.issues !== void 0) this.issues = error.issues;
|
|
13063
|
+
this.result = result;
|
|
13064
|
+
this.scope = scope;
|
|
13065
|
+
if (entryRef !== void 0) this.entryRef = entryRef;
|
|
13031
13066
|
}
|
|
13032
|
-
|
|
13033
|
-
|
|
13034
|
-
|
|
13035
|
-
|
|
13036
|
-
|
|
13037
|
-
|
|
13038
|
-
|
|
13039
|
-
|
|
13040
|
-
|
|
13041
|
-
|
|
13042
|
-
|
|
13043
|
-
|
|
13044
|
-
|
|
13045
|
-
|
|
13046
|
-
|
|
13047
|
-
|
|
13048
|
-
|
|
13049
|
-
|
|
13050
|
-
|
|
13051
|
-
|
|
13052
|
-
|
|
13053
|
-
...engine.quota?.tenant === void 0 ? {} : { tenant: engine.quota.tenant },
|
|
13054
|
-
estimate: {
|
|
13055
|
-
requests: 1,
|
|
13056
|
-
inputTokens: 0
|
|
13057
|
-
}
|
|
13058
|
-
})) {
|
|
13059
|
-
requests += 1;
|
|
13060
|
-
tokens += unit.tokensFloor;
|
|
13061
|
-
}
|
|
13067
|
+
};
|
|
13068
|
+
/**
|
|
13069
|
+
* Projects a settled AgentResult's error to its wire form, carrying the
|
|
13070
|
+
* engine-decided abort class in data. AgentError itself has no data
|
|
13071
|
+
* field, so without this every projection past the terminal entry (the
|
|
13072
|
+
* run-level outcome.error, thrown AgentCallError wires, dropped items)
|
|
13073
|
+
* would keep only the message text and lose the typed class (v1.9.0
|
|
13074
|
+
* follow-up review).
|
|
13075
|
+
*/
|
|
13076
|
+
function agentResultWire(result, fallbackMessage) {
|
|
13077
|
+
const wire = agentErrorToWire(result.error ?? {
|
|
13078
|
+
kind: "terminal",
|
|
13079
|
+
retryable: false
|
|
13080
|
+
}, result.errorMessage ?? fallbackMessage);
|
|
13081
|
+
if (result.abortClass === void 0) return wire;
|
|
13082
|
+
const data = typeof wire.data === "object" && wire.data !== null && !Array.isArray(wire.data) ? wire.data : {};
|
|
13083
|
+
return {
|
|
13084
|
+
...wire,
|
|
13085
|
+
data: {
|
|
13086
|
+
...data,
|
|
13087
|
+
abortClass: result.abortClass
|
|
13062
13088
|
}
|
|
13063
|
-
const dims = [
|
|
13064
|
-
rule.provider === void 0 ? void 0 : `provider=${rule.provider}`,
|
|
13065
|
-
rule.model === void 0 ? void 0 : `model=${rule.model}`,
|
|
13066
|
-
rule.tenant === void 0 ? void 0 : `tenant=${rule.tenant}`
|
|
13067
|
-
].filter((dim) => dim !== void 0).join(" ");
|
|
13068
|
-
const name = dims === "" ? `rule[${String(index)}]` : `rule[${String(index)}] (${dims})`;
|
|
13069
|
-
if (rule.requestsPerMinute !== void 0 && requests > rule.requestsPerMinute) say({
|
|
13070
|
-
severity: "warning",
|
|
13071
|
-
code: "quota-requests-below-wave",
|
|
13072
|
-
message: `${name}: the declared wave holds ${String(requests)} matching dispatches against requestsPerMinute ${String(rule.requestsPerMinute)}: expect synthetic rate-limit denials and backoff inside one window`
|
|
13073
|
-
});
|
|
13074
|
-
if (rule.tokensPerMinute !== void 0 && tokens > rule.tokensPerMinute) say({
|
|
13075
|
-
severity: "warning",
|
|
13076
|
-
code: "quota-tokens-below-wave",
|
|
13077
|
-
message: `${name}: the declared wave demands at least ${String(tokens)} tokens against tokensPerMinute ${String(rule.tokensPerMinute)}: expect estimate-driven throttling inside one window`
|
|
13078
|
-
});
|
|
13079
|
-
});
|
|
13080
|
-
const severityRank = {
|
|
13081
|
-
error: 0,
|
|
13082
|
-
warning: 1,
|
|
13083
|
-
info: 2
|
|
13084
13089
|
};
|
|
13085
|
-
|
|
13086
|
-
|
|
13087
|
-
|
|
13088
|
-
|
|
13089
|
-
|
|
13090
|
-
|
|
13091
|
-
|
|
13092
|
-
|
|
13093
|
-
|
|
13094
|
-
|
|
13095
|
-
|
|
13096
|
-
|
|
13097
|
-
|
|
13098
|
-
|
|
13099
|
-
|
|
13100
|
-
|
|
13101
|
-
|
|
13102
|
-
|
|
13103
|
-
|
|
13104
|
-
|
|
13105
|
-
|
|
13106
|
-
|
|
13107
|
-
...ceilingUsd === void 0 ? {} : { ceilingUsd },
|
|
13108
|
-
reservedForFinalizationUsd,
|
|
13109
|
-
wave,
|
|
13110
|
-
admitted,
|
|
13111
|
-
denied
|
|
13112
|
-
},
|
|
13113
|
-
exposure: {
|
|
13114
|
-
maxInFlight,
|
|
13115
|
-
...overshootOneTurnFloorUsd === void 0 ? {} : { overshootOneTurnFloorUsd },
|
|
13116
|
-
perProvider
|
|
13117
|
-
},
|
|
13118
|
-
findings
|
|
13090
|
+
}
|
|
13091
|
+
/** The workflow-defaults layer a Workflow value contributes, or nothing. */
|
|
13092
|
+
function workflowLayerOf(wf) {
|
|
13093
|
+
const layer = {};
|
|
13094
|
+
if (wf.model !== void 0) layer.model = wf.model;
|
|
13095
|
+
if (wf.routing !== void 0) layer.routing = wf.routing;
|
|
13096
|
+
if (wf.effort !== void 0) layer.effort = wf.effort;
|
|
13097
|
+
return Object.keys(layer).length === 0 ? void 0 : layer;
|
|
13098
|
+
}
|
|
13099
|
+
function defineWorkflow(meta, body) {
|
|
13100
|
+
const wf = {
|
|
13101
|
+
kind: "workflow",
|
|
13102
|
+
name: meta.name,
|
|
13103
|
+
errorPolicy: meta.errorPolicy ?? "strict",
|
|
13104
|
+
...meta.model === void 0 ? {} : { model: meta.model },
|
|
13105
|
+
...meta.routing === void 0 ? {} : { routing: meta.routing },
|
|
13106
|
+
...meta.effort === void 0 ? {} : { effort: meta.effort },
|
|
13107
|
+
body
|
|
13108
|
+
};
|
|
13109
|
+
if (meta.args !== void 0) return {
|
|
13110
|
+
...wf,
|
|
13111
|
+
argsSchema: meta.args
|
|
13119
13112
|
};
|
|
13113
|
+
return wf;
|
|
13114
|
+
}
|
|
13115
|
+
function bump(map, key, usd) {
|
|
13116
|
+
map.set(key, (map.get(key) ?? 0) + usd);
|
|
13120
13117
|
}
|
|
13121
|
-
//#endregion
|
|
13122
|
-
//#region src/engine/run-profiles.ts
|
|
13123
13118
|
/**
|
|
13124
|
-
*
|
|
13125
|
-
*
|
|
13126
|
-
*
|
|
13127
|
-
|
|
13128
|
-
const RUN_PROFILES = {
|
|
13129
|
-
fast: {
|
|
13130
|
-
effortByRole: {
|
|
13131
|
-
orchestrate: "low",
|
|
13132
|
-
plan: "low",
|
|
13133
|
-
summarize: "low",
|
|
13134
|
-
extract: "low"
|
|
13135
|
-
},
|
|
13136
|
-
perRunConcurrency: 16,
|
|
13137
|
-
permissionPreset: "standard",
|
|
13138
|
-
lifetimeSpawnCap: 64,
|
|
13139
|
-
maxDepth: 1
|
|
13140
|
-
},
|
|
13141
|
-
standard: {
|
|
13142
|
-
effortByRole: {
|
|
13143
|
-
orchestrate: "high",
|
|
13144
|
-
plan: "high",
|
|
13145
|
-
summarize: "low",
|
|
13146
|
-
extract: "low"
|
|
13147
|
-
},
|
|
13148
|
-
perRunConcurrency: 12,
|
|
13149
|
-
permissionPreset: "standard",
|
|
13150
|
-
lifetimeSpawnCap: 500,
|
|
13151
|
-
maxDepth: 1
|
|
13152
|
-
},
|
|
13153
|
-
deep: {
|
|
13154
|
-
effortByRole: {
|
|
13155
|
-
orchestrate: "high",
|
|
13156
|
-
plan: "high",
|
|
13157
|
-
summarize: "medium",
|
|
13158
|
-
extract: "medium"
|
|
13159
|
-
},
|
|
13160
|
-
perRunConcurrency: 8,
|
|
13161
|
-
permissionPreset: "standard",
|
|
13162
|
-
lifetimeSpawnCap: 500,
|
|
13163
|
-
maxDepth: 2
|
|
13164
|
-
},
|
|
13165
|
-
ultra: {
|
|
13166
|
-
effortByRole: {
|
|
13167
|
-
orchestrate: "max",
|
|
13168
|
-
plan: "max",
|
|
13169
|
-
summarize: "high",
|
|
13170
|
-
extract: "high"
|
|
13171
|
-
},
|
|
13172
|
-
perRunConcurrency: 8,
|
|
13173
|
-
permissionPreset: "strict",
|
|
13174
|
-
lifetimeSpawnCap: 500,
|
|
13175
|
-
maxDepth: 3
|
|
13176
|
-
}
|
|
13177
|
-
};
|
|
13178
|
-
/** Looks up a shipped RunProfile by name; undefined for unknown names. */
|
|
13179
|
-
function runProfile(name) {
|
|
13180
|
-
return RUN_PROFILES[name];
|
|
13181
|
-
}
|
|
13182
|
-
//#endregion
|
|
13183
|
-
//#region src/model/concurrency.ts
|
|
13184
|
-
/**
|
|
13185
|
-
* Per-provider concurrency keys (M4-T07): a keyed limiter beside the
|
|
13186
|
-
* router, ENGINE-scoped (keys constrain calls
|
|
13187
|
-
* across a single engine per adapter). The Appendix A default is
|
|
13188
|
-
* unlimited: an embeddable library must not surprise-throttle hosts, so
|
|
13189
|
-
* the per-run semaphore stays the only default bound and provider 429s
|
|
13190
|
-
* ride RetryPolicy; hosts with known tier limits opt in per adapter id
|
|
13191
|
-
* via createEngine concurrency.perProvider.
|
|
13192
|
-
*
|
|
13193
|
-
* This keyed limiter bounds PARALLELISM inside one engine only. Two
|
|
13194
|
-
* processes sharing one API key coordinate through the QuotaLimiter
|
|
13195
|
-
* SPI instead (RV-215, createEngine `quota`): rate and volume live
|
|
13196
|
-
* there, in shared storage; in-flight slots live here.
|
|
13197
|
-
*/
|
|
13198
|
-
var KeyedLimiter = class {
|
|
13199
|
-
semaphores = /* @__PURE__ */ new Map();
|
|
13200
|
-
constructor(caps) {
|
|
13201
|
-
for (const [key, limit] of Object.entries(caps ?? {})) this.semaphores.set(key, new Semaphore(limit));
|
|
13202
|
-
}
|
|
13203
|
-
/** Queue depth for one key (0 for unlimited keys); telemetry only. */
|
|
13204
|
-
pending(key) {
|
|
13205
|
-
return this.semaphores.get(key)?.pending ?? 0;
|
|
13206
|
-
}
|
|
13207
|
-
/**
|
|
13208
|
-
* Runs `fn` under the key's semaphore; keys without a configured cap
|
|
13209
|
-
* run unlimited (no queueing, no overhead). An aborted `signal` frees
|
|
13210
|
-
* a queued caller without a slot (the Semaphore contract), so run
|
|
13211
|
-
* cancellation drains provider queues too (v1.34.0 review P2-4).
|
|
13212
|
-
*/
|
|
13213
|
-
async withSlot(key, fn, onQueued, signal) {
|
|
13214
|
-
const semaphore = this.semaphores.get(key);
|
|
13215
|
-
if (semaphore === void 0) return fn();
|
|
13216
|
-
return semaphore.withSlot(fn, onQueued, signal);
|
|
13217
|
-
}
|
|
13218
|
-
};
|
|
13219
|
-
//#endregion
|
|
13220
|
-
//#region src/model/profile-card.ts
|
|
13221
|
-
function toolNamesOf(profile) {
|
|
13222
|
-
return (profile.tools ?? []).map((entry) => {
|
|
13223
|
-
if (typeof entry === "string") return `${entry} (registered toolset)`;
|
|
13224
|
-
if ("kind" in entry && entry.kind === "tool") return entry.name;
|
|
13225
|
-
return `${entry.id}:* (tool source)`;
|
|
13226
|
-
});
|
|
13227
|
-
}
|
|
13228
|
-
/**
|
|
13229
|
-
* Renders the registry into the shared agent vocabulary card. Sorted,
|
|
13230
|
-
* deterministic, byte-stable; an empty registry renders explicitly so
|
|
13231
|
-
* the planner never guesses at unregistered agentTypes. When the engine
|
|
13232
|
-
* registers toolsets, their names render as a closing line (v1.17.0
|
|
13233
|
-
* review P1-3): those are the ONLY values valid as string entries of a
|
|
13234
|
-
* tools option, so the planner never invents a registry name.
|
|
13235
|
-
*/
|
|
13236
|
-
function profileCard(profiles, toolsets) {
|
|
13237
|
-
const toolsetNames = Object.keys(toolsets ?? {}).sort();
|
|
13238
|
-
const toolsetsLine = toolsetNames.length === 0 ? void 0 : `Registered toolsets (valid string entries of a tools option): ${toolsetNames.join(", ")}.`;
|
|
13239
|
-
const names = Object.keys(profiles ?? {}).sort();
|
|
13240
|
-
if (profiles === void 0 || names.length === 0) {
|
|
13241
|
-
const empty = "Agent profiles: none registered. Calls take no agentType.";
|
|
13242
|
-
return toolsetsLine === void 0 ? empty : `${empty}\n${toolsetsLine}`;
|
|
13243
|
-
}
|
|
13244
|
-
const lines = ["Agent profiles (agentType values):"];
|
|
13245
|
-
for (const name of names) {
|
|
13246
|
-
const profile = profiles[name];
|
|
13247
|
-
const description = profile.description ?? "no description";
|
|
13248
|
-
lines.push(`- ${name}: ${description}`);
|
|
13249
|
-
const toolNames = toolNamesOf(profile);
|
|
13250
|
-
if (toolNames.length > 0) lines.push(` tools: ${toolNames.join(", ")}`);
|
|
13251
|
-
if (profile.taskClass !== void 0) lines.push(` taskClass: ${profile.taskClass}`);
|
|
13252
|
-
if (profile.estCost !== void 0) lines.push(` estCost: ${profile.estCost.toFixed(2)} USD`);
|
|
13253
|
-
if (profile.escalation !== void 0) lines.push(` escalation: flavor ${profile.escalation.flavor ?? "A"} (opt-in)`);
|
|
13254
|
-
}
|
|
13255
|
-
if (toolsetsLine !== void 0) lines.push(toolsetsLine);
|
|
13256
|
-
return lines.join("\n");
|
|
13257
|
-
}
|
|
13258
|
-
//#endregion
|
|
13259
|
-
//#region src/runtime/permission-chain.ts
|
|
13260
|
-
/**
|
|
13261
|
-
* The layered permission chain (M3-T03): the single approval surface for
|
|
13262
|
-
* every tool dispatch, regardless of tool origin. The order is fixed and
|
|
13263
|
-
* normative: hooks -> deny rules -> ask rules -> canUseTool -> terminal
|
|
13264
|
-
* default (allow unless needsApproval, then ask). Evaluation is
|
|
13265
|
-
* short-circuit; unconfigured layers are skipped. Rules never yield
|
|
13266
|
-
* allow: allow is only ever falling through to canUseTool or the
|
|
13267
|
-
* terminal default.
|
|
13268
|
-
*
|
|
13269
|
-
* Full contract: https://docs.rulvar.com/guide/tools.
|
|
13270
|
-
* Risk presets, the argv shell matcher, domain rules, and the
|
|
13271
|
-
* audit/dry-run surface land in M5.
|
|
13272
|
-
*/
|
|
13273
|
-
/**
|
|
13274
|
-
* Merges the engine-wide config and the profile config into one chain.
|
|
13275
|
-
* Layers concatenate engine-first; since rules only deny or ask, ordering
|
|
13276
|
-
* within a layer cannot change the verdict. The
|
|
13277
|
-
* profile's canUseTool wins over the engine's (a single slot by
|
|
13278
|
-
* construction). A declared preset compiles INTO the same layers, after
|
|
13279
|
-
* the host-authored rules, never as a fifth layer (M5-T05).
|
|
13119
|
+
* Completes a model-authored escalation request into the full report:
|
|
13120
|
+
* costToDate and salvage are runtime-filled, never model-filled. The
|
|
13121
|
+
* worktree patch ref lands after collect(); the pre-dispose preview for
|
|
13122
|
+
* flavor B decision-makers omits it.
|
|
13280
13123
|
*/
|
|
13281
|
-
function
|
|
13282
|
-
const preset = profile?.preset === void 0 ? {
|
|
13283
|
-
deny: [],
|
|
13284
|
-
ask: []
|
|
13285
|
-
} : compilePermissionPreset(profile.preset);
|
|
13286
|
-
const deny = [
|
|
13287
|
-
...engine?.deny ?? [],
|
|
13288
|
-
...profile?.deny ?? [],
|
|
13289
|
-
...preset.deny
|
|
13290
|
-
];
|
|
13291
|
-
const ask = [
|
|
13292
|
-
...engine?.ask ?? [],
|
|
13293
|
-
...profile?.ask ?? [],
|
|
13294
|
-
...preset.ask
|
|
13295
|
-
];
|
|
13296
|
-
const canUseTool = profile?.canUseTool ?? engine?.canUseTool;
|
|
13124
|
+
function buildEscalationReport(request, result, worktreePatchRef) {
|
|
13297
13125
|
return {
|
|
13298
|
-
|
|
13299
|
-
|
|
13300
|
-
|
|
13301
|
-
|
|
13126
|
+
kind: request.kind,
|
|
13127
|
+
scopeDelta: request.scopeDelta,
|
|
13128
|
+
revisedEstimate: request.revisedEstimate,
|
|
13129
|
+
blockers: request.blockers ?? [],
|
|
13130
|
+
proposedDecomposition: request.proposedDecomposition ?? [],
|
|
13131
|
+
costToDate: {
|
|
13132
|
+
usd: result.costUsd,
|
|
13133
|
+
turns: result.turns
|
|
13134
|
+
},
|
|
13135
|
+
salvage: {
|
|
13136
|
+
transcriptRef: result.transcriptRef,
|
|
13137
|
+
artifacts: (result.artifacts ?? []).map((artifact) => artifact.id),
|
|
13138
|
+
...worktreePatchRef === void 0 ? {} : { worktreePatchRef }
|
|
13139
|
+
}
|
|
13302
13140
|
};
|
|
13303
13141
|
}
|
|
13304
|
-
/** The command text an argv rule matches against. */
|
|
13305
|
-
function commandOf(input) {
|
|
13306
|
-
if (typeof input === "string") return input;
|
|
13307
|
-
if (typeof input === "object" && input !== null) {
|
|
13308
|
-
const command = input.command;
|
|
13309
|
-
if (typeof command === "string") return command;
|
|
13310
|
-
}
|
|
13311
|
-
}
|
|
13312
|
-
function ruleMatches(rule, toolName, risk, input) {
|
|
13313
|
-
if ("risk" in rule) {
|
|
13314
|
-
const risks = Array.isArray(rule.risk) ? rule.risk : [rule.risk];
|
|
13315
|
-
if (risks.includes("undeclared") && risk === void 0) return true;
|
|
13316
|
-
return risk !== void 0 && risks.includes(risk);
|
|
13317
|
-
}
|
|
13318
|
-
if ("domains" in rule) return false;
|
|
13319
|
-
if (!(Array.isArray(rule.tool) ? rule.tool : [rule.tool]).includes(toolName)) return false;
|
|
13320
|
-
if ("argv" in rule) {
|
|
13321
|
-
const command = commandOf(input);
|
|
13322
|
-
if (command === void 0) return false;
|
|
13323
|
-
const patterns = Array.isArray(rule.argv) ? rule.argv : [rule.argv];
|
|
13324
|
-
return lexShellCommand(command).some((segment) => !segment.unmatchable && patterns.some((pattern) => matchArgvPattern(pattern, segment.argv)));
|
|
13325
|
-
}
|
|
13326
|
-
return true;
|
|
13327
|
-
}
|
|
13328
|
-
/**
|
|
13329
|
-
* Advisory domain-rule matches for the audit payload:
|
|
13330
|
-
* reported, never enforced in the current release.
|
|
13331
|
-
*/
|
|
13332
|
-
function advisoryMatches(chain, toolName) {
|
|
13333
|
-
return [...chain.deny, ...chain.ask].filter((rule) => "domains" in rule && rule.tool === toolName);
|
|
13334
|
-
}
|
|
13335
13142
|
/**
|
|
13336
|
-
*
|
|
13337
|
-
*
|
|
13143
|
+
* Creates the per-run Ctx bound to `internals`. The current scope travels
|
|
13144
|
+
* through AsyncLocalStorage so parallel branches and pipeline stages keep
|
|
13145
|
+
* one ctx object while journaling under their own scope paths (I3:
|
|
13146
|
+
* structure from call-and-return only).
|
|
13338
13147
|
*/
|
|
13339
|
-
function
|
|
13340
|
-
|
|
13341
|
-
const
|
|
13342
|
-
|
|
13343
|
-
|
|
13344
|
-
|
|
13345
|
-
|
|
13346
|
-
|
|
13347
|
-
return {
|
|
13348
|
-
runId: "dry-run",
|
|
13349
|
-
spanId: `dry-run-${toolName}`,
|
|
13350
|
-
agent: { agentType: "" },
|
|
13351
|
-
cwd: process.cwd(),
|
|
13352
|
-
isolation: "none",
|
|
13353
|
-
signal: new AbortController().signal,
|
|
13354
|
-
log: () => void 0
|
|
13148
|
+
function createCtx(internals, rootWorkflow) {
|
|
13149
|
+
const als = new AsyncLocalStorage();
|
|
13150
|
+
const sites = new ParallelSiteCounter();
|
|
13151
|
+
const rootWorkflowLayer = rootWorkflow === void 0 ? void 0 : workflowLayerOf(rootWorkflow);
|
|
13152
|
+
const rootState = {
|
|
13153
|
+
scope: "",
|
|
13154
|
+
spanId: internals.rootSpanId,
|
|
13155
|
+
...rootWorkflowLayer === void 0 ? {} : { workflowLayer: rootWorkflowLayer }
|
|
13355
13156
|
};
|
|
13356
|
-
|
|
13357
|
-
|
|
13358
|
-
|
|
13359
|
-
|
|
13360
|
-
|
|
13361
|
-
|
|
13362
|
-
|
|
13363
|
-
|
|
13364
|
-
* execute receives and what the approval identity hashes (post hook
|
|
13365
|
-
* modification). Advisory domain-rule matches
|
|
13366
|
-
* ride every verdict for the audit payload.
|
|
13367
|
-
*/
|
|
13368
|
-
async function evaluatePermission(chain, tool, input, ctx) {
|
|
13369
|
-
const def = typeof tool === "string" ? {
|
|
13370
|
-
name: tool,
|
|
13371
|
-
needsApproval: false
|
|
13372
|
-
} : tool;
|
|
13373
|
-
const risk = typeof tool === "string" ? void 0 : tool.risk;
|
|
13374
|
-
const context = ctx ?? offlineContext(def.name);
|
|
13375
|
-
const advisory = advisoryMatches(chain, def.name);
|
|
13376
|
-
const withAdvisory = (verdict) => advisory.length === 0 ? verdict : {
|
|
13377
|
-
...verdict,
|
|
13378
|
-
advisory
|
|
13379
|
-
};
|
|
13380
|
-
let effective = input;
|
|
13381
|
-
for (const hook of chain.hooks) {
|
|
13382
|
-
const verdict = await hook(def.name, effective, context);
|
|
13383
|
-
if (verdict === void 0) continue;
|
|
13384
|
-
if (verdict === "allow" || verdict === "deny" || verdict === "ask") return withAdvisory({
|
|
13385
|
-
verdict,
|
|
13386
|
-
decidedBy: "hook",
|
|
13387
|
-
input: effective
|
|
13388
|
-
});
|
|
13389
|
-
effective = verdict.modifiedInput;
|
|
13390
|
-
}
|
|
13391
|
-
for (const rule of chain.deny) if (ruleMatches(rule, def.name, risk, effective)) return withAdvisory({
|
|
13392
|
-
verdict: "deny",
|
|
13393
|
-
decidedBy: "deny-rule",
|
|
13394
|
-
rule,
|
|
13395
|
-
input: effective
|
|
13396
|
-
});
|
|
13397
|
-
for (const rule of chain.ask) if (ruleMatches(rule, def.name, risk, effective)) return withAdvisory({
|
|
13398
|
-
verdict: "ask",
|
|
13399
|
-
decidedBy: "ask-rule",
|
|
13400
|
-
rule,
|
|
13401
|
-
input: effective
|
|
13402
|
-
});
|
|
13403
|
-
if (argvUnmatchableAsk(chain, def.name, effective)) return withAdvisory({
|
|
13404
|
-
verdict: "ask",
|
|
13405
|
-
decidedBy: "ask-rule",
|
|
13406
|
-
input: effective
|
|
13407
|
-
});
|
|
13408
|
-
if (chain.canUseTool !== void 0) {
|
|
13409
|
-
const verdict = await chain.canUseTool(def.name, effective, context);
|
|
13410
|
-
if (verdict === "allow") return withAdvisory({
|
|
13411
|
-
verdict: "allow",
|
|
13412
|
-
decidedBy: "canUseTool",
|
|
13413
|
-
input: effective
|
|
13414
|
-
});
|
|
13415
|
-
if (verdict === "deny") return withAdvisory({
|
|
13416
|
-
verdict: "deny",
|
|
13417
|
-
decidedBy: "canUseTool",
|
|
13418
|
-
input: effective
|
|
13419
|
-
});
|
|
13420
|
-
effective = verdict.modifiedInput;
|
|
13421
|
-
}
|
|
13422
|
-
if (def.needsApproval) return withAdvisory({
|
|
13423
|
-
verdict: "ask",
|
|
13424
|
-
decidedBy: "default",
|
|
13425
|
-
input: effective
|
|
13426
|
-
});
|
|
13427
|
-
return withAdvisory({
|
|
13428
|
-
verdict: "allow",
|
|
13429
|
-
decidedBy: "default",
|
|
13430
|
-
input: effective
|
|
13431
|
-
});
|
|
13432
|
-
}
|
|
13433
|
-
//#endregion
|
|
13434
|
-
//#region src/orchestrator/finish-validators.ts
|
|
13435
|
-
/**
|
|
13436
|
-
* Deterministic host validation of the orchestrator finish result (the
|
|
13437
|
-
* v1.40.0 improvement plan's RV-204 slice). A validator is plain
|
|
13438
|
-
* synchronous host code judging the finish({ result }) argument; the
|
|
13439
|
-
* orchestrator runtime runs the configured set on every schema valid
|
|
13440
|
-
* finish call, returns the failure reasons to the model as the call's
|
|
13441
|
-
* error tool result (a bounded repair turn), and fails the run with a
|
|
13442
|
-
* typed error when the repair bound is exhausted. Verdicts journal as
|
|
13443
|
-
* decision entries, so a resume rolls the SAME verdicts forward without
|
|
13444
|
-
* re-running validator code.
|
|
13445
|
-
*/
|
|
13446
|
-
const ok = { ok: true };
|
|
13447
|
-
function requireNonEmptyStrings(values, what) {
|
|
13448
|
-
if (!Array.isArray(values) || values.length === 0) throw new ConfigError(`${what} must be a non empty array of strings`);
|
|
13449
|
-
for (const value of values) if (typeof value !== "string" || value.length === 0) throw new ConfigError(`${what} must contain only non empty strings`);
|
|
13450
|
-
return values;
|
|
13451
|
-
}
|
|
13452
|
-
/**
|
|
13453
|
-
* Requires every named section to appear LITERALLY in the result text
|
|
13454
|
-
* (a heading like 'FINDINGS' or any marker the goal demands). Default
|
|
13455
|
-
* name 'required-sections'; pass `name` to run several instances.
|
|
13456
|
-
*/
|
|
13457
|
-
function requiredSectionsValidator(options) {
|
|
13458
|
-
const sections = requireNonEmptyStrings(options.sections, "requiredSectionsValidator sections");
|
|
13459
|
-
return {
|
|
13460
|
-
name: options.name ?? "required-sections",
|
|
13461
|
-
validate: (input) => {
|
|
13462
|
-
const missing = sections.filter((section) => !input.text.includes(section));
|
|
13463
|
-
return missing.length === 0 ? ok : {
|
|
13464
|
-
ok: false,
|
|
13465
|
-
reasons: missing.map((section) => `required section '${section}' is missing`)
|
|
13466
|
-
};
|
|
13157
|
+
const current = () => als.getStore() ?? rootState;
|
|
13158
|
+
const capsOf = (ref) => {
|
|
13159
|
+
const colon = ref.indexOf(":");
|
|
13160
|
+
const adapterId = ref.slice(0, colon);
|
|
13161
|
+
const adapter = internals.adapters.get(adapterId);
|
|
13162
|
+
if (adapter === void 0) {
|
|
13163
|
+
const registered = [...internals.adapters.keys()].sort();
|
|
13164
|
+
throw new ConfigError(`no adapter registered for '${adapterId}' (ModelRef '${ref}'); registered: ${registered.length === 0 ? "(none)" : registered.join(", ")}. Pass the adapter to createEngine, or route this role to a registered adapter through defaults.routing`);
|
|
13467
13165
|
}
|
|
13166
|
+
return adapter.caps(ref.slice(colon + 1));
|
|
13468
13167
|
};
|
|
13469
|
-
|
|
13470
|
-
|
|
13471
|
-
|
|
13472
|
-
|
|
13473
|
-
* whitespace only string (empty arrays, zero, and false COUNT as
|
|
13474
|
-
* present; emptiness rules beyond strings belong to a custom
|
|
13475
|
-
* validator). Default name 'required-fields'.
|
|
13476
|
-
*/
|
|
13477
|
-
function requiredFieldsValidator(options) {
|
|
13478
|
-
const fields = requireNonEmptyStrings(options.fields, "requiredFieldsValidator fields");
|
|
13479
|
-
return {
|
|
13480
|
-
name: options.name ?? "required-fields",
|
|
13481
|
-
validate: (input) => {
|
|
13482
|
-
const result = input.result;
|
|
13483
|
-
if (typeof result !== "object" || result === null || Array.isArray(result)) return {
|
|
13484
|
-
ok: false,
|
|
13485
|
-
reasons: ["the finish result is not a JSON object"]
|
|
13486
|
-
};
|
|
13487
|
-
const record = result;
|
|
13488
|
-
const reasons = [];
|
|
13489
|
-
for (const field of fields) {
|
|
13490
|
-
const value = record[field];
|
|
13491
|
-
if (value === void 0 || value === null) reasons.push(`required field '${field}' is missing`);
|
|
13492
|
-
else if (typeof value === "string" && value.trim().length === 0) reasons.push(`required field '${field}' is empty`);
|
|
13493
|
-
}
|
|
13494
|
-
return reasons.length === 0 ? ok : {
|
|
13495
|
-
ok: false,
|
|
13496
|
-
reasons
|
|
13497
|
-
};
|
|
13498
|
-
}
|
|
13168
|
+
const adapterOf = (resolved) => {
|
|
13169
|
+
const adapter = internals.adapters.get(resolved.adapterId);
|
|
13170
|
+
if (adapter === void 0) throw new ConfigError(`no adapter registered for '${resolved.adapterId}'`);
|
|
13171
|
+
return adapter;
|
|
13499
13172
|
};
|
|
13500
|
-
|
|
13501
|
-
|
|
13502
|
-
const
|
|
13503
|
-
|
|
13504
|
-
|
|
13505
|
-
|
|
13506
|
-
|
|
13507
|
-
|
|
13508
|
-
|
|
13509
|
-
|
|
13510
|
-
|
|
13511
|
-
|
|
13512
|
-
|
|
13513
|
-
|
|
13514
|
-
|
|
13515
|
-
|
|
13516
|
-
|
|
13517
|
-
|
|
13518
|
-
|
|
13519
|
-
|
|
13520
|
-
|
|
13521
|
-
|
|
13522
|
-
|
|
13523
|
-
|
|
13524
|
-
|
|
13525
|
-
|
|
13526
|
-
|
|
13527
|
-
const pattern = options?.pattern ?? "[\\w./-]+\\.\\w+:\\d+";
|
|
13528
|
-
const flags = options?.flags ?? "";
|
|
13529
|
-
const globalFlags = flags.includes("g") ? flags : `${flags}g`;
|
|
13530
|
-
try {
|
|
13531
|
-
new RegExp(pattern, globalFlags);
|
|
13532
|
-
} catch (thrown) {
|
|
13533
|
-
throw new ConfigError(`evidencePreservedValidator pattern does not compile: ${thrown instanceof Error ? thrown.message : String(thrown)}`);
|
|
13173
|
+
function randValue(subtype, generate, key) {
|
|
13174
|
+
const state = current();
|
|
13175
|
+
const identity = key === void 0 ? {
|
|
13176
|
+
kind: "rand",
|
|
13177
|
+
subtype
|
|
13178
|
+
} : {
|
|
13179
|
+
kind: "rand",
|
|
13180
|
+
subtype,
|
|
13181
|
+
key
|
|
13182
|
+
};
|
|
13183
|
+
const matched = internals.replayer.match(state.scope, identity, "scoped");
|
|
13184
|
+
if (matched.kind === "replay") return matched.terminal.value.value;
|
|
13185
|
+
const value = generate();
|
|
13186
|
+
const payload = {
|
|
13187
|
+
subtype,
|
|
13188
|
+
value
|
|
13189
|
+
};
|
|
13190
|
+
if (key !== void 0) payload.key = key;
|
|
13191
|
+
internals.replayer.appendSinglePhase({
|
|
13192
|
+
scope: state.scope,
|
|
13193
|
+
key: deriveContentKey(identity),
|
|
13194
|
+
kind: "rand",
|
|
13195
|
+
status: "ok",
|
|
13196
|
+
spanId: state.spanId,
|
|
13197
|
+
value: payload
|
|
13198
|
+
});
|
|
13199
|
+
return value;
|
|
13534
13200
|
}
|
|
13535
|
-
|
|
13536
|
-
|
|
13537
|
-
|
|
13538
|
-
|
|
13539
|
-
|
|
13540
|
-
|
|
13541
|
-
|
|
13542
|
-
if (child.status !== "ok") continue;
|
|
13543
|
-
for (const match of child.text.match(new RegExp(pattern, globalFlags)) ?? []) cited.add(match);
|
|
13544
|
-
}
|
|
13545
|
-
const reasons = [];
|
|
13546
|
-
if (cited.size > 0) {
|
|
13547
|
-
const missing = [...cited].filter((citation) => !input.text.includes(citation));
|
|
13548
|
-
const preserved = cited.size - missing.length;
|
|
13549
|
-
if (preserved < Math.ceil(minShare * cited.size - 1e-9)) reasons.push(`evidence preservation ${String(preserved)} of ${String(cited.size)} child citations is below the required share ${String(minShare)}; missing: ${listCitations(missing)}`);
|
|
13550
|
-
}
|
|
13551
|
-
if (options?.requireKnown === true) {
|
|
13552
|
-
const fabricated = [...new Set(input.text.match(new RegExp(pattern, globalFlags)) ?? [])].filter((citation) => !cited.has(citation));
|
|
13553
|
-
if (fabricated.length > 0) reasons.push(`unknown citations not present in any child report: ${listCitations(fabricated)}`);
|
|
13554
|
-
}
|
|
13555
|
-
return reasons.length === 0 ? ok : {
|
|
13556
|
-
ok: false,
|
|
13557
|
-
reasons
|
|
13558
|
-
};
|
|
13201
|
+
async function agentImpl(prompt, opts = {}) {
|
|
13202
|
+
const state = current();
|
|
13203
|
+
const agentType = opts.agentType ?? "";
|
|
13204
|
+
let profile;
|
|
13205
|
+
if (opts.agentType !== void 0) {
|
|
13206
|
+
profile = internals.defaults.profiles?.[opts.agentType];
|
|
13207
|
+
if (profile === void 0) throw new ConfigError(`unknown agentType '${opts.agentType}': register it under defaults.profiles`);
|
|
13559
13208
|
}
|
|
13560
|
-
|
|
13561
|
-
|
|
13562
|
-
|
|
13563
|
-
|
|
13564
|
-
|
|
13565
|
-
|
|
13566
|
-
|
|
13567
|
-
|
|
13568
|
-
|
|
13569
|
-
|
|
13570
|
-
|
|
13571
|
-
|
|
13572
|
-
|
|
13573
|
-
|
|
13574
|
-
|
|
13575
|
-
|
|
13576
|
-
|
|
13577
|
-
|
|
13578
|
-
|
|
13579
|
-
|
|
13580
|
-
|
|
13581
|
-
|
|
13582
|
-
|
|
13583
|
-
|
|
13584
|
-
ok: false,
|
|
13585
|
-
reasons: [`expected at least ${String(options.min)} matches of /${options.pattern}/${flags}; found ${String(found)}`]
|
|
13586
|
-
};
|
|
13587
|
-
}
|
|
13588
|
-
};
|
|
13589
|
-
}
|
|
13590
|
-
//#endregion
|
|
13591
|
-
//#region src/orchestrator/handles.ts
|
|
13592
|
-
/**
|
|
13593
|
-
* The committed WakeDigest render budget (Appendix A: 400
|
|
13594
|
-
* chars per outputSummary row, the character measure; committed at M10
|
|
13595
|
-
* entry by adopting the implemented distillation cap unchanged, the
|
|
13596
|
-
* value frozen into every cassette since M6). One value serves both
|
|
13597
|
-
* stages: the deterministic distillation cap here and the digest
|
|
13598
|
-
* render default in orchestrate (renderBudgetChars).
|
|
13599
|
-
*/
|
|
13600
|
-
const WAKE_SUMMARY_RENDER_BUDGET_CHARS = 400;
|
|
13601
|
-
/**
|
|
13602
|
-
* The M6 outputSummary: a deterministic truncation of the child's
|
|
13603
|
-
* output (or error message), identical live and on replay (distillation
|
|
13604
|
-
* lives with the child, ordered by
|
|
13605
|
-
* spawn ordinal; the LLM distillation upgrade is M7 territory).
|
|
13606
|
-
*/
|
|
13607
|
-
function summarizeOutput(result) {
|
|
13608
|
-
let raw;
|
|
13609
|
-
if (result.status === "ok") raw = typeof result.output === "string" ? result.output : JSON.stringify(result.output ?? null);
|
|
13610
|
-
else {
|
|
13611
|
-
raw = result.errorMessage ?? `terminal status ${result.status}`;
|
|
13612
|
-
if (result.partial !== void 0) raw = `${raw}; partial: ${JSON.stringify(result.partial)}`;
|
|
13613
|
-
}
|
|
13614
|
-
return truncateToBudget(raw, 400);
|
|
13615
|
-
}
|
|
13616
|
-
/** Folds one settled child into its digest (spawn-ordinal ordering is the caller's). */
|
|
13617
|
-
function digestOf(record, result) {
|
|
13618
|
-
return {
|
|
13619
|
-
nodeId: record.nodeId,
|
|
13620
|
-
logicalTaskId: record.logicalTaskId,
|
|
13621
|
-
status: result.status,
|
|
13622
|
-
outputSummary: summarizeOutput(result),
|
|
13623
|
-
costUsd: result.costUsd,
|
|
13624
|
-
artifactsIndex: (result.artifacts ?? []).map((artifact) => artifact.id)
|
|
13625
|
-
};
|
|
13626
|
-
}
|
|
13627
|
-
//#endregion
|
|
13628
|
-
//#region src/orchestrator/wake.ts
|
|
13629
|
-
/** The wait_for_events parameter schema (normative). */
|
|
13630
|
-
const WAIT_FOR_EVENTS_SCHEMA = {
|
|
13631
|
-
type: "object",
|
|
13632
|
-
additionalProperties: false,
|
|
13633
|
-
required: ["triggers"],
|
|
13634
|
-
properties: { triggers: {
|
|
13635
|
-
type: "array",
|
|
13636
|
-
minItems: 1,
|
|
13637
|
-
items: { oneOf: [
|
|
13638
|
-
{
|
|
13639
|
-
type: "object",
|
|
13640
|
-
additionalProperties: false,
|
|
13641
|
-
required: ["kind"],
|
|
13642
|
-
properties: { kind: { const: "quiescence" } }
|
|
13643
|
-
},
|
|
13644
|
-
{
|
|
13645
|
-
type: "object",
|
|
13646
|
-
additionalProperties: false,
|
|
13647
|
-
required: ["kind"],
|
|
13648
|
-
properties: {
|
|
13649
|
-
kind: { const: "child_terminal" },
|
|
13650
|
-
handles: {
|
|
13651
|
-
type: "array",
|
|
13652
|
-
items: {
|
|
13653
|
-
type: "integer",
|
|
13654
|
-
minimum: 1
|
|
13655
|
-
}
|
|
13209
|
+
const runFallbackAttempt = async (targetRef, trigger, decisionSpanId) => {
|
|
13210
|
+
const fallback = opts.fallback;
|
|
13211
|
+
if (internals.replayer.snapshot().find((entry) => {
|
|
13212
|
+
if (entry.kind !== "decision") return false;
|
|
13213
|
+
const value = entry.value;
|
|
13214
|
+
return value?.decisionType === "model.fallback" && value.targetRef === targetRef;
|
|
13215
|
+
}) === void 0) {
|
|
13216
|
+
internals.events.emit({
|
|
13217
|
+
type: "log",
|
|
13218
|
+
level: "warn",
|
|
13219
|
+
msg: `model.fallback: re-attempting on ${fallback.model} after ${trigger}`
|
|
13220
|
+
}, decisionSpanId);
|
|
13221
|
+
await internals.replayer.appendSinglePhase({
|
|
13222
|
+
scope: state.scope,
|
|
13223
|
+
key: "",
|
|
13224
|
+
kind: "decision",
|
|
13225
|
+
status: "ok",
|
|
13226
|
+
spanId: decisionSpanId,
|
|
13227
|
+
value: {
|
|
13228
|
+
decisionType: "model.fallback",
|
|
13229
|
+
targetRef,
|
|
13230
|
+
trigger,
|
|
13231
|
+
model: fallback.model,
|
|
13232
|
+
...internals.pricingVersion === void 0 ? {} : { pricingVersion: internals.pricingVersion }
|
|
13656
13233
|
}
|
|
13657
|
-
}
|
|
13658
|
-
},
|
|
13659
|
-
{
|
|
13660
|
-
type: "object",
|
|
13661
|
-
additionalProperties: false,
|
|
13662
|
-
required: ["kind"],
|
|
13663
|
-
properties: { kind: { const: "escalation" } }
|
|
13664
|
-
},
|
|
13665
|
-
{
|
|
13666
|
-
type: "object",
|
|
13667
|
-
additionalProperties: false,
|
|
13668
|
-
required: ["kind", "percent"],
|
|
13669
|
-
properties: {
|
|
13670
|
-
kind: { const: "budget_threshold" },
|
|
13671
|
-
percent: { enum: [50, 80] }
|
|
13672
|
-
}
|
|
13234
|
+
});
|
|
13673
13235
|
}
|
|
13674
|
-
|
|
13675
|
-
|
|
13676
|
-
|
|
13677
|
-
|
|
13678
|
-
|
|
13679
|
-
|
|
13680
|
-
|
|
13681
|
-
|
|
13682
|
-
|
|
13683
|
-
|
|
13684
|
-
|
|
13685
|
-
|
|
13686
|
-
|
|
13687
|
-
|
|
13688
|
-
|
|
13689
|
-
|
|
13690
|
-
|
|
13691
|
-
|
|
13692
|
-
|
|
13693
|
-
|
|
13694
|
-
|
|
13695
|
-
|
|
13696
|
-
|
|
13697
|
-
|
|
13698
|
-
|
|
13699
|
-
|
|
13700
|
-
|
|
13701
|
-
|
|
13702
|
-
|
|
13703
|
-
|
|
13704
|
-
//#endregion
|
|
13705
|
-
//#region src/orchestrator/spawn-tools.ts
|
|
13706
|
-
/** The spawn_agent parameter schema (normative). */
|
|
13707
|
-
const SPAWN_AGENT_SCHEMA = {
|
|
13708
|
-
type: "object",
|
|
13709
|
-
additionalProperties: false,
|
|
13710
|
-
required: ["agentType", "prompt"],
|
|
13711
|
-
properties: {
|
|
13712
|
-
agentType: { type: "string" },
|
|
13713
|
-
prompt: { type: "string" },
|
|
13714
|
-
outputSchemaRef: { type: "string" },
|
|
13715
|
-
toolsetRef: { type: "string" },
|
|
13716
|
-
budgetUsd: {
|
|
13717
|
-
type: "number",
|
|
13718
|
-
exclusiveMinimum: 0
|
|
13719
|
-
},
|
|
13720
|
-
model_hint: {
|
|
13721
|
-
type: "object",
|
|
13722
|
-
additionalProperties: false,
|
|
13723
|
-
properties: { startTier: {
|
|
13724
|
-
type: "integer",
|
|
13725
|
-
minimum: 0
|
|
13726
|
-
} }
|
|
13727
|
-
},
|
|
13728
|
-
approach: {
|
|
13729
|
-
type: "string",
|
|
13730
|
-
maxLength: 64
|
|
13731
|
-
},
|
|
13732
|
-
lineage: {
|
|
13733
|
-
type: "object",
|
|
13734
|
-
additionalProperties: false,
|
|
13735
|
-
required: ["continues", "causeRef"],
|
|
13736
|
-
properties: {
|
|
13737
|
-
continues: {
|
|
13738
|
-
type: "string",
|
|
13739
|
-
description: "LogicalTaskId to continue"
|
|
13740
|
-
},
|
|
13741
|
-
relation: { enum: [
|
|
13742
|
-
"respawn",
|
|
13743
|
-
"rung-retry",
|
|
13744
|
-
"decompose-child",
|
|
13745
|
-
"unpark-restart"
|
|
13746
|
-
] },
|
|
13747
|
-
causeRef: {
|
|
13748
|
-
type: "integer",
|
|
13749
|
-
minimum: 1,
|
|
13750
|
-
description: "seq of the journal entry that caused the rebirth"
|
|
13751
|
-
}
|
|
13236
|
+
const { fallback: _fallback, routing: _routing, ...rest } = opts;
|
|
13237
|
+
return agentImpl(prompt, {
|
|
13238
|
+
...rest,
|
|
13239
|
+
model: fallback.model
|
|
13240
|
+
});
|
|
13241
|
+
};
|
|
13242
|
+
const isolation = opts.isolation ?? profile?.isolation ?? "none";
|
|
13243
|
+
if (typeof isolation === "object" && isolation.kind === "worktree" && internals.isolation === void 0) throw new ConfigError("worktree isolation requires an IsolationProvider: pass defaults.isolation to createEngine");
|
|
13244
|
+
const floorContext = {
|
|
13245
|
+
...internals.floors === void 0 ? {} : { floors: internals.floors },
|
|
13246
|
+
...profile?.taskClass === void 0 ? {} : { taskClass: profile.taskClass }
|
|
13247
|
+
};
|
|
13248
|
+
const callLayer = {};
|
|
13249
|
+
if (opts.model !== void 0) callLayer.model = opts.model;
|
|
13250
|
+
if (opts.routing !== void 0) callLayer.routing = opts.routing;
|
|
13251
|
+
if (opts.effort !== void 0) callLayer.effort = opts.effort;
|
|
13252
|
+
const profileLayer = {};
|
|
13253
|
+
if (profile?.model !== void 0) profileLayer.model = profile.model;
|
|
13254
|
+
if (profile?.routing !== void 0) profileLayer.routing = profile.routing;
|
|
13255
|
+
if (profile?.effort !== void 0) profileLayer.effort = profile.effort;
|
|
13256
|
+
const engineLayer = {};
|
|
13257
|
+
if (internals.defaults.routing !== void 0) engineLayer.routing = internals.defaults.routing;
|
|
13258
|
+
const workflowLayer = state.workflowLayer;
|
|
13259
|
+
const telemetryNamespace = { agentType };
|
|
13260
|
+
if (opts.label !== void 0) telemetryNamespace.label = opts.label;
|
|
13261
|
+
const withTelemetry = (resolved) => ({
|
|
13262
|
+
...resolved,
|
|
13263
|
+
providerOptions: {
|
|
13264
|
+
...resolved.providerOptions,
|
|
13265
|
+
rulvar: telemetryNamespace
|
|
13752
13266
|
}
|
|
13753
|
-
}
|
|
13754
|
-
|
|
13755
|
-
|
|
13756
|
-
|
|
13757
|
-
|
|
13758
|
-
|
|
13759
|
-
|
|
13760
|
-
|
|
13761
|
-
|
|
13762
|
-
|
|
13763
|
-
|
|
13764
|
-
|
|
13765
|
-
|
|
13766
|
-
|
|
13767
|
-
|
|
13768
|
-
};
|
|
13769
|
-
|
|
13770
|
-
|
|
13771
|
-
|
|
13772
|
-
|
|
13773
|
-
|
|
13774
|
-
properties: { handles: {
|
|
13775
|
-
type: "array",
|
|
13776
|
-
minItems: 1,
|
|
13777
|
-
items: {
|
|
13778
|
-
type: "integer",
|
|
13779
|
-
minimum: 1
|
|
13267
|
+
});
|
|
13268
|
+
const primaryRole = opts.role ?? "loop";
|
|
13269
|
+
const loopResolved = withTelemetry(resolveModelInvocation({
|
|
13270
|
+
role: primaryRole,
|
|
13271
|
+
call: callLayer,
|
|
13272
|
+
profile: profileLayer,
|
|
13273
|
+
workflow: workflowLayer,
|
|
13274
|
+
engine: engineLayer,
|
|
13275
|
+
capsOf,
|
|
13276
|
+
...floorContext
|
|
13277
|
+
}));
|
|
13278
|
+
for (const scrub of loopResolved.scrubs) internals.events.emit({
|
|
13279
|
+
type: "log",
|
|
13280
|
+
level: "warn",
|
|
13281
|
+
msg: scrub.detail
|
|
13282
|
+
}, state.spanId);
|
|
13283
|
+
let canonicalSchema;
|
|
13284
|
+
let derivedSchemaHash = EMPTY_SCHEMA_HASH;
|
|
13285
|
+
if (opts.schema !== void 0) {
|
|
13286
|
+
canonicalSchema = canonicalizeSchema(projectToJsonSchema(opts.schema));
|
|
13287
|
+
derivedSchemaHash = schemaHash(canonicalSchema);
|
|
13780
13288
|
}
|
|
13781
|
-
|
|
13782
|
-
|
|
13783
|
-
|
|
13784
|
-
|
|
13785
|
-
|
|
13786
|
-
|
|
13787
|
-
|
|
13788
|
-
properties: {
|
|
13789
|
-
handle: {
|
|
13790
|
-
type: "integer",
|
|
13791
|
-
minimum: 1
|
|
13792
|
-
},
|
|
13793
|
-
reason: { type: "string" }
|
|
13794
|
-
}
|
|
13795
|
-
};
|
|
13796
|
-
/** Default and hard-max characters per child-result / artifact page. */
|
|
13797
|
-
const DEFAULT_CHILD_RESULT_PAGE_CHARS = 4e3;
|
|
13798
|
-
const MAX_CHILD_RESULT_PAGE_CHARS = 2e4;
|
|
13799
|
-
const PAGING_PROPS = {
|
|
13800
|
-
offset: {
|
|
13801
|
-
type: "integer",
|
|
13802
|
-
minimum: 0
|
|
13803
|
-
},
|
|
13804
|
-
maxChars: {
|
|
13805
|
-
type: "integer",
|
|
13806
|
-
minimum: 1
|
|
13807
|
-
}
|
|
13808
|
-
};
|
|
13809
|
-
const GET_CHILD_RESULT_SCHEMA = {
|
|
13810
|
-
type: "object",
|
|
13811
|
-
additionalProperties: false,
|
|
13812
|
-
required: ["handle"],
|
|
13813
|
-
properties: {
|
|
13814
|
-
handle: {
|
|
13815
|
-
type: "integer",
|
|
13816
|
-
minimum: 1
|
|
13817
|
-
},
|
|
13818
|
-
...PAGING_PROPS
|
|
13819
|
-
}
|
|
13820
|
-
};
|
|
13821
|
-
const READ_CHILD_ARTIFACT_SCHEMA = {
|
|
13822
|
-
type: "object",
|
|
13823
|
-
additionalProperties: false,
|
|
13824
|
-
required: ["handle", "artifactId"],
|
|
13825
|
-
properties: {
|
|
13826
|
-
handle: {
|
|
13827
|
-
type: "integer",
|
|
13828
|
-
minimum: 1
|
|
13829
|
-
},
|
|
13830
|
-
artifactId: { type: "string" },
|
|
13831
|
-
...PAGING_PROPS
|
|
13832
|
-
}
|
|
13833
|
-
};
|
|
13834
|
-
const GET_CHILD_RESULT_TOOL_NAME = "get_child_result";
|
|
13835
|
-
const READ_CHILD_ARTIFACT_TOOL_NAME = "read_child_artifact";
|
|
13836
|
-
/** finish; result validates against the declared output schema. */
|
|
13837
|
-
const FINISH_SCHEMA = {
|
|
13838
|
-
type: "object",
|
|
13839
|
-
additionalProperties: false,
|
|
13840
|
-
required: ["result"],
|
|
13841
|
-
properties: {
|
|
13842
|
-
result: { $comment: "validated against the declared output SchemaSpec of the orchestrate call; free-form JSON when none is declared" },
|
|
13843
|
-
summary: { type: "string" }
|
|
13844
|
-
}
|
|
13845
|
-
};
|
|
13846
|
-
const FINISH_TOOL_NAME = "finish";
|
|
13847
|
-
/**
|
|
13848
|
-
* Builds the mode (c) toolset over the per-call runtime. profileCardText
|
|
13849
|
-
* rides the spawn tools' descriptions so both modes speak one agent
|
|
13850
|
-
* vocabulary (M6-T04).
|
|
13851
|
-
*/
|
|
13852
|
-
function buildOrchestratorTools(runtime, profileCardText, options) {
|
|
13853
|
-
const spawnAgent = tool({
|
|
13854
|
-
name: "spawn_agent",
|
|
13855
|
-
description: `Admit and schedule one child agent. ${profileCardText}`,
|
|
13856
|
-
parameters: SPAWN_AGENT_SCHEMA,
|
|
13857
|
-
execute: (input) => runtime.spawn(input)
|
|
13858
|
-
});
|
|
13859
|
-
const parallelAgents = tool({
|
|
13860
|
-
name: "parallel_agents",
|
|
13861
|
-
description: "Admit and schedule several children at once (submission order).",
|
|
13862
|
-
parameters: PARALLEL_AGENTS_SCHEMA,
|
|
13863
|
-
execute: async (input) => {
|
|
13864
|
-
const tasks = input.tasks;
|
|
13865
|
-
const handles = [];
|
|
13866
|
-
for (const task of tasks) {
|
|
13867
|
-
const spawned = await runtime.spawn(task);
|
|
13868
|
-
handles.push(spawned.handle);
|
|
13869
|
-
}
|
|
13870
|
-
return { handles };
|
|
13289
|
+
const escalation = opts.escalation ?? profile?.escalation;
|
|
13290
|
+
if (escalation !== void 0) {
|
|
13291
|
+
if (escalation.flavor === "B" && escalation.deadlineMs === void 0) throw new ConfigError("flavor 'B' escalation requires an explicit deadlineMs: the suspension deadline has no engine default");
|
|
13292
|
+
if (escalation.deadlineMs !== void 0) requirePositiveInteger(escalation.deadlineMs, "escalation.deadlineMs");
|
|
13293
|
+
if (escalation.minSpendUsd !== void 0) requireNonNegativeNumber(escalation.minSpendUsd, "escalation.minSpendUsd");
|
|
13294
|
+
if (opts.result !== "full" && internals.onEscalation === void 0) throw new ConfigError("a spawn that opts into escalation from a plain value-form call needs an onEscalation hook (or use result: 'full')");
|
|
13295
|
+
if (escalation.flavor === "B" && escalation.deadlineMs === void 0) throw new ConfigError("escalation flavor 'B' requires an explicit deadlineMs");
|
|
13871
13296
|
}
|
|
13872
|
-
|
|
13873
|
-
|
|
13874
|
-
|
|
13875
|
-
|
|
13876
|
-
|
|
13877
|
-
|
|
13878
|
-
|
|
13879
|
-
|
|
13880
|
-
|
|
13881
|
-
|
|
13882
|
-
|
|
13883
|
-
|
|
13884
|
-
|
|
13885
|
-
|
|
13886
|
-
|
|
13887
|
-
|
|
13888
|
-
|
|
13889
|
-
|
|
13890
|
-
|
|
13891
|
-
|
|
13297
|
+
const declaredTools = opts.tools ?? profile?.tools ?? [];
|
|
13298
|
+
const toolset = await resolveToolset(escalation === void 0 ? declaredTools : [...declaredTools, escalateTool()], { runId: internals.runId }, internals.defaults.toolsets, internals.executors === void 0 ? void 0 : new Set(Object.keys(internals.executors)));
|
|
13299
|
+
const layers = [
|
|
13300
|
+
callLayer,
|
|
13301
|
+
profileLayer,
|
|
13302
|
+
engineLayer
|
|
13303
|
+
];
|
|
13304
|
+
const toolsAvailable = toolset.contracts.length > 0;
|
|
13305
|
+
const finalizeRouted = roleConfiguredInRouting("finalize", layers);
|
|
13306
|
+
let extract;
|
|
13307
|
+
if (opts.schema !== void 0 && canonicalSchema !== void 0) {
|
|
13308
|
+
const extractResolved = withTelemetry(resolveModelInvocation({
|
|
13309
|
+
role: "extract",
|
|
13310
|
+
call: callLayer,
|
|
13311
|
+
profile: profileLayer,
|
|
13312
|
+
workflow: workflowLayer,
|
|
13313
|
+
engine: engineLayer,
|
|
13314
|
+
capsOf,
|
|
13315
|
+
...floorContext
|
|
13316
|
+
}));
|
|
13317
|
+
const loopTier = selectStructuredOutputTier(capsOf(loopResolved.ref), canonicalSchema);
|
|
13318
|
+
if (needsSeparateExtract({
|
|
13319
|
+
schemaSet: true,
|
|
13320
|
+
loopRef: loopResolved.ref,
|
|
13321
|
+
extractRef: extractResolved.ref,
|
|
13322
|
+
loopTier,
|
|
13323
|
+
toolsAvailable,
|
|
13324
|
+
finalizeRouted
|
|
13325
|
+
})) {
|
|
13326
|
+
extract = {
|
|
13327
|
+
adapter: adapterOf(extractResolved),
|
|
13328
|
+
resolved: extractResolved
|
|
13329
|
+
};
|
|
13330
|
+
for (const scrub of extractResolved.scrubs) internals.events.emit({
|
|
13331
|
+
type: "log",
|
|
13332
|
+
level: "warn",
|
|
13333
|
+
msg: scrub.detail
|
|
13334
|
+
}, state.spanId);
|
|
13335
|
+
}
|
|
13892
13336
|
}
|
|
13893
|
-
|
|
13894
|
-
|
|
13895
|
-
|
|
13896
|
-
|
|
13897
|
-
|
|
13898
|
-
|
|
13899
|
-
|
|
13900
|
-
|
|
13901
|
-
|
|
13902
|
-
|
|
13903
|
-
|
|
13904
|
-
|
|
13905
|
-
|
|
13337
|
+
let finalize;
|
|
13338
|
+
if (finalizeFires({
|
|
13339
|
+
routed: finalizeRouted,
|
|
13340
|
+
toolsAvailable
|
|
13341
|
+
})) {
|
|
13342
|
+
const finalizeResolved = withTelemetry(resolveModelInvocation({
|
|
13343
|
+
role: "finalize",
|
|
13344
|
+
call: callLayer,
|
|
13345
|
+
profile: profileLayer,
|
|
13346
|
+
workflow: workflowLayer,
|
|
13347
|
+
engine: engineLayer,
|
|
13348
|
+
capsOf,
|
|
13349
|
+
...floorContext
|
|
13350
|
+
}));
|
|
13351
|
+
finalize = {
|
|
13352
|
+
adapter: adapterOf(finalizeResolved),
|
|
13353
|
+
resolved: finalizeResolved
|
|
13354
|
+
};
|
|
13355
|
+
for (const scrub of finalizeResolved.scrubs) internals.events.emit({
|
|
13356
|
+
type: "log",
|
|
13357
|
+
level: "warn",
|
|
13358
|
+
msg: scrub.detail
|
|
13359
|
+
}, state.spanId);
|
|
13906
13360
|
}
|
|
13907
|
-
|
|
13908
|
-
|
|
13909
|
-
|
|
13910
|
-
|
|
13911
|
-
|
|
13912
|
-
|
|
13913
|
-
|
|
13914
|
-
|
|
13915
|
-
|
|
13916
|
-
|
|
13917
|
-
|
|
13918
|
-
|
|
13919
|
-
|
|
13920
|
-
|
|
13921
|
-
|
|
13922
|
-
|
|
13923
|
-
|
|
13924
|
-
|
|
13361
|
+
let summarizeResolved;
|
|
13362
|
+
try {
|
|
13363
|
+
summarizeResolved = resolveModelInvocation({
|
|
13364
|
+
role: "summarize",
|
|
13365
|
+
call: callLayer,
|
|
13366
|
+
profile: profileLayer,
|
|
13367
|
+
workflow: workflowLayer,
|
|
13368
|
+
engine: engineLayer,
|
|
13369
|
+
capsOf,
|
|
13370
|
+
...floorContext
|
|
13371
|
+
});
|
|
13372
|
+
} catch {
|
|
13373
|
+
summarizeResolved = resolveModelInvocation({
|
|
13374
|
+
role: "summarize",
|
|
13375
|
+
call: callLayer,
|
|
13376
|
+
profile: profileLayer,
|
|
13377
|
+
workflow: workflowLayer,
|
|
13378
|
+
engine: {
|
|
13379
|
+
...engineLayer,
|
|
13380
|
+
model: loopResolved.ref
|
|
13381
|
+
},
|
|
13382
|
+
capsOf,
|
|
13383
|
+
...floorContext
|
|
13925
13384
|
});
|
|
13926
13385
|
}
|
|
13927
|
-
|
|
13928
|
-
|
|
13929
|
-
|
|
13930
|
-
parameters: READ_CHILD_ARTIFACT_SCHEMA,
|
|
13931
|
-
execute: (input) => {
|
|
13932
|
-
const p = input;
|
|
13933
|
-
return runtime.readChildArtifact(p.handle, p.artifactId, {
|
|
13934
|
-
offset: p.offset,
|
|
13935
|
-
maxChars: p.maxChars
|
|
13936
|
-
});
|
|
13937
|
-
}
|
|
13938
|
-
}));
|
|
13939
|
-
tools.push(finish);
|
|
13940
|
-
return tools;
|
|
13941
|
-
}
|
|
13942
|
-
//#endregion
|
|
13943
|
-
//#region src/engine/internal.ts
|
|
13944
|
-
/** Registered by createCtx; keyed by the ctx object identity. */
|
|
13945
|
-
const ctxRuntimes = /* @__PURE__ */ new WeakMap();
|
|
13946
|
-
/**
|
|
13947
|
-
* Internal AgentOpts channel (M6-T07): agentImpl reports the agent
|
|
13948
|
-
* dispatch seq (the spawn handle) through this symbol-keyed callback on
|
|
13949
|
-
* the running append, on a dangling redispatch, AND on the replay
|
|
13950
|
-
* branch, so the orchestrator learns handles that are stable across
|
|
13951
|
-
* resume. Never part of the public AgentOpts surface.
|
|
13952
|
-
*/
|
|
13953
|
-
const kOnRunning = Symbol("rulvar.onRunning");
|
|
13954
|
-
/**
|
|
13955
|
-
* Internal AgentOpts channel (M6-T07): names the terminal tool whose
|
|
13956
|
-
* accepted call ends the loop with status ok (the orchestrator finish
|
|
13957
|
-
* tool), plus the optional host validation hook over the accepted call
|
|
13958
|
-
* (the RV-204 finish validators). Never part of the public AgentOpts
|
|
13959
|
-
* surface.
|
|
13960
|
-
*/
|
|
13961
|
-
const kTerminalTool = Symbol("rulvar.terminalTool");
|
|
13962
|
-
/**
|
|
13963
|
-
* Internal AgentOpts channel (M7-T08): a transcript checkpoint ref the
|
|
13964
|
-
* fresh dispatch boots from (park/unpark continuation and the DEF-5
|
|
13965
|
-
* graft boot). Dangling redispatch checkpoints take precedence.
|
|
13966
|
-
*/
|
|
13967
|
-
const kBootCheckpoint = Symbol("rulvar.bootCheckpoint");
|
|
13968
|
-
/**
|
|
13969
|
-
* Internal AgentOpts channel: marks the orchestrator forced-finish
|
|
13970
|
-
* dispatch, whose spend draws from the released finalize reserve
|
|
13971
|
-
* (DEF-7). Settlement stamps the flag into the terminal's cost
|
|
13972
|
-
* attribution so the journal fold reproduces reserveUsedUsd.
|
|
13973
|
-
*/
|
|
13974
|
-
const kFinalizeReserve = Symbol("rulvar.finalizeReserve");
|
|
13975
|
-
/** Typed accessor used by the in-package consumers. */
|
|
13976
|
-
function runtimeOf(ctx) {
|
|
13977
|
-
const runtime = ctxRuntimes.get(ctx);
|
|
13978
|
-
if (runtime === void 0) throw new Error("ctx runtime missing: the ctx value was not created by createCtx (engine run context)");
|
|
13979
|
-
return runtime;
|
|
13980
|
-
}
|
|
13981
|
-
//#endregion
|
|
13982
|
-
//#region src/engine/spawn-events.ts
|
|
13983
|
-
function emitSpawnAdmitted(events, input) {
|
|
13984
|
-
events.emit({
|
|
13985
|
-
type: "spawn:admitted",
|
|
13986
|
-
entryRef: input.entryRef,
|
|
13987
|
-
verdict: input.verdict,
|
|
13988
|
-
agentType: input.agentType,
|
|
13989
|
-
logicalTaskId: input.logicalTaskId,
|
|
13990
|
-
...input.spawnUnitsAfter === void 0 ? {} : { spawnUnitsAfter: input.spawnUnitsAfter }
|
|
13991
|
-
}, input.spanId, input.replayed);
|
|
13992
|
-
}
|
|
13993
|
-
function emitSpawnRejected(events, input) {
|
|
13994
|
-
events.emit({
|
|
13995
|
-
type: "spawn:rejected",
|
|
13996
|
-
...input.entryRef === void 0 ? {} : { entryRef: input.entryRef },
|
|
13997
|
-
code: input.code,
|
|
13998
|
-
agentType: input.agentType
|
|
13999
|
-
}, input.spanId, input.replayed);
|
|
14000
|
-
}
|
|
14001
|
-
//#endregion
|
|
14002
|
-
//#region src/l0/long-timer.ts
|
|
14003
|
-
/**
|
|
14004
|
-
* Sliced timers for absolute wall-clock deadlines (v1.34.0 review P2-2).
|
|
14005
|
-
*
|
|
14006
|
-
* Node clamps a setTimeout delay above 2147483647 ms (about 24.8 days)
|
|
14007
|
-
* to 1 ms, so a naive timer for a far-future deadline fires immediately.
|
|
14008
|
-
* setLongTimeout never hands Node more than one MAX_TIMER_DELAY_MS
|
|
14009
|
-
* slice, re-checks the wall clock when a slice fires, and re-arms until
|
|
14010
|
-
* the clock actually reaches the deadline: firing a slice is never taken
|
|
14011
|
-
* as proof the deadline arrived. A deadline already in the past fires on
|
|
14012
|
-
* the next macrotask (delay 0), matching the plain setTimeout behavior
|
|
14013
|
-
* the callers had for near deadlines.
|
|
14014
|
-
*/
|
|
14015
|
-
/**
|
|
14016
|
-
* Schedules `onDue` for the absolute wall-clock instant `dueAtMs` as
|
|
14017
|
-
* reported by `now` (default Date.now), slicing delays beyond the Node
|
|
14018
|
-
* timer maximum.
|
|
14019
|
-
*/
|
|
14020
|
-
function setLongTimeout(onDue, dueAtMs, now = Date.now) {
|
|
14021
|
-
let handle;
|
|
14022
|
-
let cancelled = false;
|
|
14023
|
-
const arm = () => {
|
|
14024
|
-
const remaining = Math.max(0, dueAtMs - now());
|
|
14025
|
-
handle = setTimeout(() => {
|
|
14026
|
-
if (cancelled) return;
|
|
14027
|
-
if (now() >= dueAtMs) {
|
|
14028
|
-
onDue();
|
|
14029
|
-
return;
|
|
14030
|
-
}
|
|
14031
|
-
arm();
|
|
14032
|
-
}, Math.min(remaining, MAX_TIMER_DELAY_MS));
|
|
14033
|
-
};
|
|
14034
|
-
arm();
|
|
14035
|
-
return { cancel: () => {
|
|
14036
|
-
cancelled = true;
|
|
14037
|
-
if (handle !== void 0) clearTimeout(handle);
|
|
14038
|
-
} };
|
|
14039
|
-
}
|
|
14040
|
-
//#endregion
|
|
14041
|
-
//#region src/runtime/executor.ts
|
|
14042
|
-
/**
|
|
14043
|
-
* Isolated-executor dispatch helpers (RV-216). The engine routes a
|
|
14044
|
-
* non-inprocess tool call through the registered ToolExecutorProvider;
|
|
14045
|
-
* this module derives the stable per-call idempotency key the provider
|
|
14046
|
-
* receives, so an at-least-once retry of a side-effecting tool can be
|
|
14047
|
-
* folded into effectively-once.
|
|
14048
|
-
*
|
|
14049
|
-
* Public contract: https://docs.rulvar.com/guide/isolated-executor.
|
|
14050
|
-
*/
|
|
14051
|
-
/**
|
|
14052
|
-
* Derives the idempotency key for one isolated tool dispatch. The key is
|
|
14053
|
-
* a pure function of the run, the LOGICAL INVOCATION (the seq of the
|
|
14054
|
-
* containing agent's journal entry plus that call's ordinal within the
|
|
14055
|
-
* agent's tool loop), the tool name, and the JCS-canonical arguments.
|
|
14056
|
-
*
|
|
14057
|
-
* The logical-invocation component is what makes the key both stable and
|
|
14058
|
-
* distinguishing (v1.59.x review P0.4): the agent-entry seq and the
|
|
14059
|
-
* per-agent tool-call ordinal are journal- and checkpoint-stable, so a
|
|
14060
|
-
* crash-and-resume re-dispatch of the SAME logical call (the at-least-
|
|
14061
|
-
* once window between execution and the turn checkpoint) reuses the same
|
|
14062
|
-
* dispatch entry and the restored ordinal, and therefore the same key;
|
|
14063
|
-
* while two SEPARATE calls in one run, even with byte-identical
|
|
14064
|
-
* arguments, occupy different ordinals and never collide. Without it two
|
|
14065
|
-
* intended effects sharing arguments would fold into one under external
|
|
14066
|
-
* deduplication.
|
|
14067
|
-
*
|
|
14068
|
-
* The key never enters run identity (it is absent from every content key
|
|
14069
|
-
* and toolset hash); it exists only for the provider's own side-effect
|
|
14070
|
-
* deduplication.
|
|
14071
|
-
*/
|
|
14072
|
-
function deriveExecIdempotencyKey(runId, agentSeq, ordinal, tool, args) {
|
|
14073
|
-
const canonical = jcsSerialize({
|
|
14074
|
-
runId,
|
|
14075
|
-
agentSeq,
|
|
14076
|
-
ordinal,
|
|
14077
|
-
tool,
|
|
14078
|
-
args
|
|
14079
|
-
});
|
|
14080
|
-
return createHash("sha256").update(canonical, "utf8").digest("hex");
|
|
14081
|
-
}
|
|
14082
|
-
//#endregion
|
|
14083
|
-
//#region src/engine/ctx.ts
|
|
14084
|
-
/**
|
|
14085
|
-
* Ctx primitives (M1-T07) plus the parallel/pipeline composition semantics
|
|
14086
|
-
* of the scheduler (M1-T08): the canonical authoring surface bound to the
|
|
14087
|
-
* journal write path, the model router, the agent runtime, and the
|
|
14088
|
-
* three-layer budget. M1 ships agent, parallel, pipeline, step, phase,
|
|
14089
|
-
* log, budget, and the deterministic shims; workflow/orchestrate/
|
|
14090
|
-
* awaitExternal/brief land with their milestones (M2/M6).
|
|
14091
|
-
*
|
|
14092
|
-
* Public contract: https://docs.rulvar.com/guide/workflows.
|
|
14093
|
-
*/
|
|
14094
|
-
/**
|
|
14095
|
-
* The rejection carrier of ctx.agent value-form calls: a real Error that
|
|
14096
|
-
* structurally satisfies the typed AgentError and carries the full
|
|
14097
|
-
* AgentResult for Settled mapping. Deliberately not a RulvarError:
|
|
14098
|
-
* AgentError is not in the closed code registry.
|
|
14099
|
-
*/
|
|
14100
|
-
var AgentCallError = class extends Error {
|
|
14101
|
-
kind;
|
|
14102
|
-
retryable;
|
|
14103
|
-
retryAfterMs;
|
|
14104
|
-
issues;
|
|
14105
|
-
result;
|
|
14106
|
-
scope;
|
|
14107
|
-
entryRef;
|
|
14108
|
-
constructor(message, result, scope, entryRef) {
|
|
14109
|
-
super(message);
|
|
14110
|
-
this.name = "AgentCallError";
|
|
14111
|
-
const error = result.error ?? {
|
|
14112
|
-
kind: "terminal",
|
|
14113
|
-
retryable: false
|
|
13386
|
+
const summarize = {
|
|
13387
|
+
adapter: adapterOf(summarizeResolved),
|
|
13388
|
+
resolved: withTelemetry(summarizeResolved)
|
|
14114
13389
|
};
|
|
14115
|
-
|
|
14116
|
-
|
|
14117
|
-
|
|
14118
|
-
|
|
14119
|
-
|
|
14120
|
-
|
|
14121
|
-
|
|
14122
|
-
|
|
14123
|
-
|
|
14124
|
-
|
|
14125
|
-
|
|
14126
|
-
|
|
14127
|
-
|
|
14128
|
-
|
|
14129
|
-
|
|
14130
|
-
|
|
14131
|
-
|
|
14132
|
-
|
|
14133
|
-
|
|
14134
|
-
|
|
14135
|
-
|
|
14136
|
-
}, result.errorMessage ?? fallbackMessage);
|
|
14137
|
-
if (result.abortClass === void 0) return wire;
|
|
14138
|
-
const data = typeof wire.data === "object" && wire.data !== null && !Array.isArray(wire.data) ? wire.data : {};
|
|
14139
|
-
return {
|
|
14140
|
-
...wire,
|
|
14141
|
-
data: {
|
|
14142
|
-
...data,
|
|
14143
|
-
abortClass: result.abortClass
|
|
13390
|
+
const failoverChainFor = (role, resolved) => (resolved.fallbacks ?? []).map((ref) => {
|
|
13391
|
+
const fallbackLayer = { model: ref };
|
|
13392
|
+
if (callLayer.effort !== void 0) fallbackLayer.effort = callLayer.effort;
|
|
13393
|
+
const fallbackResolved = withTelemetry(resolveModelInvocation({
|
|
13394
|
+
role,
|
|
13395
|
+
call: fallbackLayer,
|
|
13396
|
+
profile: profileLayer,
|
|
13397
|
+
workflow: workflowLayer,
|
|
13398
|
+
engine: engineLayer,
|
|
13399
|
+
capsOf,
|
|
13400
|
+
...floorContext
|
|
13401
|
+
}));
|
|
13402
|
+
return {
|
|
13403
|
+
adapter: adapterOf(fallbackResolved),
|
|
13404
|
+
resolved: fallbackResolved
|
|
13405
|
+
};
|
|
13406
|
+
});
|
|
13407
|
+
const loopFallbacks = failoverChainFor(primaryRole, loopResolved);
|
|
13408
|
+
if (extract !== void 0) {
|
|
13409
|
+
const chain = failoverChainFor("extract", extract.resolved);
|
|
13410
|
+
if (chain.length > 0) extract.fallbacks = chain;
|
|
14144
13411
|
}
|
|
14145
|
-
|
|
14146
|
-
|
|
14147
|
-
|
|
14148
|
-
function workflowLayerOf(wf) {
|
|
14149
|
-
const layer = {};
|
|
14150
|
-
if (wf.model !== void 0) layer.model = wf.model;
|
|
14151
|
-
if (wf.routing !== void 0) layer.routing = wf.routing;
|
|
14152
|
-
if (wf.effort !== void 0) layer.effort = wf.effort;
|
|
14153
|
-
return Object.keys(layer).length === 0 ? void 0 : layer;
|
|
14154
|
-
}
|
|
14155
|
-
function defineWorkflow(meta, body) {
|
|
14156
|
-
const wf = {
|
|
14157
|
-
kind: "workflow",
|
|
14158
|
-
name: meta.name,
|
|
14159
|
-
errorPolicy: meta.errorPolicy ?? "strict",
|
|
14160
|
-
...meta.model === void 0 ? {} : { model: meta.model },
|
|
14161
|
-
...meta.routing === void 0 ? {} : { routing: meta.routing },
|
|
14162
|
-
...meta.effort === void 0 ? {} : { effort: meta.effort },
|
|
14163
|
-
body
|
|
14164
|
-
};
|
|
14165
|
-
if (meta.args !== void 0) return {
|
|
14166
|
-
...wf,
|
|
14167
|
-
argsSchema: meta.args
|
|
14168
|
-
};
|
|
14169
|
-
return wf;
|
|
14170
|
-
}
|
|
14171
|
-
function bump(map, key, usd) {
|
|
14172
|
-
map.set(key, (map.get(key) ?? 0) + usd);
|
|
14173
|
-
}
|
|
14174
|
-
/**
|
|
14175
|
-
* Completes a model-authored escalation request into the full report:
|
|
14176
|
-
* costToDate and salvage are runtime-filled, never model-filled. The
|
|
14177
|
-
* worktree patch ref lands after collect(); the pre-dispose preview for
|
|
14178
|
-
* flavor B decision-makers omits it.
|
|
14179
|
-
*/
|
|
14180
|
-
function buildEscalationReport(request, result, worktreePatchRef) {
|
|
14181
|
-
return {
|
|
14182
|
-
kind: request.kind,
|
|
14183
|
-
scopeDelta: request.scopeDelta,
|
|
14184
|
-
revisedEstimate: request.revisedEstimate,
|
|
14185
|
-
blockers: request.blockers ?? [],
|
|
14186
|
-
proposedDecomposition: request.proposedDecomposition ?? [],
|
|
14187
|
-
costToDate: {
|
|
14188
|
-
usd: result.costUsd,
|
|
14189
|
-
turns: result.turns
|
|
14190
|
-
},
|
|
14191
|
-
salvage: {
|
|
14192
|
-
transcriptRef: result.transcriptRef,
|
|
14193
|
-
artifacts: (result.artifacts ?? []).map((artifact) => artifact.id),
|
|
14194
|
-
...worktreePatchRef === void 0 ? {} : { worktreePatchRef }
|
|
13412
|
+
if (finalize !== void 0) {
|
|
13413
|
+
const chain = failoverChainFor("finalize", finalize.resolved);
|
|
13414
|
+
if (chain.length > 0) finalize.fallbacks = chain;
|
|
14195
13415
|
}
|
|
14196
|
-
|
|
14197
|
-
|
|
14198
|
-
|
|
14199
|
-
* Creates the per-run Ctx bound to `internals`. The current scope travels
|
|
14200
|
-
* through AsyncLocalStorage so parallel branches and pipeline stages keep
|
|
14201
|
-
* one ctx object while journaling under their own scope paths (I3:
|
|
14202
|
-
* structure from call-and-return only).
|
|
14203
|
-
*/
|
|
14204
|
-
function createCtx(internals, rootWorkflow) {
|
|
14205
|
-
const als = new AsyncLocalStorage();
|
|
14206
|
-
const sites = new ParallelSiteCounter();
|
|
14207
|
-
const rootWorkflowLayer = rootWorkflow === void 0 ? void 0 : workflowLayerOf(rootWorkflow);
|
|
14208
|
-
const rootState = {
|
|
14209
|
-
scope: "",
|
|
14210
|
-
spanId: internals.rootSpanId,
|
|
14211
|
-
...rootWorkflowLayer === void 0 ? {} : { workflowLayer: rootWorkflowLayer }
|
|
14212
|
-
};
|
|
14213
|
-
const current = () => als.getStore() ?? rootState;
|
|
14214
|
-
const capsOf = (ref) => {
|
|
14215
|
-
const colon = ref.indexOf(":");
|
|
14216
|
-
const adapterId = ref.slice(0, colon);
|
|
14217
|
-
const adapter = internals.adapters.get(adapterId);
|
|
14218
|
-
if (adapter === void 0) {
|
|
14219
|
-
const registered = [...internals.adapters.keys()].sort();
|
|
14220
|
-
throw new ConfigError(`no adapter registered for '${adapterId}' (ModelRef '${ref}'); registered: ${registered.length === 0 ? "(none)" : registered.join(", ")}. Pass the adapter to createEngine, or route this role to a registered adapter through defaults.routing`);
|
|
13416
|
+
{
|
|
13417
|
+
const chain = failoverChainFor("summarize", summarizeResolved);
|
|
13418
|
+
if (chain.length > 0) summarize.fallbacks = chain;
|
|
14221
13419
|
}
|
|
14222
|
-
|
|
14223
|
-
|
|
14224
|
-
|
|
14225
|
-
|
|
14226
|
-
|
|
14227
|
-
|
|
14228
|
-
|
|
14229
|
-
|
|
14230
|
-
|
|
14231
|
-
|
|
14232
|
-
|
|
14233
|
-
|
|
14234
|
-
} : {
|
|
14235
|
-
kind: "rand",
|
|
14236
|
-
subtype,
|
|
14237
|
-
key
|
|
14238
|
-
};
|
|
14239
|
-
const matched = internals.replayer.match(state.scope, identity, "scoped");
|
|
14240
|
-
if (matched.kind === "replay") return matched.terminal.value.value;
|
|
14241
|
-
const value = generate();
|
|
14242
|
-
const payload = {
|
|
14243
|
-
subtype,
|
|
14244
|
-
value
|
|
13420
|
+
const retryPolicy = opts.retry ?? profile?.retry ?? internals.defaults.retry;
|
|
13421
|
+
if (retryPolicy !== void 0) validateRetryPolicy(retryPolicy, opts.retry !== void 0 ? "the agent retry option" : profile?.retry !== void 0 ? `the retry of profile '${String(opts.agentType)}'` : "engine defaults.retry");
|
|
13422
|
+
if (opts.estCost !== void 0) requireNonNegativeNumber(opts.estCost, "the agent estCost option");
|
|
13423
|
+
if (opts.limits !== void 0) validateUsageLimits(opts.limits, "the agent limits option");
|
|
13424
|
+
const identityInput = {
|
|
13425
|
+
kind: "agent",
|
|
13426
|
+
agentType,
|
|
13427
|
+
modelSpec: loopResolved.canonical,
|
|
13428
|
+
prompt: opts.key ?? prompt,
|
|
13429
|
+
schemaHash: derivedSchemaHash,
|
|
13430
|
+
toolsetHash: toolset.hash,
|
|
13431
|
+
isolation
|
|
14245
13432
|
};
|
|
14246
|
-
|
|
14247
|
-
internals.replayer.
|
|
14248
|
-
|
|
14249
|
-
|
|
14250
|
-
kind
|
|
14251
|
-
|
|
14252
|
-
|
|
14253
|
-
|
|
14254
|
-
|
|
14255
|
-
|
|
14256
|
-
|
|
14257
|
-
|
|
14258
|
-
|
|
14259
|
-
|
|
14260
|
-
|
|
14261
|
-
|
|
14262
|
-
|
|
14263
|
-
|
|
14264
|
-
|
|
14265
|
-
|
|
14266
|
-
|
|
14267
|
-
|
|
14268
|
-
|
|
14269
|
-
|
|
14270
|
-
|
|
14271
|
-
|
|
14272
|
-
|
|
14273
|
-
|
|
14274
|
-
|
|
14275
|
-
|
|
14276
|
-
|
|
14277
|
-
|
|
14278
|
-
|
|
14279
|
-
|
|
14280
|
-
|
|
14281
|
-
|
|
14282
|
-
|
|
14283
|
-
|
|
14284
|
-
|
|
14285
|
-
|
|
14286
|
-
|
|
14287
|
-
|
|
14288
|
-
|
|
13433
|
+
const identityKey = deriveContentKey(identityInput);
|
|
13434
|
+
const matched = internals.replayer.match(state.scope, identityInput, opts.replay ?? "scoped");
|
|
13435
|
+
if (matched.kind === "replay" || matched.kind === "skip") {
|
|
13436
|
+
opts[kOnRunning]?.(matched.running.seq);
|
|
13437
|
+
const terminal = matched.kind === "replay" ? matched.terminal : matched.terminal;
|
|
13438
|
+
const spanId = internals.spans.mint(state.spanId);
|
|
13439
|
+
const usage = terminal?.usage ?? {
|
|
13440
|
+
inputTokens: 0,
|
|
13441
|
+
outputTokens: 0,
|
|
13442
|
+
cacheReadTokens: 0,
|
|
13443
|
+
cacheWriteTokens: 0
|
|
13444
|
+
};
|
|
13445
|
+
const replayPriced = terminal === void 0 ? void 0 : priceEntryUsage(terminal, (ref, sliceUsage) => internals.priceUsd(ref, sliceUsage));
|
|
13446
|
+
const costUsd = replayPriced?.usd ?? 0;
|
|
13447
|
+
const result = {
|
|
13448
|
+
status: matched.kind === "skip" ? "skipped" : terminal?.status ?? "ok",
|
|
13449
|
+
output: matched.kind === "skip" ? null : terminal?.value ?? null,
|
|
13450
|
+
usage,
|
|
13451
|
+
costUsd,
|
|
13452
|
+
servedBy: terminal?.servedBy ?? loopResolved.ref,
|
|
13453
|
+
turns: 0,
|
|
13454
|
+
transcriptRef: terminal?.transcriptRef ?? ""
|
|
13455
|
+
};
|
|
13456
|
+
if (terminal?.error !== void 0) {
|
|
13457
|
+
result.error = agentErrorFromWire(terminal.error);
|
|
13458
|
+
result.errorMessage = terminal.error.message;
|
|
13459
|
+
}
|
|
13460
|
+
if (terminal?.artifacts !== void 0) result.artifacts = terminal.artifacts;
|
|
13461
|
+
if (terminal?.providerCalls !== void 0) result.providerCalls = terminal.providerCalls;
|
|
13462
|
+
if (terminal?.status === "escalated" && terminal.escalation !== void 0) result.escalation = terminal.escalation;
|
|
13463
|
+
{
|
|
13464
|
+
const stampedData = terminal?.error?.data;
|
|
13465
|
+
if (stampedData?.abortClass !== void 0) result.abortClass = stampedData.abortClass;
|
|
13466
|
+
if (stampedData?.exploration !== void 0) result.exploration = stampedData.exploration;
|
|
13467
|
+
}
|
|
13468
|
+
let replayedToolResults = [];
|
|
13469
|
+
if (matched.kind === "replay" && terminal?.checkpointRef !== void 0) {
|
|
13470
|
+
const blob = await internals.transcripts.get(terminal.checkpointRef);
|
|
13471
|
+
const checkpoint = blob === null ? void 0 : decodeCheckpoint(blob);
|
|
13472
|
+
if (checkpoint !== void 0) {
|
|
13473
|
+
result.turns = checkpoint.turns;
|
|
13474
|
+
if (result.status === "limit") {
|
|
13475
|
+
const partialReport = latestProgressReport(checkpoint.messages);
|
|
13476
|
+
if (partialReport !== void 0) result.partial = partialReport;
|
|
14289
13477
|
}
|
|
14290
|
-
|
|
13478
|
+
replayedToolResults = checkpoint.messages.filter((msg) => msg.role === "tool").flatMap((msg) => msg.parts).filter((part) => part.type === "tool-result").map((part) => ({
|
|
13479
|
+
name: part.name,
|
|
13480
|
+
isError: part.isError === true
|
|
13481
|
+
}));
|
|
13482
|
+
}
|
|
14291
13483
|
}
|
|
14292
|
-
|
|
14293
|
-
|
|
14294
|
-
|
|
14295
|
-
|
|
14296
|
-
|
|
14297
|
-
|
|
14298
|
-
|
|
14299
|
-
|
|
14300
|
-
|
|
14301
|
-
|
|
14302
|
-
|
|
14303
|
-
|
|
14304
|
-
|
|
14305
|
-
|
|
14306
|
-
|
|
14307
|
-
|
|
14308
|
-
|
|
14309
|
-
|
|
14310
|
-
if (profile?.routing !== void 0) profileLayer.routing = profile.routing;
|
|
14311
|
-
if (profile?.effort !== void 0) profileLayer.effort = profile.effort;
|
|
14312
|
-
const engineLayer = {};
|
|
14313
|
-
if (internals.defaults.routing !== void 0) engineLayer.routing = internals.defaults.routing;
|
|
14314
|
-
const workflowLayer = state.workflowLayer;
|
|
14315
|
-
const telemetryNamespace = { agentType };
|
|
14316
|
-
if (opts.label !== void 0) telemetryNamespace.label = opts.label;
|
|
14317
|
-
const withTelemetry = (resolved) => ({
|
|
14318
|
-
...resolved,
|
|
14319
|
-
providerOptions: {
|
|
14320
|
-
...resolved.providerOptions,
|
|
14321
|
-
rulvar: telemetryNamespace
|
|
14322
|
-
}
|
|
14323
|
-
});
|
|
14324
|
-
const primaryRole = opts.role ?? "loop";
|
|
14325
|
-
const loopResolved = withTelemetry(resolveModelInvocation({
|
|
14326
|
-
role: primaryRole,
|
|
14327
|
-
call: callLayer,
|
|
14328
|
-
profile: profileLayer,
|
|
14329
|
-
workflow: workflowLayer,
|
|
14330
|
-
engine: engineLayer,
|
|
14331
|
-
capsOf,
|
|
14332
|
-
...floorContext
|
|
14333
|
-
}));
|
|
14334
|
-
for (const scrub of loopResolved.scrubs) internals.events.emit({
|
|
14335
|
-
type: "log",
|
|
14336
|
-
level: "warn",
|
|
14337
|
-
msg: scrub.detail
|
|
14338
|
-
}, state.spanId);
|
|
14339
|
-
let canonicalSchema;
|
|
14340
|
-
let derivedSchemaHash = EMPTY_SCHEMA_HASH;
|
|
14341
|
-
if (opts.schema !== void 0) {
|
|
14342
|
-
canonicalSchema = canonicalizeSchema(projectToJsonSchema(opts.schema));
|
|
14343
|
-
derivedSchemaHash = schemaHash(canonicalSchema);
|
|
14344
|
-
}
|
|
14345
|
-
const escalation = opts.escalation ?? profile?.escalation;
|
|
14346
|
-
if (escalation !== void 0) {
|
|
14347
|
-
if (escalation.flavor === "B" && escalation.deadlineMs === void 0) throw new ConfigError("flavor 'B' escalation requires an explicit deadlineMs: the suspension deadline has no engine default");
|
|
14348
|
-
if (escalation.deadlineMs !== void 0) requirePositiveInteger(escalation.deadlineMs, "escalation.deadlineMs");
|
|
14349
|
-
if (escalation.minSpendUsd !== void 0) requireNonNegativeNumber(escalation.minSpendUsd, "escalation.minSpendUsd");
|
|
14350
|
-
if (opts.result !== "full" && internals.onEscalation === void 0) throw new ConfigError("a spawn that opts into escalation from a plain value-form call needs an onEscalation hook (or use result: 'full')");
|
|
14351
|
-
if (escalation.flavor === "B" && escalation.deadlineMs === void 0) throw new ConfigError("escalation flavor 'B' requires an explicit deadlineMs");
|
|
14352
|
-
}
|
|
14353
|
-
const declaredTools = opts.tools ?? profile?.tools ?? [];
|
|
14354
|
-
const toolset = await resolveToolset(escalation === void 0 ? declaredTools : [...declaredTools, escalateTool()], { runId: internals.runId }, internals.defaults.toolsets, internals.executors === void 0 ? void 0 : new Set(Object.keys(internals.executors)));
|
|
14355
|
-
const layers = [
|
|
14356
|
-
callLayer,
|
|
14357
|
-
profileLayer,
|
|
14358
|
-
engineLayer
|
|
14359
|
-
];
|
|
14360
|
-
const toolsAvailable = toolset.contracts.length > 0;
|
|
14361
|
-
const finalizeRouted = roleConfiguredInRouting("finalize", layers);
|
|
14362
|
-
let extract;
|
|
14363
|
-
if (opts.schema !== void 0 && canonicalSchema !== void 0) {
|
|
14364
|
-
const extractResolved = withTelemetry(resolveModelInvocation({
|
|
14365
|
-
role: "extract",
|
|
14366
|
-
call: callLayer,
|
|
14367
|
-
profile: profileLayer,
|
|
14368
|
-
workflow: workflowLayer,
|
|
14369
|
-
engine: engineLayer,
|
|
14370
|
-
capsOf,
|
|
14371
|
-
...floorContext
|
|
14372
|
-
}));
|
|
14373
|
-
const loopTier = selectStructuredOutputTier(capsOf(loopResolved.ref), canonicalSchema);
|
|
14374
|
-
if (needsSeparateExtract({
|
|
14375
|
-
schemaSet: true,
|
|
14376
|
-
loopRef: loopResolved.ref,
|
|
14377
|
-
extractRef: extractResolved.ref,
|
|
14378
|
-
loopTier,
|
|
14379
|
-
toolsAvailable,
|
|
14380
|
-
finalizeRouted
|
|
14381
|
-
})) {
|
|
14382
|
-
extract = {
|
|
14383
|
-
adapter: adapterOf(extractResolved),
|
|
14384
|
-
resolved: extractResolved
|
|
14385
|
-
};
|
|
14386
|
-
for (const scrub of extractResolved.scrubs) internals.events.emit({
|
|
14387
|
-
type: "log",
|
|
14388
|
-
level: "warn",
|
|
14389
|
-
msg: scrub.detail
|
|
14390
|
-
}, state.spanId);
|
|
14391
|
-
}
|
|
14392
|
-
}
|
|
14393
|
-
let finalize;
|
|
14394
|
-
if (finalizeFires({
|
|
14395
|
-
routed: finalizeRouted,
|
|
14396
|
-
toolsAvailable
|
|
14397
|
-
})) {
|
|
14398
|
-
const finalizeResolved = withTelemetry(resolveModelInvocation({
|
|
14399
|
-
role: "finalize",
|
|
14400
|
-
call: callLayer,
|
|
14401
|
-
profile: profileLayer,
|
|
14402
|
-
workflow: workflowLayer,
|
|
14403
|
-
engine: engineLayer,
|
|
14404
|
-
capsOf,
|
|
14405
|
-
...floorContext
|
|
14406
|
-
}));
|
|
14407
|
-
finalize = {
|
|
14408
|
-
adapter: adapterOf(finalizeResolved),
|
|
14409
|
-
resolved: finalizeResolved
|
|
14410
|
-
};
|
|
14411
|
-
for (const scrub of finalizeResolved.scrubs) internals.events.emit({
|
|
14412
|
-
type: "log",
|
|
14413
|
-
level: "warn",
|
|
14414
|
-
msg: scrub.detail
|
|
14415
|
-
}, state.spanId);
|
|
14416
|
-
}
|
|
14417
|
-
let summarizeResolved;
|
|
14418
|
-
try {
|
|
14419
|
-
summarizeResolved = resolveModelInvocation({
|
|
14420
|
-
role: "summarize",
|
|
14421
|
-
call: callLayer,
|
|
14422
|
-
profile: profileLayer,
|
|
14423
|
-
workflow: workflowLayer,
|
|
14424
|
-
engine: engineLayer,
|
|
14425
|
-
capsOf,
|
|
14426
|
-
...floorContext
|
|
14427
|
-
});
|
|
14428
|
-
} catch {
|
|
14429
|
-
summarizeResolved = resolveModelInvocation({
|
|
14430
|
-
role: "summarize",
|
|
14431
|
-
call: callLayer,
|
|
14432
|
-
profile: profileLayer,
|
|
14433
|
-
workflow: workflowLayer,
|
|
14434
|
-
engine: {
|
|
14435
|
-
...engineLayer,
|
|
14436
|
-
model: loopResolved.ref
|
|
14437
|
-
},
|
|
14438
|
-
capsOf,
|
|
14439
|
-
...floorContext
|
|
14440
|
-
});
|
|
14441
|
-
}
|
|
14442
|
-
const summarize = {
|
|
14443
|
-
adapter: adapterOf(summarizeResolved),
|
|
14444
|
-
resolved: withTelemetry(summarizeResolved)
|
|
14445
|
-
};
|
|
14446
|
-
const failoverChainFor = (role, resolved) => (resolved.fallbacks ?? []).map((ref) => {
|
|
14447
|
-
const fallbackLayer = { model: ref };
|
|
14448
|
-
if (callLayer.effort !== void 0) fallbackLayer.effort = callLayer.effort;
|
|
14449
|
-
const fallbackResolved = withTelemetry(resolveModelInvocation({
|
|
14450
|
-
role,
|
|
14451
|
-
call: fallbackLayer,
|
|
14452
|
-
profile: profileLayer,
|
|
14453
|
-
workflow: workflowLayer,
|
|
14454
|
-
engine: engineLayer,
|
|
14455
|
-
capsOf,
|
|
14456
|
-
...floorContext
|
|
14457
|
-
}));
|
|
14458
|
-
return {
|
|
14459
|
-
adapter: adapterOf(fallbackResolved),
|
|
14460
|
-
resolved: fallbackResolved
|
|
14461
|
-
};
|
|
14462
|
-
});
|
|
14463
|
-
const loopFallbacks = failoverChainFor(primaryRole, loopResolved);
|
|
14464
|
-
if (extract !== void 0) {
|
|
14465
|
-
const chain = failoverChainFor("extract", extract.resolved);
|
|
14466
|
-
if (chain.length > 0) extract.fallbacks = chain;
|
|
14467
|
-
}
|
|
14468
|
-
if (finalize !== void 0) {
|
|
14469
|
-
const chain = failoverChainFor("finalize", finalize.resolved);
|
|
14470
|
-
if (chain.length > 0) finalize.fallbacks = chain;
|
|
14471
|
-
}
|
|
14472
|
-
{
|
|
14473
|
-
const chain = failoverChainFor("summarize", summarizeResolved);
|
|
14474
|
-
if (chain.length > 0) summarize.fallbacks = chain;
|
|
14475
|
-
}
|
|
14476
|
-
const retryPolicy = opts.retry ?? profile?.retry ?? internals.defaults.retry;
|
|
14477
|
-
if (retryPolicy !== void 0) validateRetryPolicy(retryPolicy, opts.retry !== void 0 ? "the agent retry option" : profile?.retry !== void 0 ? `the retry of profile '${String(opts.agentType)}'` : "engine defaults.retry");
|
|
14478
|
-
if (opts.estCost !== void 0) requireNonNegativeNumber(opts.estCost, "the agent estCost option");
|
|
14479
|
-
if (opts.limits !== void 0) validateUsageLimits(opts.limits, "the agent limits option");
|
|
14480
|
-
const identityInput = {
|
|
14481
|
-
kind: "agent",
|
|
14482
|
-
agentType,
|
|
14483
|
-
modelSpec: loopResolved.canonical,
|
|
14484
|
-
prompt: opts.key ?? prompt,
|
|
14485
|
-
schemaHash: derivedSchemaHash,
|
|
14486
|
-
toolsetHash: toolset.hash,
|
|
14487
|
-
isolation
|
|
14488
|
-
};
|
|
14489
|
-
const identityKey = deriveContentKey(identityInput);
|
|
14490
|
-
const matched = internals.replayer.match(state.scope, identityInput, opts.replay ?? "scoped");
|
|
14491
|
-
if (matched.kind === "replay" || matched.kind === "skip") {
|
|
14492
|
-
opts[kOnRunning]?.(matched.running.seq);
|
|
14493
|
-
const terminal = matched.kind === "replay" ? matched.terminal : matched.terminal;
|
|
14494
|
-
const spanId = internals.spans.mint(state.spanId);
|
|
14495
|
-
const usage = terminal?.usage ?? {
|
|
14496
|
-
inputTokens: 0,
|
|
14497
|
-
outputTokens: 0,
|
|
14498
|
-
cacheReadTokens: 0,
|
|
14499
|
-
cacheWriteTokens: 0
|
|
14500
|
-
};
|
|
14501
|
-
const replayPriced = terminal === void 0 ? void 0 : priceEntryUsage(terminal, (ref, sliceUsage) => internals.priceUsd(ref, sliceUsage));
|
|
14502
|
-
const costUsd = replayPriced?.usd ?? 0;
|
|
14503
|
-
const result = {
|
|
14504
|
-
status: matched.kind === "skip" ? "skipped" : terminal?.status ?? "ok",
|
|
14505
|
-
output: matched.kind === "skip" ? null : terminal?.value ?? null,
|
|
14506
|
-
usage,
|
|
14507
|
-
costUsd,
|
|
14508
|
-
servedBy: terminal?.servedBy ?? loopResolved.ref,
|
|
14509
|
-
turns: 0,
|
|
14510
|
-
transcriptRef: terminal?.transcriptRef ?? ""
|
|
14511
|
-
};
|
|
14512
|
-
if (terminal?.error !== void 0) {
|
|
14513
|
-
result.error = agentErrorFromWire(terminal.error);
|
|
14514
|
-
result.errorMessage = terminal.error.message;
|
|
14515
|
-
}
|
|
14516
|
-
if (terminal?.artifacts !== void 0) result.artifacts = terminal.artifacts;
|
|
14517
|
-
if (terminal?.providerCalls !== void 0) result.providerCalls = terminal.providerCalls;
|
|
14518
|
-
if (terminal?.status === "escalated" && terminal.escalation !== void 0) result.escalation = terminal.escalation;
|
|
14519
|
-
{
|
|
14520
|
-
const stampedData = terminal?.error?.data;
|
|
14521
|
-
if (stampedData?.abortClass !== void 0) result.abortClass = stampedData.abortClass;
|
|
14522
|
-
if (stampedData?.exploration !== void 0) result.exploration = stampedData.exploration;
|
|
14523
|
-
}
|
|
14524
|
-
let replayedToolResults = [];
|
|
14525
|
-
if (matched.kind === "replay" && terminal?.checkpointRef !== void 0) {
|
|
14526
|
-
const blob = await internals.transcripts.get(terminal.checkpointRef);
|
|
14527
|
-
const checkpoint = blob === null ? void 0 : decodeCheckpoint(blob);
|
|
14528
|
-
if (checkpoint !== void 0) {
|
|
14529
|
-
result.turns = checkpoint.turns;
|
|
14530
|
-
if (result.status === "limit") {
|
|
14531
|
-
const partialReport = latestProgressReport(checkpoint.messages);
|
|
14532
|
-
if (partialReport !== void 0) result.partial = partialReport;
|
|
14533
|
-
}
|
|
14534
|
-
replayedToolResults = checkpoint.messages.filter((msg) => msg.role === "tool").flatMap((msg) => msg.parts).filter((part) => part.type === "tool-result").map((part) => ({
|
|
14535
|
-
name: part.name,
|
|
14536
|
-
isError: part.isError === true
|
|
14537
|
-
}));
|
|
14538
|
-
}
|
|
14539
|
-
}
|
|
14540
|
-
internals.events.emit({
|
|
14541
|
-
type: "agent:start",
|
|
14542
|
-
agentType,
|
|
14543
|
-
label: opts.label,
|
|
14544
|
-
model: loopResolved.ref,
|
|
14545
|
-
role: primaryRole
|
|
14546
|
-
}, spanId, true);
|
|
14547
|
-
for (const toolResult of replayedToolResults) {
|
|
14548
|
-
internals.events.emit({
|
|
14549
|
-
type: "tool:start",
|
|
14550
|
-
toolName: toolResult.name
|
|
14551
|
-
}, spanId, true);
|
|
14552
|
-
internals.events.emit({
|
|
14553
|
-
type: "tool:end",
|
|
14554
|
-
toolName: toolResult.name,
|
|
14555
|
-
outcome: toolResult.isError ? "error" : "ok",
|
|
14556
|
-
durationMs: 0
|
|
14557
|
-
}, spanId, true);
|
|
13484
|
+
internals.events.emit({
|
|
13485
|
+
type: "agent:start",
|
|
13486
|
+
agentType,
|
|
13487
|
+
label: opts.label,
|
|
13488
|
+
model: loopResolved.ref,
|
|
13489
|
+
role: primaryRole
|
|
13490
|
+
}, spanId, true);
|
|
13491
|
+
for (const toolResult of replayedToolResults) {
|
|
13492
|
+
internals.events.emit({
|
|
13493
|
+
type: "tool:start",
|
|
13494
|
+
toolName: toolResult.name
|
|
13495
|
+
}, spanId, true);
|
|
13496
|
+
internals.events.emit({
|
|
13497
|
+
type: "tool:end",
|
|
13498
|
+
toolName: toolResult.name,
|
|
13499
|
+
outcome: toolResult.isError ? "error" : "ok",
|
|
13500
|
+
durationMs: 0
|
|
13501
|
+
}, spanId, true);
|
|
14558
13502
|
}
|
|
14559
13503
|
if (terminal !== void 0) entryUsageSlices(terminal).forEach((slice, index) => {
|
|
14560
13504
|
const priced = internals.priceUsd(slice.servedBy, slice.usage) ?? 0;
|
|
@@ -15724,216 +14668,592 @@ function dedupeRepeatedClaims(rows) {
|
|
|
15724
14668
|
};
|
|
15725
14669
|
}
|
|
15726
14670
|
//#endregion
|
|
15727
|
-
//#region src/orchestrator/
|
|
14671
|
+
//#region src/orchestrator/handles.ts
|
|
15728
14672
|
/**
|
|
15729
|
-
* The
|
|
15730
|
-
*
|
|
15731
|
-
*
|
|
15732
|
-
*
|
|
15733
|
-
*
|
|
15734
|
-
*
|
|
15735
|
-
* AdmissionController clamps depth and budget for free.
|
|
15736
|
-
*
|
|
15737
|
-
* Resume semantics (the M6 gate): orchestrator turns checkpoint at every
|
|
15738
|
-
* turn boundary (mandatory for the orchestrate role); every spawn is an
|
|
15739
|
-
* ordinary kind 'agent' entry; a crashed orchestrate() restores its
|
|
15740
|
-
* history from the checkpoint and finds child results by content keys,
|
|
15741
|
-
* WITHOUT regenerating spawn decisions and without re-paying children.
|
|
15742
|
-
* Non-PlanRunner applicability: only the lifetime
|
|
15743
|
-
* cap, maxDepth, and the budget layers apply; no termination.init is
|
|
15744
|
-
* written; escalated children simply settle into their digests.
|
|
14673
|
+
* The committed WakeDigest render budget (Appendix A: 400
|
|
14674
|
+
* chars per outputSummary row, the character measure; committed at M10
|
|
14675
|
+
* entry by adopting the implemented distillation cap unchanged, the
|
|
14676
|
+
* value frozen into every cassette since M6). One value serves both
|
|
14677
|
+
* stages: the deterministic distillation cap here and the digest
|
|
14678
|
+
* render default in orchestrate (renderBudgetChars).
|
|
15745
14679
|
*/
|
|
15746
|
-
|
|
15747
|
-
const DEFAULT_FINISH_MAX_REPAIRS = 1;
|
|
14680
|
+
const WAKE_SUMMARY_RENDER_BUDGET_CHARS = 400;
|
|
15748
14681
|
/**
|
|
15749
|
-
*
|
|
15750
|
-
*
|
|
14682
|
+
* The M6 outputSummary: a deterministic truncation of the child's
|
|
14683
|
+
* output (or error message), identical live and on replay (distillation
|
|
14684
|
+
* lives with the child, ordered by
|
|
14685
|
+
* spawn ordinal; the LLM distillation upgrade is M7 territory).
|
|
15751
14686
|
*/
|
|
15752
|
-
|
|
15753
|
-
|
|
15754
|
-
|
|
15755
|
-
|
|
15756
|
-
|
|
15757
|
-
|
|
15758
|
-
const
|
|
15759
|
-
|
|
15760
|
-
|
|
15761
|
-
|
|
15762
|
-
|
|
15763
|
-
|
|
15764
|
-
|
|
15765
|
-
|
|
15766
|
-
|
|
15767
|
-
function pageOf(content, rawOffset, rawMaxChars) {
|
|
15768
|
-
const totalChars = content.length;
|
|
15769
|
-
const offset = Math.min(Math.max(0, Math.trunc(rawOffset ?? 0)), totalChars);
|
|
15770
|
-
const end = Math.min(offset + Math.min(Math.max(1, Math.trunc(rawMaxChars ?? 4e3)), MAX_CHILD_RESULT_PAGE_CHARS), totalChars);
|
|
14687
|
+
function summarizeOutput(result) {
|
|
14688
|
+
let raw;
|
|
14689
|
+
if (result.status === "ok") raw = typeof result.output === "string" ? result.output : JSON.stringify(result.output ?? null);
|
|
14690
|
+
else {
|
|
14691
|
+
raw = result.errorMessage ?? `terminal status ${result.status}`;
|
|
14692
|
+
if (result.status === "limit" && result.output !== null && result.output !== void 0) {
|
|
14693
|
+
const final = typeof result.output === "string" ? result.output : JSON.stringify(result.output);
|
|
14694
|
+
raw = `${raw}; final: ${final}`;
|
|
14695
|
+
}
|
|
14696
|
+
if (result.partial !== void 0) raw = `${raw}; partial: ${JSON.stringify(result.partial)}`;
|
|
14697
|
+
}
|
|
14698
|
+
return truncateToBudget(raw, 400);
|
|
14699
|
+
}
|
|
14700
|
+
/** Folds one settled child into its digest (spawn-ordinal ordering is the caller's). */
|
|
14701
|
+
function digestOf(record, result) {
|
|
15771
14702
|
return {
|
|
15772
|
-
|
|
15773
|
-
|
|
15774
|
-
|
|
15775
|
-
|
|
14703
|
+
nodeId: record.nodeId,
|
|
14704
|
+
logicalTaskId: record.logicalTaskId,
|
|
14705
|
+
status: result.status,
|
|
14706
|
+
outputSummary: summarizeOutput(result),
|
|
14707
|
+
costUsd: result.costUsd,
|
|
14708
|
+
artifactsIndex: (result.artifacts ?? []).map((artifact) => artifact.id)
|
|
15776
14709
|
};
|
|
15777
14710
|
}
|
|
15778
|
-
|
|
15779
|
-
|
|
15780
|
-
|
|
15781
|
-
|
|
15782
|
-
|
|
15783
|
-
|
|
15784
|
-
|
|
15785
|
-
|
|
15786
|
-
|
|
15787
|
-
|
|
15788
|
-
|
|
14711
|
+
//#endregion
|
|
14712
|
+
//#region src/orchestrator/wake.ts
|
|
14713
|
+
/** The wait_for_events parameter schema (normative). */
|
|
14714
|
+
const WAIT_FOR_EVENTS_SCHEMA = {
|
|
14715
|
+
type: "object",
|
|
14716
|
+
additionalProperties: false,
|
|
14717
|
+
required: ["triggers"],
|
|
14718
|
+
properties: { triggers: {
|
|
14719
|
+
type: "array",
|
|
14720
|
+
minItems: 1,
|
|
14721
|
+
items: { oneOf: [
|
|
14722
|
+
{
|
|
14723
|
+
type: "object",
|
|
14724
|
+
additionalProperties: false,
|
|
14725
|
+
required: ["kind"],
|
|
14726
|
+
properties: { kind: { const: "quiescence" } }
|
|
14727
|
+
},
|
|
14728
|
+
{
|
|
14729
|
+
type: "object",
|
|
14730
|
+
additionalProperties: false,
|
|
14731
|
+
required: ["kind"],
|
|
14732
|
+
properties: {
|
|
14733
|
+
kind: { const: "child_terminal" },
|
|
14734
|
+
handles: {
|
|
14735
|
+
type: "array",
|
|
14736
|
+
items: {
|
|
14737
|
+
type: "integer",
|
|
14738
|
+
minimum: 1
|
|
14739
|
+
}
|
|
14740
|
+
}
|
|
14741
|
+
}
|
|
14742
|
+
},
|
|
14743
|
+
{
|
|
14744
|
+
type: "object",
|
|
14745
|
+
additionalProperties: false,
|
|
14746
|
+
required: ["kind"],
|
|
14747
|
+
properties: { kind: { const: "escalation" } }
|
|
14748
|
+
},
|
|
14749
|
+
{
|
|
14750
|
+
type: "object",
|
|
14751
|
+
additionalProperties: false,
|
|
14752
|
+
required: ["kind", "percent"],
|
|
14753
|
+
properties: {
|
|
14754
|
+
kind: { const: "budget_threshold" },
|
|
14755
|
+
percent: { enum: [50, 80] }
|
|
14756
|
+
}
|
|
14757
|
+
}
|
|
14758
|
+
] }
|
|
14759
|
+
} }
|
|
14760
|
+
};
|
|
14761
|
+
const WAIT_FOR_EVENTS_TOOL_NAME = "wait_for_events";
|
|
14762
|
+
/** The all-zero blocks of runs without the PlanRunner extension. */
|
|
14763
|
+
function emptyDigestBlocks() {
|
|
14764
|
+
return {
|
|
14765
|
+
planHash: "",
|
|
14766
|
+
termination: {
|
|
14767
|
+
revisionUnitsRemaining: 0,
|
|
14768
|
+
spawnUnitsRemaining: 0,
|
|
14769
|
+
perLineage: {},
|
|
14770
|
+
phi: 0
|
|
14771
|
+
},
|
|
14772
|
+
budget: {
|
|
14773
|
+
runSpentUsd: 0,
|
|
14774
|
+
runCeilingUsd: 0,
|
|
14775
|
+
orchestratorSpentUsd: 0,
|
|
14776
|
+
orchestratorCapUsd: 0,
|
|
14777
|
+
finalizeReserveUsd: 0,
|
|
14778
|
+
orchestratorShare: 0,
|
|
14779
|
+
softWarning: false
|
|
14780
|
+
},
|
|
14781
|
+
reuse: {
|
|
14782
|
+
abandonedUsd: 0,
|
|
14783
|
+
reclaimedUsd: 0,
|
|
14784
|
+
netLostUsd: 0
|
|
14785
|
+
}
|
|
14786
|
+
};
|
|
15789
14787
|
}
|
|
15790
|
-
|
|
15791
|
-
|
|
15792
|
-
|
|
15793
|
-
|
|
15794
|
-
|
|
15795
|
-
|
|
15796
|
-
|
|
15797
|
-
|
|
15798
|
-
|
|
15799
|
-
|
|
15800
|
-
|
|
15801
|
-
|
|
15802
|
-
|
|
15803
|
-
|
|
15804
|
-
|
|
15805
|
-
|
|
15806
|
-
|
|
15807
|
-
|
|
15808
|
-
|
|
15809
|
-
|
|
15810
|
-
|
|
14788
|
+
//#endregion
|
|
14789
|
+
//#region src/orchestrator/spawn-tools.ts
|
|
14790
|
+
/** The spawn_agent parameter schema (normative). */
|
|
14791
|
+
const SPAWN_AGENT_SCHEMA = {
|
|
14792
|
+
type: "object",
|
|
14793
|
+
additionalProperties: false,
|
|
14794
|
+
required: ["agentType", "prompt"],
|
|
14795
|
+
properties: {
|
|
14796
|
+
agentType: { type: "string" },
|
|
14797
|
+
prompt: { type: "string" },
|
|
14798
|
+
outputSchemaRef: { type: "string" },
|
|
14799
|
+
toolsetRef: { type: "string" },
|
|
14800
|
+
budgetUsd: {
|
|
14801
|
+
type: "number",
|
|
14802
|
+
exclusiveMinimum: 0
|
|
14803
|
+
},
|
|
14804
|
+
model_hint: {
|
|
14805
|
+
type: "object",
|
|
14806
|
+
additionalProperties: false,
|
|
14807
|
+
properties: { startTier: {
|
|
14808
|
+
type: "integer",
|
|
14809
|
+
minimum: 0
|
|
14810
|
+
} }
|
|
14811
|
+
},
|
|
14812
|
+
approach: {
|
|
14813
|
+
type: "string",
|
|
14814
|
+
maxLength: 64
|
|
14815
|
+
},
|
|
14816
|
+
lineage: {
|
|
14817
|
+
type: "object",
|
|
14818
|
+
additionalProperties: false,
|
|
14819
|
+
required: ["continues", "causeRef"],
|
|
14820
|
+
properties: {
|
|
14821
|
+
continues: {
|
|
14822
|
+
type: "string",
|
|
14823
|
+
description: "LogicalTaskId to continue"
|
|
14824
|
+
},
|
|
14825
|
+
relation: { enum: [
|
|
14826
|
+
"respawn",
|
|
14827
|
+
"rung-retry",
|
|
14828
|
+
"decompose-child",
|
|
14829
|
+
"unpark-restart"
|
|
14830
|
+
] },
|
|
14831
|
+
causeRef: {
|
|
14832
|
+
type: "integer",
|
|
14833
|
+
minimum: 1,
|
|
14834
|
+
description: "seq of the journal entry that caused the rebirth"
|
|
14835
|
+
}
|
|
14836
|
+
}
|
|
14837
|
+
},
|
|
14838
|
+
taskClass: { type: "string" }
|
|
15811
14839
|
}
|
|
15812
|
-
|
|
15813
|
-
|
|
15814
|
-
|
|
15815
|
-
|
|
15816
|
-
|
|
15817
|
-
|
|
15818
|
-
|
|
15819
|
-
|
|
15820
|
-
|
|
15821
|
-
|
|
14840
|
+
};
|
|
14841
|
+
/** parallel_agents wraps the spawn_agent params. */
|
|
14842
|
+
const PARALLEL_AGENTS_SCHEMA = {
|
|
14843
|
+
type: "object",
|
|
14844
|
+
additionalProperties: false,
|
|
14845
|
+
required: ["tasks"],
|
|
14846
|
+
properties: { tasks: {
|
|
14847
|
+
type: "array",
|
|
14848
|
+
minItems: 1,
|
|
14849
|
+
items: { $ref: "#/$defs/spawnAgentParams" }
|
|
14850
|
+
} },
|
|
14851
|
+
$defs: { spawnAgentParams: SPAWN_AGENT_SCHEMA }
|
|
14852
|
+
};
|
|
14853
|
+
/** await_any and await_all share one parameter shape. */
|
|
14854
|
+
const AWAIT_SCHEMA = {
|
|
14855
|
+
type: "object",
|
|
14856
|
+
additionalProperties: false,
|
|
14857
|
+
required: ["handles"],
|
|
14858
|
+
properties: { handles: {
|
|
14859
|
+
type: "array",
|
|
14860
|
+
minItems: 1,
|
|
14861
|
+
items: {
|
|
14862
|
+
type: "integer",
|
|
14863
|
+
minimum: 1
|
|
15822
14864
|
}
|
|
15823
|
-
|
|
14865
|
+
} }
|
|
14866
|
+
};
|
|
14867
|
+
/** The cancel_agent parameter schema. */
|
|
14868
|
+
const CANCEL_AGENT_SCHEMA = {
|
|
14869
|
+
type: "object",
|
|
14870
|
+
additionalProperties: false,
|
|
14871
|
+
required: ["handle"],
|
|
14872
|
+
properties: {
|
|
14873
|
+
handle: {
|
|
14874
|
+
type: "integer",
|
|
14875
|
+
minimum: 1
|
|
14876
|
+
},
|
|
14877
|
+
reason: { type: "string" }
|
|
15824
14878
|
}
|
|
15825
|
-
|
|
15826
|
-
|
|
15827
|
-
|
|
15828
|
-
|
|
15829
|
-
|
|
15830
|
-
|
|
15831
|
-
|
|
15832
|
-
|
|
15833
|
-
|
|
15834
|
-
|
|
15835
|
-
|
|
15836
|
-
|
|
15837
|
-
].includes(synthesis.effort)) throw new ConfigError(`orchestrate synthesis.effort must be one of 'low' | 'medium' | 'high' | 'xhigh' | 'max'; got ${JSON.stringify(synthesis.effort)}`);
|
|
15838
|
-
if (synthesis.limits !== void 0) validateUsageLimits(synthesis.limits, "orchestrate synthesis.limits");
|
|
15839
|
-
if (synthesis.instructions !== void 0 && typeof synthesis.instructions !== "string") throw new ConfigError(`orchestrate synthesis.instructions must be a string; got ${typeof synthesis.instructions}`);
|
|
15840
|
-
if (synthesis.estCost !== void 0) requireNonNegativeNumber(synthesis.estCost, "orchestrate synthesis.estCost");
|
|
14879
|
+
};
|
|
14880
|
+
/** Default and hard-max characters per child-result / artifact page. */
|
|
14881
|
+
const DEFAULT_CHILD_RESULT_PAGE_CHARS = 4e3;
|
|
14882
|
+
const MAX_CHILD_RESULT_PAGE_CHARS = 2e4;
|
|
14883
|
+
const PAGING_PROPS = {
|
|
14884
|
+
offset: {
|
|
14885
|
+
type: "integer",
|
|
14886
|
+
minimum: 0
|
|
14887
|
+
},
|
|
14888
|
+
maxChars: {
|
|
14889
|
+
type: "integer",
|
|
14890
|
+
minimum: 1
|
|
15841
14891
|
}
|
|
15842
|
-
|
|
15843
|
-
|
|
15844
|
-
|
|
15845
|
-
|
|
15846
|
-
|
|
15847
|
-
|
|
15848
|
-
|
|
15849
|
-
|
|
15850
|
-
|
|
15851
|
-
|
|
15852
|
-
|
|
15853
|
-
|
|
15854
|
-
|
|
15855
|
-
|
|
15856
|
-
|
|
15857
|
-
|
|
15858
|
-
|
|
15859
|
-
|
|
15860
|
-
|
|
15861
|
-
|
|
14892
|
+
};
|
|
14893
|
+
const GET_CHILD_RESULT_SCHEMA = {
|
|
14894
|
+
type: "object",
|
|
14895
|
+
additionalProperties: false,
|
|
14896
|
+
required: ["handle"],
|
|
14897
|
+
properties: {
|
|
14898
|
+
handle: {
|
|
14899
|
+
type: "integer",
|
|
14900
|
+
minimum: 1
|
|
14901
|
+
},
|
|
14902
|
+
...PAGING_PROPS
|
|
14903
|
+
}
|
|
14904
|
+
};
|
|
14905
|
+
const READ_CHILD_ARTIFACT_SCHEMA = {
|
|
14906
|
+
type: "object",
|
|
14907
|
+
additionalProperties: false,
|
|
14908
|
+
required: ["handle", "artifactId"],
|
|
14909
|
+
properties: {
|
|
14910
|
+
handle: {
|
|
14911
|
+
type: "integer",
|
|
14912
|
+
minimum: 1
|
|
14913
|
+
},
|
|
14914
|
+
artifactId: { type: "string" },
|
|
14915
|
+
...PAGING_PROPS
|
|
14916
|
+
}
|
|
14917
|
+
};
|
|
14918
|
+
const GET_CHILD_RESULT_TOOL_NAME = "get_child_result";
|
|
14919
|
+
const READ_CHILD_ARTIFACT_TOOL_NAME = "read_child_artifact";
|
|
14920
|
+
/** finish; result validates against the declared output schema. */
|
|
14921
|
+
const FINISH_SCHEMA = {
|
|
14922
|
+
type: "object",
|
|
14923
|
+
additionalProperties: false,
|
|
14924
|
+
required: ["result"],
|
|
14925
|
+
properties: {
|
|
14926
|
+
result: { $comment: "validated against the declared output SchemaSpec of the orchestrate call; free-form JSON when none is declared" },
|
|
14927
|
+
summary: { type: "string" }
|
|
14928
|
+
}
|
|
14929
|
+
};
|
|
14930
|
+
const FINISH_TOOL_NAME = "finish";
|
|
15862
14931
|
/**
|
|
15863
|
-
*
|
|
15864
|
-
* the
|
|
15865
|
-
*
|
|
15866
|
-
* than the evidence tools opt in, which changes it by design).
|
|
14932
|
+
* Builds the mode (c) toolset over the per-call runtime. profileCardText
|
|
14933
|
+
* rides the spawn tools' descriptions so both modes speak one agent
|
|
14934
|
+
* vocabulary (M6-T04).
|
|
15867
14935
|
*/
|
|
15868
|
-
function
|
|
15869
|
-
|
|
15870
|
-
|
|
15871
|
-
|
|
15872
|
-
|
|
14936
|
+
function buildOrchestratorTools(runtime, profileCardText, options) {
|
|
14937
|
+
const spawnAgent = tool({
|
|
14938
|
+
name: "spawn_agent",
|
|
14939
|
+
description: `Admit and schedule one child agent. ${profileCardText}`,
|
|
14940
|
+
parameters: SPAWN_AGENT_SCHEMA,
|
|
14941
|
+
execute: (input) => runtime.spawn(input)
|
|
14942
|
+
});
|
|
14943
|
+
const parallelAgents = tool({
|
|
14944
|
+
name: "parallel_agents",
|
|
14945
|
+
description: "Admit and schedule several children at once (submission order).",
|
|
14946
|
+
parameters: PARALLEL_AGENTS_SCHEMA,
|
|
14947
|
+
execute: async (input) => {
|
|
14948
|
+
const tasks = input.tasks;
|
|
14949
|
+
const handles = [];
|
|
14950
|
+
for (const task of tasks) {
|
|
14951
|
+
const spawned = await runtime.spawn(task);
|
|
14952
|
+
handles.push(spawned.handle);
|
|
14953
|
+
}
|
|
14954
|
+
return { handles };
|
|
14955
|
+
}
|
|
14956
|
+
});
|
|
14957
|
+
const awaitAny = tool({
|
|
14958
|
+
name: "await_any",
|
|
14959
|
+
description: "Wait for the FIRST of the handles to settle; returns its TaskDigest.",
|
|
14960
|
+
parameters: AWAIT_SCHEMA,
|
|
14961
|
+
execute: (input) => runtime.awaitAny(input.handles)
|
|
14962
|
+
});
|
|
14963
|
+
const awaitAll = tool({
|
|
14964
|
+
name: "await_all",
|
|
14965
|
+
description: "Wait for ALL handles to settle; returns their TaskDigests in handle order.",
|
|
14966
|
+
parameters: AWAIT_SCHEMA,
|
|
14967
|
+
execute: (input) => runtime.awaitAll(input.handles)
|
|
14968
|
+
});
|
|
14969
|
+
const cancelAgent = tool({
|
|
14970
|
+
name: "cancel_agent",
|
|
14971
|
+
description: "Cancel an in-flight child. Cancellation is caller intent: the entry journals cancelled and reruns on a later resume unless covered by abandon (M7).",
|
|
14972
|
+
parameters: CANCEL_AGENT_SCHEMA,
|
|
14973
|
+
execute: (input) => {
|
|
14974
|
+
const params = input;
|
|
14975
|
+
return runtime.cancel(params.handle, params.reason);
|
|
14976
|
+
}
|
|
14977
|
+
});
|
|
14978
|
+
const waitForEvents = tool({
|
|
14979
|
+
name: WAIT_FOR_EVENTS_TOOL_NAME,
|
|
14980
|
+
description: "Sleep until a coalesced WakeDigest: quiescence (always armed), child_terminal, escalation, or budget_threshold at 50/80 percent. A trigger set that can never fire is a typed error.",
|
|
14981
|
+
parameters: WAIT_FOR_EVENTS_SCHEMA,
|
|
14982
|
+
execute: (input) => runtime.waitForEvents(input.triggers)
|
|
14983
|
+
});
|
|
14984
|
+
const finish = tool({
|
|
14985
|
+
name: FINISH_TOOL_NAME,
|
|
14986
|
+
description: "Terminate the orchestration with a result (run outcome ok).",
|
|
14987
|
+
parameters: FINISH_SCHEMA,
|
|
14988
|
+
execute: () => {
|
|
14989
|
+
throw new Error("finish is intercepted by the agent runtime, never executed");
|
|
14990
|
+
}
|
|
14991
|
+
});
|
|
14992
|
+
const tools = [
|
|
14993
|
+
spawnAgent,
|
|
14994
|
+
parallelAgents,
|
|
14995
|
+
awaitAny,
|
|
14996
|
+
awaitAll,
|
|
14997
|
+
cancelAgent,
|
|
14998
|
+
waitForEvents
|
|
14999
|
+
];
|
|
15000
|
+
if (options?.childResultTools === true) tools.push(tool({
|
|
15001
|
+
name: GET_CHILD_RESULT_TOOL_NAME,
|
|
15002
|
+
description: "Read a page of a SETTLED child's FULL output (the digest is truncated to 400 chars). Pages with offset and maxChars; the reply reports totalChars and hasMore.",
|
|
15003
|
+
parameters: GET_CHILD_RESULT_SCHEMA,
|
|
15004
|
+
execute: (input) => {
|
|
15005
|
+
const p = input;
|
|
15006
|
+
return runtime.getChildResult(p.handle, {
|
|
15007
|
+
offset: p.offset,
|
|
15008
|
+
maxChars: p.maxChars
|
|
15009
|
+
});
|
|
15010
|
+
}
|
|
15011
|
+
}), tool({
|
|
15012
|
+
name: READ_CHILD_ARTIFACT_TOOL_NAME,
|
|
15013
|
+
description: "Read a page of a SETTLED child's artifact content by id (ids come from get_child_result or a digest). Pages with offset and maxChars.",
|
|
15014
|
+
parameters: READ_CHILD_ARTIFACT_SCHEMA,
|
|
15015
|
+
execute: (input) => {
|
|
15016
|
+
const p = input;
|
|
15017
|
+
return runtime.readChildArtifact(p.handle, p.artifactId, {
|
|
15018
|
+
offset: p.offset,
|
|
15019
|
+
maxChars: p.maxChars
|
|
15020
|
+
});
|
|
15021
|
+
}
|
|
15022
|
+
}));
|
|
15023
|
+
tools.push(finish);
|
|
15024
|
+
return tools;
|
|
15873
15025
|
}
|
|
15026
|
+
//#endregion
|
|
15027
|
+
//#region src/orchestrator/orchestrate.ts
|
|
15874
15028
|
/**
|
|
15875
|
-
* The
|
|
15876
|
-
*
|
|
15877
|
-
*
|
|
15878
|
-
*
|
|
15029
|
+
* The mode (c) dynamic orchestrator (M6-T07/T08).
|
|
15030
|
+
*
|
|
15031
|
+
* Full contract: https://docs.rulvar.com/guide/adaptive-orchestration. An ordinary
|
|
15032
|
+
* workflow whose agent (role 'orchestrate') holds the typed spawn tools;
|
|
15033
|
+
* both surfaces (top-level orchestrate() and ctx.orchestrate) share this
|
|
15034
|
+
* one implementation, the nested surface riding ctx.workflow so the
|
|
15035
|
+
* AdmissionController clamps depth and budget for free.
|
|
15036
|
+
*
|
|
15037
|
+
* Resume semantics (the M6 gate): orchestrator turns checkpoint at every
|
|
15038
|
+
* turn boundary (mandatory for the orchestrate role); every spawn is an
|
|
15039
|
+
* ordinary kind 'agent' entry; a crashed orchestrate() restores its
|
|
15040
|
+
* history from the checkpoint and finds child results by content keys,
|
|
15041
|
+
* WITHOUT regenerating spawn decisions and without re-paying children.
|
|
15042
|
+
* Non-PlanRunner applicability: only the lifetime
|
|
15043
|
+
* cap, maxDepth, and the budget layers apply; no termination.init is
|
|
15044
|
+
* written; escalated children simply settle into their digests.
|
|
15879
15045
|
*/
|
|
15880
|
-
|
|
15881
|
-
|
|
15882
|
-
return ["Partial salvage is on: a child that ends at its limit AFTER recording progress with report_progress counts as a partial success for acceptance; its digest carries the partial and get_child_result (when enabled) pages the full report. When the gap matters, respawn a NARROWED child carrying the partial instead of repeating the task."];
|
|
15883
|
-
}
|
|
15046
|
+
/** How many rejected finishes are repaired by default: the plan's repair once. */
|
|
15047
|
+
const DEFAULT_FINISH_MAX_REPAIRS = 1;
|
|
15884
15048
|
/**
|
|
15885
|
-
*
|
|
15886
|
-
*
|
|
15887
|
-
* unknown ref is a typed ConfigError, surfaced as a tool error to the
|
|
15888
|
-
* orchestrator and never a run failure.
|
|
15049
|
+
* Default maxTurns of the synthesize invocation (RV-211): the finish
|
|
15050
|
+
* call plus headroom for one validator repair exchange.
|
|
15889
15051
|
*/
|
|
15890
|
-
|
|
15891
|
-
const opts = {};
|
|
15892
|
-
if (spec.outputSchemaRef !== void 0) {
|
|
15893
|
-
const schema = defaults.schemas?.[spec.outputSchemaRef];
|
|
15894
|
-
if (schema === void 0) throw new ConfigError(`unknown outputSchemaRef '${spec.outputSchemaRef}': register it under defaults.schemas`);
|
|
15895
|
-
opts.schema = schema;
|
|
15896
|
-
}
|
|
15897
|
-
if (spec.toolsetRef !== void 0) {
|
|
15898
|
-
const tools = defaults.toolsets?.[spec.toolsetRef];
|
|
15899
|
-
if (tools === void 0) throw new ConfigError(`unknown toolsetRef '${spec.toolsetRef}': register it under defaults.toolsets (https://docs.rulvar.com/guide/tools)`);
|
|
15900
|
-
opts.tools = tools;
|
|
15901
|
-
}
|
|
15902
|
-
const extended = spec;
|
|
15903
|
-
if (extended.isolation !== void 0) opts.isolation = extended.isolation;
|
|
15904
|
-
if (extended.usageLimits !== void 0) opts.limits = extended.usageLimits;
|
|
15905
|
-
if (extended.escalation !== void 0) opts.escalation = extended.escalation;
|
|
15906
|
-
if (extended.bootCheckpointRef !== void 0) opts[kBootCheckpoint] = extended.bootCheckpointRef;
|
|
15907
|
-
if (extended.model !== void 0) opts.model = extended.model;
|
|
15908
|
-
if (extended.memoizeOutcome !== void 0) opts.memoizeOutcome = extended.memoizeOutcome;
|
|
15909
|
-
if (extended.schema !== void 0 && opts.schema === void 0) opts.schema = extended.schema;
|
|
15910
|
-
return opts;
|
|
15911
|
-
}
|
|
15912
|
-
function filterProfiles(registered, names) {
|
|
15913
|
-
if (registered === void 0) return {};
|
|
15914
|
-
if (names === void 0) return registered;
|
|
15915
|
-
const filtered = {};
|
|
15916
|
-
for (const name of names) if (registered[name] !== void 0) filtered[name] = registered[name];
|
|
15917
|
-
return filtered;
|
|
15918
|
-
}
|
|
15052
|
+
const DEFAULT_SYNTHESIS_MAX_TURNS = 4;
|
|
15919
15053
|
/**
|
|
15920
|
-
*
|
|
15921
|
-
*
|
|
15922
|
-
*
|
|
15923
|
-
* orchestrator agent with the finish terminal tool.
|
|
15054
|
+
* Default maxTurns of ONE incremental synthesis note (RV-211 remainder):
|
|
15055
|
+
* a note summarizes a single settled child into a bounded finish call,
|
|
15056
|
+
* so it needs less headroom than the full synthesis invocation.
|
|
15924
15057
|
*/
|
|
15925
|
-
|
|
15926
|
-
|
|
15927
|
-
|
|
15928
|
-
|
|
15929
|
-
|
|
15930
|
-
|
|
15931
|
-
|
|
15932
|
-
|
|
15933
|
-
|
|
15934
|
-
|
|
15935
|
-
|
|
15936
|
-
|
|
15058
|
+
const DEFAULT_SYNTHESIS_NOTE_MAX_TURNS = 2;
|
|
15059
|
+
const ORCHESTRATE_WORKFLOW_NAME = "rulvar-orchestrate";
|
|
15060
|
+
/**
|
|
15061
|
+
* One page of a string, for the child result evidence tools: maxChars is
|
|
15062
|
+
* clamped to [1, MAX] and offset to [0, length], so a hostile or absent
|
|
15063
|
+
* paging argument can never throw or read past the end. The window is measured in
|
|
15064
|
+
* UTF-16 code units, the same unit the model counts, so hasMore and the
|
|
15065
|
+
* next offset are exact.
|
|
15066
|
+
*/
|
|
15067
|
+
function pageOf(content, rawOffset, rawMaxChars) {
|
|
15068
|
+
const totalChars = content.length;
|
|
15069
|
+
const offset = Math.min(Math.max(0, Math.trunc(rawOffset ?? 0)), totalChars);
|
|
15070
|
+
const end = Math.min(offset + Math.min(Math.max(1, Math.trunc(rawMaxChars ?? 4e3)), MAX_CHILD_RESULT_PAGE_CHARS), totalChars);
|
|
15071
|
+
return {
|
|
15072
|
+
totalChars,
|
|
15073
|
+
offset,
|
|
15074
|
+
content: content.slice(offset, end),
|
|
15075
|
+
hasMore: end < totalChars
|
|
15076
|
+
};
|
|
15077
|
+
}
|
|
15078
|
+
/** The serialized full result of a settled child: the raw string, or JSON. */
|
|
15079
|
+
function serializeChildOutput(result) {
|
|
15080
|
+
if (result.status !== "ok") {
|
|
15081
|
+
const base = result.errorMessage ?? `terminal status ${result.status}`;
|
|
15082
|
+
const limitOutput = result.status === "limit" && result.output !== null && result.output !== void 0;
|
|
15083
|
+
if (limitOutput || result.partial !== void 0) return JSON.stringify({
|
|
15084
|
+
error: base,
|
|
15085
|
+
...limitOutput ? { output: result.output } : {},
|
|
15086
|
+
...result.partial === void 0 ? {} : { partial: result.partial }
|
|
15087
|
+
});
|
|
15088
|
+
return base;
|
|
15089
|
+
}
|
|
15090
|
+
return typeof result.output === "string" ? result.output : JSON.stringify(result.output ?? null);
|
|
15091
|
+
}
|
|
15092
|
+
/**
|
|
15093
|
+
* The orchestrate intake gate (v1.35.0 review P2-2): every numeric
|
|
15094
|
+
* option and the atCap literal validate SYNCHRONOUSLY at workflow
|
|
15095
|
+
* construction, shared by both surfaces (the top level orchestrate() throws
|
|
15096
|
+
* before a run exists; ctx.orchestrate throws before any journal entry,
|
|
15097
|
+
* provider call, or child dispatch). A NaN here previously disabled the
|
|
15098
|
+
* spawn cap (`spawnOrdinal >= NaN` is false forever) and the digest
|
|
15099
|
+
* render bound, and a negative finalize reserve WIDENED the soft cap
|
|
15100
|
+
* boundary instead of reserving from it.
|
|
15101
|
+
*/
|
|
15102
|
+
function validateOrchestrateOptions(opts) {
|
|
15103
|
+
if (opts === void 0) return;
|
|
15104
|
+
if (opts.maxSpawns !== void 0) requireNonNegativeInteger(opts.maxSpawns, "orchestrate maxSpawns");
|
|
15105
|
+
if (opts.renderBudgetChars !== void 0) requireNonNegativeInteger(opts.renderBudgetChars, "orchestrate renderBudgetChars");
|
|
15106
|
+
if (opts.acceptance !== void 0) {
|
|
15107
|
+
const policy = opts.acceptance.childPolicy;
|
|
15108
|
+
const minSuccessful = typeof policy === "object" && policy !== null && !Array.isArray(policy) ? policy.minSuccessful : void 0;
|
|
15109
|
+
if (policy !== "all-ok" && minSuccessful === void 0) throw new ConfigError(`orchestrate acceptance.childPolicy must be 'all-ok' or { minSuccessful: N }; got ${JSON.stringify(policy)}`);
|
|
15110
|
+
if (policy !== "all-ok") requirePositiveInteger(minSuccessful, "orchestrate acceptance.childPolicy.minSuccessful");
|
|
15111
|
+
const acceptPartial = opts.acceptance.acceptPartialChildren;
|
|
15112
|
+
if (acceptPartial !== void 0 && typeof acceptPartial !== "boolean") throw new ConfigError(`orchestrate acceptance.acceptPartialChildren must be a boolean; got ${typeof acceptPartial}`);
|
|
15113
|
+
const acceptOutput = opts.acceptance.acceptValidatedTerminalOutputOnLimit;
|
|
15114
|
+
if (acceptOutput !== void 0 && typeof acceptOutput !== "boolean") throw new ConfigError("orchestrate acceptance.acceptValidatedTerminalOutputOnLimit must be a boolean; got " + typeof acceptOutput);
|
|
15115
|
+
}
|
|
15116
|
+
if (opts.finishValidation !== void 0) {
|
|
15117
|
+
const fv = opts.finishValidation;
|
|
15118
|
+
if (!Array.isArray(fv.validators) || fv.validators.length === 0) throw new ConfigError("orchestrate finishValidation.validators must be a non empty array of validators");
|
|
15119
|
+
const seen = /* @__PURE__ */ new Set();
|
|
15120
|
+
for (const candidate of fv.validators) {
|
|
15121
|
+
const validator = candidate;
|
|
15122
|
+
if (typeof validator.name !== "string" || validator.name.length === 0) throw new ConfigError("every orchestrate finish validator must carry a non empty string name");
|
|
15123
|
+
if (typeof validator.validate !== "function") throw new ConfigError(`orchestrate finish validator '${validator.name}' has no validate function`);
|
|
15124
|
+
if (seen.has(validator.name)) throw new ConfigError(`orchestrate finishValidation.validators names must be unique; '${validator.name}' repeats (pass name to the factory to run several instances)`);
|
|
15125
|
+
seen.add(validator.name);
|
|
15126
|
+
}
|
|
15127
|
+
if (fv.maxRepairs !== void 0) requireNonNegativeInteger(fv.maxRepairs, "orchestrate finishValidation.maxRepairs");
|
|
15128
|
+
}
|
|
15129
|
+
if (opts.synthesis !== void 0) {
|
|
15130
|
+
const synthesis = opts.synthesis;
|
|
15131
|
+
if (synthesis.mode !== void 0 && synthesis.mode !== "single" && synthesis.mode !== "incremental") throw new ConfigError("orchestrate synthesis.mode must be 'single' or 'incremental'; got " + JSON.stringify(synthesis.mode));
|
|
15132
|
+
if (synthesis.mode === "incremental" && opts.finishValidation !== void 0) throw new ConfigError("orchestrate synthesis.mode 'incremental' reconciles deterministically and has no model-composed final finish for finishValidation to bind; configure validators with mode 'single', or drop them");
|
|
15133
|
+
if (synthesis.dedupeClaims !== void 0 && typeof synthesis.dedupeClaims !== "boolean") throw new ConfigError("orchestrate synthesis.dedupeClaims must be a boolean; got " + typeof synthesis.dedupeClaims);
|
|
15134
|
+
if (synthesis.noteLimits !== void 0) validateUsageLimits(synthesis.noteLimits, "orchestrate synthesis.noteLimits");
|
|
15135
|
+
if (synthesis.effort !== void 0 && ![
|
|
15136
|
+
"low",
|
|
15137
|
+
"medium",
|
|
15138
|
+
"high",
|
|
15139
|
+
"xhigh",
|
|
15140
|
+
"max"
|
|
15141
|
+
].includes(synthesis.effort)) throw new ConfigError(`orchestrate synthesis.effort must be one of 'low' | 'medium' | 'high' | 'xhigh' | 'max'; got ${JSON.stringify(synthesis.effort)}`);
|
|
15142
|
+
if (synthesis.limits !== void 0) validateUsageLimits(synthesis.limits, "orchestrate synthesis.limits");
|
|
15143
|
+
if (synthesis.instructions !== void 0 && typeof synthesis.instructions !== "string") throw new ConfigError(`orchestrate synthesis.instructions must be a string; got ${typeof synthesis.instructions}`);
|
|
15144
|
+
if (synthesis.estCost !== void 0) requireNonNegativeNumber(synthesis.estCost, "orchestrate synthesis.estCost");
|
|
15145
|
+
}
|
|
15146
|
+
const spec = opts.budget;
|
|
15147
|
+
if (spec === void 0) return;
|
|
15148
|
+
if (spec.capUsd !== void 0) requireNonNegativeNumber(spec.capUsd, "orchestrate budget.capUsd");
|
|
15149
|
+
if (spec.capFraction !== void 0) requireFraction(spec.capFraction, "orchestrate budget.capFraction");
|
|
15150
|
+
if (spec.finalizeReserveUsd !== void 0) requireNonNegativeNumber(spec.finalizeReserveUsd, "orchestrate budget.finalizeReserveUsd");
|
|
15151
|
+
if (spec.finalizeTurns !== void 0) requirePositiveInteger(spec.finalizeTurns, "orchestrate budget.finalizeTurns");
|
|
15152
|
+
if (spec.atCap !== void 0 && spec.atCap !== "finish-with-partial" && spec.atCap !== "fail-run") throw new ConfigError(`orchestrate budget.atCap must be 'finish-with-partial' or 'fail-run'; got ${String(spec.atCap)}`);
|
|
15153
|
+
}
|
|
15154
|
+
function orchestratorPrompt(goal, maxSpawns, extensionLines) {
|
|
15155
|
+
return [
|
|
15156
|
+
"You are the orchestrator of a multi-agent run.",
|
|
15157
|
+
`GOAL: ${goal}`,
|
|
15158
|
+
"",
|
|
15159
|
+
"Decompose the goal into child agents with spawn_agent or parallel_agents,",
|
|
15160
|
+
"wait on their handles with await_any or await_all, cancel stragglers with",
|
|
15161
|
+
"cancel_agent, and terminate with finish({ result }) when the goal is met.",
|
|
15162
|
+
maxSpawns === void 0 ? "Spawn only what the goal needs." : `You may spawn at most ${String(maxSpawns)} children.`,
|
|
15163
|
+
...extensionLines ?? []
|
|
15164
|
+
].join("\n");
|
|
15165
|
+
}
|
|
15166
|
+
/**
|
|
15167
|
+
* The finish validation contract rides the PROMPT, never the toolset:
|
|
15168
|
+
* the finish tool definition stays byte identical in every
|
|
15169
|
+
* configuration, so the orchestrator toolset hash never moves (stricter
|
|
15170
|
+
* than the evidence tools opt in, which changes it by design).
|
|
15171
|
+
*/
|
|
15172
|
+
function finishValidationPromptLines(spec) {
|
|
15173
|
+
if (spec === void 0) return [];
|
|
15174
|
+
const names = spec.validators.map((validator) => validator.name).join(", ");
|
|
15175
|
+
const repairs = spec.maxRepairs ?? 1;
|
|
15176
|
+
return [`The host validates every finish({ result }) with deterministic validators: ${names}.`, "A rejected finish returns the failure reasons as the tool error result; repair the result and call finish again. " + (repairs === 0 ? "No repair attempt is granted: the first rejected finish fails the run." : repairs === 1 ? "At most one repair attempt is granted before the run fails." : `At most ${String(repairs)} repair attempts are granted before the run fails.`)];
|
|
15177
|
+
}
|
|
15178
|
+
/**
|
|
15179
|
+
* The partial-salvage contract rides the PROMPT exactly like finish
|
|
15180
|
+
* validation (RV-210 close-out): present only when
|
|
15181
|
+
* acceptance.acceptPartialChildren is set, so every other configuration
|
|
15182
|
+
* keeps byte-identical coordination prompts.
|
|
15183
|
+
*/
|
|
15184
|
+
function acceptancePromptLines(acceptance) {
|
|
15185
|
+
const lines = [];
|
|
15186
|
+
if (acceptance?.acceptPartialChildren === true) lines.push("Partial salvage is on: a child that ends at its limit AFTER recording progress with report_progress counts as a partial success for acceptance; its digest carries the partial and get_child_result (when enabled) pages the full report. When the gap matters, respawn a NARROWED child carrying the partial instead of repeating the task.");
|
|
15187
|
+
if (acceptance?.acceptValidatedTerminalOutputOnLimit === true) lines.push("Terminal-output salvage is on: a child that ends at its limit WITH a final answer (its finalization reserve summary, already validated against the declared output schema) counts as a successful child for acceptance; its digest carries it after the 'final:' marker and get_child_result (when enabled) pages it in full.");
|
|
15188
|
+
return lines;
|
|
15189
|
+
}
|
|
15190
|
+
/**
|
|
15191
|
+
* Resolves per-spawn dispatch options against the engine registries
|
|
15192
|
+
* (registered SchemaSpec and tool profile names; M7-T05). An
|
|
15193
|
+
* unknown ref is a typed ConfigError, surfaced as a tool error to the
|
|
15194
|
+
* orchestrator and never a run failure.
|
|
15195
|
+
*/
|
|
15196
|
+
/**
|
|
15197
|
+
* The capped orchestrator's own admission estimate (the 1.63.0
|
|
15198
|
+
* experiment review, P0.3): the effective cap MINUS the finalize
|
|
15199
|
+
* carve-out already committed on the cap account, so the dispatch
|
|
15200
|
+
* admits at EXACT FILL by construction (a capped orchestrator can never
|
|
15201
|
+
* spend past its effectiveCap, and pricing the model's full
|
|
15202
|
+
* maxOutputTokens instead pinned small run ceilings at zero remainder;
|
|
15203
|
+
* the M12 checkpoint measured a self-solving orchestrator because no
|
|
15204
|
+
* child was ever admitted). Exported so the live dispatch and
|
|
15205
|
+
* preflightEstimate share ONE formula: both call this function.
|
|
15206
|
+
*/
|
|
15207
|
+
function orchestratorAdmissionEstCostUsd(effectiveCapUsd, committedFinalizeReserveUsd) {
|
|
15208
|
+
return effectiveCapUsd - committedFinalizeReserveUsd;
|
|
15209
|
+
}
|
|
15210
|
+
function resolveDispatchOpts(spec, defaults) {
|
|
15211
|
+
const opts = {};
|
|
15212
|
+
if (spec.outputSchemaRef !== void 0) {
|
|
15213
|
+
const schema = defaults.schemas?.[spec.outputSchemaRef];
|
|
15214
|
+
if (schema === void 0) throw new ConfigError(`unknown outputSchemaRef '${spec.outputSchemaRef}': register it under defaults.schemas`);
|
|
15215
|
+
opts.schema = schema;
|
|
15216
|
+
}
|
|
15217
|
+
if (spec.toolsetRef !== void 0) {
|
|
15218
|
+
const tools = defaults.toolsets?.[spec.toolsetRef];
|
|
15219
|
+
if (tools === void 0) throw new ConfigError(`unknown toolsetRef '${spec.toolsetRef}': register it under defaults.toolsets (https://docs.rulvar.com/guide/tools)`);
|
|
15220
|
+
opts.tools = tools;
|
|
15221
|
+
}
|
|
15222
|
+
const extended = spec;
|
|
15223
|
+
if (extended.isolation !== void 0) opts.isolation = extended.isolation;
|
|
15224
|
+
if (extended.usageLimits !== void 0) opts.limits = extended.usageLimits;
|
|
15225
|
+
if (extended.escalation !== void 0) opts.escalation = extended.escalation;
|
|
15226
|
+
if (extended.bootCheckpointRef !== void 0) opts[kBootCheckpoint] = extended.bootCheckpointRef;
|
|
15227
|
+
if (extended.model !== void 0) opts.model = extended.model;
|
|
15228
|
+
if (extended.memoizeOutcome !== void 0) opts.memoizeOutcome = extended.memoizeOutcome;
|
|
15229
|
+
if (extended.schema !== void 0 && opts.schema === void 0) opts.schema = extended.schema;
|
|
15230
|
+
return opts;
|
|
15231
|
+
}
|
|
15232
|
+
function filterProfiles(registered, names) {
|
|
15233
|
+
if (registered === void 0) return {};
|
|
15234
|
+
if (names === void 0) return registered;
|
|
15235
|
+
const filtered = {};
|
|
15236
|
+
for (const name of names) if (registered[name] !== void 0) filtered[name] = registered[name];
|
|
15237
|
+
return filtered;
|
|
15238
|
+
}
|
|
15239
|
+
/**
|
|
15240
|
+
* Builds the orchestrator workflow: ONE implementation behind both
|
|
15241
|
+
* surfaces. The body wires the spawn tools over the per-call runtime,
|
|
15242
|
+
* recovers spawn records from the journal on resume, and runs the
|
|
15243
|
+
* orchestrator agent with the finish terminal tool.
|
|
15244
|
+
*/
|
|
15245
|
+
function makeOrchestratorWorkflow(goal, opts) {
|
|
15246
|
+
validateOrchestrateOptions(opts);
|
|
15247
|
+
return defineWorkflow({ name: ORCHESTRATE_WORKFLOW_NAME }, async (ctx) => {
|
|
15248
|
+
const runtime = runtimeOf(ctx);
|
|
15249
|
+
const { internals } = runtime;
|
|
15250
|
+
if (internals.admission === void 0) throw new ConfigError("orchestrate requires the engine run context (createEngine)");
|
|
15251
|
+
const admission = internals.admission;
|
|
15252
|
+
const callingState = runtime.currentState();
|
|
15253
|
+
const advertisedProfiles = filterProfiles(internals.defaults.profiles, opts?.profiles);
|
|
15254
|
+
const spawnableProfiles = {};
|
|
15255
|
+
const declaredLadderNames = [];
|
|
15256
|
+
for (const [name, profile] of Object.entries(advertisedProfiles)) {
|
|
15937
15257
|
const spec = profile.model;
|
|
15938
15258
|
if (spec !== void 0 && typeof spec !== "string" && "ladder" in spec) declaredLadderNames.push(name);
|
|
15939
15259
|
else spawnableProfiles[name] = profile;
|
|
@@ -16770,11 +16090,13 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
16770
16090
|
let decision = known.find((candidate) => candidate.callId === call.id);
|
|
16771
16091
|
if (decision === void 0) {
|
|
16772
16092
|
const result = call.result ?? null;
|
|
16093
|
+
const salvageOutputOn = opts?.acceptance?.acceptValidatedTerminalOutputOnLimit === true;
|
|
16773
16094
|
const children = [...records.values()].sort((a, b) => a.spawnOrdinal - b.spawnOrdinal).map((record) => ({
|
|
16774
16095
|
handle: record.handle,
|
|
16775
16096
|
nodeId: record.nodeId,
|
|
16776
16097
|
status: record.settled?.status ?? "running",
|
|
16777
|
-
text: record.settled === void 0 ? "" : serializeChildOutput(record.settled)
|
|
16098
|
+
text: record.settled === void 0 ? "" : serializeChildOutput(record.settled),
|
|
16099
|
+
...salvageOutputOn && record.settled?.status === "limit" && record.settled.output !== null && record.settled.output !== void 0 ? { salvageableOutput: true } : {}
|
|
16778
16100
|
}));
|
|
16779
16101
|
const input = {
|
|
16780
16102
|
result,
|
|
@@ -16843,7 +16165,7 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
16843
16165
|
role: "orchestrate",
|
|
16844
16166
|
result: "full",
|
|
16845
16167
|
tools,
|
|
16846
|
-
...capState === void 0 ? {} : { estCost: capState.effectiveCapUsd
|
|
16168
|
+
...capState === void 0 ? {} : { estCost: orchestratorAdmissionEstCostUsd(capState.effectiveCapUsd, orchestratorAccount === void 0 ? 0 : internals.budget.accountView(orchestratorAccount)?.finalizeReserveUsd ?? 0) },
|
|
16847
16169
|
...opts?.model === void 0 ? {} : { model: opts.model },
|
|
16848
16170
|
...opts?.limits === void 0 ? {} : { limits: opts.limits },
|
|
16849
16171
|
[kOnRunning]: (seq) => {
|
|
@@ -17156,151 +16478,912 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
17156
16478
|
...validationSpec === void 0 ? {} : { validate: validateFinish }
|
|
17157
16479
|
}
|
|
17158
16480
|
};
|
|
17159
|
-
const synthesized = await runtime.runInScope(synthesisState, () => ctx.agent(prompt, synthesisOpts));
|
|
17160
|
-
if (validationTermination !== void 0) throw validationTermination;
|
|
17161
|
-
if (synthesized.status === "ok") return synthesized.output;
|
|
17162
|
-
if (validationSpec !== void 0) throw new FailRunError(`the synthesis invocation terminated with status '${synthesized.status}'` + (synthesized.errorMessage === void 0 ? "" : `: ${synthesized.errorMessage}`) + "; finish validators are configured, so the unvalidated draft cannot stand", { data: {
|
|
17163
|
-
source: "orchestrator_synthesis",
|
|
17164
|
-
status: synthesized.status,
|
|
17165
|
-
turnsUsed: synthesized.turns
|
|
17166
|
-
} });
|
|
17167
|
-
const fallbackKey = deriverV2.deriveKey({ kind: "orchestrator-synthesis-fallback" });
|
|
17168
|
-
if (!internals.replayer.snapshot().some((entry) => entry.kind === "decision" && entry.key === fallbackKey)) await internals.replayer.appendSinglePhase({
|
|
17169
|
-
scope: callingState.scope,
|
|
17170
|
-
key: fallbackKey,
|
|
17171
|
-
kind: "decision",
|
|
17172
|
-
status: "ok",
|
|
17173
|
-
spanId: internals.spans.mint(callingState.spanId),
|
|
17174
|
-
site: "orchestrator-synthesis",
|
|
17175
|
-
value: {
|
|
17176
|
-
decisionType: "orchestrator_synthesis_fallback",
|
|
17177
|
-
status: synthesized.status,
|
|
17178
|
-
turnsUsed: synthesized.turns
|
|
17179
|
-
}
|
|
17180
|
-
});
|
|
17181
|
-
internals.events.emit({
|
|
17182
|
-
type: "log",
|
|
17183
|
-
level: "warn",
|
|
17184
|
-
msg: `the synthesis invocation terminated with status '${synthesized.status}'; falling back to the coordination draft (journaled decision 'orchestrator_synthesis_fallback')`
|
|
17185
|
-
}, callingState.spanId);
|
|
17186
|
-
return draft;
|
|
17187
|
-
};
|
|
17188
|
-
/**
|
|
17189
|
-
* The settle at the cap: the JOURNALED cap decision drives the policy
|
|
17190
|
-
* branch (its `fallback` field froze budget.atCap when the cap
|
|
17191
|
-
* tripped), so a crash between the decision and its effect rolls the
|
|
17192
|
-
* SAME outcome forward on resume, immune to drift of the live options.
|
|
17193
|
-
* 'finish-with-partial' runs the reserved finalizer;
|
|
17194
|
-
* 'fail-run' skips it and fails the run typed (v1.35.0 review P2-1:
|
|
17195
|
-
* the policy used to be journaled and then ignored).
|
|
17196
|
-
*/
|
|
17197
|
-
const settleCapOutcome = async () => {
|
|
17198
|
-
const capValue = internals.replayer.snapshot().find((entry) => entry.seq === capDecisionRef)?.value;
|
|
17199
|
-
if (capValue?.fallback === "fail-run") throw new FailRunError(`the orchestrator budget cap was reached (decision entry ${String(capDecisionRef ?? -1)}) and budget.atCap is 'fail-run': the reserved finalizer is skipped and the run fails instead of returning a partial result`, { data: {
|
|
17200
|
-
source: "orchestrator_budget_cap",
|
|
17201
|
-
capDecisionRef: capDecisionRef ?? -1,
|
|
17202
|
-
spentUsd: capValue.spentUsd ?? 0,
|
|
17203
|
-
capUsd: capValue.capUsd ?? 0
|
|
17204
|
-
} });
|
|
17205
|
-
return await runForcedFinish();
|
|
17206
|
-
};
|
|
17207
|
-
const bootTermination = extensionTermination;
|
|
17208
|
-
if (bootTermination !== void 0) throw bootTermination;
|
|
17209
|
-
if (capDecisionRef !== void 0) return await settleCapOutcome();
|
|
17210
|
-
if (validationSpec !== void 0) {
|
|
17211
|
-
const priorRejection = validationDecisions().find((decision) => decision.verdict === "rejected");
|
|
17212
|
-
if (priorRejection !== void 0) throw finishValidationError(priorRejection);
|
|
16481
|
+
const synthesized = await runtime.runInScope(synthesisState, () => ctx.agent(prompt, synthesisOpts));
|
|
16482
|
+
if (validationTermination !== void 0) throw validationTermination;
|
|
16483
|
+
if (synthesized.status === "ok") return synthesized.output;
|
|
16484
|
+
if (validationSpec !== void 0) throw new FailRunError(`the synthesis invocation terminated with status '${synthesized.status}'` + (synthesized.errorMessage === void 0 ? "" : `: ${synthesized.errorMessage}`) + "; finish validators are configured, so the unvalidated draft cannot stand", { data: {
|
|
16485
|
+
source: "orchestrator_synthesis",
|
|
16486
|
+
status: synthesized.status,
|
|
16487
|
+
turnsUsed: synthesized.turns
|
|
16488
|
+
} });
|
|
16489
|
+
const fallbackKey = deriverV2.deriveKey({ kind: "orchestrator-synthesis-fallback" });
|
|
16490
|
+
if (!internals.replayer.snapshot().some((entry) => entry.kind === "decision" && entry.key === fallbackKey)) await internals.replayer.appendSinglePhase({
|
|
16491
|
+
scope: callingState.scope,
|
|
16492
|
+
key: fallbackKey,
|
|
16493
|
+
kind: "decision",
|
|
16494
|
+
status: "ok",
|
|
16495
|
+
spanId: internals.spans.mint(callingState.spanId),
|
|
16496
|
+
site: "orchestrator-synthesis",
|
|
16497
|
+
value: {
|
|
16498
|
+
decisionType: "orchestrator_synthesis_fallback",
|
|
16499
|
+
status: synthesized.status,
|
|
16500
|
+
turnsUsed: synthesized.turns
|
|
16501
|
+
}
|
|
16502
|
+
});
|
|
16503
|
+
internals.events.emit({
|
|
16504
|
+
type: "log",
|
|
16505
|
+
level: "warn",
|
|
16506
|
+
msg: `the synthesis invocation terminated with status '${synthesized.status}'; falling back to the coordination draft (journaled decision 'orchestrator_synthesis_fallback')`
|
|
16507
|
+
}, callingState.spanId);
|
|
16508
|
+
return draft;
|
|
16509
|
+
};
|
|
16510
|
+
/**
|
|
16511
|
+
* The settle at the cap: the JOURNALED cap decision drives the policy
|
|
16512
|
+
* branch (its `fallback` field froze budget.atCap when the cap
|
|
16513
|
+
* tripped), so a crash between the decision and its effect rolls the
|
|
16514
|
+
* SAME outcome forward on resume, immune to drift of the live options.
|
|
16515
|
+
* 'finish-with-partial' runs the reserved finalizer;
|
|
16516
|
+
* 'fail-run' skips it and fails the run typed (v1.35.0 review P2-1:
|
|
16517
|
+
* the policy used to be journaled and then ignored).
|
|
16518
|
+
*/
|
|
16519
|
+
const settleCapOutcome = async () => {
|
|
16520
|
+
const capValue = internals.replayer.snapshot().find((entry) => entry.seq === capDecisionRef)?.value;
|
|
16521
|
+
if (capValue?.fallback === "fail-run") throw new FailRunError(`the orchestrator budget cap was reached (decision entry ${String(capDecisionRef ?? -1)}) and budget.atCap is 'fail-run': the reserved finalizer is skipped and the run fails instead of returning a partial result`, { data: {
|
|
16522
|
+
source: "orchestrator_budget_cap",
|
|
16523
|
+
capDecisionRef: capDecisionRef ?? -1,
|
|
16524
|
+
spentUsd: capValue.spentUsd ?? 0,
|
|
16525
|
+
capUsd: capValue.capUsd ?? 0
|
|
16526
|
+
} });
|
|
16527
|
+
return await runForcedFinish();
|
|
16528
|
+
};
|
|
16529
|
+
const bootTermination = extensionTermination;
|
|
16530
|
+
if (bootTermination !== void 0) throw bootTermination;
|
|
16531
|
+
if (capDecisionRef !== void 0) return await settleCapOutcome();
|
|
16532
|
+
if (validationSpec !== void 0) {
|
|
16533
|
+
const priorRejection = validationDecisions().find((decision) => decision.verdict === "rejected");
|
|
16534
|
+
if (priorRejection !== void 0) throw finishValidationError(priorRejection);
|
|
16535
|
+
}
|
|
16536
|
+
const promptLines = [
|
|
16537
|
+
...extension?.promptLines?.() ?? [],
|
|
16538
|
+
...finishValidationPromptLines(validationSpec),
|
|
16539
|
+
...acceptancePromptLines(opts?.acceptance)
|
|
16540
|
+
];
|
|
16541
|
+
const result = await runtime.runInScope(orchestratorState, () => ctx.agent(orchestratorPrompt(goal, opts?.maxSpawns, promptLines.length === 0 ? void 0 : promptLines), agentOpts));
|
|
16542
|
+
const liveTermination = extensionTermination;
|
|
16543
|
+
if (liveTermination !== void 0) throw liveTermination;
|
|
16544
|
+
if (capDecisionRef !== void 0) return await settleCapOutcome();
|
|
16545
|
+
if (validationTermination !== void 0) throw validationTermination;
|
|
16546
|
+
if (orchestratorAccount !== void 0) internals.cost.orchestrator.spentUsd = internals.budget.accountView(orchestratorAccount)?.spentUsd ?? 0;
|
|
16547
|
+
if (result.status !== "ok") throw new ConfigError(`the orchestrator agent terminated with status '${result.status}'` + (result.errorMessage === void 0 ? "" : `: ${result.errorMessage}`));
|
|
16548
|
+
if (opts?.acceptance === void 0) return await runSynthesis(result.output);
|
|
16549
|
+
const acceptanceKey = "acceptance";
|
|
16550
|
+
const priorAcceptance = internals.replayer.snapshot().find((entry) => entry.kind === "decision" && entry.scope === callingState.scope && entry.key === acceptanceKey);
|
|
16551
|
+
let decision;
|
|
16552
|
+
if (priorAcceptance !== void 0) decision = priorAcceptance.value;
|
|
16553
|
+
else {
|
|
16554
|
+
const childStatusCounts = {};
|
|
16555
|
+
const degradedReasons = [];
|
|
16556
|
+
const salvaged = [];
|
|
16557
|
+
const salvagedOutput = [];
|
|
16558
|
+
let hardDegraded = 0;
|
|
16559
|
+
const acceptPartial = opts.acceptance.acceptPartialChildren === true;
|
|
16560
|
+
const acceptOutput = opts.acceptance.acceptValidatedTerminalOutputOnLimit === true;
|
|
16561
|
+
const sortedRecords = [...records.values()].sort((a, b) => a.spawnOrdinal - b.spawnOrdinal);
|
|
16562
|
+
for (const record of sortedRecords) {
|
|
16563
|
+
const status = record.settled?.status ?? "running";
|
|
16564
|
+
childStatusCounts[status] = (childStatusCounts[status] ?? 0) + 1;
|
|
16565
|
+
if (status === "ok") continue;
|
|
16566
|
+
if (acceptOutput && status === "limit" && record.settled?.output !== null && record.settled?.output !== void 0) {
|
|
16567
|
+
salvagedOutput.push(record.nodeId);
|
|
16568
|
+
degradedReasons.push(`child ${record.nodeId} accepted with its validated terminal output (settled 'limit' after the finalization reserve summary)`);
|
|
16569
|
+
continue;
|
|
16570
|
+
}
|
|
16571
|
+
if (acceptPartial && status === "limit" && record.settled?.partial !== void 0) {
|
|
16572
|
+
salvaged.push(record.nodeId);
|
|
16573
|
+
degradedReasons.push(`child ${record.nodeId} accepted as partial (settled 'limit' with a structured partial)`);
|
|
16574
|
+
continue;
|
|
16575
|
+
}
|
|
16576
|
+
hardDegraded += 1;
|
|
16577
|
+
degradedReasons.push(status === "running" ? `child ${record.nodeId} was still running when finish validated` : `child ${record.nodeId} settled '${status}'`);
|
|
16578
|
+
}
|
|
16579
|
+
const childPolicy = opts.acceptance.childPolicy;
|
|
16580
|
+
const accepted = childPolicy === "all-ok" ? hardDegraded === 0 : (childStatusCounts.ok ?? 0) + salvaged.length + salvagedOutput.length >= childPolicy.minSuccessful;
|
|
16581
|
+
decision = {
|
|
16582
|
+
decisionType: "orchestrator_acceptance",
|
|
16583
|
+
verdict: accepted ? "accepted" : "rejected",
|
|
16584
|
+
completion: !accepted ? "rejected" : degradedReasons.length === 0 ? "complete" : "partial",
|
|
16585
|
+
childPolicy,
|
|
16586
|
+
childStatusCounts,
|
|
16587
|
+
degradedReasons,
|
|
16588
|
+
...salvaged.length === 0 ? {} : { salvagedPartialChildren: salvaged },
|
|
16589
|
+
...salvagedOutput.length === 0 ? {} : { salvagedTerminalOutputChildren: salvagedOutput }
|
|
16590
|
+
};
|
|
16591
|
+
await internals.replayer.appendSinglePhase({
|
|
16592
|
+
scope: callingState.scope,
|
|
16593
|
+
key: acceptanceKey,
|
|
16594
|
+
kind: "decision",
|
|
16595
|
+
status: "ok",
|
|
16596
|
+
spanId: internals.spans.mint(callingState.spanId),
|
|
16597
|
+
site: "orchestrator-acceptance",
|
|
16598
|
+
value: decision
|
|
16599
|
+
});
|
|
16600
|
+
}
|
|
16601
|
+
if (decision.verdict === "rejected") {
|
|
16602
|
+
const required = decision.childPolicy === "all-ok" ? "every child ok" : `at least ${String(decision.childPolicy.minSuccessful)} children ok`;
|
|
16603
|
+
throw new FailRunError(`the orchestrator acceptance policy rejected the finish: ${String(decision.childStatusCounts.ok ?? 0)} children settled 'ok' but the policy requires ${required}; degraded: ${decision.degradedReasons.join("; ")}`, { data: {
|
|
16604
|
+
source: "orchestrator_acceptance",
|
|
16605
|
+
completion: "rejected",
|
|
16606
|
+
childPolicy: decision.childPolicy,
|
|
16607
|
+
childStatusCounts: decision.childStatusCounts,
|
|
16608
|
+
degradedReasons: decision.degradedReasons,
|
|
16609
|
+
...decision.salvagedPartialChildren === void 0 ? {} : { salvagedPartialChildren: decision.salvagedPartialChildren },
|
|
16610
|
+
...decision.salvagedTerminalOutputChildren === void 0 ? {} : { salvagedTerminalOutputChildren: decision.salvagedTerminalOutputChildren }
|
|
16611
|
+
} });
|
|
16612
|
+
}
|
|
16613
|
+
return {
|
|
16614
|
+
result: await runSynthesis(result.output),
|
|
16615
|
+
completion: decision.completion,
|
|
16616
|
+
childStatusCounts: decision.childStatusCounts,
|
|
16617
|
+
degradedReasons: decision.degradedReasons,
|
|
16618
|
+
...decision.salvagedPartialChildren === void 0 ? {} : { salvagedPartialChildren: decision.salvagedPartialChildren },
|
|
16619
|
+
...decision.salvagedTerminalOutputChildren === void 0 ? {} : { salvagedTerminalOutputChildren: decision.salvagedTerminalOutputChildren }
|
|
16620
|
+
};
|
|
16621
|
+
});
|
|
16622
|
+
}
|
|
16623
|
+
/**
|
|
16624
|
+
* Top-level surface: creates a run. `runOptions` are the ordinary
|
|
16625
|
+
* engine {@link RunOptions} of the created run; in particular
|
|
16626
|
+
* `runOptions.budgetUsd` is the ROOT hard ceiling over the WHOLE tree
|
|
16627
|
+
* (the orchestrator and every child), immutable after start, while
|
|
16628
|
+
* `opts.budget` only shapes the orchestrator's own sub-account inside
|
|
16629
|
+
* that ceiling. The shortcut previously accepted no RunOptions at all,
|
|
16630
|
+
* so the canonical entry point could not set a root ceiling without
|
|
16631
|
+
* dropping to `engine.run(makeOrchestratorWorkflow(...))` (v1.18.0
|
|
16632
|
+
* review P1-5).
|
|
16633
|
+
*/
|
|
16634
|
+
function orchestrate(engine, goal, opts, runOptions) {
|
|
16635
|
+
return engine.run(makeOrchestratorWorkflow(goal, opts), void 0, runOptions);
|
|
16636
|
+
}
|
|
16637
|
+
//#endregion
|
|
16638
|
+
//#region src/engine/preflight.ts
|
|
16639
|
+
const ANY_TOOL = "(any)";
|
|
16640
|
+
function resolveServing(spec) {
|
|
16641
|
+
if (spec === void 0) return;
|
|
16642
|
+
if (typeof spec === "string") return spec;
|
|
16643
|
+
if ("model" in spec) return spec.model;
|
|
16644
|
+
return spec.ladder.rungs[spec.ladder.startTier]?.model;
|
|
16645
|
+
}
|
|
16646
|
+
/**
|
|
16647
|
+
* Per-tool executed-call ceilings from the merged limits: for every
|
|
16648
|
+
* tool a per-tool cap or a unit cost names (plus the '(any)' tool that
|
|
16649
|
+
* nothing names, unit cost 1), the smallest of maxCallsPerTool[T],
|
|
16650
|
+
* floor(toolUnits.max / cost(T)) for a positive cost (a zero cost is
|
|
16651
|
+
* free), and maxToolCalls.
|
|
16652
|
+
*/
|
|
16653
|
+
function toolCeilingsOf(limits) {
|
|
16654
|
+
const names = /* @__PURE__ */ new Set();
|
|
16655
|
+
for (const name of Object.keys(limits.maxCallsPerTool ?? {})) names.add(name);
|
|
16656
|
+
for (const name of Object.keys(limits.toolUnits?.costs ?? {})) names.add(name);
|
|
16657
|
+
const rows = [];
|
|
16658
|
+
for (const tool of [...[...names].sort(), ANY_TOOL]) {
|
|
16659
|
+
const terms = [];
|
|
16660
|
+
const cap = tool === ANY_TOOL ? void 0 : limits.maxCallsPerTool?.[tool];
|
|
16661
|
+
if (cap !== void 0) terms.push({
|
|
16662
|
+
boundBy: "maxCallsPerTool",
|
|
16663
|
+
ceiling: cap
|
|
16664
|
+
});
|
|
16665
|
+
if (limits.toolUnits !== void 0) {
|
|
16666
|
+
const cost = tool === ANY_TOOL ? 1 : limits.toolUnits.costs?.[tool] ?? 1;
|
|
16667
|
+
if (cost > 0) terms.push({
|
|
16668
|
+
boundBy: "toolUnits",
|
|
16669
|
+
ceiling: Math.floor(limits.toolUnits.max / cost)
|
|
16670
|
+
});
|
|
16671
|
+
}
|
|
16672
|
+
if (limits.maxToolCalls !== void 0) terms.push({
|
|
16673
|
+
boundBy: "maxToolCalls",
|
|
16674
|
+
ceiling: limits.maxToolCalls
|
|
16675
|
+
});
|
|
16676
|
+
if (terms.length === 0) {
|
|
16677
|
+
rows.push({
|
|
16678
|
+
tool,
|
|
16679
|
+
ceiling: null
|
|
16680
|
+
});
|
|
16681
|
+
continue;
|
|
16682
|
+
}
|
|
16683
|
+
const min = terms.reduce((best, term) => term.ceiling < best.ceiling ? term : best);
|
|
16684
|
+
rows.push({
|
|
16685
|
+
tool,
|
|
16686
|
+
ceiling: min.ceiling,
|
|
16687
|
+
boundBy: min.boundBy
|
|
16688
|
+
});
|
|
16689
|
+
}
|
|
16690
|
+
return rows;
|
|
16691
|
+
}
|
|
16692
|
+
function validateSpawnSpec(spec, index) {
|
|
16693
|
+
const site = `preflight.spawns[${index}]`;
|
|
16694
|
+
if (spec.limits !== void 0) validateUsageLimits(spec.limits, `${site}.limits`);
|
|
16695
|
+
if (spec.estCost !== void 0) requireNonNegativeNumber(spec.estCost, `${site}.estCost`);
|
|
16696
|
+
if (spec.estInputTokens !== void 0) requireNonNegativeInteger(spec.estInputTokens, `${site}.estInputTokens`);
|
|
16697
|
+
if (spec.count !== void 0) requirePositiveInteger(spec.count, `${site}.count`);
|
|
16698
|
+
if (spec.budgetUsd !== void 0) requireNonNegativeNumber(spec.budgetUsd, `${site}.budgetUsd`);
|
|
16699
|
+
}
|
|
16700
|
+
/**
|
|
16701
|
+
* Computes the preflight report: the effective merged limits per
|
|
16702
|
+
* declared spawn, the layer-1 admission projection over the declared
|
|
16703
|
+
* wave, the per-tool and weighted-unit bottleneck ordering, the
|
|
16704
|
+
* concurrency and quota exposure at the declared estimates, and the
|
|
16705
|
+
* linter findings. Pure: no engine is constructed, no store is opened,
|
|
16706
|
+
* no adapter stream is dispatched, and no journal entry is written.
|
|
16707
|
+
*/
|
|
16708
|
+
function preflightEstimate(input) {
|
|
16709
|
+
const engine = input.engine ?? {};
|
|
16710
|
+
const defaults = engine.defaults ?? {};
|
|
16711
|
+
if (defaults.limits !== void 0) validateUsageLimits(defaults.limits, "preflight.engine.defaults.limits");
|
|
16712
|
+
if (input.run?.limits !== void 0) validateUsageLimits(input.run.limits, "preflight.run.limits");
|
|
16713
|
+
if (input.orchestrator?.limits !== void 0) validateUsageLimits(input.orchestrator.limits, "preflight.orchestrator.limits");
|
|
16714
|
+
const findings = [];
|
|
16715
|
+
const say = (finding) => {
|
|
16716
|
+
findings.push(finding);
|
|
16717
|
+
};
|
|
16718
|
+
const adapters = new Map((engine.adapters ?? []).map((adapter) => [adapter.id, adapter]));
|
|
16719
|
+
const capsOf = (ref) => {
|
|
16720
|
+
const { adapterId, model } = parseModelRef(ref);
|
|
16721
|
+
return adapters.get(adapterId)?.caps(model);
|
|
16722
|
+
};
|
|
16723
|
+
const pricingOf = (ref) => resolvePricing(ref, engine.pricing, capsOf(ref)?.pricing);
|
|
16724
|
+
const ceilingUsd = input.run?.budgetUsd;
|
|
16725
|
+
const flatReserveUsd = engine.budgetDefaults?.flatReserveUsd ?? .5;
|
|
16726
|
+
const lifetimeSpawnCap = engine.budgetDefaults?.lifetimeSpawnCap ?? 500;
|
|
16727
|
+
const childBudgetFraction = engine.budgetDefaults?.childBudgetFraction ?? .3;
|
|
16728
|
+
const maxDepth = engine.budgetDefaults?.maxDepth ?? 1;
|
|
16729
|
+
const perRun = engine.concurrency?.perRun ?? 12;
|
|
16730
|
+
const runLimits = mergeUsageLimits(void 0, input.run?.limits, defaults.limits);
|
|
16731
|
+
let orchestratorEcho;
|
|
16732
|
+
let reservedForFinalizationUsd = 0;
|
|
16733
|
+
let effectiveCapUsd;
|
|
16734
|
+
if (input.orchestrator !== void 0) {
|
|
16735
|
+
if (input.orchestrator.estInputTokens !== void 0) requireNonNegativeInteger(input.orchestrator.estInputTokens, "preflight.orchestrator.estInputTokens");
|
|
16736
|
+
const spec = input.orchestrator.budget;
|
|
16737
|
+
const fraction = spec?.capFraction ?? .2;
|
|
16738
|
+
const fromFraction = ceilingUsd === void 0 ? void 0 : fraction * ceilingUsd;
|
|
16739
|
+
const bounds = [spec?.capUsd, fromFraction].filter((bound) => bound !== void 0);
|
|
16740
|
+
effectiveCapUsd = bounds.length === 0 ? void 0 : Math.min(...bounds);
|
|
16741
|
+
const finalizeTurns = spec?.finalizeTurns ?? 2;
|
|
16742
|
+
const finalizeReserveUsd = spec?.finalizeReserveUsd ?? finalizeTurns * flatReserveUsd;
|
|
16743
|
+
const reserveCommitted = input.orchestrator.extension === true;
|
|
16744
|
+
if (reserveCommitted) reservedForFinalizationUsd = finalizeReserveUsd;
|
|
16745
|
+
orchestratorEcho = {
|
|
16746
|
+
...effectiveCapUsd === void 0 ? {} : { effectiveCapUsd },
|
|
16747
|
+
finalizeReserveUsd,
|
|
16748
|
+
finalizeTurns,
|
|
16749
|
+
reserveCommitted
|
|
16750
|
+
};
|
|
16751
|
+
if (spec?.capUsd !== void 0 && spec.capFraction === void 0 && effectiveCapUsd !== void 0 && effectiveCapUsd < spec.capUsd) say({
|
|
16752
|
+
severity: "warning",
|
|
16753
|
+
code: "orchestrator-cap-fraction-bound",
|
|
16754
|
+
message: `orchestrator budget.capUsd ${spec.capUsd.toFixed(4)} USD is bounded to ${effectiveCapUsd.toFixed(4)} USD by the default capFraction 0.2 of the run ceiling; pass capFraction: 1.0 to make capUsd the sole bound`
|
|
16755
|
+
});
|
|
16756
|
+
if (input.orchestrator.extension === true && effectiveCapUsd !== void 0 && effectiveCapUsd < finalizeReserveUsd) say({
|
|
16757
|
+
severity: "error",
|
|
16758
|
+
code: "orchestrator-cap-below-finalize-reserve",
|
|
16759
|
+
message: `effectiveCap ${effectiveCapUsd.toFixed(4)} USD is below the finalize reserve ${finalizeReserveUsd.toFixed(4)} USD: the run would refuse to start`
|
|
16760
|
+
});
|
|
16761
|
+
}
|
|
16762
|
+
const spawnSpecs = input.spawns ?? [];
|
|
16763
|
+
spawnSpecs.forEach(validateSpawnSpec);
|
|
16764
|
+
const spawnReports = [];
|
|
16765
|
+
/**
|
|
16766
|
+
* The layer-2 spawn-gate inputs per report, in report order: the
|
|
16767
|
+
* DECLARED estimate (estCost or the profile's; never the priced
|
|
16768
|
+
* arm, which the embedded gate cannot reach) and the explicit spawn
|
|
16769
|
+
* budget. Only an orchestrate wave consumes them.
|
|
16770
|
+
*/
|
|
16771
|
+
const waveGateInputs = [];
|
|
16772
|
+
const units = [];
|
|
16773
|
+
for (const spec of spawnSpecs) {
|
|
16774
|
+
const role = spec.role ?? "loop";
|
|
16775
|
+
const label = spec.label ?? role;
|
|
16776
|
+
const count = spec.count ?? 1;
|
|
16777
|
+
const profile = spec.profile === void 0 ? void 0 : defaults.profiles?.[spec.profile];
|
|
16778
|
+
if (spec.profile !== void 0 && profile === void 0) say({
|
|
16779
|
+
severity: "error",
|
|
16780
|
+
code: "unknown-profile",
|
|
16781
|
+
message: `spawn '${label}' names profile '${spec.profile}', which defaults.profiles does not register`,
|
|
16782
|
+
spawn: label
|
|
16783
|
+
});
|
|
16784
|
+
const limits = mergeUsageLimits(spec.limits, profile?.limits, defaults.limits);
|
|
16785
|
+
const servedBy = resolveServing(spec.model ?? profile?.routing?.[role] ?? profile?.model ?? defaults.routing?.[role]);
|
|
16786
|
+
if (servedBy === void 0) say({
|
|
16787
|
+
severity: "error",
|
|
16788
|
+
code: "unrouted-role",
|
|
16789
|
+
message: `spawn '${label}' resolves no model for role '${role}': the run would fail with a ConfigError at spawn time; set a model, a profile model, or defaults.routing.${role}`,
|
|
16790
|
+
spawn: label
|
|
16791
|
+
});
|
|
16792
|
+
const caps = servedBy === void 0 ? void 0 : capsOf(servedBy);
|
|
16793
|
+
const pricing = servedBy === void 0 ? void 0 : pricingOf(servedBy);
|
|
16794
|
+
const unpriced = servedBy !== void 0 && pricing === void 0;
|
|
16795
|
+
let reserveSource;
|
|
16796
|
+
let reserveUsd;
|
|
16797
|
+
if (unpriced && spec.estCost === void 0 && profile?.estCost === void 0) {
|
|
16798
|
+
reserveSource = "unpriced-zero";
|
|
16799
|
+
reserveUsd = 0;
|
|
16800
|
+
} else {
|
|
16801
|
+
reserveSource = spec.estCost !== void 0 ? "estCost" : profile?.estCost !== void 0 ? "profile-estCost" : spec.estInputTokens !== void 0 && caps?.pricing !== void 0 ? "priced-estimate" : "flat-default";
|
|
16802
|
+
reserveUsd = admissionReserveUsd({
|
|
16803
|
+
...spec.estCost === void 0 ? {} : { estCost: spec.estCost },
|
|
16804
|
+
...profile?.estCost === void 0 ? {} : { profileEstCost: profile.estCost },
|
|
16805
|
+
...spec.estInputTokens === void 0 ? {} : { inputTokens: spec.estInputTokens },
|
|
16806
|
+
...caps === void 0 ? {} : { caps },
|
|
16807
|
+
...limits.maxOutputTokensPerTurn === void 0 ? {} : { maxOutputTokensPerTurn: limits.maxOutputTokensPerTurn },
|
|
16808
|
+
flatReserveUsd
|
|
16809
|
+
});
|
|
16810
|
+
}
|
|
16811
|
+
const outputBound = caps === void 0 ? limits.maxOutputTokensPerTurn : limits.maxOutputTokensPerTurn === void 0 ? caps.maxOutputTokens : Math.min(caps.maxOutputTokens, limits.maxOutputTokensPerTurn);
|
|
16812
|
+
if (caps !== void 0 && limits.maxOutputTokensPerTurn !== void 0 && limits.maxOutputTokensPerTurn > caps.maxOutputTokens) say({
|
|
16813
|
+
severity: "warning",
|
|
16814
|
+
code: "output-cap-above-model",
|
|
16815
|
+
message: `spawn '${label}' sets maxOutputTokensPerTurn ${String(limits.maxOutputTokensPerTurn)} above the model's maxOutputTokens ${String(caps.maxOutputTokens)}: the model clamp wins`,
|
|
16816
|
+
spawn: label
|
|
16817
|
+
});
|
|
16818
|
+
const turnFloorUsd = pricing === void 0 || outputBound === void 0 ? void 0 : priceUsdOf(pricing, {
|
|
16819
|
+
inputTokens: spec.estInputTokens ?? 0,
|
|
16820
|
+
outputTokens: outputBound,
|
|
16821
|
+
cacheReadTokens: 0,
|
|
16822
|
+
cacheWriteTokens: 0
|
|
16823
|
+
});
|
|
16824
|
+
const toolCeilings = toolCeilingsOf(limits);
|
|
16825
|
+
const overall = toolCeilings.reduce((best, row) => row.ceiling === null ? best : best === null ? row.ceiling : Math.max(best, row.ceiling), null);
|
|
16826
|
+
const executedToolCallCeiling = limits.maxToolCalls !== void 0 && (overall === null || limits.maxToolCalls < overall) ? limits.maxToolCalls : overall;
|
|
16827
|
+
for (const row of toolCeilings) {
|
|
16828
|
+
if (row.tool === ANY_TOOL) continue;
|
|
16829
|
+
const cost = limits.toolUnits?.costs?.[row.tool];
|
|
16830
|
+
if (cost !== void 0 && cost > 0 && limits.toolUnits !== void 0 && cost > limits.toolUnits.max) {
|
|
16831
|
+
say({
|
|
16832
|
+
severity: "warning",
|
|
16833
|
+
code: "tool-unaffordable",
|
|
16834
|
+
message: `spawn '${label}' prices tool '${row.tool}' at ${String(cost)} units against toolUnits.max ${String(limits.toolUnits.max)}: the tool can never execute`,
|
|
16835
|
+
spawn: label
|
|
16836
|
+
});
|
|
16837
|
+
continue;
|
|
16838
|
+
}
|
|
16839
|
+
if (row.boundBy === "toolUnits" && row.ceiling !== null) {
|
|
16840
|
+
const nominal = limits.maxToolCalls;
|
|
16841
|
+
const cap = limits.maxCallsPerTool?.[row.tool];
|
|
16842
|
+
if (nominal !== void 0 && row.ceiling < nominal || cap !== void 0 && row.ceiling < cap) say({
|
|
16843
|
+
severity: "warning",
|
|
16844
|
+
code: "weighted-units-bind-first",
|
|
16845
|
+
message: `spawn '${label}': toolUnits is the first bottleneck for '${row.tool}': ${String(row.ceiling)} executed calls (cost ${String(limits.toolUnits?.costs?.[row.tool] ?? 1)} of max ${String(limits.toolUnits?.max ?? 0)})` + (nominal === void 0 ? "" : ` while maxToolCalls suggests ${String(nominal)}`),
|
|
16846
|
+
spawn: label
|
|
16847
|
+
});
|
|
16848
|
+
}
|
|
16849
|
+
const cap = limits.maxCallsPerTool?.[row.tool];
|
|
16850
|
+
if (cap !== void 0 && cap > 0 && row.ceiling !== null && row.boundBy !== "maxCallsPerTool") say({
|
|
16851
|
+
severity: "info",
|
|
16852
|
+
code: "per-tool-cap-unreachable",
|
|
16853
|
+
message: `spawn '${label}': maxCallsPerTool['${row.tool}'] ${String(cap)} can never bind: ${row.boundBy ?? "another limiter"} already stops at ${String(row.ceiling)}`,
|
|
16854
|
+
spawn: label
|
|
16855
|
+
});
|
|
16856
|
+
}
|
|
16857
|
+
if (limits.finalizationReserve !== void 0 && limits.maxToolCalls === void 0 && limits.toolUnits === void 0) say({
|
|
16858
|
+
severity: "warning",
|
|
16859
|
+
code: "inert-finalization-reserve",
|
|
16860
|
+
message: `spawn '${label}' sets finalizationReserve without maxToolCalls or toolUnits: no tool budget limiter exists for it to fire on`,
|
|
16861
|
+
spawn: label
|
|
16862
|
+
});
|
|
16863
|
+
if (limits.toolBudgetNotices === true && limits.maxToolCalls === void 0) say({
|
|
16864
|
+
severity: "warning",
|
|
16865
|
+
code: "inert-tool-budget-notices",
|
|
16866
|
+
message: `spawn '${label}' sets toolBudgetNotices without maxToolCalls: the notices never fire`,
|
|
16867
|
+
spawn: label
|
|
16868
|
+
});
|
|
16869
|
+
if (unpriced && ceilingUsd !== void 0) say({
|
|
16870
|
+
severity: "warning",
|
|
16871
|
+
code: "unpriced-under-ceiling",
|
|
16872
|
+
message: `spawn '${label}' is served by '${servedBy ?? ""}' with no price row: the ${ceilingUsd.toFixed(4)} USD run ceiling does NOT bound it and its admission reserve is zero`,
|
|
16873
|
+
spawn: label
|
|
16874
|
+
});
|
|
16875
|
+
spawnReports.push({
|
|
16876
|
+
label,
|
|
16877
|
+
role,
|
|
16878
|
+
count,
|
|
16879
|
+
...servedBy === void 0 ? {} : { servedBy },
|
|
16880
|
+
...unpriced ? { unpriced: true } : {},
|
|
16881
|
+
limits,
|
|
16882
|
+
admissionReserveUsd: reserveUsd,
|
|
16883
|
+
reserveSource,
|
|
16884
|
+
...outputBound === void 0 ? {} : { maxOutputTokensPerTurn: outputBound },
|
|
16885
|
+
...turnFloorUsd === void 0 ? {} : { turnFloorUsd },
|
|
16886
|
+
executedToolCallCeiling,
|
|
16887
|
+
toolCeilings
|
|
16888
|
+
});
|
|
16889
|
+
{
|
|
16890
|
+
const declaredEstCostUsd = spec.estCost ?? profile?.estCost;
|
|
16891
|
+
waveGateInputs.push({
|
|
16892
|
+
...declaredEstCostUsd === void 0 ? {} : { estCostUsd: declaredEstCostUsd },
|
|
16893
|
+
...spec.budgetUsd === void 0 ? {} : { budgetUsd: spec.budgetUsd }
|
|
16894
|
+
});
|
|
16895
|
+
}
|
|
16896
|
+
for (let i = 0; i < count; i += 1) {
|
|
16897
|
+
const unit = {
|
|
16898
|
+
label: count === 1 ? label : `${label}#${String(i + 1)}`,
|
|
16899
|
+
tokensFloor: (spec.estInputTokens ?? 0) + (outputBound ?? 0)
|
|
16900
|
+
};
|
|
16901
|
+
if (servedBy !== void 0) {
|
|
16902
|
+
const { adapterId, model } = parseModelRef(servedBy);
|
|
16903
|
+
unit.provider = adapterId;
|
|
16904
|
+
unit.model = model;
|
|
16905
|
+
}
|
|
16906
|
+
if (turnFloorUsd !== void 0) unit.turnFloorUsd = turnFloorUsd;
|
|
16907
|
+
units.push(unit);
|
|
16908
|
+
}
|
|
16909
|
+
}
|
|
16910
|
+
let orchestratorReserveUsd;
|
|
16911
|
+
if (input.orchestrator !== void 0) {
|
|
16912
|
+
const servedBy = resolveServing(defaults.routing?.orchestrate);
|
|
16913
|
+
if (servedBy === void 0) say({
|
|
16914
|
+
severity: "error",
|
|
16915
|
+
code: "unrouted-role",
|
|
16916
|
+
message: "the orchestrator resolves no model for role 'orchestrate': set defaults.routing.orchestrate or an orchestrate model on the call",
|
|
16917
|
+
spawn: "orchestrator"
|
|
16918
|
+
});
|
|
16919
|
+
else {
|
|
16920
|
+
const caps = capsOf(servedBy);
|
|
16921
|
+
const pricing = pricingOf(servedBy);
|
|
16922
|
+
const orchLimits = mergeUsageLimits(input.orchestrator.limits, void 0, defaults.limits);
|
|
16923
|
+
const outputBound = caps === void 0 ? orchLimits.maxOutputTokensPerTurn : orchLimits.maxOutputTokensPerTurn === void 0 ? caps.maxOutputTokens : Math.min(caps.maxOutputTokens, orchLimits.maxOutputTokensPerTurn);
|
|
16924
|
+
const { adapterId, model } = parseModelRef(servedBy);
|
|
16925
|
+
const unit = {
|
|
16926
|
+
label: "orchestrator",
|
|
16927
|
+
provider: adapterId,
|
|
16928
|
+
model,
|
|
16929
|
+
tokensFloor: outputBound ?? 0
|
|
16930
|
+
};
|
|
16931
|
+
if (pricing !== void 0 && outputBound !== void 0) unit.turnFloorUsd = priceUsdOf(pricing, {
|
|
16932
|
+
inputTokens: 0,
|
|
16933
|
+
outputTokens: outputBound,
|
|
16934
|
+
cacheReadTokens: 0,
|
|
16935
|
+
cacheWriteTokens: 0
|
|
16936
|
+
});
|
|
16937
|
+
units.push(unit);
|
|
16938
|
+
if (effectiveCapUsd !== void 0) orchestratorReserveUsd = Math.max(0, orchestratorAdmissionEstCostUsd(effectiveCapUsd, reservedForFinalizationUsd));
|
|
16939
|
+
else if (pricing === void 0) orchestratorReserveUsd = 0;
|
|
16940
|
+
else orchestratorReserveUsd = admissionReserveUsd({
|
|
16941
|
+
...input.orchestrator.estInputTokens === void 0 ? {} : { inputTokens: input.orchestrator.estInputTokens },
|
|
16942
|
+
...caps === void 0 ? {} : { caps },
|
|
16943
|
+
...orchLimits.maxOutputTokensPerTurn === void 0 ? {} : { maxOutputTokensPerTurn: orchLimits.maxOutputTokensPerTurn },
|
|
16944
|
+
flatReserveUsd
|
|
16945
|
+
});
|
|
16946
|
+
}
|
|
16947
|
+
}
|
|
16948
|
+
const wave = [];
|
|
16949
|
+
let committed = 0;
|
|
16950
|
+
let spawned = 0;
|
|
16951
|
+
let children = 0;
|
|
16952
|
+
const admitAgainstRoot = (reserveUsd) => {
|
|
16953
|
+
if (ceilingUsd === void 0) return true;
|
|
16954
|
+
const held = committed + reservedForFinalizationUsd;
|
|
16955
|
+
return !(held >= ceilingUsd || held + reserveUsd > ceilingUsd);
|
|
16956
|
+
};
|
|
16957
|
+
if (input.orchestrator !== void 0) {
|
|
16958
|
+
const reserveUsd = orchestratorReserveUsd ?? flatReserveUsd;
|
|
16959
|
+
let deniedBy;
|
|
16960
|
+
if (spawned >= lifetimeSpawnCap) deniedBy = "spawn-cap";
|
|
16961
|
+
else if (!admitAgainstRoot(reserveUsd)) deniedBy = "budget";
|
|
16962
|
+
wave.push({
|
|
16963
|
+
label: "orchestrator",
|
|
16964
|
+
reserveUsd,
|
|
16965
|
+
admitted: deniedBy === void 0,
|
|
16966
|
+
...deniedBy === void 0 ? {} : { deniedBy }
|
|
16967
|
+
});
|
|
16968
|
+
if (deniedBy === void 0) {
|
|
16969
|
+
committed += reserveUsd;
|
|
16970
|
+
spawned += 1;
|
|
16971
|
+
}
|
|
16972
|
+
}
|
|
16973
|
+
const maxSpawns = input.orchestrator?.maxSpawns;
|
|
16974
|
+
const orchestrateWave = input.orchestrator !== void 0;
|
|
16975
|
+
for (const [reportIndex, report] of spawnReports.entries()) {
|
|
16976
|
+
const gate = waveGateInputs[reportIndex] ?? {};
|
|
16977
|
+
for (let i = 0; i < report.count; i += 1) {
|
|
16978
|
+
const label = report.count === 1 ? report.label : `${report.label}#${String(i + 1)}`;
|
|
16979
|
+
const reserveUsd = report.admissionReserveUsd;
|
|
16980
|
+
let deniedBy;
|
|
16981
|
+
if (spawned >= lifetimeSpawnCap) deniedBy = "spawn-cap";
|
|
16982
|
+
else if (maxSpawns !== void 0 && children >= maxSpawns) deniedBy = "orchestrator-max-spawns";
|
|
16983
|
+
else {
|
|
16984
|
+
if (orchestrateWave && ceilingUsd !== void 0) {
|
|
16985
|
+
const remainder = ceilingUsd - committed - reservedForFinalizationUsd;
|
|
16986
|
+
const projection = dispatchProjectionReserveUsd(gate, flatReserveUsd);
|
|
16987
|
+
if (remainder <= 0 || remainder < projection) deniedBy = "budget";
|
|
16988
|
+
}
|
|
16989
|
+
if (deniedBy === void 0 && !admitAgainstRoot(reserveUsd)) deniedBy = "budget";
|
|
16990
|
+
}
|
|
16991
|
+
wave.push({
|
|
16992
|
+
label,
|
|
16993
|
+
reserveUsd,
|
|
16994
|
+
admitted: deniedBy === void 0,
|
|
16995
|
+
...deniedBy === void 0 ? {} : { deniedBy }
|
|
16996
|
+
});
|
|
16997
|
+
if (deniedBy === void 0) {
|
|
16998
|
+
committed += reserveUsd;
|
|
16999
|
+
spawned += 1;
|
|
17000
|
+
children += 1;
|
|
17001
|
+
}
|
|
17002
|
+
}
|
|
17003
|
+
}
|
|
17004
|
+
const admitted = wave.filter((row) => row.admitted).length;
|
|
17005
|
+
const denied = wave.length - admitted;
|
|
17006
|
+
if (wave.length > 0 && denied > 0) {
|
|
17007
|
+
const deniedLabels = wave.filter((row) => !row.admitted).map((row) => row.label);
|
|
17008
|
+
if (admitted === 0) say({
|
|
17009
|
+
severity: "error",
|
|
17010
|
+
code: "nothing-admitted",
|
|
17011
|
+
message: `the declared wave admits NOTHING: every spawn is denied (${deniedLabels.join(", ")}); no paid work can start`
|
|
17012
|
+
});
|
|
17013
|
+
else say({
|
|
17014
|
+
severity: "warning",
|
|
17015
|
+
code: "partial-admission",
|
|
17016
|
+
message: `the declared wave admits ${String(admitted)} of ${String(wave.length)} spawns; denied before any work: ${deniedLabels.join(", ")}`
|
|
17017
|
+
});
|
|
17018
|
+
}
|
|
17019
|
+
if (ceilingUsd === void 0 && wave.length > 0) say({
|
|
17020
|
+
severity: "info",
|
|
17021
|
+
code: "no-usd-ceiling",
|
|
17022
|
+
message: "the run has no budgetUsd ceiling: only turn, tool, and time limits bound spend, and the whole declared wave admits"
|
|
17023
|
+
});
|
|
17024
|
+
const declaredUnits = units.length;
|
|
17025
|
+
const maxInFlight = declaredUnits === 0 ? perRun : Math.min(perRun, declaredUnits);
|
|
17026
|
+
const perProviderCaps = engine.concurrency?.perProvider;
|
|
17027
|
+
const perProvider = {};
|
|
17028
|
+
const byProvider = /* @__PURE__ */ new Map();
|
|
17029
|
+
for (const unit of units) {
|
|
17030
|
+
if (unit.provider === void 0) continue;
|
|
17031
|
+
const list = byProvider.get(unit.provider) ?? [];
|
|
17032
|
+
list.push(unit);
|
|
17033
|
+
byProvider.set(unit.provider, list);
|
|
17034
|
+
}
|
|
17035
|
+
for (const [provider, list] of [...byProvider.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
17036
|
+
const cap = perProviderCaps?.[provider];
|
|
17037
|
+
const inFlight = Math.min(list.length, maxInFlight, cap ?? Number.POSITIVE_INFINITY);
|
|
17038
|
+
perProvider[provider] = {
|
|
17039
|
+
inFlight,
|
|
17040
|
+
requestsPerWave: inFlight,
|
|
17041
|
+
tokensPerWaveFloor: [...list].sort((a, b) => b.tokensFloor - a.tokensFloor).slice(0, inFlight).reduce((sum, unit) => sum + unit.tokensFloor, 0)
|
|
17042
|
+
};
|
|
17043
|
+
}
|
|
17044
|
+
const pricedTurns = units.map((unit) => unit.turnFloorUsd).filter((usd) => usd !== void 0).sort((a, b) => b - a).slice(0, maxInFlight);
|
|
17045
|
+
const overshootOneTurnFloorUsd = pricedTurns.length === 0 ? void 0 : pricedTurns.reduce((sum, usd) => sum + usd, 0);
|
|
17046
|
+
if (ceilingUsd !== void 0 && overshootOneTurnFloorUsd !== void 0 && units.length > 0) say({
|
|
17047
|
+
severity: "info",
|
|
17048
|
+
code: "overshoot-exposure",
|
|
17049
|
+
message: `past a ceiling crossing, up to ${String(Math.min(maxInFlight, units.length))} in-flight turns may still complete: at least ${overshootOneTurnFloorUsd.toFixed(4)} USD past the ${ceilingUsd.toFixed(4)} USD ceiling at the declared estimates, growing with prompt size`
|
|
17050
|
+
});
|
|
17051
|
+
const quotaConfigured = engine.quota !== void 0;
|
|
17052
|
+
if (!quotaConfigured && maxInFlight > 1 && units.length > 0) say({
|
|
17053
|
+
severity: "info",
|
|
17054
|
+
code: "no-quota",
|
|
17055
|
+
message: `no shared quota limiter is configured while up to ${String(maxInFlight)} turns run concurrently: provider-side rate limits are unprotected (createEngine quota)`
|
|
17056
|
+
});
|
|
17057
|
+
if (input.quotaRules !== void 0) input.quotaRules.forEach((rule, index) => {
|
|
17058
|
+
let requests = 0;
|
|
17059
|
+
let tokens = 0;
|
|
17060
|
+
for (const unit of units) {
|
|
17061
|
+
if (unit.provider === void 0 || unit.model === void 0) continue;
|
|
17062
|
+
if (quotaRuleMatches(rule, {
|
|
17063
|
+
provider: unit.provider,
|
|
17064
|
+
model: unit.model,
|
|
17065
|
+
...engine.quota?.tenant === void 0 ? {} : { tenant: engine.quota.tenant },
|
|
17066
|
+
estimate: {
|
|
17067
|
+
requests: 1,
|
|
17068
|
+
inputTokens: 0
|
|
17069
|
+
}
|
|
17070
|
+
})) {
|
|
17071
|
+
requests += 1;
|
|
17072
|
+
tokens += unit.tokensFloor;
|
|
17073
|
+
}
|
|
17074
|
+
}
|
|
17075
|
+
const dims = [
|
|
17076
|
+
rule.provider === void 0 ? void 0 : `provider=${rule.provider}`,
|
|
17077
|
+
rule.model === void 0 ? void 0 : `model=${rule.model}`,
|
|
17078
|
+
rule.tenant === void 0 ? void 0 : `tenant=${rule.tenant}`
|
|
17079
|
+
].filter((dim) => dim !== void 0).join(" ");
|
|
17080
|
+
const name = dims === "" ? `rule[${String(index)}]` : `rule[${String(index)}] (${dims})`;
|
|
17081
|
+
if (rule.requestsPerMinute !== void 0 && requests > rule.requestsPerMinute) say({
|
|
17082
|
+
severity: "warning",
|
|
17083
|
+
code: "quota-requests-below-wave",
|
|
17084
|
+
message: `${name}: the declared wave holds ${String(requests)} matching dispatches against requestsPerMinute ${String(rule.requestsPerMinute)}: expect synthetic rate-limit denials and backoff inside one window`
|
|
17085
|
+
});
|
|
17086
|
+
if (rule.tokensPerMinute !== void 0 && tokens > rule.tokensPerMinute) say({
|
|
17087
|
+
severity: "warning",
|
|
17088
|
+
code: "quota-tokens-below-wave",
|
|
17089
|
+
message: `${name}: the declared wave demands at least ${String(tokens)} tokens against tokensPerMinute ${String(rule.tokensPerMinute)}: expect estimate-driven throttling inside one window`
|
|
17090
|
+
});
|
|
17091
|
+
});
|
|
17092
|
+
const severityRank = {
|
|
17093
|
+
error: 0,
|
|
17094
|
+
warning: 1,
|
|
17095
|
+
info: 2
|
|
17096
|
+
};
|
|
17097
|
+
findings.sort((a, b) => severityRank[a.severity] - severityRank[b.severity]);
|
|
17098
|
+
return {
|
|
17099
|
+
concurrency: {
|
|
17100
|
+
perRun,
|
|
17101
|
+
...perProviderCaps === void 0 ? {} : { perProvider: { ...perProviderCaps } }
|
|
17102
|
+
},
|
|
17103
|
+
budget: {
|
|
17104
|
+
...ceilingUsd === void 0 ? {} : { ceilingUsd },
|
|
17105
|
+
flatReserveUsd,
|
|
17106
|
+
lifetimeSpawnCap,
|
|
17107
|
+
childBudgetFraction,
|
|
17108
|
+
maxDepth,
|
|
17109
|
+
...orchestratorEcho === void 0 ? {} : { orchestrator: orchestratorEcho }
|
|
17110
|
+
},
|
|
17111
|
+
quota: {
|
|
17112
|
+
configured: quotaConfigured,
|
|
17113
|
+
...engine.quota?.tenant === void 0 ? {} : { tenant: engine.quota.tenant },
|
|
17114
|
+
...input.quotaRules === void 0 ? {} : { rules: input.quotaRules.length }
|
|
17115
|
+
},
|
|
17116
|
+
runLimits,
|
|
17117
|
+
spawns: spawnReports,
|
|
17118
|
+
admission: {
|
|
17119
|
+
...ceilingUsd === void 0 ? {} : { ceilingUsd },
|
|
17120
|
+
reservedForFinalizationUsd,
|
|
17121
|
+
wave,
|
|
17122
|
+
admitted,
|
|
17123
|
+
denied
|
|
17124
|
+
},
|
|
17125
|
+
exposure: {
|
|
17126
|
+
maxInFlight,
|
|
17127
|
+
...overshootOneTurnFloorUsd === void 0 ? {} : { overshootOneTurnFloorUsd },
|
|
17128
|
+
perProvider
|
|
17129
|
+
},
|
|
17130
|
+
findings
|
|
17131
|
+
};
|
|
17132
|
+
}
|
|
17133
|
+
//#endregion
|
|
17134
|
+
//#region src/engine/run-profiles.ts
|
|
17135
|
+
/**
|
|
17136
|
+
* The shipped presets (fast / standard / deep / ultra "and similar").
|
|
17137
|
+
* Data only; a review-time assertion checks the
|
|
17138
|
+
* engine has zero behavioral branches keyed on these names.
|
|
17139
|
+
*/
|
|
17140
|
+
const RUN_PROFILES = {
|
|
17141
|
+
fast: {
|
|
17142
|
+
effortByRole: {
|
|
17143
|
+
orchestrate: "low",
|
|
17144
|
+
plan: "low",
|
|
17145
|
+
summarize: "low",
|
|
17146
|
+
extract: "low"
|
|
17147
|
+
},
|
|
17148
|
+
perRunConcurrency: 16,
|
|
17149
|
+
permissionPreset: "standard",
|
|
17150
|
+
lifetimeSpawnCap: 64,
|
|
17151
|
+
maxDepth: 1
|
|
17152
|
+
},
|
|
17153
|
+
standard: {
|
|
17154
|
+
effortByRole: {
|
|
17155
|
+
orchestrate: "high",
|
|
17156
|
+
plan: "high",
|
|
17157
|
+
summarize: "low",
|
|
17158
|
+
extract: "low"
|
|
17159
|
+
},
|
|
17160
|
+
perRunConcurrency: 12,
|
|
17161
|
+
permissionPreset: "standard",
|
|
17162
|
+
lifetimeSpawnCap: 500,
|
|
17163
|
+
maxDepth: 1
|
|
17164
|
+
},
|
|
17165
|
+
deep: {
|
|
17166
|
+
effortByRole: {
|
|
17167
|
+
orchestrate: "high",
|
|
17168
|
+
plan: "high",
|
|
17169
|
+
summarize: "medium",
|
|
17170
|
+
extract: "medium"
|
|
17171
|
+
},
|
|
17172
|
+
perRunConcurrency: 8,
|
|
17173
|
+
permissionPreset: "standard",
|
|
17174
|
+
lifetimeSpawnCap: 500,
|
|
17175
|
+
maxDepth: 2
|
|
17176
|
+
},
|
|
17177
|
+
ultra: {
|
|
17178
|
+
effortByRole: {
|
|
17179
|
+
orchestrate: "max",
|
|
17180
|
+
plan: "max",
|
|
17181
|
+
summarize: "high",
|
|
17182
|
+
extract: "high"
|
|
17183
|
+
},
|
|
17184
|
+
perRunConcurrency: 8,
|
|
17185
|
+
permissionPreset: "strict",
|
|
17186
|
+
lifetimeSpawnCap: 500,
|
|
17187
|
+
maxDepth: 3
|
|
17188
|
+
}
|
|
17189
|
+
};
|
|
17190
|
+
/** Looks up a shipped RunProfile by name; undefined for unknown names. */
|
|
17191
|
+
function runProfile(name) {
|
|
17192
|
+
return RUN_PROFILES[name];
|
|
17193
|
+
}
|
|
17194
|
+
//#endregion
|
|
17195
|
+
//#region src/model/concurrency.ts
|
|
17196
|
+
/**
|
|
17197
|
+
* Per-provider concurrency keys (M4-T07): a keyed limiter beside the
|
|
17198
|
+
* router, ENGINE-scoped (keys constrain calls
|
|
17199
|
+
* across a single engine per adapter). The Appendix A default is
|
|
17200
|
+
* unlimited: an embeddable library must not surprise-throttle hosts, so
|
|
17201
|
+
* the per-run semaphore stays the only default bound and provider 429s
|
|
17202
|
+
* ride RetryPolicy; hosts with known tier limits opt in per adapter id
|
|
17203
|
+
* via createEngine concurrency.perProvider.
|
|
17204
|
+
*
|
|
17205
|
+
* This keyed limiter bounds PARALLELISM inside one engine only. Two
|
|
17206
|
+
* processes sharing one API key coordinate through the QuotaLimiter
|
|
17207
|
+
* SPI instead (RV-215, createEngine `quota`): rate and volume live
|
|
17208
|
+
* there, in shared storage; in-flight slots live here.
|
|
17209
|
+
*/
|
|
17210
|
+
var KeyedLimiter = class {
|
|
17211
|
+
semaphores = /* @__PURE__ */ new Map();
|
|
17212
|
+
constructor(caps) {
|
|
17213
|
+
for (const [key, limit] of Object.entries(caps ?? {})) this.semaphores.set(key, new Semaphore(limit));
|
|
17214
|
+
}
|
|
17215
|
+
/** Queue depth for one key (0 for unlimited keys); telemetry only. */
|
|
17216
|
+
pending(key) {
|
|
17217
|
+
return this.semaphores.get(key)?.pending ?? 0;
|
|
17218
|
+
}
|
|
17219
|
+
/**
|
|
17220
|
+
* Runs `fn` under the key's semaphore; keys without a configured cap
|
|
17221
|
+
* run unlimited (no queueing, no overhead). An aborted `signal` frees
|
|
17222
|
+
* a queued caller without a slot (the Semaphore contract), so run
|
|
17223
|
+
* cancellation drains provider queues too (v1.34.0 review P2-4).
|
|
17224
|
+
*/
|
|
17225
|
+
async withSlot(key, fn, onQueued, signal) {
|
|
17226
|
+
const semaphore = this.semaphores.get(key);
|
|
17227
|
+
if (semaphore === void 0) return fn();
|
|
17228
|
+
return semaphore.withSlot(fn, onQueued, signal);
|
|
17229
|
+
}
|
|
17230
|
+
};
|
|
17231
|
+
//#endregion
|
|
17232
|
+
//#region src/orchestrator/finish-validators.ts
|
|
17233
|
+
/**
|
|
17234
|
+
* Deterministic host validation of the orchestrator finish result (the
|
|
17235
|
+
* v1.40.0 improvement plan's RV-204 slice). A validator is plain
|
|
17236
|
+
* synchronous host code judging the finish({ result }) argument; the
|
|
17237
|
+
* orchestrator runtime runs the configured set on every schema valid
|
|
17238
|
+
* finish call, returns the failure reasons to the model as the call's
|
|
17239
|
+
* error tool result (a bounded repair turn), and fails the run with a
|
|
17240
|
+
* typed error when the repair bound is exhausted. Verdicts journal as
|
|
17241
|
+
* decision entries, so a resume rolls the SAME verdicts forward without
|
|
17242
|
+
* re-running validator code.
|
|
17243
|
+
*/
|
|
17244
|
+
const ok = { ok: true };
|
|
17245
|
+
function requireNonEmptyStrings(values, what) {
|
|
17246
|
+
if (!Array.isArray(values) || values.length === 0) throw new ConfigError(`${what} must be a non empty array of strings`);
|
|
17247
|
+
for (const value of values) if (typeof value !== "string" || value.length === 0) throw new ConfigError(`${what} must contain only non empty strings`);
|
|
17248
|
+
return values;
|
|
17249
|
+
}
|
|
17250
|
+
/**
|
|
17251
|
+
* Requires every named section to appear LITERALLY in the result text
|
|
17252
|
+
* (a heading like 'FINDINGS' or any marker the goal demands). Default
|
|
17253
|
+
* name 'required-sections'; pass `name` to run several instances.
|
|
17254
|
+
*/
|
|
17255
|
+
function requiredSectionsValidator(options) {
|
|
17256
|
+
const sections = requireNonEmptyStrings(options.sections, "requiredSectionsValidator sections");
|
|
17257
|
+
return {
|
|
17258
|
+
name: options.name ?? "required-sections",
|
|
17259
|
+
validate: (input) => {
|
|
17260
|
+
const missing = sections.filter((section) => !input.text.includes(section));
|
|
17261
|
+
return missing.length === 0 ? ok : {
|
|
17262
|
+
ok: false,
|
|
17263
|
+
reasons: missing.map((section) => `required section '${section}' is missing`)
|
|
17264
|
+
};
|
|
17213
17265
|
}
|
|
17214
|
-
|
|
17215
|
-
|
|
17216
|
-
|
|
17217
|
-
|
|
17218
|
-
|
|
17219
|
-
|
|
17220
|
-
|
|
17221
|
-
|
|
17222
|
-
|
|
17223
|
-
|
|
17224
|
-
|
|
17225
|
-
|
|
17226
|
-
|
|
17227
|
-
|
|
17228
|
-
|
|
17229
|
-
|
|
17230
|
-
|
|
17231
|
-
|
|
17232
|
-
|
|
17233
|
-
const
|
|
17234
|
-
const
|
|
17235
|
-
|
|
17236
|
-
|
|
17237
|
-
|
|
17238
|
-
|
|
17239
|
-
const status = record.settled?.status ?? "running";
|
|
17240
|
-
childStatusCounts[status] = (childStatusCounts[status] ?? 0) + 1;
|
|
17241
|
-
if (status === "ok") continue;
|
|
17242
|
-
if (acceptPartial && status === "limit" && record.settled?.partial !== void 0) {
|
|
17243
|
-
salvaged.push(record.nodeId);
|
|
17244
|
-
degradedReasons.push(`child ${record.nodeId} accepted as partial (settled 'limit' with a structured partial)`);
|
|
17245
|
-
continue;
|
|
17246
|
-
}
|
|
17247
|
-
hardDegraded += 1;
|
|
17248
|
-
degradedReasons.push(status === "running" ? `child ${record.nodeId} was still running when finish validated` : `child ${record.nodeId} settled '${status}'`);
|
|
17266
|
+
};
|
|
17267
|
+
}
|
|
17268
|
+
/**
|
|
17269
|
+
* Requires the result to be a JSON object carrying every named field
|
|
17270
|
+
* with a substantial value: present, not null, and not an empty or
|
|
17271
|
+
* whitespace only string (empty arrays, zero, and false COUNT as
|
|
17272
|
+
* present; emptiness rules beyond strings belong to a custom
|
|
17273
|
+
* validator). Default name 'required-fields'.
|
|
17274
|
+
*/
|
|
17275
|
+
function requiredFieldsValidator(options) {
|
|
17276
|
+
const fields = requireNonEmptyStrings(options.fields, "requiredFieldsValidator fields");
|
|
17277
|
+
return {
|
|
17278
|
+
name: options.name ?? "required-fields",
|
|
17279
|
+
validate: (input) => {
|
|
17280
|
+
const result = input.result;
|
|
17281
|
+
if (typeof result !== "object" || result === null || Array.isArray(result)) return {
|
|
17282
|
+
ok: false,
|
|
17283
|
+
reasons: ["the finish result is not a JSON object"]
|
|
17284
|
+
};
|
|
17285
|
+
const record = result;
|
|
17286
|
+
const reasons = [];
|
|
17287
|
+
for (const field of fields) {
|
|
17288
|
+
const value = record[field];
|
|
17289
|
+
if (value === void 0 || value === null) reasons.push(`required field '${field}' is missing`);
|
|
17290
|
+
else if (typeof value === "string" && value.trim().length === 0) reasons.push(`required field '${field}' is empty`);
|
|
17249
17291
|
}
|
|
17250
|
-
|
|
17251
|
-
|
|
17252
|
-
|
|
17253
|
-
decisionType: "orchestrator_acceptance",
|
|
17254
|
-
verdict: accepted ? "accepted" : "rejected",
|
|
17255
|
-
completion: !accepted ? "rejected" : degradedReasons.length === 0 ? "complete" : "partial",
|
|
17256
|
-
childPolicy,
|
|
17257
|
-
childStatusCounts,
|
|
17258
|
-
degradedReasons,
|
|
17259
|
-
...salvaged.length === 0 ? {} : { salvagedPartialChildren: salvaged }
|
|
17292
|
+
return reasons.length === 0 ? ok : {
|
|
17293
|
+
ok: false,
|
|
17294
|
+
reasons
|
|
17260
17295
|
};
|
|
17261
|
-
await internals.replayer.appendSinglePhase({
|
|
17262
|
-
scope: callingState.scope,
|
|
17263
|
-
key: acceptanceKey,
|
|
17264
|
-
kind: "decision",
|
|
17265
|
-
status: "ok",
|
|
17266
|
-
spanId: internals.spans.mint(callingState.spanId),
|
|
17267
|
-
site: "orchestrator-acceptance",
|
|
17268
|
-
value: decision
|
|
17269
|
-
});
|
|
17270
17296
|
}
|
|
17271
|
-
|
|
17272
|
-
|
|
17273
|
-
|
|
17274
|
-
|
|
17275
|
-
|
|
17276
|
-
|
|
17277
|
-
|
|
17278
|
-
|
|
17279
|
-
|
|
17280
|
-
|
|
17297
|
+
};
|
|
17298
|
+
}
|
|
17299
|
+
/** The default citation shape: a path with an extension, a colon, a line number. */
|
|
17300
|
+
const DEFAULT_CITATION_PATTERN = "[\\w./-]+\\.\\w+:\\d+";
|
|
17301
|
+
/** The default preserved share, the improvement plan's RV-202 gate. */
|
|
17302
|
+
const DEFAULT_EVIDENCE_MIN_SHARE = .95;
|
|
17303
|
+
const MAX_LISTED_CITATIONS = 20;
|
|
17304
|
+
function listCitations(values) {
|
|
17305
|
+
return values.length <= MAX_LISTED_CITATIONS ? values.join(", ") : `${values.slice(0, MAX_LISTED_CITATIONS).join(", ")} and ${String(values.length - MAX_LISTED_CITATIONS)} more`;
|
|
17306
|
+
}
|
|
17307
|
+
/**
|
|
17308
|
+
* The RV-202 evidence preservation contract: the finish result must
|
|
17309
|
+
* PRESERVE the citations the children actually produced. Distinct
|
|
17310
|
+
* matches of `pattern` are collected across the outputs of children
|
|
17311
|
+
* settled 'ok' (spawn order); at least `minShare` of them (default
|
|
17312
|
+
* {@link DEFAULT_EVIDENCE_MIN_SHARE}, the plan's 95 percent gate,
|
|
17313
|
+
* compared as a ceiling on the required count so an exact boundary like
|
|
17314
|
+
* 19 of 20 passes) must appear literally in the result text. Zero child
|
|
17315
|
+
* citations pass vacuously. With `requireKnown: true` the contract also
|
|
17316
|
+
* runs in reverse: every citation in the RESULT must appear in some
|
|
17317
|
+
* child's output, so a fabricated but pattern valid citation is
|
|
17318
|
+
* rejected instead of silently counting as evidence. Rejection reasons
|
|
17319
|
+
* list the missing (and unknown) citations, capped at 20, so the repair
|
|
17320
|
+
* turn can restore them. Purely textual and deterministic; checking
|
|
17321
|
+
* that cited targets EXIST on disk is host territory (a custom
|
|
17322
|
+
* validator), not this contract. Default name 'evidence-preserved'.
|
|
17323
|
+
*/
|
|
17324
|
+
function evidencePreservedValidator(options) {
|
|
17325
|
+
const pattern = options?.pattern ?? "[\\w./-]+\\.\\w+:\\d+";
|
|
17326
|
+
const flags = options?.flags ?? "";
|
|
17327
|
+
const globalFlags = flags.includes("g") ? flags : `${flags}g`;
|
|
17328
|
+
try {
|
|
17329
|
+
new RegExp(pattern, globalFlags);
|
|
17330
|
+
} catch (thrown) {
|
|
17331
|
+
throw new ConfigError(`evidencePreservedValidator pattern does not compile: ${thrown instanceof Error ? thrown.message : String(thrown)}`);
|
|
17332
|
+
}
|
|
17333
|
+
const minShare = options?.minShare ?? .95;
|
|
17334
|
+
if (typeof minShare !== "number" || !Number.isFinite(minShare) || minShare <= 0 || minShare > 1) throw new ConfigError(`evidencePreservedValidator minShare must be a number in (0, 1]; got ${String(minShare)}`);
|
|
17335
|
+
return {
|
|
17336
|
+
name: options?.name ?? "evidence-preserved",
|
|
17337
|
+
validate: (input) => {
|
|
17338
|
+
const cited = /* @__PURE__ */ new Set();
|
|
17339
|
+
for (const child of input.children ?? []) {
|
|
17340
|
+
if (child.status !== "ok" && child.salvageableOutput !== true) continue;
|
|
17341
|
+
for (const match of child.text.match(new RegExp(pattern, globalFlags)) ?? []) cited.add(match);
|
|
17342
|
+
}
|
|
17343
|
+
const reasons = [];
|
|
17344
|
+
if (cited.size > 0) {
|
|
17345
|
+
const missing = [...cited].filter((citation) => !input.text.includes(citation));
|
|
17346
|
+
const preserved = cited.size - missing.length;
|
|
17347
|
+
if (preserved < Math.ceil(minShare * cited.size - 1e-9)) reasons.push(`evidence preservation ${String(preserved)} of ${String(cited.size)} child citations is below the required share ${String(minShare)}; missing: ${listCitations(missing)}`);
|
|
17348
|
+
}
|
|
17349
|
+
if (options?.requireKnown === true) {
|
|
17350
|
+
const fabricated = [...new Set(input.text.match(new RegExp(pattern, globalFlags)) ?? [])].filter((citation) => !cited.has(citation));
|
|
17351
|
+
if (fabricated.length > 0) reasons.push(`unknown citations not present in any child report: ${listCitations(fabricated)}`);
|
|
17352
|
+
}
|
|
17353
|
+
return reasons.length === 0 ? ok : {
|
|
17354
|
+
ok: false,
|
|
17355
|
+
reasons
|
|
17356
|
+
};
|
|
17281
17357
|
}
|
|
17282
|
-
|
|
17283
|
-
result: await runSynthesis(result.output),
|
|
17284
|
-
completion: decision.completion,
|
|
17285
|
-
childStatusCounts: decision.childStatusCounts,
|
|
17286
|
-
degradedReasons: decision.degradedReasons,
|
|
17287
|
-
...decision.salvagedPartialChildren === void 0 ? {} : { salvagedPartialChildren: decision.salvagedPartialChildren }
|
|
17288
|
-
};
|
|
17289
|
-
});
|
|
17358
|
+
};
|
|
17290
17359
|
}
|
|
17291
17360
|
/**
|
|
17292
|
-
*
|
|
17293
|
-
*
|
|
17294
|
-
*
|
|
17295
|
-
*
|
|
17296
|
-
*
|
|
17297
|
-
*
|
|
17298
|
-
* so the canonical entry point could not set a root ceiling without
|
|
17299
|
-
* dropping to `engine.run(makeOrchestratorWorkflow(...))` (v1.18.0
|
|
17300
|
-
* review P1-5).
|
|
17361
|
+
* Requires at least `min` matches of `pattern` in the result text (the
|
|
17362
|
+
* plan's citation and source count checks: a file:line pattern, a URL
|
|
17363
|
+
* pattern). The pattern compiles at construction (invalid patterns are a
|
|
17364
|
+
* ConfigError before any run exists) and matches globally; `min` is a
|
|
17365
|
+
* positive integer. Default name 'min-matches'; pass `name` to run
|
|
17366
|
+
* several instances, because names must be unique per orchestrate call.
|
|
17301
17367
|
*/
|
|
17302
|
-
function
|
|
17303
|
-
|
|
17368
|
+
function minMatchesValidator(options) {
|
|
17369
|
+
const flags = options.flags ?? "";
|
|
17370
|
+
const globalFlags = flags.includes("g") ? flags : `${flags}g`;
|
|
17371
|
+
try {
|
|
17372
|
+
new RegExp(options.pattern, globalFlags);
|
|
17373
|
+
} catch (thrown) {
|
|
17374
|
+
throw new ConfigError(`minMatchesValidator pattern does not compile: ${thrown instanceof Error ? thrown.message : String(thrown)}`);
|
|
17375
|
+
}
|
|
17376
|
+
if (!Number.isInteger(options.min) || options.min < 1) throw new ConfigError(`minMatchesValidator min must be a positive integer; got ${String(options.min)}`);
|
|
17377
|
+
return {
|
|
17378
|
+
name: options.name ?? "min-matches",
|
|
17379
|
+
validate: (input) => {
|
|
17380
|
+
const found = input.text.match(new RegExp(options.pattern, globalFlags))?.length ?? 0;
|
|
17381
|
+
return found >= options.min ? ok : {
|
|
17382
|
+
ok: false,
|
|
17383
|
+
reasons: [`expected at least ${String(options.min)} matches of /${options.pattern}/${flags}; found ${String(found)}`]
|
|
17384
|
+
};
|
|
17385
|
+
}
|
|
17386
|
+
};
|
|
17304
17387
|
}
|
|
17305
17388
|
//#endregion
|
|
17306
17389
|
//#region src/engine/events.ts
|
|
@@ -18976,4 +19059,4 @@ function createSandboxBridge(ctx, options) {
|
|
|
18976
19059
|
};
|
|
18977
19060
|
}
|
|
18978
19061
|
//#endregion
|
|
18979
|
-
export { AWAIT_SCHEMA, AdmissionController, AdmissionRejectedError, AgentCallError, BUDGET_ABORT_REASON, BudgetExhaustedError, CANCEL_AGENT_SCHEMA, CHECKPOINT_FORMAT_V1, CLAIM_STATEMENT_MAX_CHARS, CLAIM_TTL_DAYS, COMPACTION_SUMMARY_PREFIX, CURRENT_HASH_VERSION, ConfigError, DEFAULT_CHILD_BUDGET_FRACTION, DEFAULT_CHILD_RESULT_PAGE_CHARS, DEFAULT_CITATION_PATTERN, DEFAULT_COMPACTION_THRESHOLD, DEFAULT_ESCALATION_LIMITS, DEFAULT_EVIDENCE_MIN_SHARE, DEFAULT_FINISH_MAX_REPAIRS, DEFAULT_FLAT_RESERVE_USD, DEFAULT_MAX_CHILDREN_PER_NODE, DEFAULT_MAX_DEPTH, DEFAULT_MAX_OSCILLATIONS_PER_KEY, DEFAULT_MAX_PINNED_WORKTREES, DEFAULT_MAX_REVISIONS_PER_RUN, DEFAULT_MAX_TOTAL_SPAWNS, DEFAULT_MAX_TURNS, DEFAULT_MODEL_RETRY_ATTEMPTS, DEFAULT_NO_PROGRESS_TURNS, DEFAULT_PER_RUN_CONCURRENCY, DEFAULT_RETRY_POLICY, DEFAULT_STREAM_IDLE_TIMEOUT_MS, DEFAULT_SYNTHESIS_MAX_TURNS, DEFAULT_SYNTHESIS_NOTE_MAX_TURNS, DedupIndex, DeterminismError, EMIT_RESULT_TOOL, EMPTY_SCHEMA_HASH, EMPTY_TOOLSET_HASH, ESCALATE_TOOL_NAME, ESCALATION_REPORT_SCHEMA, ESCALATION_REQUEST_SCHEMA, EVENT_SEGMENT_STRIDE, EscalationDecisionAbortedError, EventBus, ExternalRegistry, FINALIZE_SYNTHESIS_INSTRUCTION, FINISH_SCHEMA, FINISH_TOOL_NAME, FailRunError, FileModelKnowledgeStore, FileTranscriptStore, GET_CHILD_RESULT_SCHEMA, GET_CHILD_RESULT_TOOL_NAME, GitWorktreeProvider, IMPLEMENTATION_PROFILE_LIMITS, INBOX_PROPOSAL_TTL_DAYS, InMemoryStore, InMemoryTranscriptStore, InProcessRunner, InvalidResolutionError, JOURNAL_ENVELOPE_MARKER, JournalCompatibilityError, JournalMatcher, JournalMissError, JournalOrderViolation, JsonlFileStore, KB_ACTIVE_CLAIMS_CAP, KB_CARD_RENDER_BUDGET_CHARS, KeyedLimiter, KnowledgeCasError, LARGE_VALUE_WARN_BYTES, LEGACY_LTID_PREFIX, LEGACY_SIGNATURE_INPUTS, LINEAGE_SIG_VERSION, LeaseHeldError, LineageIndex, MASKED_SECRET, MAX_CHILD_RESULT_PAGE_CHARS, MAX_DEPTH_CEILING, ModelRetry, NoProgressDetector, NonSerializableValueError, ORCHESTRATE_WORKFLOW_NAME, OrchestratorCapConfigError, PARALLEL_AGENTS_SCHEMA, PROGRESS_REPORT_TOOL_NAME, ParallelSiteCounter, PlanInvariantError, QUOTA_WINDOW_MS, READ_CHILD_ARTIFACT_SCHEMA, READ_CHILD_ARTIFACT_TOOL_NAME, RESEARCH_PROFILE_LIMITS, REVIEW_PROFILE_LIMITS, ROLE_EFFORT_DEFAULTS, ROOT_ACCOUNT, ROOT_SCOPE, RUN_PROFILES, RUN_SETTLE_DECISION_TYPE, ReplayPlanHashMismatch, Replayer, ResolutionArbiter, ResolutionFold, RulvarError, RunBudget, SANDBOX_AGENT_OPT_KEYS, SPAWN_AGENT_SCHEMA, SandboxError, ScriptRejected, Semaphore, SettlementError, SpanRegistry, TOOL_NAME_PATTERN, TerminationAccount, WAIT_FOR_EVENTS_SCHEMA, WAIT_FOR_EVENTS_TOOL_NAME, WAKE_SUMMARY_RENDER_BUDGET_CHARS, admissionReserveUsd, affordableOutputTokens, agentErrorFromWire, agentErrorToWire, agentResultWire, agentScope, applyClaimOps, applyStructuredOutputTier, approachSigCoarse, approachSigOf, archiveDeprecatedModelOps, assertFencedWrites, atCompactionThreshold, auditRun, auditRuns, buildAbandonFold, buildAdapterRegistry, buildCostReport, buildDeriverRegistry, buildOrchestratorTools, buildTerminationInitValue, buildToolContext, canRideLoopTurn, canonicalIsolationTag, canonicalizeLadder, canonicalizeSchema, capIssues, capsHashOf, checkFloors, checkpointRefFor, childCoveragePrefix, claimExpired, claimExpiry, claimIssues, claimOpIssues, classifyAgentError, classifyAttemptOutcome, collectDeclaredLadders, compactMessages, compilePermissionChain, compilePermissionPreset, compileSecretMasker, compileVerifiedLayer, constantTimeEqual, costReportFromJournal, countsAgainstLimit, createCanonicalIdMinter, createCtx, createEngine, createEnvelopeEncryption, createSandboxBridge, currentOnlyKeyRing, decodeCheckpoint, dedupeRepeatedClaims, defineWorkflow, deriveContentKey, deriverV1, deriverV2, digestOf, dispositionHook, emptyDigestBlocks, emptyToolset, encodeCheckpoint, entryUsageSlices, escalateTool, evaluatePermission, evaluateReuse, evidencePreservedValidator, executeWorkflow, exhaustionCodeOf, extractCandidate, failoverTriggerOf, fallbackTriggerOf, filterClaimsForRun, finalizeFires, foldTermination, formatRePrompt, formatScopePath, hasFencedWrites, hasMetaLookup, hashRunArgs, hashRunOutput, hashWorkflowBody, hashWorkflowSource, identityJcs, implementationAgentProfile, invoiceFromJournal, isEscalated, isSchemaPairSpec, isStandardSchemaSpec, isStrictCompatibleSchema, kMaxOf, knowledgeHash, ladderLengthOf, ladderRungChoice, lastRunSettle, latestProgressReport, lexShellCommand, liftRetainedParts, lineageWeightOf, localKeyProvider, makeOrchestratorWorkflow, maskSecrets, maskSecretsDeep, maskSecretsJson, matchArgvPattern, matchShellCommand, mcp, memoryQuotaLimiter, mergeQuotaDenial, mergeUsageLimits, metaMatchesFilter, minMatchesValidator, modelEpochOf, modelKnowledgeCard, modelSpecIdentity, needsSeparateExtract, nextFailover, nodeLinkKey, normalizeApproachTag, normalizeEntry, normalizeFallbacks, orchestrate, parallelScope, parseModelRef, parseScopePath, phiInitialOf, pipelineScope, planNodeScope, preflightEstimate, priceEntryUsage, priceUsdOf, profileCard, profileRegistrySnapshotHash, progressReportTool, projectHistory, projectIdentity, projectToJsonSchema, proposalStatement, providerOf, quotaActualTokens, quotaEstimateTokens, quotaRuleAdmission, quotaRuleMatches, readRunMeta, readTerminationInit, reconcileRunMeta, reduceAuditTrail, reduceCriticalPath, reduceInvocationTable, registryKeyRing, remeasureQueue, replayDisposition, repositoryResearchToolset, requiredFieldsValidator, requiredSectionsValidator, researchAgentProfile, resolveModelInvocation, resolvePricing, resolveToolset, retryClassOf, retryDelayMs, reviewAgentProfile, roleConfiguredInRouting, roundOneDisposition, runAgent, runProfile, sanitizeTerminalText, sanitizeTokenCount, sanitizeUsage, sanitizeUsageDelta, scanJournalCompatibility, schemaHash, schemaHashOfSpec, selectStructuredOutputTier, shouldCompact, snapshotUsage, spawnDepthOf, summarizeInstruction, summarizeOutput, terminationConfigDrift, tierWithinCaps, toApprovalDecision, toJournalValue, tool, toolContract, toolsetHash, ttlState, usageViolations, validateEditorialCommit, validateEngineQuotaConfig, validateEntryShape, validateEscalationLimits, validateEscalationReport, validateQuotaRules, validateRetryPolicy, validateSchemaSpec, validateTerminationLimits, validateUsageLimits, workflowScope, workflowSourceRef, wrapJournalStore, wrapTranscriptStore };
|
|
19062
|
+
export { AWAIT_SCHEMA, AdmissionController, AdmissionRejectedError, AgentCallError, BUDGET_ABORT_REASON, BudgetExhaustedError, CANCEL_AGENT_SCHEMA, CHECKPOINT_FORMAT_V1, CLAIM_STATEMENT_MAX_CHARS, CLAIM_TTL_DAYS, COMPACTION_SUMMARY_PREFIX, CURRENT_HASH_VERSION, ConfigError, DEFAULT_CHILD_BUDGET_FRACTION, DEFAULT_CHILD_RESULT_PAGE_CHARS, DEFAULT_CITATION_PATTERN, DEFAULT_COMPACTION_THRESHOLD, DEFAULT_ESCALATION_LIMITS, DEFAULT_EVIDENCE_MIN_SHARE, DEFAULT_FINISH_MAX_REPAIRS, DEFAULT_FLAT_RESERVE_USD, DEFAULT_MAX_CHILDREN_PER_NODE, DEFAULT_MAX_DEPTH, DEFAULT_MAX_OSCILLATIONS_PER_KEY, DEFAULT_MAX_PINNED_WORKTREES, DEFAULT_MAX_REVISIONS_PER_RUN, DEFAULT_MAX_TOTAL_SPAWNS, DEFAULT_MAX_TURNS, DEFAULT_MODEL_RETRY_ATTEMPTS, DEFAULT_NO_PROGRESS_TURNS, DEFAULT_PER_RUN_CONCURRENCY, DEFAULT_RETRY_POLICY, DEFAULT_STREAM_IDLE_TIMEOUT_MS, DEFAULT_SYNTHESIS_MAX_TURNS, DEFAULT_SYNTHESIS_NOTE_MAX_TURNS, DedupIndex, DeterminismError, EMIT_RESULT_TOOL, EMPTY_SCHEMA_HASH, EMPTY_TOOLSET_HASH, ESCALATE_TOOL_NAME, ESCALATION_REPORT_SCHEMA, ESCALATION_REQUEST_SCHEMA, EVENT_SEGMENT_STRIDE, EscalationDecisionAbortedError, EventBus, ExternalRegistry, FINALIZE_SYNTHESIS_INSTRUCTION, FINISH_SCHEMA, FINISH_TOOL_NAME, FailRunError, FileModelKnowledgeStore, FileTranscriptStore, GET_CHILD_RESULT_SCHEMA, GET_CHILD_RESULT_TOOL_NAME, GitWorktreeProvider, IMPLEMENTATION_PROFILE_LIMITS, INBOX_PROPOSAL_TTL_DAYS, InMemoryStore, InMemoryTranscriptStore, InProcessRunner, InvalidResolutionError, JOURNAL_ENVELOPE_MARKER, JournalCompatibilityError, JournalMatcher, JournalMissError, JournalOrderViolation, JsonlFileStore, KB_ACTIVE_CLAIMS_CAP, KB_CARD_RENDER_BUDGET_CHARS, KeyedLimiter, KnowledgeCasError, LARGE_VALUE_WARN_BYTES, LEGACY_LTID_PREFIX, LEGACY_SIGNATURE_INPUTS, LINEAGE_SIG_VERSION, LeaseHeldError, LineageIndex, MASKED_SECRET, MAX_CHILD_RESULT_PAGE_CHARS, MAX_DEPTH_CEILING, ModelRetry, NoProgressDetector, NonSerializableValueError, ORCHESTRATE_WORKFLOW_NAME, OrchestratorCapConfigError, PARALLEL_AGENTS_SCHEMA, PROGRESS_REPORT_TOOL_NAME, ParallelSiteCounter, PlanInvariantError, QUOTA_WINDOW_MS, READ_CHILD_ARTIFACT_SCHEMA, READ_CHILD_ARTIFACT_TOOL_NAME, RESEARCH_PROFILE_LIMITS, REVIEW_PROFILE_LIMITS, ROLE_EFFORT_DEFAULTS, ROOT_ACCOUNT, ROOT_SCOPE, RUN_PROFILES, RUN_SETTLE_DECISION_TYPE, ReplayPlanHashMismatch, Replayer, ResolutionArbiter, ResolutionFold, RulvarError, RunBudget, SANDBOX_AGENT_OPT_KEYS, SPAWN_AGENT_SCHEMA, SandboxError, ScriptRejected, Semaphore, SettlementError, SpanRegistry, TOOL_NAME_PATTERN, TerminationAccount, WAIT_FOR_EVENTS_SCHEMA, WAIT_FOR_EVENTS_TOOL_NAME, WAKE_SUMMARY_RENDER_BUDGET_CHARS, admissionReserveUsd, affordableOutputTokens, agentErrorFromWire, agentErrorToWire, agentResultWire, agentScope, applyClaimOps, applyStructuredOutputTier, approachSigCoarse, approachSigOf, archiveDeprecatedModelOps, assertFencedWrites, atCompactionThreshold, auditRun, auditRuns, buildAbandonFold, buildAdapterRegistry, buildCostReport, buildDeriverRegistry, buildOrchestratorTools, buildTerminationInitValue, buildToolContext, canRideLoopTurn, canonicalIsolationTag, canonicalizeLadder, canonicalizeSchema, capIssues, capsHashOf, checkFloors, checkpointRefFor, childCoveragePrefix, claimExpired, claimExpiry, claimIssues, claimOpIssues, classifyAgentError, classifyAttemptOutcome, collectDeclaredLadders, compactMessages, compilePermissionChain, compilePermissionPreset, compileSecretMasker, compileVerifiedLayer, constantTimeEqual, costReportFromJournal, countsAgainstLimit, createCanonicalIdMinter, createCtx, createEngine, createEnvelopeEncryption, createSandboxBridge, currentOnlyKeyRing, decodeCheckpoint, dedupeRepeatedClaims, defineWorkflow, deriveContentKey, deriverV1, deriverV2, digestOf, dispatchProjectionReserveUsd, dispositionHook, emptyDigestBlocks, emptyToolset, encodeCheckpoint, entryUsageSlices, escalateTool, evaluatePermission, evaluateReuse, evidencePreservedValidator, executeWorkflow, exhaustionCodeOf, extractCandidate, failoverTriggerOf, fallbackTriggerOf, filterClaimsForRun, finalizeFires, foldTermination, formatRePrompt, formatScopePath, hasFencedWrites, hasMetaLookup, hashRunArgs, hashRunOutput, hashWorkflowBody, hashWorkflowSource, identityJcs, implementationAgentProfile, invoiceFromJournal, isEscalated, isSchemaPairSpec, isStandardSchemaSpec, isStrictCompatibleSchema, kMaxOf, knowledgeHash, ladderLengthOf, ladderRungChoice, lastRunSettle, latestProgressReport, lexShellCommand, liftRetainedParts, lineageWeightOf, localKeyProvider, makeOrchestratorWorkflow, maskSecrets, maskSecretsDeep, maskSecretsJson, matchArgvPattern, matchShellCommand, mcp, memoryQuotaLimiter, mergeQuotaDenial, mergeUsageLimits, metaMatchesFilter, minMatchesValidator, modelEpochOf, modelKnowledgeCard, modelSpecIdentity, needsSeparateExtract, nextFailover, nodeLinkKey, normalizeApproachTag, normalizeEntry, normalizeFallbacks, orchestrate, orchestratorAdmissionEstCostUsd, parallelScope, parseModelRef, parseScopePath, phiInitialOf, pipelineScope, planNodeScope, preflightEstimate, priceEntryUsage, priceUsdOf, profileCard, profileRegistrySnapshotHash, progressReportTool, projectHistory, projectIdentity, projectToJsonSchema, proposalStatement, providerOf, quotaActualTokens, quotaEstimateTokens, quotaRuleAdmission, quotaRuleMatches, readRunMeta, readTerminationInit, reconcileRunMeta, reduceAuditTrail, reduceCriticalPath, reduceInvocationTable, registryKeyRing, remeasureQueue, replayDisposition, repositoryResearchToolset, requiredFieldsValidator, requiredSectionsValidator, researchAgentProfile, resolveModelInvocation, resolvePricing, resolveToolset, retryClassOf, retryDelayMs, reviewAgentProfile, roleConfiguredInRouting, roundOneDisposition, runAgent, runProfile, sanitizeTerminalText, sanitizeTokenCount, sanitizeUsage, sanitizeUsageDelta, scanJournalCompatibility, schemaHash, schemaHashOfSpec, selectStructuredOutputTier, shouldCompact, snapshotUsage, spawnDepthOf, summarizeInstruction, summarizeOutput, terminationConfigDrift, tierWithinCaps, toApprovalDecision, toJournalValue, tool, toolContract, toolsetHash, ttlState, usageViolations, validateEditorialCommit, validateEngineQuotaConfig, validateEntryShape, validateEscalationLimits, validateEscalationReport, validateQuotaRules, validateRetryPolicy, validateSchemaSpec, validateTerminationLimits, validateUsageLimits, workflowScope, workflowSourceRef, wrapJournalStore, wrapTranscriptStore };
|