@sema-agent/core 5.60.1 → 5.62.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/CHANGELOG.md +125 -0
- package/dist/agents/subagent.d.ts +4 -2
- package/dist/agents/subagent.js +9 -9
- package/dist/brain/open-responses.js +8 -3
- package/dist/brain/openai.js +4 -4
- package/dist/brain/stream-engine.d.ts +13 -2
- package/dist/brain/stream-engine.js +3 -3
- package/dist/core/auto-mode-prompt-assets.js +1 -1
- package/dist/core/checkpoint-store.d.ts +36 -4
- package/dist/core/checkpoint-store.js +1 -0
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +4 -2
- package/dist/core/hooks.d.ts +83 -4
- package/dist/core/hooks.js +3 -3
- package/dist/core/memory-engine/consolidation-driver.d.ts +19 -1
- package/dist/core/memory-engine/consolidation-driver.js +75 -3
- package/dist/core/memory-engine/consolidation.d.ts +52 -5
- package/dist/core/memory-engine/consolidation.js +3 -1
- package/dist/core/memory-engine/distiller.d.ts +89 -1
- package/dist/core/memory-engine/distiller.js +94 -5
- package/dist/core/memory-engine/engine.d.ts +8 -0
- package/dist/core/memory-engine/engine.js +51 -8
- package/dist/core/memory-engine/index.d.ts +1 -1
- package/dist/core/memory-engine/index.js +1 -1
- package/dist/core/park-selfcheck.js +2 -0
- package/dist/core/pricing.d.ts +24 -0
- package/dist/core/pricing.js +18 -0
- package/dist/core/runner/prepare-config-doors.d.ts +34 -0
- package/dist/core/runner/prepare-config-doors.js +55 -0
- package/dist/core/runner/prepare-task.d.ts +52 -10
- package/dist/core/runner/prepare-task.js +77 -42
- package/dist/core/runner/runtask.d.ts +7 -0
- package/dist/core/runner/runtask.js +254 -38
- package/dist/core/runner/turn-attachments.d.ts +137 -5
- package/dist/core/runner/turn-attachments.js +25 -2
- package/dist/core/store-contracts/checkpoint-store-contract.js +19 -0
- package/dist/core/task-notification.d.ts +50 -23
- package/dist/core/task-notification.js +20 -4
- package/dist/core/tool-errors.d.ts +2 -1
- package/dist/core/tool-policy.d.ts +27 -0
- package/dist/core/types.d.ts +214 -31
- package/dist/core/untrusted-text.d.ts +5 -4
- package/dist/core/untrusted-text.js +8 -0
- package/dist/core/usage-window-store.d.ts +109 -8
- package/dist/core/usage-window-store.js +79 -12
- package/dist/engine/harness/agent-harness.d.ts +58 -2
- package/dist/engine/harness/agent-harness.js +115 -5
- package/dist/engine/loop/agent-loop.js +153 -15
- package/dist/engine/loop/types.d.ts +32 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/orchestration/run-workflow-tool.d.ts +9 -4
- package/dist/orchestration/run-workflow-tool.js +1 -1
- package/dist/orchestration/workflow.d.ts +2 -2
- package/dist/prompt-assembly/event-registry.js +2 -0
- package/dist/server/http.d.ts +1 -1
- package/dist/stores/file/usage-window-store.d.ts +1 -1
- package/dist/stores/file/usage-window-store.js +27 -6
- package/dist/tools/loop-tick.js +1 -1
- package/dist/tools/monitor.d.ts +3 -3
- package/dist/tools/monitor.js +1 -1
- package/dist/tools/scheduler-tools.js +9 -1
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +7 -1
|
@@ -2596,7 +2596,7 @@ export class MemoryEngine {
|
|
|
2596
2596
|
if (fresh.epoch !== epoch) {
|
|
2597
2597
|
throw new ConsolidationRefusedError("memory.consolidation_stale_snapshot", `memory consolidation refused: the ${JSON.stringify(scope)} watermark advanced during the snapshot read — retake the snapshot.`);
|
|
2598
2598
|
}
|
|
2599
|
-
fresh.snapshot = { token: cycleToken, at, epoch, candidates: candidateRevs, eligible: eligibleRevs, marked };
|
|
2599
|
+
fresh.snapshot = { token: cycleToken, at, epoch, candidates: candidateRevs, eligible: eligibleRevs, marked, markedIds: candidates.filter((c) => c.marked).map((c) => c.entry.id) };
|
|
2600
2600
|
let announce = false;
|
|
2601
2601
|
if (opts.force !== undefined && fresh.lastForcedRequestId !== opts.force.requestId) {
|
|
2602
2602
|
fresh.lastForcedRequestId = opts.force.requestId;
|
|
@@ -2656,6 +2656,9 @@ export class MemoryEngine {
|
|
|
2656
2656
|
else if (p.inputs.length > screened.maxInputsPerProduct)
|
|
2657
2657
|
reasons.push(`product #${i}: ${p.inputs.length} inputs > maxInputsPerProduct ${screened.maxInputsPerProduct}`);
|
|
2658
2658
|
}
|
|
2659
|
+
if (proposal.mintExposure !== undefined && proposal.mintExposure !== "partitioned") {
|
|
2660
|
+
reasons.push(`mintExposure ${JSON.stringify(proposal.mintExposure)} is not a member of the closed attestation set {"partitioned"} — omit it (run-level fold) or spell it exactly`);
|
|
2661
|
+
}
|
|
2659
2662
|
if (proposal.products.length === 0 && intents.length === 0) {
|
|
2660
2663
|
reasons.push("empty plan: zero products and zero directed intents — a plan with nothing to apply must not settle as a completed run (the vacuous completion would stamp the eligible set into the fingerprint and blind the incremental face)");
|
|
2661
2664
|
}
|
|
@@ -2741,19 +2744,43 @@ export class MemoryEngine {
|
|
|
2741
2744
|
}
|
|
2742
2745
|
const activeSetSize = headers.filter((h) => !superseded.has(h.id) && !exclusions.has(h.id)).length;
|
|
2743
2746
|
const ceiling = supersessionFuseCeiling(activeSetSize, screened);
|
|
2747
|
+
const attested = proposal.mintExposure === "partitioned";
|
|
2748
|
+
if (attested) {
|
|
2749
|
+
const staleAxes = [];
|
|
2750
|
+
const baseline = snapshot.markedIds !== undefined ? new Set(snapshot.markedIds) : undefined;
|
|
2751
|
+
for (const [id, rev] of Object.entries(snapshot.candidates)) {
|
|
2752
|
+
const h = headerById.get(id);
|
|
2753
|
+
if (h === undefined)
|
|
2754
|
+
staleAxes.push(`served candidate ${id} left the committed listing`);
|
|
2755
|
+
else if (h.rev !== rev)
|
|
2756
|
+
staleAxes.push(`served candidate ${id} moved rev since the snapshot`);
|
|
2757
|
+
else if (exclusions.has(id))
|
|
2758
|
+
staleAxes.push(`served candidate ${id} entered the challenge/latch exclusion set`);
|
|
2759
|
+
else if (h.exposure !== undefined && (baseline === undefined || !baseline.has(id))) {
|
|
2760
|
+
staleAxes.push(baseline === undefined
|
|
2761
|
+
? `served candidate ${id} is marked and the snapshot row predates the markedIds baseline — no proof the mark predates the mint (conservative arm)`
|
|
2762
|
+
: `served candidate ${id} turned marked since the snapshot`);
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
if (staleAxes.length > 0) {
|
|
2766
|
+
throw new ConsolidationRefusedError("memory.consolidation_stale_snapshot", `memory consolidation refused: the attested (exposure-partitioned) mint's clean-arm premise is stale — the ${JSON.stringify(scope)} world moved between snapshot and freeze; retake the snapshot and re-mint`, staleAxes);
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2744
2769
|
let visibleMarked = snapshot.marked;
|
|
2745
|
-
if (!visibleMarked) {
|
|
2770
|
+
if (!attested && !visibleMarked) {
|
|
2746
2771
|
const inputIds = [...new Set(proposal.products.flatMap((p) => p.inputs.map((i2) => i2.id)))];
|
|
2747
2772
|
const committedInputs = await face.getByIds(inputIds);
|
|
2748
2773
|
visibleMarked = committedInputs.some((e) => committedOriginOf(e.frontmatter) !== undefined);
|
|
2749
2774
|
}
|
|
2750
|
-
const foldedOrigin = visibleMarked ? { taint: "external", cause: "derived", at } : undefined;
|
|
2775
|
+
const foldedOrigin = !attested && visibleMarked ? { taint: "external", cause: "derived", at } : undefined;
|
|
2776
|
+
const productMarked = (p) => p.inputs.some((i2) => headerById.get(i2.id)?.exposure !== undefined);
|
|
2751
2777
|
for (let i = 0; i < proposal.products.length; i++) {
|
|
2752
2778
|
const p = proposal.products[i];
|
|
2753
2779
|
if (!isInstructionEntry({ ...(p.type !== undefined ? { type: p.type } : {}) }))
|
|
2754
2780
|
continue;
|
|
2755
|
-
|
|
2756
|
-
|
|
2781
|
+
const hardArm = attested ? productMarked(p) : visibleMarked;
|
|
2782
|
+
if (hardArm) {
|
|
2783
|
+
wholeReasons.push(`product #${i}: instruction-form product (type ${JSON.stringify(p.type)}) refused — ${attested ? "the product's declared inputs contain marked content" : "the run's visible set contains marked content"} (the laundering hard gate, G7/G26; no option opens this arm)`);
|
|
2757
2784
|
}
|
|
2758
2785
|
else if (!screened.allowInstructionProducts) {
|
|
2759
2786
|
wholeReasons.push(`product #${i}: instruction-form product (type ${JSON.stringify(p.type)}) refused — instruction-form consolidation products are refused unconditionally by default (a consolidation must not mint privileged entries out of ordinary notes); the host escape hatch is consolidation.allowInstructionProducts: true`);
|
|
@@ -2784,6 +2811,7 @@ export class MemoryEngine {
|
|
|
2784
2811
|
}
|
|
2785
2812
|
const id = uuidv7();
|
|
2786
2813
|
const slug = deriveProductSlug(p.name, `consolidated-${id.slice(0, 8)}`);
|
|
2814
|
+
const productOrigin = attested ? (productMarked(p) ? { taint: "external", cause: "derived", at } : undefined) : foldedOrigin;
|
|
2787
2815
|
const entry = {
|
|
2788
2816
|
id,
|
|
2789
2817
|
slug,
|
|
@@ -2792,7 +2820,7 @@ export class MemoryEngine {
|
|
|
2792
2820
|
name: p.name ?? slug,
|
|
2793
2821
|
...(p.description !== undefined ? { description: p.description } : {}),
|
|
2794
2822
|
...(p.type !== undefined ? { type: p.type } : {}),
|
|
2795
|
-
...(
|
|
2823
|
+
...(productOrigin !== undefined ? { origin: { ...productOrigin } } : {}),
|
|
2796
2824
|
distilled: {
|
|
2797
2825
|
planId,
|
|
2798
2826
|
at,
|
|
@@ -2917,6 +2945,7 @@ export class MemoryEngine {
|
|
|
2917
2945
|
epoch: snapshot.epoch,
|
|
2918
2946
|
visibleMarked,
|
|
2919
2947
|
...(foldedOrigin !== undefined ? { foldedOrigin } : {}),
|
|
2948
|
+
...(attested ? { mintExposure: "partitioned" } : {}),
|
|
2920
2949
|
products: assembled,
|
|
2921
2950
|
productStates: Object.fromEntries(assembled.map((e) => [e.id, "pending"])),
|
|
2922
2951
|
...(freezeRefusedInputIds.length > 0 ? { freezeRefusedInputIds } : {}),
|
|
@@ -2924,7 +2953,21 @@ export class MemoryEngine {
|
|
|
2924
2953
|
intents: intents.map((it) => ({ requestId: it.requestId, state: "pending" })),
|
|
2925
2954
|
state: "open",
|
|
2926
2955
|
audit: [
|
|
2927
|
-
{
|
|
2956
|
+
{
|
|
2957
|
+
at,
|
|
2958
|
+
event: "frozen",
|
|
2959
|
+
requestId: input.requestId,
|
|
2960
|
+
...((() => {
|
|
2961
|
+
const parts = [];
|
|
2962
|
+
if (attested) {
|
|
2963
|
+
const markedProducts = assembled.filter((e) => e.frontmatter.origin !== undefined).length;
|
|
2964
|
+
parts.push(`attested exposure-partitioned mint: ${markedProducts} marked / ${assembled.length - markedProducts} clean product(s)`);
|
|
2965
|
+
}
|
|
2966
|
+
if (refusedProducts.length > 0)
|
|
2967
|
+
parts.push(`${refusedProducts.length} product(s) refused at freeze: ${refusedProducts.map((r) => `#${r.index}: ${r.reason}`).join(" | ")}`);
|
|
2968
|
+
return parts.length > 0 ? { detail: parts.join("; ") } : {};
|
|
2969
|
+
})()),
|
|
2970
|
+
},
|
|
2928
2971
|
],
|
|
2929
2972
|
};
|
|
2930
2973
|
writeConsolidationPlan(this.controlDir, plan);
|
|
@@ -3492,7 +3535,7 @@ export class MemoryEngine {
|
|
|
3492
3535
|
if (read.state === "absent")
|
|
3493
3536
|
continue;
|
|
3494
3537
|
const p = read.plan;
|
|
3495
|
-
out.push({ planId, scope: p.scope, state: p.state, createdAt: p.createdAt, products: p.products.length, directed: p.directed.length, intents: p.intents.length });
|
|
3538
|
+
out.push({ planId, scope: p.scope, state: p.state, createdAt: p.createdAt, products: p.products.length, markedProducts: p.products.filter((e) => e.frontmatter?.origin !== undefined).length, directed: p.directed.length, intents: p.intents.length });
|
|
3496
3539
|
}
|
|
3497
3540
|
return out;
|
|
3498
3541
|
}
|
|
@@ -9,7 +9,7 @@ export { readV2HeaderHints, isInstructionEntry, type V2HeaderHints } from "./hea
|
|
|
9
9
|
export { parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, committedOriginOf, originEquals, ambiguousOriginRepresentation, type ParsedEntryFile } from "./frontmatter.js";
|
|
10
10
|
export { committedDistilledOf, distilledEquals } from "./frontmatter.js";
|
|
11
11
|
export { CONSOLIDATION_DEFAULTS, ConsolidationRefusedError, MEMORY_SEARCH_SUPERSEDED_TAG, consolidationTypeEligible, deriveSupersededSet, memorySupersededNote, readIntentCredentials, supersessionFuseCeiling, type ConsolidationGateRead, type ConsolidationGateRow, type ConsolidationIntent, type ConsolidationIntentCredentialRow, type ConsolidationLeaseSeat, type ConsolidationProductProposal, type ConsolidationProposal, type MemoryConsolidationOptions, CONSOLIDATION_RUN_STOP_REASONS, type ConsolidationRunStopReason, } from "./consolidation.js";
|
|
12
|
-
export { DISTILLER_DEFAULT_MAX_INPUTS_PER_PRODUCT, LLM_DISTILLER_CONTRACT, LLM_DISTILLER_CONTRACT_DL2, LLM_DISTILLER_CONTRACT_DL3, LLM_DISTILLER_CONTRACTS, MEMORY_DISTILLER_CONTRACT_V1, contractGroupingDiff, driveConsolidationToFixpoint, isAliasModelId, llmPlanDistiller, mintLlmConsolidationPlan, openAiCompatChatSeat, parseJsonAnswer, planParseRepairs, sanitizeLlmGroups, scheduleUnderFuse, type ConsolidationDistillFn, type ConsolidationDriveCycleRow, type ConsolidationDriveEngine, type ConsolidationDriveResult, type ConsolidationFoldState, type DistillerCandidate, type DistillerChatAnswer, type DistillerChatFn, type DistillerChatRequest, type FuseSchedule, type LlmConsolidationPlan, type LlmConsolidationPlanProduct, type LlmDistillerContract, type MintLlmConsolidationPlanResult, type PlanParseRepairs, type SanitizedLlmGroups, } from "./distiller.js";
|
|
12
|
+
export { DISTILLER_DEFAULT_MAX_INPUTS_PER_PRODUCT, LLM_DISTILLER_CONTRACT, LLM_DISTILLER_CONTRACT_DL2, LLM_DISTILLER_CONTRACT_DL3, LLM_DISTILLER_CONTRACTS, MEMORY_DISTILLER_CONTRACT_V1, contractGroupingDiff, driveConsolidationToFixpoint, isAliasModelId, llmPlanDistiller, mintExposurePartitionedPlan, mintLlmConsolidationPlan, openAiCompatChatSeat, parseJsonAnswer, planParseRepairs, sanitizeLlmGroups, scheduleUnderFuse, type ConsolidationDistillFn, type ConsolidationDriveCycleRow, type ConsolidationDriveEngine, type ConsolidationDriveResult, type ConsolidationFoldState, type DistillerCandidate, type DistillerChatAnswer, type DistillerChatFn, type DistillerChatRequest, type FuseSchedule, type LlmConsolidationPlan, type LlmConsolidationPlanArm, type LlmConsolidationPlanProduct, type LlmDistillerContract, type MintLlmConsolidationPlanResult, type PlanParseRepairs, type SanitizedLlmGroups, } from "./distiller.js";
|
|
13
13
|
export { CONSOLIDATION_DRIVER_PLANS_DIR, CONSOLIDATION_DRIVER_RUNS_FILE, archiveDistillerPlan, readConsolidationDriverRun, runMemoryConsolidationDriver, type ConsolidationDriverEngine, type ConsolidationDriverRunRow, type ConsolidationRunReceipt, type RunMemoryConsolidationOptions, } from "./consolidation-driver.js";
|
|
14
14
|
export type { OriginClearanceRow, OriginClearanceEvent } from "./origin-clearance.js";
|
|
15
15
|
export { MEMORY_ORIGIN_CAUSES } from "./types.js";
|
|
@@ -9,7 +9,7 @@ export { readV2HeaderHints, isInstructionEntry } from "./header-hints.js";
|
|
|
9
9
|
export { parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, committedOriginOf, originEquals, ambiguousOriginRepresentation } from "./frontmatter.js";
|
|
10
10
|
export { committedDistilledOf, distilledEquals } from "./frontmatter.js";
|
|
11
11
|
export { CONSOLIDATION_DEFAULTS, ConsolidationRefusedError, MEMORY_SEARCH_SUPERSEDED_TAG, consolidationTypeEligible, deriveSupersededSet, memorySupersededNote, readIntentCredentials, supersessionFuseCeiling, CONSOLIDATION_RUN_STOP_REASONS, } from "./consolidation.js";
|
|
12
|
-
export { DISTILLER_DEFAULT_MAX_INPUTS_PER_PRODUCT, LLM_DISTILLER_CONTRACT, LLM_DISTILLER_CONTRACT_DL2, LLM_DISTILLER_CONTRACT_DL3, LLM_DISTILLER_CONTRACTS, MEMORY_DISTILLER_CONTRACT_V1, contractGroupingDiff, driveConsolidationToFixpoint, isAliasModelId, llmPlanDistiller, mintLlmConsolidationPlan, openAiCompatChatSeat, parseJsonAnswer, planParseRepairs, sanitizeLlmGroups, scheduleUnderFuse, } from "./distiller.js";
|
|
12
|
+
export { DISTILLER_DEFAULT_MAX_INPUTS_PER_PRODUCT, LLM_DISTILLER_CONTRACT, LLM_DISTILLER_CONTRACT_DL2, LLM_DISTILLER_CONTRACT_DL3, LLM_DISTILLER_CONTRACTS, MEMORY_DISTILLER_CONTRACT_V1, contractGroupingDiff, driveConsolidationToFixpoint, isAliasModelId, llmPlanDistiller, mintExposurePartitionedPlan, mintLlmConsolidationPlan, openAiCompatChatSeat, parseJsonAnswer, planParseRepairs, sanitizeLlmGroups, scheduleUnderFuse, } from "./distiller.js";
|
|
13
13
|
export { CONSOLIDATION_DRIVER_PLANS_DIR, CONSOLIDATION_DRIVER_RUNS_FILE, archiveDistillerPlan, readConsolidationDriverRun, runMemoryConsolidationDriver, } from "./consolidation-driver.js";
|
|
14
14
|
export { MEMORY_ORIGIN_CAUSES } from "./types.js";
|
|
15
15
|
export { memoryBackendContract, assertMemoryBackendSearchEquivalence, } from "./memory-backend-contract.js";
|
|
@@ -71,6 +71,8 @@ function syntheticCheckpoint(scope) {
|
|
|
71
71
|
args: { probe: true, nested: { depth: 2, list: [1, 2, 3] } },
|
|
72
72
|
boundInputHash: boundInputHashOf({ probe: true, nested: { depth: 2, list: [1, 2, 3] } }),
|
|
73
73
|
hasBidiControls: true,
|
|
74
|
+
preview: { probe: true, note: "park wiring self-check display stub" },
|
|
75
|
+
previewWithheld: "oversize",
|
|
74
76
|
batchToolCallIds: [`${scope}:call`],
|
|
75
77
|
completedCallIds: [],
|
|
76
78
|
},
|
package/dist/core/pricing.d.ts
CHANGED
|
@@ -37,6 +37,30 @@ export interface TokenCounts {
|
|
|
37
37
|
* pricing the cache subset separately instead of trusting the brain's possibly-overstated cost total.
|
|
38
38
|
*/
|
|
39
39
|
export declare function computeCostMicroUsd(counts: TokenCounts, pricing: ModelPricing): number;
|
|
40
|
+
/**
|
|
41
|
+
* RB-368's predicate, single-sourced: does a price table EXIST for this model — an id-keyed
|
|
42
|
+
* `RunnerDeps.pricing` entry, or the model's own `cost` declaration? When neither does,
|
|
43
|
+
* `modelCostToPricing(undefined)` yields an all-zero table and every computed cost is a fabricated 0,
|
|
44
|
+
* indistinguishable from "declared free". Every consumer that must keep those apart (the disclosure faces
|
|
45
|
+
* that go absent rather than lie; the deployment $ governance window that refuses rather than charge a
|
|
46
|
+
* fabricated 0) asks HERE, so the two can never drift into two different answers.
|
|
47
|
+
*/
|
|
48
|
+
export declare function isModelPriced(model: {
|
|
49
|
+
id: string;
|
|
50
|
+
cost?: unknown;
|
|
51
|
+
}, pricing: Record<string, ModelPricing> | undefined): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* The first UNEVALUABLE member of a price table (`"inputPer1M"`, …), or `undefined` when every declared
|
|
54
|
+
* price is a finite, non-negative number.
|
|
55
|
+
*
|
|
56
|
+
* {@link isModelPriced} answers "does a table EXIST", which is the RB-368 question. It is deliberately not
|
|
57
|
+
* the same question as "can this table price anything": a `NaN` rate silently computes a cost of ZERO (the
|
|
58
|
+
* `price && tokens` guard treats NaN as absent) and a negative rate computes a negative one, so a table of
|
|
59
|
+
* garbage looks priced and prices everything at nothing. Wherever a number from this table becomes a
|
|
60
|
+
* CEILING rather than a report, the caller must ask this too — a bad value has to be loud, never folded
|
|
61
|
+
* into a silent default.
|
|
62
|
+
*/
|
|
63
|
+
export declare function malformedPricingField(pricing: ModelPricing): string | undefined;
|
|
40
64
|
/** Map a vendor `Model.cost` (already per-1M absolute) to `ModelPricing`. We don't emit 1h cache
|
|
41
65
|
* writes, so `cacheWriteLongPer1M` defaults to the 5-min write price (irrelevant while that count is 0). */
|
|
42
66
|
export declare function modelCostToPricing(cost: {
|
package/dist/core/pricing.js
CHANGED
|
@@ -11,6 +11,24 @@ export function computeCostMicroUsd(counts, pricing) {
|
|
|
11
11
|
per1M(counts.outputTokens, pricing.outputPer1M);
|
|
12
12
|
return Math.round(usd * 1e6);
|
|
13
13
|
}
|
|
14
|
+
export function isModelPriced(model, pricing) {
|
|
15
|
+
return pricing?.[model.id] !== undefined || model.cost !== undefined;
|
|
16
|
+
}
|
|
17
|
+
export function malformedPricingField(pricing) {
|
|
18
|
+
for (const f of ["inputPer1M", "outputPer1M"]) {
|
|
19
|
+
const v = pricing[f];
|
|
20
|
+
if (typeof v !== "number" || !Number.isFinite(v) || v < 0)
|
|
21
|
+
return f;
|
|
22
|
+
}
|
|
23
|
+
for (const f of ["cacheReadPer1M", "cacheWritePer1M", "cacheWriteLongPer1M"]) {
|
|
24
|
+
const v = pricing[f];
|
|
25
|
+
if (v === undefined)
|
|
26
|
+
continue;
|
|
27
|
+
if (typeof v !== "number" || !Number.isFinite(v) || v < 0)
|
|
28
|
+
return f;
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
14
32
|
export function modelCostToPricing(cost) {
|
|
15
33
|
return {
|
|
16
34
|
inputPer1M: cost?.input ?? 0,
|
|
@@ -46,6 +46,40 @@ export declare function limitConfigError(code: string, message: string): Error &
|
|
|
46
46
|
* sentinel, and on the budget axes it is an exhausted window (absurd but honest).
|
|
47
47
|
*/
|
|
48
48
|
export declare function resolveTaskLimits(limits: TaskLimits | undefined): TaskLimits | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* RB-318 — validate the MODE-VALUED members of `TaskSpec.attachments` at the same door, and return the
|
|
51
|
+
* config unchanged. Today that is exactly one member, `totalTokensReminderMode`.
|
|
52
|
+
*
|
|
53
|
+
* WHY A DOOR AT ALL (坏值响亮度律): a knob whose bad value silently folds to the default is a knob the
|
|
54
|
+
* deployment believes is armed. `totalTokensReminderMode: "padded_countdown"` (underscore) or
|
|
55
|
+
* `"Countdown"` are the near-misses a hand-written config produces, and both would otherwise read as
|
|
56
|
+
* "absent" and take the default arm — a DIFFERENT readout than the one that was asked for, with nothing
|
|
57
|
+
* anywhere saying so.
|
|
58
|
+
*
|
|
59
|
+
* DELIBERATELY NARROW, twice over:
|
|
60
|
+
* - no unknown-KEY refusal (the `resolveTaskLimits` half that has no counterpart here): a wire caller
|
|
61
|
+
* that sends an attachment flag this engine does not know is asking for a lane that stays off, which
|
|
62
|
+
* is the family's own default posture — not a ceiling silently disarmed. Widening it would also be a
|
|
63
|
+
* behavior change for every existing caller, which this additive lane does not license.
|
|
64
|
+
* - the pre-existing `todoReminderMode` is NOT validated here. It has the same shape and the same
|
|
65
|
+
* weakness, but tightening it would refuse specs that run today; recorded as a follow-up rather than
|
|
66
|
+
* folded into this lane's change.
|
|
67
|
+
*
|
|
68
|
+
* A NON-OBJECT CONTAINER (`attachments: null` off a wire, above all) is neither refused nor crashed on
|
|
69
|
+
* (review finding, verified by reading): every consumer reads this config through `attachmentsCfg?.…`,
|
|
70
|
+
* so a null container already means "no lanes are wired" and the spec RUNS today. An unguarded property
|
|
71
|
+
* read here — the first cut — turned that running spec into an untyped `TypeError` from inside a door
|
|
72
|
+
* that promises a typed `config.attachment_invalid`, and refusing it instead would newly reject a spec
|
|
73
|
+
* that works. There is no container-shape door in this family yet; that is a separate widening, not
|
|
74
|
+
* something this lane gets to invent on the way past.
|
|
75
|
+
*
|
|
76
|
+
* The bypass is narrowed to values that CANNOT supply attachment fields (round-2 review finding): a
|
|
77
|
+
* plain-JS caller can hand this a FUNCTION carrying `totalTokensReminderMode` as a property, and every
|
|
78
|
+
* downstream `attachmentsCfg?.…` read would happily find it — so skipping a function here would fold a
|
|
79
|
+
* typo'd mode to the default through exactly the hole this door exists to close. Functions are
|
|
80
|
+
* property-bearing containers and are validated like objects; only `null` and primitives pass through.
|
|
81
|
+
*/
|
|
82
|
+
export declare function resolveAttachmentsConfig(attachments: TaskSpec["attachments"]): TaskSpec["attachments"];
|
|
49
83
|
/**
|
|
50
84
|
* R2 双形轴(追加令 2026-07-18): CC 2.1.212's fable-variant prompt gate (b9e —
|
|
51
85
|
* `fable_5_mitigations` capability / claude-mythos-5), ORTHOGONAL to the simple/classic profile.
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { resolveBrainCallGuardrailMs } from "../../brain/timeout.js";
|
|
2
2
|
import { assertReadFaceValue } from "../../tools/fs/index.js";
|
|
3
3
|
import { resolveCheckpointStore } from "../checkpoint-store.js";
|
|
4
|
+
import { isModelPriced, malformedPricingField, modelCostToPricing } from "../pricing.js";
|
|
4
5
|
import { preflightLockedConfig } from "../locked-config.js";
|
|
5
6
|
import { assertRetentionCapability } from "../retention.js";
|
|
6
7
|
import { resolveModel, resolveTaskModel, roleModelIfSet } from "../roles.js";
|
|
7
8
|
import { applyToolModelGate, assertRestoreGatedToolsValue, modelIdTail } from "../tool-model-gate.js";
|
|
8
9
|
import { resolveUsageWindows } from "../usage-window-store.js";
|
|
9
10
|
import { deriveAskEffective, resolveAskSeamForm, resolveQuestionSeam } from "../wiring-manifest.js";
|
|
11
|
+
import { TOTAL_TOKENS_REMINDER_MODES } from "./turn-attachments.js";
|
|
10
12
|
const TASK_LIMIT_KEY_DICT = {
|
|
11
13
|
maxTokens: true,
|
|
12
14
|
maxCostUsd: true,
|
|
@@ -31,6 +33,16 @@ export function limitConfigError(code, message) {
|
|
|
31
33
|
e.code = code;
|
|
32
34
|
return e;
|
|
33
35
|
}
|
|
36
|
+
function describeRejectedValue(value) {
|
|
37
|
+
try {
|
|
38
|
+
const quoted = JSON.stringify(value);
|
|
39
|
+
if (quoted !== undefined)
|
|
40
|
+
return quoted;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
}
|
|
44
|
+
return `a ${typeof value} value`;
|
|
45
|
+
}
|
|
34
46
|
export function resolveTaskLimits(limits) {
|
|
35
47
|
if (limits === undefined)
|
|
36
48
|
return undefined;
|
|
@@ -73,6 +85,19 @@ export function resolveTaskLimits(limits) {
|
|
|
73
85
|
}
|
|
74
86
|
return limits;
|
|
75
87
|
}
|
|
88
|
+
export function resolveAttachmentsConfig(attachments) {
|
|
89
|
+
if (attachments === null || (typeof attachments !== "object" && typeof attachments !== "function"))
|
|
90
|
+
return attachments;
|
|
91
|
+
const mode = attachments.totalTokensReminderMode;
|
|
92
|
+
if (mode !== undefined) {
|
|
93
|
+
const legal = TOTAL_TOKENS_REMINDER_MODES;
|
|
94
|
+
if (typeof mode !== "string" || !legal.includes(mode)) {
|
|
95
|
+
throw limitConfigError("config.attachment_invalid", `TaskSpec.attachments.totalTokensReminderMode must be one of ${TOTAL_TOKENS_REMINDER_MODES.map((m) => `"${m}"`).join(", ")} ` +
|
|
96
|
+
`(got ${describeRejectedValue(mode)}) — refused rather than folded to the default, which would publish a different readout than the one the deployment asked for.`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return attachments;
|
|
100
|
+
}
|
|
76
101
|
export function isFableFamilyModelId(id) {
|
|
77
102
|
const tail = modelIdTail(id);
|
|
78
103
|
return /^claude-fable-\d/.test(tail) || /^claude-mythos-5(?!\d)/.test(tail);
|
|
@@ -290,6 +315,7 @@ export function prepareConfigDoors(input) {
|
|
|
290
315
|
}
|
|
291
316
|
}
|
|
292
317
|
resolveTaskLimits(spec.limits);
|
|
318
|
+
resolveAttachmentsConfig(spec.attachments);
|
|
293
319
|
if (spec.resourceSuspend !== undefined) {
|
|
294
320
|
const rsus = spec.resourceSuspend;
|
|
295
321
|
if (typeof rsus.scope !== "string" || rsus.scope === "") {
|
|
@@ -327,6 +353,35 @@ export function prepareConfigDoors(input) {
|
|
|
327
353
|
}
|
|
328
354
|
const resolvedRole = resolveTaskModel(spec, deps);
|
|
329
355
|
const model = resolvedRole.model;
|
|
356
|
+
if (usageWindows !== undefined && deps.usageWindowStore !== undefined) {
|
|
357
|
+
const costed = usageWindows.find((w) => w.maxCostUsd !== undefined);
|
|
358
|
+
const why = costed === undefined
|
|
359
|
+
? undefined
|
|
360
|
+
: !isModelPriced(model, deps.pricing)
|
|
361
|
+
? `model ${JSON.stringify(model.id)} is unpriced — it has neither a RunnerDeps.pricing entry nor a Model.cost declaration, so this run produces NO cost figure to charge the window with (an unpriced run has no cost, which is not the same as a cost of 0). Price the model, or drop maxCostUsd from the window.`
|
|
362
|
+
: (() => {
|
|
363
|
+
const bad = malformedPricingField(deps.pricing?.[model.id] ?? modelCostToPricing(model.cost));
|
|
364
|
+
return bad === undefined
|
|
365
|
+
? undefined
|
|
366
|
+
: `the price table for model ${JSON.stringify(model.id)} declares an unevaluable ${bad} — every rate must be a finite, non-negative number of USD per 1M tokens. A table like this prices every turn at zero (or below), so the ceiling would never fill: refused rather than enforced in name only.`;
|
|
367
|
+
})();
|
|
368
|
+
if (costed !== undefined && why !== undefined) {
|
|
369
|
+
const e = new Error(`RunnerDeps.usageWindows declares a cost ceiling (maxCostUsd ${String(costed.maxCostUsd)} over ${String(costed.windowMs)}ms, ${costed.anchor}) but ${why}`);
|
|
370
|
+
e.code = "config.usage_window_unpriced";
|
|
371
|
+
throw e;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
const dollarCeiling = spec.limits?.maxCostUsd !== undefined
|
|
375
|
+
? "limits.maxCostUsd"
|
|
376
|
+
: spec.resourceSuspend?.totalBudgetUsd !== undefined
|
|
377
|
+
? "resourceSuspend.totalBudgetUsd"
|
|
378
|
+
: undefined;
|
|
379
|
+
if (dollarCeiling !== undefined && isModelPriced(model, deps.pricing)) {
|
|
380
|
+
const bad = malformedPricingField(deps.pricing?.[model.id] ?? modelCostToPricing(model.cost));
|
|
381
|
+
if (bad !== undefined) {
|
|
382
|
+
throw limitConfigError("config.limit_invalid", `TaskSpec.${dollarCeiling} is a money ceiling, but the price table for model ${JSON.stringify(model.id)} declares an unevaluable ${bad} — every rate must be a finite, non-negative number of USD per 1M tokens. A table like this prices every turn at zero (or below), so the ceiling would never trip: refused rather than enforced in name only.`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
330
385
|
const fableMitigations = resolveModelPromptTraits(model, spec, internals).fableMitigations;
|
|
331
386
|
const gateDecision = applyToolModelGate({
|
|
332
387
|
tools: spec.tools,
|
|
@@ -73,14 +73,24 @@ export declare const USAGE_WINDOW_REAP_MARGIN_MS: number;
|
|
|
73
73
|
export interface UsageGovernance {
|
|
74
74
|
/** The ledger key this run is governed under — the principal, or the shared global key. */
|
|
75
75
|
readonly key: string;
|
|
76
|
+
/** Does any governed window carry a MONEY ceiling? Read by the run loop's pricing seats: only a
|
|
77
|
+
* cost-governing deployment has to treat an unevaluable price table as unpriced spend. */
|
|
78
|
+
readonly governsCost: boolean;
|
|
76
79
|
/** Ms the caller must wait before ANY window would admit work again, or `undefined` when none is
|
|
77
80
|
* exhausted as of `now`. Reads the ledger; propagates a store failure (an unreadable ceiling must not
|
|
78
81
|
* read as an open one). */
|
|
79
82
|
check(now: number): Promise<number | undefined>;
|
|
80
|
-
/**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Charge whatever of the run's cumulative spend has not been charged yet. Takes the run's CUMULATIVE
|
|
85
|
+
* totals rather than deltas so no caller can double-charge by calling twice, and so a caller that skips
|
|
86
|
+
* a boundary loses nothing.
|
|
87
|
+
*
|
|
88
|
+
* `cumulativeCostMicroUsd` is the MONEY half (integer micro-USD, `stats.costMicroUsd`). Pass `undefined`
|
|
89
|
+
* when the run's spend has no cost figure at all (RB-368's unpriced state) — a deployment governing a
|
|
90
|
+
* `maxCostUsd` window then REFUSES here rather than charging the fabricated 0 that would let the ceiling
|
|
91
|
+
* silently stop applying. A token-only deployment ignores the argument entirely.
|
|
92
|
+
*/
|
|
93
|
+
commit(cumulativeTokens: number, cumulativeCostMicroUsd: number | undefined, now: number): Promise<void>;
|
|
84
94
|
}
|
|
85
95
|
/**
|
|
86
96
|
* design/164 件四 — resolve the moment an env's lifetime EXPIRES (epoch ms), or `undefined` when the env
|
|
@@ -133,7 +143,7 @@ export declare function checkpointScopeOf(spec: {
|
|
|
133
143
|
principal?: string;
|
|
134
144
|
}): string;
|
|
135
145
|
export { resolveCheckpointStore } from "../checkpoint-store.js";
|
|
136
|
-
export { isFableFamilyModelId, resolveModelPromptTraits, resolveTaskLimits } from "./prepare-config-doors.js";
|
|
146
|
+
export { isFableFamilyModelId, resolveAttachmentsConfig, resolveModelPromptTraits, resolveTaskLimits } from "./prepare-config-doors.js";
|
|
137
147
|
export { rebaseWorkspacePath, rebaseWorkspacePathAcross } from "./prepare-workspace-restore.js";
|
|
138
148
|
export interface Prepared {
|
|
139
149
|
harness: AgentHarness;
|
|
@@ -1463,8 +1473,8 @@ export interface RunInternals {
|
|
|
1463
1473
|
* receive a subagent's live `task_progress` ticks (which otherwise stay in the child's ISOLATED stream). Threaded
|
|
1464
1474
|
* recursively down the delegation tree (via `ctx.forwardEvent`), so every nested subagent's ticks bubble to the
|
|
1465
1475
|
* SAME sink. The Runner's ctx wrapper forwards `task_progress` always; when the run's spec sets
|
|
1466
|
-
* `forwardSubagentEvents: true` it ALSO forwards the child's content events (`text_delta` / `
|
|
1467
|
-
* `tool_start` / `tool_end` — the subagent viewing pane, carrying the same UNTRUSTED-RAW/consumer-must-redact
|
|
1476
|
+
* `forwardSubagentEvents: true` it ALSO forwards the child's content events (`text_delta` / `text_end` (#447) /
|
|
1477
|
+
* `reasoning_delta` / `tool_start` / `tool_end` — the subagent viewing pane, carrying the same UNTRUSTED-RAW/consumer-must-redact
|
|
1468
1478
|
* contract as the main stream's tool events). Either way the child stream is NEVER merged into the parent's
|
|
1469
1479
|
* MODEL context (this is purely a render channel). Absent unless the deployment opted in.
|
|
1470
1480
|
*/
|
|
@@ -1490,9 +1500,12 @@ export interface RunInternals {
|
|
|
1490
1500
|
* `TaskStream.detach(toolCallId)`; the hands Bash tool threads `signalFor(toolCallId)` into env.exec. */
|
|
1491
1501
|
detachHub?: import("../tool-detach.js").ToolDetachHub;
|
|
1492
1502
|
onTaskNotification?: (notification: TaskNotificationPayload,
|
|
1493
|
-
/**
|
|
1494
|
-
*
|
|
1495
|
-
* "
|
|
1503
|
+
/** Injection tier (design/373 — the ladder is LIVE): "next" = the running turn's next boundary
|
|
1504
|
+
* (arrival order, consecutive frames batch); "later" = the run's would-otherwise-stop seat
|
|
1505
|
+
* (never folded into work in progress); "now" = class-head + earliest natural boundary on this
|
|
1506
|
+
* lane (interrupt authority belongs to the steer face, never to notifications). Internal
|
|
1507
|
+
* producers declare their tier explicitly (§3.7 census — completion-class lanes are "next");
|
|
1508
|
+
* the parameterless default "later" serves the external verb's omitting callers only. */
|
|
1496
1509
|
opts?: {
|
|
1497
1510
|
priority?: import("../task-notification.js").SystemInjectionPriority;
|
|
1498
1511
|
}) => void;
|
|
@@ -1626,6 +1639,35 @@ export interface ResolvedWorkspace {
|
|
|
1626
1639
|
* which `cwd` is a host path a consumer may diff / merge / remove. */
|
|
1627
1640
|
remote: boolean;
|
|
1628
1641
|
}
|
|
1642
|
+
/**
|
|
1643
|
+
* RB-330 + 5.38 r2 件2 — the SINGLE effective-delegation derivation for a leg, minted once per prepare
|
|
1644
|
+
* and read by EVERY consumer face; two facets, one source:
|
|
1645
|
+
*
|
|
1646
|
+
* - `isDelegatedChild` — the raw delegation fact, forks included. Trusted internals first; on a
|
|
1647
|
+
* resume leg where the caller supplied NO delegation fact, the checkpoint's persisted axis
|
|
1648
|
+
* ({@link import("../checkpoint-store.js").CheckpointState.isDelegatedChild}) stands in — else a
|
|
1649
|
+
* deps-only `resume(token, outcome, config)` of a parked delegation read `false` at every station:
|
|
1650
|
+
* persona flip (subagent consent/notes pack dropped), ask refusal texts on the parent-thread arm
|
|
1651
|
+
* ("wait for the user" in a transcript no user turn ever lands in), and a lifecycle-observer gate
|
|
1652
|
+
* that never opened. Consumed by `askSourceIdentity` (a fork's refusal posture is the child's) and
|
|
1653
|
+
* the `hookIdentity` mint.
|
|
1654
|
+
* - `isNonForkChild` — the fact minus forks, for the authority/context faces: the `loadProjectMemory`
|
|
1655
|
+
* ctx flag ({@link RunnerDeps.loadProjectMemory} `isSubagent`) and the prompt runtime fact
|
|
1656
|
+
* `isSubagent` (RB-204's `SUBAGENT_CONSENT_NOTICE` gate). A FORK is excluded on both, deliberately:
|
|
1657
|
+
* a fork IS the parent continuing (design/110), so it inherits the parent's authority — and, on the
|
|
1658
|
+
* memory face, the parent's own project context; trimming a fork's CLAUDE.md would hand the
|
|
1659
|
+
* continuation LESS context than the run it continues. (RB-330 history: the memory face once used
|
|
1660
|
+
* `parentTaskId !== undefined` alone, which {@link RunInternals.isDelegatedChild}'s own docstring
|
|
1661
|
+
* forbids — a directly-started workflow's children have no nameable parent.)
|
|
1662
|
+
*
|
|
1663
|
+
* `insideFork` is NOT persisted (the checkpoint's documented honest absence), so on a deps-only resume
|
|
1664
|
+
* a FORK child reads as a plain delegated child on the non-fork facet — the honest residue is written
|
|
1665
|
+
* on the checkpoint field's own list, not papered over with a new persistence axis.
|
|
1666
|
+
*/
|
|
1667
|
+
export declare function effectiveDelegationFacts(internals: Pick<RunInternals, "isDelegatedChild" | "insideFork"> | undefined, seedIsDelegatedChild: boolean | undefined): {
|
|
1668
|
+
isDelegatedChild: boolean;
|
|
1669
|
+
isNonForkChild: boolean;
|
|
1670
|
+
};
|
|
1629
1671
|
/**
|
|
1630
1672
|
* From the resumed/active transcript, the batch position of `currentId` (design/45 §4.ter): the tool-call
|
|
1631
1673
|
* ids of the assistant message that issued it (the batch), and the subset already resolved (executed
|