@sema-agent/core 5.54.0 → 5.56.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 +160 -0
- package/dist/agents/cumulative-stats.d.ts +26 -0
- package/dist/agents/cumulative-stats.js +56 -0
- package/dist/agents/observer.d.ts +11 -7
- package/dist/agents/observer.js +2 -4
- package/dist/agents/send-message-tool.js +48 -2
- package/dist/agents/subagent.js +250 -89
- package/dist/agents/verify.d.ts +27 -3
- package/dist/agents/verify.js +7 -2
- package/dist/core/auto-compaction.d.ts +17 -4
- package/dist/core/auto-compaction.js +3 -0
- package/dist/core/context-edit.d.ts +55 -6
- package/dist/core/context-edit.js +12 -1
- package/dist/core/governance-codes.js +14 -0
- package/dist/core/hooks.d.ts +293 -11
- package/dist/core/hooks.js +159 -12
- package/dist/core/human-input-projection.d.ts +20 -2
- package/dist/core/human-input-projection.js +9 -0
- package/dist/core/lsp-diagnostics.d.ts +19 -17
- package/dist/core/lsp-diagnostics.js +11 -5
- package/dist/core/mcp.d.ts +46 -0
- package/dist/core/mcp.js +132 -6
- package/dist/core/memory-engine/consolidation.d.ts +378 -0
- package/dist/core/memory-engine/consolidation.js +342 -0
- package/dist/core/memory-engine/dual-root.js +3 -0
- package/dist/core/memory-engine/engine.d.ts +237 -4
- package/dist/core/memory-engine/engine.js +1111 -4
- package/dist/core/memory-engine/export-bundle.js +9 -0
- package/dist/core/memory-engine/file-backend.js +27 -1
- package/dist/core/memory-engine/frontmatter.d.ts +20 -1
- package/dist/core/memory-engine/frontmatter.js +111 -0
- package/dist/core/memory-engine/index.d.ts +4 -2
- package/dist/core/memory-engine/index.js +3 -1
- package/dist/core/memory-engine/memory-backend-contract.js +131 -0
- package/dist/core/memory-engine/sync-client.js +26 -0
- package/dist/core/memory-engine/tools.d.ts +9 -0
- package/dist/core/memory-engine/tools.js +57 -13
- package/dist/core/memory-engine/types.d.ts +99 -0
- package/dist/core/memory-recall.js +4 -3
- package/dist/core/memory.d.ts +33 -3
- package/dist/core/memory.js +6 -4
- package/dist/core/permission-rules.d.ts +30 -0
- package/dist/core/permission-rules.js +71 -8
- package/dist/core/reminder-disclosure.d.ts +29 -4
- package/dist/core/reminder-disclosure.js +60 -12
- package/dist/core/runner/prepare-memory.js +7 -2
- package/dist/core/runner/prepare-task.d.ts +39 -1
- package/dist/core/runner/prepare-task.js +63 -35
- package/dist/core/runner/runtask.d.ts +8 -1
- package/dist/core/runner/runtask.js +170 -31
- package/dist/core/runner/session-rule-policy.js +5 -3
- package/dist/core/runner/synthetic-tools.js +4 -2
- package/dist/core/runner/turn-attachments.d.ts +16 -6
- package/dist/core/runner/turn-attachments.js +34 -20
- package/dist/core/session-reconcile.d.ts +32 -0
- package/dist/core/session-reconcile.js +15 -0
- package/dist/core/task-notification.d.ts +34 -7
- package/dist/core/task-notification.js +11 -1
- package/dist/core/task-registry-agent.d.ts +20 -3
- package/dist/core/task-registry-agent.js +31 -2
- package/dist/core/tool-policy.d.ts +23 -0
- package/dist/core/tool-policy.js +29 -13
- package/dist/core/types.d.ts +126 -17
- package/dist/core/untrusted-egress.js +12 -2
- package/dist/core/untrusted-text.d.ts +189 -3
- package/dist/core/untrusted-text.js +424 -6
- package/dist/engine/compaction/compaction.d.ts +77 -7
- package/dist/engine/compaction/compaction.js +98 -9
- package/dist/engine/compaction/utils.d.ts +4 -0
- package/dist/engine/compaction/utils.js +6 -0
- package/dist/engine/harness/agent-harness.d.ts +84 -0
- package/dist/engine/harness/agent-harness.js +88 -12
- package/dist/engine/harness/messages.d.ts +4 -2
- package/dist/engine/harness/messages.js +7 -2
- package/dist/engine/harness/types.d.ts +11 -5
- package/dist/engine/loop/types.d.ts +14 -0
- package/dist/engine/session/import-validate.js +10 -0
- package/dist/engine/session/session.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/orchestration/run-spec.js +8 -1
- package/dist/prompts/default.d.ts +22 -6
- package/dist/tools/fs/index.d.ts +3 -1
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +28 -1
package/dist/agents/subagent.js
CHANGED
|
@@ -1032,6 +1032,134 @@ export function delegationEntryLedgerFootprint(registry) {
|
|
|
1032
1032
|
handles += s.size;
|
|
1033
1033
|
return { keys: byKey.size, handles };
|
|
1034
1034
|
}
|
|
1035
|
+
const localDelegationInFlight = new WeakMap();
|
|
1036
|
+
function localDelegationInFlightCount(anchor, key) {
|
|
1037
|
+
return localDelegationInFlight.get(anchor)?.get(key) ?? 0;
|
|
1038
|
+
}
|
|
1039
|
+
function chargeLocalDelegationInFlight(anchor, key) {
|
|
1040
|
+
let byKey = localDelegationInFlight.get(anchor);
|
|
1041
|
+
if (byKey === undefined) {
|
|
1042
|
+
byKey = new Map();
|
|
1043
|
+
localDelegationInFlight.set(anchor, byKey);
|
|
1044
|
+
}
|
|
1045
|
+
byKey.set(key, (byKey.get(key) ?? 0) + 1);
|
|
1046
|
+
let released = false;
|
|
1047
|
+
return () => {
|
|
1048
|
+
if (released)
|
|
1049
|
+
return;
|
|
1050
|
+
released = true;
|
|
1051
|
+
const live = localDelegationInFlight.get(anchor);
|
|
1052
|
+
if (live === undefined)
|
|
1053
|
+
return;
|
|
1054
|
+
const n = (live.get(key) ?? 0) - 1;
|
|
1055
|
+
if (n > 0)
|
|
1056
|
+
live.set(key, n);
|
|
1057
|
+
else
|
|
1058
|
+
live.delete(key);
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
const spawnAbortedResult = (wt) => ({
|
|
1062
|
+
isError: true,
|
|
1063
|
+
content: `Sub-agent not started: the delegating call was aborted.${wt ? `\n${wt}` : ""}`,
|
|
1064
|
+
details: { error: "aborted" },
|
|
1065
|
+
});
|
|
1066
|
+
const noopRelease = () => undefined;
|
|
1067
|
+
const DELEGATION_POOL_DEFAULT_SCOPE = "default";
|
|
1068
|
+
async function untilAbort(p, signal) {
|
|
1069
|
+
if (signal === undefined)
|
|
1070
|
+
return await p;
|
|
1071
|
+
if (signal.aborted)
|
|
1072
|
+
throw new Error("delegation entry gate: aborted before the durable floor was read");
|
|
1073
|
+
let onAbort;
|
|
1074
|
+
try {
|
|
1075
|
+
return await Promise.race([
|
|
1076
|
+
p,
|
|
1077
|
+
new Promise((_resolve, reject) => {
|
|
1078
|
+
onAbort = () => reject(new Error("delegation entry gate: aborted while reading the durable floor"));
|
|
1079
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1080
|
+
}),
|
|
1081
|
+
]);
|
|
1082
|
+
}
|
|
1083
|
+
finally {
|
|
1084
|
+
if (onAbort !== undefined)
|
|
1085
|
+
signal.removeEventListener("abort", onAbort);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
async function openDelegationEntryGate(input) {
|
|
1089
|
+
const { poolAnchor, registry, store, root, caps, revival, signal } = input;
|
|
1090
|
+
if (root === undefined)
|
|
1091
|
+
return { key: undefined, admit: () => ({ admitted: true, release: noopRelease }) };
|
|
1092
|
+
const scope = input.scope ?? DELEGATION_POOL_DEFAULT_SCOPE;
|
|
1093
|
+
const key = JSON.stringify([scope, root]);
|
|
1094
|
+
let storedHandles = [];
|
|
1095
|
+
let storedEnumerationOk = false;
|
|
1096
|
+
if (store !== undefined && !revival) {
|
|
1097
|
+
try {
|
|
1098
|
+
storedHandles = (await untilAbort(store.listBySession(scope, root), signal)).map((r) => r.handle);
|
|
1099
|
+
storedEnumerationOk = true;
|
|
1100
|
+
}
|
|
1101
|
+
catch {
|
|
1102
|
+
storedHandles = [];
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
const admit = (kind) => {
|
|
1106
|
+
const activeFace = registry === undefined ? undefined : registry.activeDelegationHandles;
|
|
1107
|
+
const active = typeof activeFace === "function" ? activeFace.call(registry, scope, root) : [];
|
|
1108
|
+
const local = localDelegationInFlightCount(poolAnchor, key);
|
|
1109
|
+
const running = active.length + local;
|
|
1110
|
+
if (running >= caps.maxConcurrent) {
|
|
1111
|
+
return {
|
|
1112
|
+
admitted: false,
|
|
1113
|
+
refusal: {
|
|
1114
|
+
code: "delegation.concurrency_cap",
|
|
1115
|
+
text: `this session tree already has ${running} agents running — the concurrency cap (${caps.maxConcurrent}; RunnerDeps.delegationEntryCaps.maxConcurrent) refuses another. ${kind === "registered" ? "Wait for one to complete (you will be notified) or stop one, then relaunch." : "Wait for one of them to finish or stop one, then try again."}`,
|
|
1116
|
+
},
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
if (!revival) {
|
|
1120
|
+
const ledger = delegationEntryLedger(poolAnchor, key);
|
|
1121
|
+
let retained;
|
|
1122
|
+
if (store !== undefined && storedEnumerationOk) {
|
|
1123
|
+
const keep = new Set([...storedHandles, ...active]);
|
|
1124
|
+
if (kind === "registered") {
|
|
1125
|
+
for (const h of [...ledger])
|
|
1126
|
+
if (!keep.has(h))
|
|
1127
|
+
ledger.delete(h);
|
|
1128
|
+
retained = new Set([...storedHandles, ...ledger]).size;
|
|
1129
|
+
}
|
|
1130
|
+
else {
|
|
1131
|
+
const survivors = new Set(storedHandles);
|
|
1132
|
+
for (const h of ledger)
|
|
1133
|
+
if (keep.has(h))
|
|
1134
|
+
survivors.add(h);
|
|
1135
|
+
retained = survivors.size;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
else {
|
|
1139
|
+
retained = ledger.size;
|
|
1140
|
+
}
|
|
1141
|
+
const cumulative = retained + local;
|
|
1142
|
+
if (cumulative >= caps.maxCumulativePerSession) {
|
|
1143
|
+
return {
|
|
1144
|
+
admitted: false,
|
|
1145
|
+
refusal: {
|
|
1146
|
+
code: "delegation.session_cap",
|
|
1147
|
+
text: `this session tree has already launched ${cumulative} agents in its retained window — the cumulative cap (${caps.maxCumulativePerSession}; RunnerDeps.delegationEntryCaps.maxCumulativePerSession) refuses more. Continue an existing agent (SendMessage) instead of launching new ones.`,
|
|
1148
|
+
},
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
return { admitted: true, release: kind === "local" ? chargeLocalDelegationInFlight(poolAnchor, key) : noopRelease };
|
|
1153
|
+
};
|
|
1154
|
+
return { key, admit };
|
|
1155
|
+
}
|
|
1156
|
+
function chargeDelegationEntryHandle(registry, key, store, handle) {
|
|
1157
|
+
if (handle !== undefined)
|
|
1158
|
+
delegationEntryLedger(registry, key).add(handle);
|
|
1159
|
+
bindDelegationLedgerKeyStore(registry, key, store);
|
|
1160
|
+
armDelegationLedgerLifecycle(registry);
|
|
1161
|
+
sweepDelegationLedgerRound(registry, 3);
|
|
1162
|
+
}
|
|
1035
1163
|
export function normalizeSubagentType(value) {
|
|
1036
1164
|
return value.normalize("NFKC").toLowerCase().replace(/[\p{White_Space}\p{Pd}_]+/gu, "");
|
|
1037
1165
|
}
|
|
@@ -1314,7 +1442,6 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1314
1442
|
prepareArguments: (args) => foldGeneralPurposeAlias(args, generalPurposeShadowed),
|
|
1315
1443
|
execute: async (args, ctx) => {
|
|
1316
1444
|
const a = foldGeneralPurposeAlias(args, generalPurposeShadowed);
|
|
1317
|
-
const wantsBackground = opts.background !== undefined && a.run_in_background !== false;
|
|
1318
1445
|
const reviveClaim = ctx.reviveClaim;
|
|
1319
1446
|
if (reviveClaim !== undefined && opts.background === undefined) {
|
|
1320
1447
|
return {
|
|
@@ -1358,6 +1485,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1358
1485
|
}
|
|
1359
1486
|
const omitted = rawType === "" || (!generalPurposeShadowed && rawType === GENERAL_PURPOSE_SUBAGENT_TYPE);
|
|
1360
1487
|
const def = hasAgents && !wantsFork && !omitted ? agentMap.get(rawType) : undefined;
|
|
1488
|
+
const wantsBackground = opts.background !== undefined && (a.run_in_background !== false || def?.background === true);
|
|
1361
1489
|
const spawnAgentType = wantsFork ? FORK_SUBAGENT_TYPE : omitted ? GENERAL_PURPOSE_SUBAGENT_TYPE : rawType;
|
|
1362
1490
|
if (!def && !wantsFork && !omitted) {
|
|
1363
1491
|
return {
|
|
@@ -1789,6 +1917,21 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1789
1917
|
return { ...(recentSteps ? { recentSteps } : {}), ...(editedFiles ? { editedFiles } : {}) };
|
|
1790
1918
|
};
|
|
1791
1919
|
const treeScope = reviveClaim?.row.scope ?? ctx.principal ?? opts.background?.scope;
|
|
1920
|
+
const entryCapPoolAnchor = opts.background?.registry ?? opts;
|
|
1921
|
+
const entryCaps = ctx.delegationEntryCaps ?? { maxConcurrent: DELEGATION_MAX_CONCURRENT_DEFAULT, maxCumulativePerSession: DELEGATION_MAX_PER_SESSION_DEFAULT };
|
|
1922
|
+
const entryCapRoot = reviveClaim !== undefined
|
|
1923
|
+
? (reviveClaim.row.rootSessionId ?? reviveClaim.row.parentSessionId ?? (reviveClaim.row.sessionScoped ? reviveClaim.row.owner : undefined))
|
|
1924
|
+
: (ctx.rootSessionId ?? ctx.sessionId);
|
|
1925
|
+
const openEntryGate = () => openDelegationEntryGate({
|
|
1926
|
+
poolAnchor: entryCapPoolAnchor,
|
|
1927
|
+
registry: opts.background?.registry,
|
|
1928
|
+
store: opts.background?.agentStore,
|
|
1929
|
+
scope: treeScope,
|
|
1930
|
+
root: entryCapRoot,
|
|
1931
|
+
caps: entryCaps,
|
|
1932
|
+
revival: reviveClaim !== undefined,
|
|
1933
|
+
signal: ctx.signal,
|
|
1934
|
+
});
|
|
1792
1935
|
const provenanceRequest = ctx.delegationProvenanceForChildren?.();
|
|
1793
1936
|
const childProvenanceRef = provenanceRequest !== undefined ? { current: newDelegationProvenanceAggregate() } : undefined;
|
|
1794
1937
|
const childAttestation = (status) => childProvenanceRef !== undefined ? reduceDelegationAttestation(childProvenanceRef.current, { completed: status === "completed" }) : undefined;
|
|
@@ -2053,7 +2196,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2053
2196
|
if (ctx.signal?.aborted) {
|
|
2054
2197
|
await cancelObserver();
|
|
2055
2198
|
const wt = await finishWorktree();
|
|
2056
|
-
return
|
|
2199
|
+
return spawnAbortedResult(wt);
|
|
2057
2200
|
}
|
|
2058
2201
|
if (spawnVerdict.kind === "block") {
|
|
2059
2202
|
await cancelObserver();
|
|
@@ -2106,6 +2249,21 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2106
2249
|
const shortDesc = `fork: ${(typeof a.description === "string" && a.description.trim() ? a.description.trim() : prompt).slice(0, 180)}`;
|
|
2107
2250
|
const bgOwner = sessionScopedBg ? ctx.sessionId : ctx.taskId ?? bg.owner;
|
|
2108
2251
|
const bgScope = treeScope;
|
|
2252
|
+
const forkEntryGate = await openEntryGate();
|
|
2253
|
+
{
|
|
2254
|
+
const admission = forkEntryGate.admit("registered");
|
|
2255
|
+
if (!admission.admitted) {
|
|
2256
|
+
dropHostAbortListener();
|
|
2257
|
+
try {
|
|
2258
|
+
await (releaseForked ? releaseForked() : opts.runner.sessions.release?.(forkedId));
|
|
2259
|
+
}
|
|
2260
|
+
catch {
|
|
2261
|
+
}
|
|
2262
|
+
await cancelObserver();
|
|
2263
|
+
const wt = await finishWorktree();
|
|
2264
|
+
return errorResult(`Sub-agent not started in background: ${admission.refusal.text}${wt ? `\n${wt}` : ""}`, { error: admission.refusal.code, code: admission.refusal.code });
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2109
2267
|
const forkSettlementSeat = ctx.delegationSettlement?.();
|
|
2110
2268
|
const forkSettleId = forkSettlementSeat !== undefined ? `bg-${uuidv7()}` : undefined;
|
|
2111
2269
|
if (forkSettlementSeat !== undefined && forkSettleId !== undefined) {
|
|
@@ -2195,6 +2353,8 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2195
2353
|
}
|
|
2196
2354
|
return { isError: true, content: `Sub-agent not started in background: ${e instanceof Error ? e.message : String(e)}${wt ? `\n${wt}` : ""}`, details: { error: "register_failed" } };
|
|
2197
2355
|
}
|
|
2356
|
+
if (forkEntryGate.key !== undefined)
|
|
2357
|
+
chargeDelegationEntryHandle(bg.registry, forkEntryGate.key, bg.agentStore, taskId);
|
|
2198
2358
|
childInternals.peerSelfRef?.addAxis("h", taskId);
|
|
2199
2359
|
bg.registry.bindBackgroundAgentSession(taskId, forkedId);
|
|
2200
2360
|
const forkBgHangAt = Date.now();
|
|
@@ -2507,6 +2667,28 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2507
2667
|
const bgIgnoredNote = a.run_in_background === true
|
|
2508
2668
|
? `note: run_in_background was ignored for subagent_type "${FORK_SUBAGENT_TYPE}" — the fork ran synchronously (this delegation tool has no background surface configured).`
|
|
2509
2669
|
: undefined;
|
|
2670
|
+
const syncForkEntryGate = await openEntryGate();
|
|
2671
|
+
if (ctx.signal?.aborted) {
|
|
2672
|
+
try {
|
|
2673
|
+
await (releaseForked ? releaseForked() : opts.runner.sessions.release?.(forkedId));
|
|
2674
|
+
}
|
|
2675
|
+
catch {
|
|
2676
|
+
}
|
|
2677
|
+
await cancelObserver();
|
|
2678
|
+
const wt = await finishWorktree();
|
|
2679
|
+
return spawnAbortedResult(wt);
|
|
2680
|
+
}
|
|
2681
|
+
const syncForkAdmission = syncForkEntryGate.admit("local");
|
|
2682
|
+
if (!syncForkAdmission.admitted) {
|
|
2683
|
+
try {
|
|
2684
|
+
await (releaseForked ? releaseForked() : opts.runner.sessions.release?.(forkedId));
|
|
2685
|
+
}
|
|
2686
|
+
catch {
|
|
2687
|
+
}
|
|
2688
|
+
await cancelObserver();
|
|
2689
|
+
const wt = await finishWorktree();
|
|
2690
|
+
return errorResult(`Sub-agent not started: ${syncForkAdmission.refusal.text}${wt ? `\n${wt}` : ""}`, { error: syncForkAdmission.refusal.code, code: syncForkAdmission.refusal.code });
|
|
2691
|
+
}
|
|
2510
2692
|
let forkChild;
|
|
2511
2693
|
try {
|
|
2512
2694
|
forkChild = await opts.runner.runTask({ ...buildChildSpec(ctx.signal), sessionId: forkedId, requireExistingSession: true, objective: forkObjective }, forkInternals);
|
|
@@ -2527,6 +2709,9 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2527
2709
|
const wt = await finishWorktree();
|
|
2528
2710
|
return { isError: true, content: `Fork failed: the forked run threw (${e instanceof Error ? e.message : String(e)}).${wt ? `\n${wt}` : ""}`, details: { error: "fork run failed" } };
|
|
2529
2711
|
}
|
|
2712
|
+
finally {
|
|
2713
|
+
syncForkAdmission.release();
|
|
2714
|
+
}
|
|
2530
2715
|
const forkHandbackWarning = await reviewHandback({
|
|
2531
2716
|
review: ctx.autoModeReview,
|
|
2532
2717
|
toolCallId: ctx.toolCallId,
|
|
@@ -2601,51 +2786,15 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2601
2786
|
const shortDesc = reviveRow?.description ?? String(a.description ?? "sub-agent").slice(0, 200);
|
|
2602
2787
|
const bgOwner = reviveRow !== undefined ? reviveRow.owner : sessionScopedBg ? ctx.sessionId : ctx.taskId ?? bg.owner;
|
|
2603
2788
|
const bgScope = treeScope;
|
|
2604
|
-
const
|
|
2605
|
-
const
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
const capLedgerKey = capScope !== undefined && capRoot !== undefined ? JSON.stringify([capScope, capRoot]) : undefined;
|
|
2610
|
-
if (capScope !== undefined && capRoot !== undefined) {
|
|
2611
|
-
let storedHandles = [];
|
|
2612
|
-
let storedEnumerationOk = false;
|
|
2613
|
-
if (bg.agentStore !== undefined && reviveRow === undefined) {
|
|
2614
|
-
try {
|
|
2615
|
-
storedHandles = (await bg.agentStore.listBySession(capScope, capRoot)).map((r) => r.handle);
|
|
2616
|
-
storedEnumerationOk = true;
|
|
2617
|
-
}
|
|
2618
|
-
catch {
|
|
2619
|
-
storedHandles = [];
|
|
2620
|
-
}
|
|
2621
|
-
}
|
|
2622
|
-
const capRefusal = async (code, text) => {
|
|
2789
|
+
const bgEntryGate = await openEntryGate();
|
|
2790
|
+
const capLedgerKey = bgEntryGate.key;
|
|
2791
|
+
{
|
|
2792
|
+
const admission = bgEntryGate.admit("registered");
|
|
2793
|
+
if (!admission.admitted) {
|
|
2623
2794
|
dropHostAbortListener();
|
|
2624
2795
|
await cancelObserver();
|
|
2625
2796
|
const wt = await finishWorktree();
|
|
2626
|
-
return errorResult(`Sub-agent not started in background: ${text}${wt ? `\n${wt}` : ""}`, { error: code, code });
|
|
2627
|
-
};
|
|
2628
|
-
const activeFace = bg.registry.activeDelegationHandles;
|
|
2629
|
-
const active = typeof activeFace === "function" ? activeFace.call(bg.registry, capScope, capRoot) : [];
|
|
2630
|
-
if (typeof activeFace === "function" && active.length >= entryCaps.maxConcurrent) {
|
|
2631
|
-
return await capRefusal("delegation.concurrency_cap", `this session tree already has ${active.length} background agents running — the concurrency cap (${entryCaps.maxConcurrent}; RunnerDeps.delegationEntryCaps.maxConcurrent) refuses another. Wait for one to complete (you will be notified) or stop one, then relaunch.`);
|
|
2632
|
-
}
|
|
2633
|
-
if (reviveRow === undefined) {
|
|
2634
|
-
const ledger = delegationEntryLedger(bg.registry, capLedgerKey);
|
|
2635
|
-
let cumulative;
|
|
2636
|
-
if (bg.agentStore !== undefined && storedEnumerationOk) {
|
|
2637
|
-
const keep = new Set([...storedHandles, ...active]);
|
|
2638
|
-
for (const h of [...ledger])
|
|
2639
|
-
if (!keep.has(h))
|
|
2640
|
-
ledger.delete(h);
|
|
2641
|
-
cumulative = new Set([...storedHandles, ...ledger]).size;
|
|
2642
|
-
}
|
|
2643
|
-
else {
|
|
2644
|
-
cumulative = ledger.size;
|
|
2645
|
-
}
|
|
2646
|
-
if (cumulative >= entryCaps.maxCumulativePerSession) {
|
|
2647
|
-
return await capRefusal("delegation.session_cap", `this session tree has already launched ${cumulative} background agents in its retained window — the cumulative cap (${entryCaps.maxCumulativePerSession}; RunnerDeps.delegationEntryCaps.maxCumulativePerSession) refuses more. Continue an existing agent (SendMessage) instead of launching new ones.`);
|
|
2648
|
-
}
|
|
2797
|
+
return errorResult(`Sub-agent not started in background: ${admission.refusal.text}${wt ? `\n${wt}` : ""}`, { error: admission.refusal.code, code: admission.refusal.code });
|
|
2649
2798
|
}
|
|
2650
2799
|
}
|
|
2651
2800
|
const settlementSeat = ctx.delegationSettlement?.();
|
|
@@ -2770,13 +2919,8 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2770
2919
|
}
|
|
2771
2920
|
return { isError: true, content: `Sub-agent not started in background: ${e instanceof Error ? e.message : String(e)}${wt ? `\n${wt}` : ""}`, details: { error: "register_failed" } };
|
|
2772
2921
|
}
|
|
2773
|
-
if (capLedgerKey !== undefined
|
|
2774
|
-
|
|
2775
|
-
if (capLedgerKey !== undefined) {
|
|
2776
|
-
bindDelegationLedgerKeyStore(bg.registry, capLedgerKey, bg.agentStore);
|
|
2777
|
-
armDelegationLedgerLifecycle(bg.registry);
|
|
2778
|
-
sweepDelegationLedgerRound(bg.registry, 3);
|
|
2779
|
-
}
|
|
2922
|
+
if (capLedgerKey !== undefined)
|
|
2923
|
+
chargeDelegationEntryHandle(bg.registry, capLedgerKey, bg.agentStore, reviveRow === undefined ? taskId : undefined);
|
|
2780
2924
|
childInternals.peerSelfRef?.addAxis("h", taskId);
|
|
2781
2925
|
if (agentName !== undefined) {
|
|
2782
2926
|
recordRosterSpawn(ctx.roster, { name: agentName, agentId: taskId, toolUseId: ctx.toolCallId, owner: bgOwner, scope: bgScope, ...((typeof childModel === "string" ? resolveModelDisplayLabel(childModel) : childModel?.id) !== undefined ? { model: typeof childModel === "string" ? childModel : childModel?.id } : {}), ...(reviveRow !== undefined ? ((reviveRow.rootSessionId ?? reviveRow.parentSessionId) !== undefined ? { rootSessionId: reviveRow.rootSessionId ?? reviveRow.parentSessionId } : {}) : (ctx.rootSessionId ?? ctx.sessionId) !== undefined ? { rootSessionId: ctx.rootSessionId ?? ctx.sessionId } : {}), ...(sessionScopedBg ? { sessionScoped: true } : {}), createdAt: reviveRow?.spawnedAt ?? Date.now() }, (err) => opts.onObserverError?.(err, { site: "roster.recordSpawn" }));
|
|
@@ -3411,49 +3555,66 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
3411
3555
|
};
|
|
3412
3556
|
}
|
|
3413
3557
|
let child;
|
|
3558
|
+
const syncEntryGate = await openEntryGate();
|
|
3559
|
+
if (ctx.signal?.aborted) {
|
|
3560
|
+
await cancelObserver();
|
|
3561
|
+
const wt = await finishWorktree();
|
|
3562
|
+
return spawnAbortedResult(wt);
|
|
3563
|
+
}
|
|
3564
|
+
const syncAdmission = syncEntryGate.admit("local");
|
|
3565
|
+
if (!syncAdmission.admitted) {
|
|
3566
|
+
await cancelObserver();
|
|
3567
|
+
const wt = await finishWorktree();
|
|
3568
|
+
return errorResult(`Sub-agent not started: ${syncAdmission.refusal.text}${wt ? `\n${wt}` : ""}`, { error: syncAdmission.refusal.code, code: syncAdmission.refusal.code });
|
|
3569
|
+
}
|
|
3414
3570
|
const syncStartedAt = Date.now();
|
|
3415
3571
|
let retainEntry;
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3572
|
+
try {
|
|
3573
|
+
if (ctx.onSubagentSpawn || observerPairing) {
|
|
3574
|
+
retainEntry = ctx.onSubagentSpawn ? await tryRetainChild(ctx.subagentRetain) : undefined;
|
|
3575
|
+
if (retainEntry)
|
|
3576
|
+
stepRecorder.lockTo(retainEntry.childSessionId);
|
|
3577
|
+
const stream = opts.runner.runTaskStream({ ...buildChildSpec(ctx.signal), ...(retainEntry ? { sessionId: retainEntry.childSessionId } : {}) }, undefined, childInternals);
|
|
3578
|
+
observedSteerRef.steer = (text, o) => stream.steer(text, o);
|
|
3579
|
+
let settleResolve;
|
|
3580
|
+
const settled = new Promise((r) => { settleResolve = r; });
|
|
3581
|
+
if (ctx.onSubagentSpawn) {
|
|
3582
|
+
const resume = createSubagentResume({
|
|
3583
|
+
ledger: ctx.subagentRetain,
|
|
3584
|
+
parentToolCallId: ctx.toolCallId,
|
|
3585
|
+
runner: opts.runner,
|
|
3586
|
+
...(opts.background?.notify ? { notify: opts.background.notify } : {}),
|
|
3587
|
+
sink: ctx.onSubagentSpawn,
|
|
3588
|
+
...(opts.background ? { registry: opts.background.registry } : {}),
|
|
3589
|
+
...(opts.onObserverError !== undefined ? { onNotifyError: (f) => opts.onObserverError?.(f.error, { site: f.site }) } : {}),
|
|
3590
|
+
...(ctx.onQuestion !== undefined ? { currentOnQuestion: ctx.onQuestion } : {}),
|
|
3591
|
+
...(ctx.autoModeReview !== undefined ? { currentAutoModeReview: ctx.autoModeReview } : {}),
|
|
3592
|
+
});
|
|
3593
|
+
notifier.notify(() => ctx.onSubagentSpawn?.(createSteerHandle(stream, ctx.toolCallId, childAgentName, settled, {
|
|
3594
|
+
resume,
|
|
3595
|
+
...(retainEntry ? { childSessionId: retainEntry.childSessionId } : {}),
|
|
3596
|
+
})), "subagent.onSubagentSpawn");
|
|
3597
|
+
}
|
|
3598
|
+
try {
|
|
3599
|
+
for await (const ev of stream)
|
|
3600
|
+
observerTap?.record(ev);
|
|
3601
|
+
child = await stream.result();
|
|
3602
|
+
}
|
|
3603
|
+
catch (e) {
|
|
3604
|
+
await settleObserver("failed");
|
|
3605
|
+
throw e;
|
|
3606
|
+
}
|
|
3607
|
+
finally {
|
|
3608
|
+
settleResolve();
|
|
3609
|
+
ctx.subagentRetain?.markSettled(ctx.toolCallId);
|
|
3610
|
+
}
|
|
3449
3611
|
}
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
ctx.subagentRetain?.markSettled(ctx.toolCallId);
|
|
3612
|
+
else {
|
|
3613
|
+
child = await opts.runner.runTask(buildChildSpec(ctx.signal), childInternals);
|
|
3453
3614
|
}
|
|
3454
3615
|
}
|
|
3455
|
-
|
|
3456
|
-
|
|
3616
|
+
finally {
|
|
3617
|
+
syncAdmission.release();
|
|
3457
3618
|
}
|
|
3458
3619
|
await settleObserver(child.status);
|
|
3459
3620
|
const handbackWarning = await reviewHandback({
|
package/dist/agents/verify.d.ts
CHANGED
|
@@ -172,9 +172,20 @@ export interface VerificationOutcome {
|
|
|
172
172
|
* Total cost (micro-USD) of the VERIFIER run(s) across all rounds — the verification OVERHEAD, separate from
|
|
173
173
|
* the implementation's own cost (which is the returned `TaskResult.stats`, as the verifier runs in its own
|
|
174
174
|
* session). Mirrors `runWithTeacher`'s `teacherStats` work-vs-overhead split. Σ of each verifier run's
|
|
175
|
-
* cost+nested.
|
|
176
|
-
*
|
|
177
|
-
* (the
|
|
175
|
+
* cost+nested.
|
|
176
|
+
*
|
|
177
|
+
* THE OPERATION TOTAL, exactly (note the middle term — it is easy to miss and this doc used to omit it):
|
|
178
|
+
* `stats.costMicroUsd + (stats.nested?.costMicroUsd ?? 0) + verification.verifierCost`. The axes are
|
|
179
|
+
* DISJOINT, so nothing is counted twice — but they are not symmetric, which is why the nested term is
|
|
180
|
+
* spelled out: THIS field already folds the verifier's own delegated spend into itself, while the
|
|
181
|
+
* implementation side keeps own and nested apart (the family convention — a consumer adds `stats.X +
|
|
182
|
+
* stats.nested.X` for the true total, and nested cost is never folded into `costMicroUsd`).
|
|
183
|
+
*
|
|
184
|
+
* That total is now exact **in the multi-round fix case too**, since the returned `stats` accumulates
|
|
185
|
+
* every impl-side leg (see {@link VerificationResult}). It held only for the single-pass case before: the
|
|
186
|
+
* returned stats were the FINAL impl attempt's alone, so once a fix round ran, the ORIGINAL attempt's
|
|
187
|
+
* cost was absent from the entire return value and any such sum silently undercounted the operation by
|
|
188
|
+
* exactly that much. Omitted
|
|
178
189
|
* (undefined) when no verifier ran (e.g. an impl that suspended/was opted out before verification).
|
|
179
190
|
*/
|
|
180
191
|
verifierCost?: number;
|
|
@@ -201,6 +212,19 @@ export interface VerificationOutcome {
|
|
|
201
212
|
export interface VerificationResult extends TaskResult {
|
|
202
213
|
/** The verification outcome. The task `result`/`status` is the implementation's; consult `verdict` for quality. */
|
|
203
214
|
verification: VerificationOutcome;
|
|
215
|
+
/**
|
|
216
|
+
* ACCOUNTING NOTE for the inherited `stats` (it is the ONE inherited field the gate does not simply pass
|
|
217
|
+
* through). The gate can run the implementation session SEVERAL times — the impl leg, then a continuation
|
|
218
|
+
* per fix round — and each leg's own `stats` cover only that leg. So `stats` here is the CUMULATIVE
|
|
219
|
+
* impl-side account (every impl + fix leg summed, own and nested kept disjoint as everywhere else), not
|
|
220
|
+
* the final leg's; the non-accumulated fields such as `model` come from the final leg. When exactly one
|
|
221
|
+
* leg contributed (the common no-fix path, and every early hand-back before a fix round), it is that
|
|
222
|
+
* leg's own object, untouched.
|
|
223
|
+
*
|
|
224
|
+
* The VERIFIER's spend is NOT in here — it is the separate overhead axis on
|
|
225
|
+
* {@link VerificationOutcome.verifierCost}, which documents the sum that gives the operation total.
|
|
226
|
+
*/
|
|
227
|
+
stats: TaskResult["stats"];
|
|
204
228
|
}
|
|
205
229
|
/**
|
|
206
230
|
* Verify an already-**completed** implementation `result` behind the independent falsification-style verifier,
|
package/dist/agents/verify.js
CHANGED
|
@@ -4,6 +4,7 @@ import { releaseSession } from "./session-util.js";
|
|
|
4
4
|
import { mapNestedSuspend, isDurablePause } from "./suspend-guard.js";
|
|
5
5
|
import { delimitUntrusted, sanitizeUntrustedText } from "../core/untrusted-text.js";
|
|
6
6
|
import { createSafeNotifier } from "../core/safe-notify.js";
|
|
7
|
+
import { createCumulativeStatsTracker } from "./cumulative-stats.js";
|
|
7
8
|
export const VERIFICATION_PROMPT = `You are a verification specialist. Your job is NOT to confirm the implementation works — it is to try to BREAK it.
|
|
8
9
|
|
|
9
10
|
You have two documented failure patterns. First, verification avoidance: faced with a check, you find reasons not to run it — you read code, narrate what you would test, declare "PASS," and move on. Second, being seduced by the first 80%: a polished result or a passing test suite makes you inclined to pass it, not noticing the edge that crashes, the state that vanishes, the bad input that is unhandled. The first 80% is the easy part. Your entire value is in finding the last 20%.
|
|
@@ -172,6 +173,9 @@ export async function verifyCompleted(runner, result, specBase, objective, confi
|
|
|
172
173
|
}
|
|
173
174
|
};
|
|
174
175
|
let current = result;
|
|
176
|
+
const implAccount = createCumulativeStatsTracker();
|
|
177
|
+
implAccount.add(result.stats);
|
|
178
|
+
const withImplAccount = (r) => implAccount.legs > 1 ? { ...r, stats: implAccount.build(r.stats) } : r;
|
|
175
179
|
let outcome = { verdict: "unverified", rounds: 0, findings: [] };
|
|
176
180
|
const startedAt = Date.now();
|
|
177
181
|
let spend = 0;
|
|
@@ -240,13 +244,14 @@ export async function verifyCompleted(runner, result, specBase, objective, confi
|
|
|
240
244
|
...(foldedReadDeny.length > 0 ? { readDenyPatterns: [...foldedReadDeny] } : {}),
|
|
241
245
|
}, internals);
|
|
242
246
|
spend += (current.stats.costMicroUsd ?? 0) + (current.stats.nested?.costMicroUsd ?? 0);
|
|
247
|
+
implAccount.add(current.stats);
|
|
243
248
|
if (isDurablePause(current.status)) {
|
|
244
|
-
return { ...mapNestedSuspend(carryFrozenPosture(current)), verification: outcome };
|
|
249
|
+
return { ...mapNestedSuspend(carryFrozenPosture(withImplAccount(current))), verification: outcome };
|
|
245
250
|
}
|
|
246
251
|
if (current.status !== "completed")
|
|
247
252
|
break;
|
|
248
253
|
}
|
|
249
|
-
return { ...carryFrozenPosture(current), verification: outcome };
|
|
254
|
+
return { ...carryFrozenPosture(withImplAccount(current)), verification: outcome };
|
|
250
255
|
}
|
|
251
256
|
export async function runWithVerification(runner, implSpec, config = {}, internals) {
|
|
252
257
|
refuseUnhonorableInternals(internals, "door");
|
|
@@ -383,7 +383,12 @@ export interface MaybeCompactOptions {
|
|
|
383
383
|
* compaction is FORCED through the real `generateSummary` LLM path even if the provider would hit — this
|
|
384
384
|
* bounds summary drift from indefinite reuse. The caller owns the counter via {@link onCompaction}
|
|
385
385
|
* (`reused`) and feeds it back via this option; core treats `summaryProvider` as if absent for that one
|
|
386
|
-
* boundary when `consecutiveProviderReuse >= maxConsecutiveProviderReuse`. Default `3
|
|
386
|
+
* boundary when `consecutiveProviderReuse >= maxConsecutiveProviderReuse`. Default `3`, a
|
|
387
|
+
* SEMA choice — the earlier "(CC parity)" label was unsupported and is withdrawn (anchoring
|
|
388
|
+
* re-check 2026-08-23: CC 2.1.223 has no external summary provider and no reuse concept at all;
|
|
389
|
+
* its three 3s are the PTL retry cap `b$d`, the consecutive-compaction-failure breaker `C$d` and
|
|
390
|
+
* the rapid-refill trip `Q3u`, none of them this). 3 is picked for the same reason those are: a
|
|
391
|
+
* small bound on how long a degraded path may keep answering before the real one is forced.
|
|
387
392
|
* `0`/undefined with no `consecutiveProviderReuse` = provider always consulted (no forced refresh).
|
|
388
393
|
*/
|
|
389
394
|
maxConsecutiveProviderReuse?: number;
|
|
@@ -618,9 +623,17 @@ export declare function maybeCompact(opts: MaybeCompactOptions): Promise<{
|
|
|
618
623
|
* (clamped ≥0, so ≤0 means "freed nothing") — orthogonal to rule 2/3's threshold test, which
|
|
619
624
|
* misses this shape because the bloated post (44609) can still sit UNDER the threshold (44800),
|
|
620
625
|
* so rule 3 alone would re-enable the force and repeat the negative-yield pass every boundary.
|
|
621
|
-
* SEMA-ONLY DEFENSE, not a CC port
|
|
622
|
-
*
|
|
623
|
-
*
|
|
626
|
+
* SEMA-ONLY DEFENSE, not a CC port — but the ORIGINAL justification for that label was wrong
|
|
627
|
+
* on both of its premises, and the corrected pair still supports it. (a) "CC's full compact
|
|
628
|
+
* keeps tail 0 and monotonically shrinks" describes the `GNo` branch, which a LOCAL threshold
|
|
629
|
+
* compaction never reaches (CC 2.1.223 `y9s` :432704 routes local passes to the reactive
|
|
630
|
+
* pipeline, which preserves the last group verbatim — `YMo` :399971, `s = 1` :399982); CC's
|
|
631
|
+
* default posture therefore has a keep-tail, exactly as ours does. (b) "CC has no
|
|
632
|
+
* request-layer trim" holds only for the MAIN conversation request: CC does own a
|
|
633
|
+
* keep-the-newest-groups-that-fit primitive (`UPb` :645223), whose only caller is the
|
|
634
|
+
* prompt-hook evaluator's own transcript window, not a main-lane request. What actually makes
|
|
635
|
+
* this regime ours is the third factor — the trim seam feeding a deceived usage anchor, and
|
|
636
|
+
* attachment mass re-entering after the fold — not the absence of a keep-tail. Scoped to trim-forced
|
|
624
637
|
* passes only (`trimForced`), so Seam C reused-summary landings (freed≈0 by design) on the
|
|
625
638
|
* natural/manual paths never trip it. Release path unchanged: backoff gates only the FORCE, a
|
|
626
639
|
* later natural landing that posts under threshold (rule 3, non-trim-forced) turns it back off.
|
|
@@ -196,6 +196,9 @@ export async function maybeCompact(opts) {
|
|
|
196
196
|
if (prep.value.elidedMessages !== undefined && prep.value.elidedMessages > 0) {
|
|
197
197
|
details.elidedMessages = prep.value.elidedMessages;
|
|
198
198
|
}
|
|
199
|
+
if (prep.value.carriedUnsummarizedMessages !== undefined && prep.value.carriedUnsummarizedMessages > 0) {
|
|
200
|
+
details.unsummarizedMessages = prep.value.carriedUnsummarizedMessages;
|
|
201
|
+
}
|
|
199
202
|
}
|
|
200
203
|
else {
|
|
201
204
|
let summaryModel = opts.compactionModel ?? opts.model;
|
|
@@ -14,11 +14,18 @@ export declare const EDIT_FRACTION = 0.7;
|
|
|
14
14
|
* effectiveWindow = autocompactWindow − min(maxOutputTokens, 20000) (CC `Nye`, cap `uMd`)
|
|
15
15
|
* trigger = effectiveWindow − 13000 (CC `dSo`, buffer `rMd`)
|
|
16
16
|
* i.e. trigger = W − 33000 for every model whose max output is ≥ 20k: a 200k window triggers at
|
|
17
|
-
* 167000 (83.5% of nominal), a 1M window at 967000.
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
17
|
+
* 167000 (83.5% of nominal), a 1M window at 967000.
|
|
18
|
+
*
|
|
19
|
+
* 1M DIVERGENCE, stated as fact rather than as an equivalence (anchoring re-check 2026-08-23): CC
|
|
20
|
+
* carries a MODEL-DEFAULT autocompact-window table (2.1.223 `j3u` :242918 — `claude-sonnet-5`
|
|
21
|
+
* default 967000, and since 223 a surface dimension: `remote_cowork` / `local-agent` 500000), so a
|
|
22
|
+
* 1M sonnet-5 gets 967000 as its window and 934000 as its trigger WITHOUT anyone configuring
|
|
23
|
+
* anything. We ship no such table: a model declaring only `contextWindow: 1e6` triggers at 967000
|
|
24
|
+
* here, 33000 LATER than CC. `model.autoCompactTokens = 967000` reproduces CC's number — but that
|
|
25
|
+
* is an EMBEDDER action against CC's product default, not the same posture, and the choice not to
|
|
26
|
+
* follow 934000 is deliberate (design/146 §1.3 argues against living on that thin a cushion). The
|
|
27
|
+
* knob itself is real and unchanged: `autoCompactTokens` lowers only this trigger-side window while
|
|
28
|
+
* the guard and physical request budgeting stay on the 1M window — CC's dual-window shape. We take the
|
|
22
29
|
* 20000 cap branch unconditionally (flat 33000), i.e. the `min(maxOutputTokens, 20000)` branch is
|
|
23
30
|
* deliberately NOT ported: the deduction is exact parity only for models with max output ≥ 20k. A
|
|
24
31
|
* model with a smaller max output would deduct less in CC (trigger LATER); we deduct the full
|
|
@@ -51,11 +58,53 @@ export declare function contextEditFrontier(window: number): number;
|
|
|
51
58
|
* PowerShell is kept for CC fidelity even though sema does not mount it. Overridable per call via
|
|
52
59
|
* {@link ContextEditOptions.compactableTools} for custom-tool-heavy embedders.
|
|
53
60
|
*/
|
|
61
|
+
/**
|
|
62
|
+
* How many most-recent CANDIDATE tool results keep their content when the stale-result pass runs.
|
|
63
|
+
*
|
|
64
|
+
* 3, UNCHANGED — and the fact that CC's corresponding number is 5 (2.1.223 `uAp`, and the same value
|
|
65
|
+
* on CC 88's `timeBasedMCConfig`, so it is stable across both corpus generations) is deliberately NOT
|
|
66
|
+
* adopted here. Two reasons, in order of weight:
|
|
67
|
+
*
|
|
68
|
+
* 1. MEASURED: widening the window removes this pass's only lever in the shape where a terminal
|
|
69
|
+
* parallel batch holds exactly as many large results as the window keeps. Probed on a 200k model
|
|
70
|
+
* with a 140k usage anchor and five 36k-char Bash results: at 3 the pass clears two and the
|
|
71
|
+
* request lands at ~167k, UNDER the 177k request guard; at 5 nothing is clearable, the request
|
|
72
|
+
* stays at 185k, and the guard cannot recover it either — `trimToBudget` must turn-align back to
|
|
73
|
+
* the emitting assistant, so the whole batch is retained. The window is count-based on both
|
|
74
|
+
* sides (CC's `slice(-keepRecent)` too), so this ceiling exists at every value; 5 simply widens
|
|
75
|
+
* the band that reaches it.
|
|
76
|
+
* 2. SEAT: the two numbers do not govern the same machine. CC's keep-recent clear runs ONLY on the
|
|
77
|
+
* `context_hint` rejection leg — gated off by default — and behind a hard "saves ≥ 20000 tokens
|
|
78
|
+
* or don't bother" test, with request REFUSAL as the real backstop. Ours is on by default, is
|
|
79
|
+
* the only reduction between the frontier and the guard, and its backstop drops messages instead
|
|
80
|
+
* of refusing. Copying a constant across that difference is the "same name, different question"
|
|
81
|
+
* mistake, not parity.
|
|
82
|
+
*
|
|
83
|
+
* So the VALUE is an open adjudication (recorded with the probe above), while the two things CC
|
|
84
|
+
* unambiguously answers — the floor and the candidate-scoped window — are followed exactly.
|
|
85
|
+
*/
|
|
86
|
+
export declare const DEFAULT_KEEP_RECENT_TOOL_RESULTS = 3;
|
|
87
|
+
/** Minimum kept results — CC `EUs` :397710 `Math.max(1, keepRecent)`. See
|
|
88
|
+
* {@link ContextEditOptions.keepRecentToolResults} for why both degenerate ends are unusable. */
|
|
89
|
+
export declare const MIN_KEEP_RECENT_TOOL_RESULTS = 1;
|
|
54
90
|
export declare const COMPACTABLE_TOOLS: ReadonlySet<string>;
|
|
55
91
|
export interface ContextEditOptions {
|
|
56
92
|
/** Start clearing once estimated context tokens exceed this. */
|
|
57
93
|
budgetTokens: number;
|
|
58
|
-
/**
|
|
94
|
+
/**
|
|
95
|
+
* Always keep the content of this many most-recent CANDIDATE tool results (candidates = results
|
|
96
|
+
* from {@link compactableTools}). Default {@link DEFAULT_KEEP_RECENT_TOOL_RESULTS} = 3 — see that
|
|
97
|
+
* constant for why CC's 5 is not adopted here.
|
|
98
|
+
*
|
|
99
|
+
* Floored at 1, which IS CC's rule (`EUs` `Math.max(1, keepRecent)`), whose own source note gives
|
|
100
|
+
* the reason: 0 leaves the model with zero working tool context, and in CC's `slice(-0)` spelling
|
|
101
|
+
* it degenerates the other way into keeping everything — two unusable extremes for one value.
|
|
102
|
+
* Ours degenerated identically (a 0 or negative `keep` cleared EVERY candidate). Values that are
|
|
103
|
+
* not a non-negative safe integer are REFUSED rather than floored: a NaN used to make this whole
|
|
104
|
+
* defense silently inert (`slice(0, NaN)` = clear nothing), which is the one outcome a context
|
|
105
|
+
* defense must never reach quietly, and there is no config door upstream to announce at — this
|
|
106
|
+
* option only ever arrives as a direct argument from an embedder.
|
|
107
|
+
*/
|
|
59
108
|
keepRecentToolResults?: number;
|
|
60
109
|
/**
|
|
61
110
|
* roadmap #6② (CC contentReplacementState parity): when set, a result's FULL TEXT is offloaded to
|