@sema-agent/core 5.59.0 → 5.60.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 +40 -0
- package/dist/brain/anthropic.js +15 -5
- package/dist/brain/errors.d.ts +18 -1
- package/dist/brain/errors.js +7 -1
- package/dist/brain/input-too-long.d.ts +57 -0
- package/dist/brain/input-too-long.js +35 -0
- package/dist/brain/stream-engine.js +9 -1
- package/dist/core/auto-compaction.js +2 -2
- package/dist/core/checkpoint-store.d.ts +116 -19
- package/dist/core/checkpoint-store.js +15 -8
- package/dist/core/context-edit.d.ts +243 -41
- package/dist/core/context-edit.js +247 -32
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +5 -0
- package/dist/core/locked-config.d.ts +36 -4
- package/dist/core/locked-config.js +34 -1
- package/dist/core/mcp.js +10 -6
- package/dist/core/memory-engine/content-origin.d.ts +24 -2
- package/dist/core/memory-engine/content-origin.js +6 -1
- package/dist/core/memory.d.ts +10 -0
- package/dist/core/park-selfcheck.js +1 -0
- package/dist/core/permission-rule-consent.js +9 -5
- package/dist/core/runner/prepare-config-doors.d.ts +22 -1
- package/dist/core/runner/prepare-config-doors.js +36 -0
- package/dist/core/runner/prepare-task.d.ts +28 -1
- package/dist/core/runner/prepare-task.js +106 -8
- package/dist/core/runner/runtask.js +41 -4
- package/dist/core/store-contracts/checkpoint-store-contract.js +32 -0
- package/dist/core/tool-policy.d.ts +24 -8
- package/dist/core/tool-policy.js +3 -3
- package/dist/core/tools.js +1 -1
- package/dist/core/trace.d.ts +36 -0
- package/dist/core/types.d.ts +127 -2
- package/dist/core/untrusted-text.d.ts +11 -0
- package/dist/core/untrusted-text.js +1 -0
- package/dist/engine/llm/types.d.ts +21 -2
- package/dist/engine/loop/agent-loop.js +7 -1
- package/dist/engine/loop/types.d.ts +4 -1
- package/dist/tools/fs/fs-bash.js +1 -2
- package/package.json +1 -1
|
@@ -20,7 +20,7 @@ import { createSubagentWorktreeHelper, forkGovernanceDenial, resolveDelegationEn
|
|
|
20
20
|
import { createAgentTranscriptTool, AGENT_TRANSCRIPT_TOOL_NAME } from "../../agents/agent-transcript-tool.js";
|
|
21
21
|
import { createSendMessageTool, SEND_MESSAGE_TOOL_NAME } from "../../agents/send-message-tool.js";
|
|
22
22
|
import { SubagentRetainLedger } from "../../agents/retain-ledger.js";
|
|
23
|
-
import { askApproverIdentity, checkToolPolicyProjection, combinePolicies, constraintChainDigest, constraintChainEntryOf, isApprovalSettledBy, isAskDenyResolution, screenApproverAttribution, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, describeThrown, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets, tryCloneArgs } from "../tool-policy.js";
|
|
23
|
+
import { askApproverIdentity, carriesBidiControls, checkToolPolicyProjection, combinePolicies, constraintChainDigest, constraintChainEntryOf, isApprovalSettledBy, isAskDenyResolution, screenApproverAttribution, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, describeThrown, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets, tryCloneArgs } from "../tool-policy.js";
|
|
24
24
|
const PERSISTED_RULE_TOOL = "Bash";
|
|
25
25
|
import { findAdmittingRule, segmentCoverageOf, suggestRulesForCommand } from "../permission-rule-model.js";
|
|
26
26
|
import { ActiveSkillScope, createActiveSkillScopePolicy } from "./active-skill-scope.js";
|
|
@@ -70,7 +70,7 @@ import { resolveEpochAgainstBundled } from "../../prompt-assembly/epoch.js";
|
|
|
70
70
|
import { artifactDeclarations } from "../../prompt-assembly/artifact.js";
|
|
71
71
|
import { buildTurnPromptSnapshot } from "../../prompt-assembly/turn-snapshot.js";
|
|
72
72
|
import { SEMA_DEFAULT_PACK } from "../../prompt-assembly/packs/sema-default.js";
|
|
73
|
-
import { clearStaleToolResults, dropEmptyFailureAssistants, editBudget } from "../context-edit.js";
|
|
73
|
+
import { clearStaleToolResults, createClearedProjectionLedger, dropEmptyFailureAssistants, editBudget, replayClearedProjection, resolveTriggerWindow, } from "../context-edit.js";
|
|
74
74
|
import { capAggregateToolResults } from "../tool-result-budget.js";
|
|
75
75
|
import { capAggregateMediaBytes, AGGREGATE_MEDIA_BUDGET_BYTES } from "../media-byte-cap.js";
|
|
76
76
|
import { dropOrphanToolResults, guardBudget, insertTrimNotice, trimToBudget } from "../context-guard.js";
|
|
@@ -124,7 +124,7 @@ function warnCompactionWindowHazard(tracer, spec, model, compModel, hostTaskId)
|
|
|
124
124
|
if (compModel === undefined)
|
|
125
125
|
return;
|
|
126
126
|
const compWindow = compModel.contextTokens ?? compModel.contextWindow;
|
|
127
|
-
const mainWindow = model.
|
|
127
|
+
const mainWindow = resolveTriggerWindow(model).window;
|
|
128
128
|
if (compWindow > 0 && mainWindow > 0 && compWindow < mainWindow) {
|
|
129
129
|
const merged = { ...DEFAULT_COMPACTION_SETTINGS, ...spec.compaction };
|
|
130
130
|
const sanitized = sanitizeCompactionSettings(merged, mainWindow);
|
|
@@ -195,6 +195,33 @@ function announceToolModelGate(onNotice, modelId, gate) {
|
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
|
+
function announceDeclaredMcpContentClasses(args) {
|
|
199
|
+
for (const entry of args.entries ?? []) {
|
|
200
|
+
const declaredClass = entry.contentOrigin;
|
|
201
|
+
if (declaredClass === undefined)
|
|
202
|
+
continue;
|
|
203
|
+
const server = inlineUntrusted(String(entry.name), 160);
|
|
204
|
+
const toolCount = args.statuses?.find((s) => s.name === entry.name)?.toolNames?.length ?? 0;
|
|
205
|
+
const resourceFaceCaveat = " (Not covered: the cross-server resource faces — ListMcpResourcesTool/ReadMcpResourceTool/ReadMcpResourceDirTool — stay externally classified and still mark.)";
|
|
206
|
+
const posture = declaredClass === "local"
|
|
207
|
+
? `this server's mounted tools' invocations will not mark this session's memory as externally exposed.${resourceFaceCaveat}`
|
|
208
|
+
: declaredClass === "execution"
|
|
209
|
+
? `this server's mounted tools' invocations will not mark this session's memory unless the execIsExternalContent strict upgrade is armed (currently: ${args.execIsExternalContent ? "armed" : "off"}).${resourceFaceCaveat}`
|
|
210
|
+
: "its tools stay externally classified and this declaration PINS that — the trustedTools allowlist no longer exempts them, and invocations mark this session's memory.";
|
|
211
|
+
deliverEngineNotice(args.onNotice, {
|
|
212
|
+
code: "memory.content_class_declared",
|
|
213
|
+
message: `MCP server "${server}" (${toolCount} tool(s) mounted) is declared ` +
|
|
214
|
+
`contentOrigin "${declaredClass}" by this deployment's configuration — ${posture}`,
|
|
215
|
+
detail: {
|
|
216
|
+
server,
|
|
217
|
+
contentOrigin: declaredClass,
|
|
218
|
+
toolCount,
|
|
219
|
+
...(declaredClass === "execution" ? { execIsExternalContent: args.execIsExternalContent } : {}),
|
|
220
|
+
sessionId: args.sessionId,
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
198
225
|
export { __resetReadFaceClampAnnouncement } from "./prepare-hands-readface.js";
|
|
199
226
|
const DEFAULT_MAX_SUSPENDS = 5;
|
|
200
227
|
const ULTRA_REASONING_TIERS = new Set(["xhigh", "max"]);
|
|
@@ -242,6 +269,49 @@ class ParkRefusal extends Error {
|
|
|
242
269
|
export { resolveCheckpointStore } from "../checkpoint-store.js";
|
|
243
270
|
export { isFableFamilyModelId, resolveModelPromptTraits, resolveTaskLimits } from "./prepare-config-doors.js";
|
|
244
271
|
export { rebaseWorkspacePath, rebaseWorkspacePathAcross } from "./prepare-workspace-restore.js";
|
|
272
|
+
function buildMicroCompactState(deps, model, sessionId, offloadStore, knob) {
|
|
273
|
+
const triggerWindow = resolveTriggerWindow(model);
|
|
274
|
+
if (triggerWindow.clamped) {
|
|
275
|
+
deliverEngineNotice(deps.onNotice, {
|
|
276
|
+
code: "config.autocompact_window_clamped",
|
|
277
|
+
message: `model "${model.id}" declares autoCompactTokens=${triggerWindow.declaredAutoCompactTokens} ABOVE its physical window ` +
|
|
278
|
+
`${triggerWindow.physicalWindow}; the autocompact window only ever LOWERS the trigger-side geometry, so the value was ` +
|
|
279
|
+
`clamped to the physical window — fix the model config (declare a value at or below the physical window, or omit it)`,
|
|
280
|
+
detail: {
|
|
281
|
+
modelId: model.id,
|
|
282
|
+
declaredAutoCompactTokens: triggerWindow.declaredAutoCompactTokens,
|
|
283
|
+
physicalWindow: triggerWindow.physicalWindow,
|
|
284
|
+
sessionId,
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
machine: knob.machine,
|
|
290
|
+
clearOnRejection: knob.clearOnRejection,
|
|
291
|
+
ledger: createClearedProjectionLedger(),
|
|
292
|
+
projectionRef: {},
|
|
293
|
+
...(offloadStore ? { offloadPersist: createOffloadPersist(offloadStore, sessionId, deps.onNotice) } : {}),
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
function recordFrontierClears(args) {
|
|
297
|
+
for (const { index } of args.pass.clears) {
|
|
298
|
+
const at = args.index.keyAt(index);
|
|
299
|
+
const clearedMsg = args.edited[index];
|
|
300
|
+
const markerText = clearedMsg.content?.[0]?.text;
|
|
301
|
+
if (at !== undefined && typeof markerText === "string") {
|
|
302
|
+
args.ledger.entries.set(at.key, { marker: markerText, groupCount: at.groupCount, fp: at.fp });
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
emitTrace(args.tracer, () => ({
|
|
306
|
+
kind: "context.mc_clear",
|
|
307
|
+
version: 1,
|
|
308
|
+
taskId: args.taskId,
|
|
309
|
+
clearedCount: args.pass.clears.length,
|
|
310
|
+
tokensSavedEstimate: args.pass.tokensSavedEstimate,
|
|
311
|
+
trigger: "frontier",
|
|
312
|
+
ts: Date.now(),
|
|
313
|
+
}));
|
|
314
|
+
}
|
|
245
315
|
export function gatedCallIdOf(p) {
|
|
246
316
|
if (p.suspendRef.token !== undefined)
|
|
247
317
|
return p.suspendRef.gatedCallId;
|
|
@@ -492,7 +562,7 @@ async function derivedRouteFallsBack(args) {
|
|
|
492
562
|
export async function prepareTask(spec, deps, sessions, resume, internals, runnerSelf) {
|
|
493
563
|
const doors = prepareConfigDoors({ spec, deps, sessions, resume, internals });
|
|
494
564
|
spec = doors.spec;
|
|
495
|
-
const { toolFaceSnapshot, promptProfile, lockedPreflight, resolvedInteractionPosture, resolvedRole, model, thinking, compModel, fableMitigations, memoryDelegationEvidence, memoryProvenance, usageWindows, brainCallGuardrailRef, brainCallGuardrailMs } = doors;
|
|
565
|
+
const { toolFaceSnapshot, promptProfile, lockedPreflight, resolvedInteractionPosture, microCompactKnob, resolvedRole, model, thinking, compModel, fableMitigations, memoryDelegationEvidence, memoryProvenance, usageWindows, brainCallGuardrailRef, brainCallGuardrailMs } = doors;
|
|
496
566
|
announceToolModelGate(deps.onNotice, model.id, doors.modelGate);
|
|
497
567
|
const { toolEffects, egressTools, irreversibleTools, irreversibilityTier, axisExplicitNegatives, reversibilityProbes, ownToolNames } = prepareSafetyScan({ spec, deps });
|
|
498
568
|
let shellGatedBash = false;
|
|
@@ -2079,6 +2149,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2079
2149
|
const trustedTools = effectiveSafety.trustedTools;
|
|
2080
2150
|
const execIsExternalContent = effectiveSafety.execIsExternalContent;
|
|
2081
2151
|
provenanceForChildrenRef.current = { trustedTools: [...trustedTools], execIsExternalContent };
|
|
2152
|
+
announceDeclaredMcpContentClasses({
|
|
2153
|
+
entries: lockedPreflight.mcp,
|
|
2154
|
+
statuses: mcp?.statuses,
|
|
2155
|
+
execIsExternalContent,
|
|
2156
|
+
sessionId,
|
|
2157
|
+
onNotice: deps.onNotice,
|
|
2158
|
+
});
|
|
2082
2159
|
if (memoryEngineSession?.settlement !== undefined) {
|
|
2083
2160
|
delegationSettlementRef.current = { controlDir: memoryEngineSession.settlement.controlDir, sessionId: memoryEngineSession.settlement.sessionId };
|
|
2084
2161
|
}
|
|
@@ -4229,7 +4306,11 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4229
4306
|
args: parkedArgs,
|
|
4230
4307
|
...(() => {
|
|
4231
4308
|
const preview = approvalPreviewOf(req.toolName, parkedArgs);
|
|
4232
|
-
|
|
4309
|
+
const bidi = carriesBidiControls(parkedArgs) || carriesBidiControls(preview);
|
|
4310
|
+
return {
|
|
4311
|
+
...(preview !== undefined ? { preview } : {}),
|
|
4312
|
+
...(bidi ? { hasBidiControls: true } : {}),
|
|
4313
|
+
};
|
|
4233
4314
|
})(),
|
|
4234
4315
|
...ruleOffersOf(req.toolName, parkedArgs, {
|
|
4235
4316
|
...(realApproval !== undefined ? { requiresRealApproval: true } : {}),
|
|
@@ -4502,11 +4583,14 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4502
4583
|
}
|
|
4503
4584
|
const editAt = editBudget(model);
|
|
4504
4585
|
const guardAt = guardBudget(model);
|
|
4586
|
+
const microCompact = buildMicroCompactState(deps, model, sessionId, offloadStore, microCompactKnob);
|
|
4505
4587
|
const charsPerToken = model.charsPerToken ?? DEFAULT_CHARS_PER_TOKEN;
|
|
4506
4588
|
harness.on("context", async ({ messages }) => {
|
|
4507
4589
|
batchHaltRef.current = undefined;
|
|
4508
4590
|
const healed = dropEmptyFailureAssistants(messages);
|
|
4509
|
-
const
|
|
4591
|
+
const replay = replayClearedProjection(healed, microCompact.ledger);
|
|
4592
|
+
const replayed = replay.messages;
|
|
4593
|
+
const capped = await capAggregateToolResults(replayed, {
|
|
4510
4594
|
store: offloadStore,
|
|
4511
4595
|
sessionId,
|
|
4512
4596
|
onCapped: (info) => emitTrace(deps.tracer, () => ({
|
|
@@ -4523,8 +4607,17 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4523
4607
|
limitBytes: deps.mediaByteCapBytes ?? AGGREGATE_MEDIA_BUDGET_BYTES,
|
|
4524
4608
|
onStripped: deps.onMediaStripped,
|
|
4525
4609
|
});
|
|
4610
|
+
let ccPass;
|
|
4526
4611
|
const edited = clearStaleToolResults(mediaCapped, {
|
|
4527
4612
|
budgetTokens: editAt,
|
|
4613
|
+
...(microCompact.machine === "cc" || microCompact.clearOnRejection ? { recognizeCcMarkers: true } : {}),
|
|
4614
|
+
...(microCompact.machine === "cc"
|
|
4615
|
+
? {
|
|
4616
|
+
machine: "cc",
|
|
4617
|
+
clearSource: replayed,
|
|
4618
|
+
onCleared: (pass) => { ccPass = pass; },
|
|
4619
|
+
}
|
|
4620
|
+
: {}),
|
|
4528
4621
|
anchoredTotalTokens: estimateContextTokens(mediaCapped, charsPerToken).tokens,
|
|
4529
4622
|
charsPerToken,
|
|
4530
4623
|
...(offloadStore
|
|
@@ -4535,6 +4628,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4535
4628
|
}
|
|
4536
4629
|
: {}),
|
|
4537
4630
|
});
|
|
4631
|
+
if (ccPass !== undefined) {
|
|
4632
|
+
recordFrontierClears({ pass: ccPass, index: replay.index, edited, ledger: microCompact.ledger, tracer: deps.tracer, taskId: spec.taskId ?? sessionId });
|
|
4633
|
+
}
|
|
4538
4634
|
let trimmed = trimToBudget(edited, guardAt, estimateContextTokens(edited, charsPerToken).tokens, charsPerToken);
|
|
4539
4635
|
const trimDroppedMessages = trimmed.length < edited.length;
|
|
4540
4636
|
if (trimDroppedMessages) {
|
|
@@ -4575,12 +4671,14 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4575
4671
|
}
|
|
4576
4672
|
}
|
|
4577
4673
|
requestLossyRef.current =
|
|
4578
|
-
|
|
4674
|
+
replayed !== healed ||
|
|
4675
|
+
capped !== replayed ||
|
|
4579
4676
|
mediaCapped !== capped ||
|
|
4580
4677
|
edited !== mediaCapped ||
|
|
4581
4678
|
trimDroppedMessages ||
|
|
4582
4679
|
swept.dropped.length > 0 ||
|
|
4583
4680
|
gitStatusRef.overBudgetShrunk === true;
|
|
4681
|
+
microCompact.projectionRef.current = { messages: swept.messages, keyOf: replay.index.keyOf };
|
|
4584
4682
|
return { messages: swept.messages };
|
|
4585
4683
|
});
|
|
4586
4684
|
const cacheBreakDetector = deps.cacheBreakDetection === false ? undefined : new CacheBreakDetector();
|
|
@@ -4769,7 +4867,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4769
4867
|
const effectiveReadFaceObserved = carrierReadFace();
|
|
4770
4868
|
const effectiveReadDenyObserved = readDenyAdditionsNormalized.length > 0 ? readDenyAdditionsNormalized.map((e) => ({ ...e })) : undefined;
|
|
4771
4869
|
const preparedHolder = {};
|
|
4772
|
-
const buildPrepared = () => ({ harness, session, sessionId, reminderMark, reminderDisclosureCounts, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, approvalSettlement, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, suspendRef, suspendProgressRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, gitStatusRef, listBackgroundTasks, hookIdentity, hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
4870
|
+
const buildPrepared = () => ({ harness, session, sessionId, reminderMark, reminderDisclosureCounts, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, approvalSettlement, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, suspendRef, suspendProgressRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, microCompact, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, gitStatusRef, listBackgroundTasks, hookIdentity, hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
4773
4871
|
const prepared = buildPrepared();
|
|
4774
4872
|
preparedHolder.current = prepared;
|
|
4775
4873
|
return prepared;
|
|
@@ -2,6 +2,7 @@ import { persistedReadDenyEntryProblem } from "../../tools/fs/read-deny.js";
|
|
|
2
2
|
import { createSafeNotifier, observeThenableRejection } from "../safe-notify.js";
|
|
3
3
|
import { mintSystemReminder, openSystemReminder } from "../reminder-mint.js";
|
|
4
4
|
import { deliverDelegationLifecycle, deliverEngineNotice, undrainedUserInputNotices } from "../types.js";
|
|
5
|
+
import { planRejectionClears, resolveTriggerWindow } from "../context-edit.js";
|
|
5
6
|
import { AgentHarness, DEFAULT_COMPACTION_SETTINGS, uuidv7 } from "../../internal/harness.js";
|
|
6
7
|
import { snapshotActorAssertion } from "../../internal/llm.js";
|
|
7
8
|
import { CheckpointError, BINDING_CHECKPOINT_VERSION, checkpointVersionOf, F012_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, FACE_CHECKPOINT_VERSION, remainingBudgetMicroUsd, readPendingSteerQueue, remainingTokens, LEGACY_PENDING_STEER_INPUT_ID, MAX_STEER_INPUT_ID_CHARS, validatePendingSteer, winnerFromOutcome, } from "../checkpoint-store.js";
|
|
@@ -2734,7 +2735,7 @@ export class Runner {
|
|
|
2734
2735
|
const withinTaskCompaction = (spec.compaction?.enabled ?? true) && (spec.compaction?.withinTask ?? true);
|
|
2735
2736
|
const compactionBreaker = { failures: 0 };
|
|
2736
2737
|
if (spec.compaction?.enabled ?? true) {
|
|
2737
|
-
const prefixWindow = prepared.model.
|
|
2738
|
+
const prefixWindow = resolveTriggerWindow(prepared.model).window;
|
|
2738
2739
|
if (Number.isFinite(prefixWindow) && prefixWindow > 0) {
|
|
2739
2740
|
const prefixSettings = sanitizeCompactionSettings({ ...DEFAULT_COMPACTION_SETTINGS, ...spec.compaction }, prefixWindow);
|
|
2740
2741
|
const prefixCompactAt = prefixWindow - prefixSettings.reserveTokens;
|
|
@@ -2940,13 +2941,49 @@ export class Runner {
|
|
|
2940
2941
|
thinkingOnly: {},
|
|
2941
2942
|
degenerateOutput: { detect: (m) => isDegenerateCutMessage(m) },
|
|
2942
2943
|
promptTooLong: {
|
|
2943
|
-
recover: async () => {
|
|
2944
|
+
recover: async (attempt) => {
|
|
2945
|
+
if (prepared.abortController.signal.aborted || prepared.suspendRef.token !== undefined)
|
|
2946
|
+
return false;
|
|
2947
|
+
if (prepared.microCompact.clearOnRejection && attempt === 1) {
|
|
2948
|
+
const proj = prepared.microCompact.projectionRef.current;
|
|
2949
|
+
const compactionAvailable = (spec.compaction?.enabled ?? true) && compactionBreaker.failures < MAX_CONSECUTIVE_COMPACTION_FAILURES;
|
|
2950
|
+
const declineArm = compactionAvailable ? "forced_compaction" : "none";
|
|
2951
|
+
const plan = proj === undefined
|
|
2952
|
+
? { declined: "no_candidates" }
|
|
2953
|
+
: planRejectionClears(proj.messages, {
|
|
2954
|
+
keyOf: proj.keyOf,
|
|
2955
|
+
...(prepared.microCompact.offloadPersist ? { offload: { persist: prepared.microCompact.offloadPersist } } : {}),
|
|
2956
|
+
});
|
|
2957
|
+
if ("declined" in plan) {
|
|
2958
|
+
emitTrace(rs.telemetry.tracer, () => ({
|
|
2959
|
+
kind: "context.mc_null",
|
|
2960
|
+
version: 1,
|
|
2961
|
+
taskId: rs.telemetry.taskId,
|
|
2962
|
+
reason: plan.declined,
|
|
2963
|
+
nextArm: declineArm,
|
|
2964
|
+
ts: Date.now(),
|
|
2965
|
+
}));
|
|
2966
|
+
}
|
|
2967
|
+
else {
|
|
2968
|
+
for (const e of plan.cleared) {
|
|
2969
|
+
prepared.microCompact.ledger.entries.set(e.key, { marker: e.marker, groupCount: e.groupCount, fp: e.fp });
|
|
2970
|
+
}
|
|
2971
|
+
emitTrace(rs.telemetry.tracer, () => ({
|
|
2972
|
+
kind: "context.mc_clear",
|
|
2973
|
+
version: 1,
|
|
2974
|
+
taskId: rs.telemetry.taskId,
|
|
2975
|
+
clearedCount: plan.cleared.length,
|
|
2976
|
+
tokensSavedEstimate: plan.tokensSavedEstimate,
|
|
2977
|
+
trigger: "refusal",
|
|
2978
|
+
ts: Date.now(),
|
|
2979
|
+
}));
|
|
2980
|
+
return true;
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2944
2983
|
if (!(spec.compaction?.enabled ?? true))
|
|
2945
2984
|
return false;
|
|
2946
2985
|
if (compactionBreaker.failures >= MAX_CONSECUTIVE_COMPACTION_FAILURES)
|
|
2947
2986
|
return false;
|
|
2948
|
-
if (prepared.abortController.signal.aborted || prepared.suspendRef.token !== undefined)
|
|
2949
|
-
return false;
|
|
2950
2987
|
try {
|
|
2951
2988
|
const comp = await maybeCompact({
|
|
2952
2989
|
session: prepared.session,
|
|
@@ -180,6 +180,38 @@ export async function checkpointStoreContract(make, runAssertion) {
|
|
|
180
180
|
assert.equal(list[0].token, pending.token);
|
|
181
181
|
assert.equal((await store.get(pending.token)).status, "pending");
|
|
182
182
|
});
|
|
183
|
+
run("#438 hasBidiControls three-form matrix: a row's `true` survives + projects; absent stays absent (clean args); an out-of-contract `false` reads as absent; a bit-less marked row backfills at projection", async () => {
|
|
184
|
+
const store = make();
|
|
185
|
+
const RLO = String.fromCodePoint(0x202e);
|
|
186
|
+
const asserted = createCheckpointFixture({ token: mintCheckpointToken(), sessionId: "bidi-true" });
|
|
187
|
+
asserted.pendingAction.hasBidiControls = true;
|
|
188
|
+
const absent = createCheckpointFixture({ token: mintCheckpointToken(), sessionId: "bidi-absent" });
|
|
189
|
+
const falsed = createCheckpointFixture({ token: mintCheckpointToken(), sessionId: "bidi-false" });
|
|
190
|
+
falsed.pendingAction.hasBidiControls = false;
|
|
191
|
+
const bitless = createCheckpointFixture({
|
|
192
|
+
token: mintCheckpointToken(),
|
|
193
|
+
sessionId: "bidi-bitless",
|
|
194
|
+
pendingAction: {
|
|
195
|
+
kind: "tool_approval",
|
|
196
|
+
toolCallId: "call-3",
|
|
197
|
+
toolName: "Write",
|
|
198
|
+
args: { path: `/x/${RLO}txt.exe` },
|
|
199
|
+
boundInputHash: "h0",
|
|
200
|
+
batchToolCallIds: ["call-3"],
|
|
201
|
+
completedCallIds: [],
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
for (const cp of [asserted, absent, falsed, bitless])
|
|
205
|
+
await store.put(cp.token, cp);
|
|
206
|
+
const byId = new Map((await store.listByScope("tenant-a")).map((s) => [s.sessionId, s]));
|
|
207
|
+
assert.equal(byId.get("bidi-true")?.hasBidiControls, true);
|
|
208
|
+
assert.equal("hasBidiControls" in byId.get("bidi-absent"), false, "clean+absent must project the key OMITTED, not null/false");
|
|
209
|
+
assert.equal("hasBidiControls" in byId.get("bidi-false"), false, "a written false is out-of-contract and must read as absent, never project");
|
|
210
|
+
assert.equal(byId.get("bidi-bitless")?.hasBidiControls, true, "a marked row without the bit must be backfilled at projection time");
|
|
211
|
+
assert.equal(byId.get("bidi-bitless").toolInput.includes(RLO), true);
|
|
212
|
+
const stored = (await store.get(bitless.token)).pendingAction;
|
|
213
|
+
assert.equal("hasBidiControls" in stored, false, "projection backfill must not write back to the row");
|
|
214
|
+
});
|
|
183
215
|
await settle();
|
|
184
216
|
}
|
|
185
217
|
const sortByToken = (a, b) => a.token.localeCompare(b.token);
|
|
@@ -986,10 +986,15 @@ export interface AskRequest {
|
|
|
986
986
|
*
|
|
987
987
|
* SCOPE, stated so absence is not read as a clean bill on the other routes: this is the
|
|
988
988
|
* SYNCHRONOUS `onAsk` ask, like `principal`/`sourceTaskId` — the closure claim is over AskRequest
|
|
989
|
-
* mint sites, not over every approval callback.
|
|
990
|
-
* ① a durable park/suspend never invokes `onAsk
|
|
991
|
-
*
|
|
992
|
-
*
|
|
989
|
+
* mint sites, not over every approval callback. The other two human-decision routes:
|
|
990
|
+
* ① a durable park/suspend never invokes `onAsk` and never mints an AskRequest — its twin of this
|
|
991
|
+
* bit is the PERSISTED `PendingAction.tool_approval.hasBidiControls` on the parked row (judged at
|
|
992
|
+
* the park mint over the row's own fidelity-projected args snapshot + preview, so the two faces
|
|
993
|
+
* can honestly differ on a shape-changing backend), projected to the inbox as
|
|
994
|
+
* `CheckpointSummary.hasBidiControls` — see those fields in `checkpoint-store.ts` for the reading
|
|
995
|
+
* contract; absence there is still "not detected", never a clean bill, and an inbox rendering
|
|
996
|
+
* `RiskDescriptor` still owes its own screen (or the display baseline above);
|
|
997
|
+
* ② {@link createApprovalPolicy}'s
|
|
993
998
|
* `approve` seat resolves in-place inside the policy and mints no AskRequest at all — its
|
|
994
999
|
* callback receives a bare `ToolCallRequest` (no bit, no {@link boundInputHash}, none of this
|
|
995
1000
|
* surface), so a deployment doing HITL through that seat must render through
|
|
@@ -1222,18 +1227,29 @@ export type ResolvedAsk = PermissionResult & {
|
|
|
1222
1227
|
};
|
|
1223
1228
|
/**
|
|
1224
1229
|
* Does any string reachable in `value` carry a {@link BIDI_CONTROL_RE} member? Bounded, cycle-safe,
|
|
1225
|
-
* and never throwing —
|
|
1226
|
-
* to "not detected" rather than turn an ask into an error (the `deliverEngineNotice`
|
|
1227
|
-
* derived disclosure must never become the failure of the thing it describes).
|
|
1230
|
+
* and never throwing — every caller is on an approval/projection path, where a scan that failed must
|
|
1231
|
+
* degrade to "not detected" rather than turn an ask into an error (the `deliverEngineNotice`
|
|
1232
|
+
* posture: a derived disclosure must never become the failure of the thing it describes).
|
|
1228
1233
|
*
|
|
1229
1234
|
* OBJECT KEYS are scanned as well as values: a key is displayed text too, and an argument object
|
|
1230
1235
|
* `{ "cmd<RLO>": … }` renders its own reordering in any card that prints the shape (the marker
|
|
1231
1236
|
* is spelled out here on purpose — a literal one in this comment would reorder the comment).
|
|
1232
1237
|
*
|
|
1238
|
+
* `limits` parameterizes the two budgets WITHOUT opening a second reader: the character class and
|
|
1239
|
+
* the traversal stay defined exactly here, only the allowance varies by caller. Omitted ⇒ the mint
|
|
1240
|
+
* tier above (the resolveAsk chokepoint and the durable park mint, which scan one call's own
|
|
1241
|
+
* payload). A per-row projection caller (the checkpoint summary backfill, which may face
|
|
1242
|
+
* deployment-written rows of arbitrary size on a many-row read) passes its own smaller tier so one
|
|
1243
|
+
* row's work is bounded regardless of the row's provenance. Both members are required when the
|
|
1244
|
+
* object is given — a half-specified tier would silently mix two tiers in one scan.
|
|
1245
|
+
*
|
|
1233
1246
|
* Not a public export: the contract is the {@link AskRequest.hasBidiControls} bit, and a second
|
|
1234
1247
|
* spelling of "does this carry bidi" on the public surface would be one more thing to keep in step.
|
|
1235
1248
|
*/
|
|
1236
|
-
export declare function carriesBidiControls(value: unknown
|
|
1249
|
+
export declare function carriesBidiControls(value: unknown, limits?: {
|
|
1250
|
+
maxNodes: number;
|
|
1251
|
+
maxChars: number;
|
|
1252
|
+
}): boolean;
|
|
1237
1253
|
/**
|
|
1238
1254
|
* Resolve an `ask` decision to a terminal `allow`/`deny` via {@link OnAsk}. Centralizes the headless
|
|
1239
1255
|
* auto-deny default, fail-closed error handling, and stable deny reasons so every ask site is
|
package/dist/core/tool-policy.js
CHANGED
|
@@ -903,9 +903,9 @@ export function coreMintedResolutionOf(d, call) {
|
|
|
903
903
|
const BIDI_CONTROL_RE = /[\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/u;
|
|
904
904
|
const BIDI_SCAN_MAX_NODES = 5_000;
|
|
905
905
|
const BIDI_SCAN_MAX_CHARS = 1_000_000;
|
|
906
|
-
export function carriesBidiControls(value) {
|
|
907
|
-
let budget = BIDI_SCAN_MAX_NODES;
|
|
908
|
-
let charBudget = BIDI_SCAN_MAX_CHARS;
|
|
906
|
+
export function carriesBidiControls(value, limits) {
|
|
907
|
+
let budget = limits?.maxNodes ?? BIDI_SCAN_MAX_NODES;
|
|
908
|
+
let charBudget = limits?.maxChars ?? BIDI_SCAN_MAX_CHARS;
|
|
909
909
|
const seen = new WeakSet();
|
|
910
910
|
const scan = (s) => {
|
|
911
911
|
if (charBudget <= 0)
|
package/dist/core/tools.js
CHANGED
|
@@ -45,7 +45,7 @@ export function defineTool(spec, options) {
|
|
|
45
45
|
executionMode,
|
|
46
46
|
...(spec.isConcurrencySafe ? { isConcurrencySafe: spec.isConcurrencySafe } : {}),
|
|
47
47
|
...(spec.effect ? { effect: spec.effect } : {}),
|
|
48
|
-
...(spec.contentOrigin ? { contentOrigin: spec.contentOrigin } : {}),
|
|
48
|
+
...(spec.contentOrigin !== undefined ? { contentOrigin: spec.contentOrigin } : {}),
|
|
49
49
|
...(spec.egress ? { egress: true } : {}),
|
|
50
50
|
...(spec.irreversibility !== undefined ? { irreversibility: spec.irreversibility } : {}),
|
|
51
51
|
...(spec.reversibilityProbe ? { reversibilityProbe: spec.reversibilityProbe } : {}),
|
package/dist/core/trace.d.ts
CHANGED
|
@@ -729,6 +729,42 @@ export type TraceEvent = {
|
|
|
729
729
|
/** Messages dropped from THIS request view. */
|
|
730
730
|
dropped: number;
|
|
731
731
|
ts: number;
|
|
732
|
+
} | {
|
|
733
|
+
/** design/374 (G11) — the microCompact clearing machine FIRED: `clearedCount` tool-result
|
|
734
|
+
* occurrences were replaced with markers in one pass. `trigger` names the arm: `"frontier"`
|
|
735
|
+
* = the proactive request-build pass (anchored estimate crossed the edit budget),
|
|
736
|
+
* `"refusal"` = the MC-R rejection-recovery arm (provider said input-too-long), `"blocking"`
|
|
737
|
+
* = the slice-3 pre-guard arm (reserved; not emitted before slice 3). SCOPE (X1): emitted
|
|
738
|
+
* only when the design/374 machinery is enabled — the `"frontier"` arm requires
|
|
739
|
+
* `microCompact.machine: "cc"`, the `"refusal"` arm requires `microCompact.clearOnRejection`;
|
|
740
|
+
* the legacy DEFAULT machine clears silently exactly as pre-374 (its clears are ledger-less
|
|
741
|
+
* and re-fire per request, so a frame there would re-count the same occurrences and break
|
|
742
|
+
* this frame's cardinality clause). Cardinality: exactly ONE frame per firing pass — a retry
|
|
743
|
+
* chain re-sending an already-cleared view emits none. A `"refusal"` frame also marks one
|
|
744
|
+
* attempt of the shared prompt-too-long recovery budget spent (the MC-R arm's retry rides
|
|
745
|
+
* the same per-chain account the forced-compaction arm draws on — the knob doc on
|
|
746
|
+
* `RunnerDeps.microCompact` carries the full accounting). */
|
|
747
|
+
kind: "context.mc_clear";
|
|
748
|
+
version: 1;
|
|
749
|
+
taskId: string;
|
|
750
|
+
clearedCount: number;
|
|
751
|
+
/** The pass's structural savings estimate (the ≥20k gate's own number). */
|
|
752
|
+
tokensSavedEstimate: number;
|
|
753
|
+
trigger: "frontier" | "refusal" | "blocking";
|
|
754
|
+
ts: number;
|
|
755
|
+
} | {
|
|
756
|
+
/** design/374 (G11) — the MC-R rejection arm ran and DECLINED to clear: `reason` says why
|
|
757
|
+
* (`"no_candidates"` = nothing clearable beyond the keep window on the rejected projection;
|
|
758
|
+
* `"below_min_savings"` = clearable but under the 20k gate), `nextArm` names where the
|
|
759
|
+
* recovery chain goes instead (`"forced_compaction"` when the compaction arm is available,
|
|
760
|
+
* `"none"` when it is disabled/tripped — the turn will surface the provider error). Emitted
|
|
761
|
+
* only on the refusal arm; the frontier pass declines silently every request by design. */
|
|
762
|
+
kind: "context.mc_null";
|
|
763
|
+
version: 1;
|
|
764
|
+
taskId: string;
|
|
765
|
+
reason: "no_candidates" | "below_min_savings";
|
|
766
|
+
nextArm: "forced_compaction" | "none";
|
|
767
|
+
ts: number;
|
|
732
768
|
} | {
|
|
733
769
|
/** C8 — the per-turn aggregate tool-result budget capped a result (offloaded to the store, or
|
|
734
770
|
* degraded to a self-contained truncation preview when the store failed/absent). */
|
package/dist/core/types.d.ts
CHANGED
|
@@ -353,7 +353,14 @@ export interface ToolSpec<TParams extends TSchema = TSchema> {
|
|
|
353
353
|
* - `"local"` — purely local reads/computation.
|
|
354
354
|
* Classification is single-sourced with the tool definition: core built-ins declare theirs here;
|
|
355
355
|
* a HOST tool that omits it is classified fail-closed as `"external"` (unknown = external) unless
|
|
356
|
-
* the deployment exempts the name via `TaskSpec.memory.trustedTools`.
|
|
356
|
+
* the deployment exempts the name via `TaskSpec.memory.trustedTools`. A declaration BEATS that
|
|
357
|
+
* allowlist (the allowlist is the channel for UNDECLARED tools, never a lever to re-grade a tool
|
|
358
|
+
* that classified itself), and only a member of the closed vocabulary counts as one — an unreadable
|
|
359
|
+
* value classifies `"external"` rather than exempting anything.
|
|
360
|
+
*
|
|
361
|
+
* Tools core mints on the deployment's behalf get the same seat one level up: an MCP server's whole
|
|
362
|
+
* tool set is declared at its entry ({@link McpServerSpec.contentOrigin}, design/378), because the
|
|
363
|
+
* host never touches those tool objects.
|
|
357
364
|
*/
|
|
358
365
|
contentOrigin?: ToolContentOrigin;
|
|
359
366
|
/**
|
|
@@ -1299,7 +1306,11 @@ export interface McpServerSpec {
|
|
|
1299
1306
|
*/
|
|
1300
1307
|
principalHeader?: string;
|
|
1301
1308
|
};
|
|
1302
|
-
/** Optional allowlist of tool names to expose (others are dropped).
|
|
1309
|
+
/** Optional allowlist of tool names to expose (others are dropped). ORTHOGONAL to
|
|
1310
|
+
* {@link contentOrigin} and usefully paired with it (design/378 §2): the class declaration follows
|
|
1311
|
+
* the server's roster, so a high-assurance deployment that wants a CLOSED tool set writes both —
|
|
1312
|
+
* a tool a later refresh adds then lands outside this list and is simply not mounted. Existing
|
|
1313
|
+
* semantics, no new mechanism; the refresh receipt still names every added tool either way. */
|
|
1303
1314
|
allowTools?: string[];
|
|
1304
1315
|
/**
|
|
1305
1316
|
* design/99 §E23 — opt in to INBOUND elicitation for THIS server: when `true` AND a {@link RunnerDeps.onElicit}
|
|
@@ -1316,12 +1327,66 @@ export interface McpServerSpec {
|
|
|
1316
1327
|
* override is AUTHORITATIVE and may LOWER an effect (vouch a tool is `read`/`idempotent`) as well as raise it
|
|
1317
1328
|
* (`egress` / irreversible). This is the ONLY trusted way to drop an MCP tool below the fail-closed `write`
|
|
1318
1329
|
* default (design F). Folds over the server hints in prepare-task (caller > server hint > fail-closed write).
|
|
1330
|
+
*
|
|
1331
|
+
* A DIFFERENT AXIS from {@link contentOrigin}: this one is about what a call DOES (repeat-safety,
|
|
1332
|
+
* blast radius, reversibility — it feeds the approval gate); the content class is about what a call
|
|
1333
|
+
* BRINGS BACK (memory-write governance, no gate/policy/roster effect). Neither implies the other —
|
|
1334
|
+
* a read-only tool can return third-party text, and a deployment's own writer brings back nothing
|
|
1335
|
+
* external — so vouching on one axis never quietly vouches on the other.
|
|
1319
1336
|
*/
|
|
1320
1337
|
toolAxes?: Record<string, {
|
|
1321
1338
|
effect?: ToolEffect;
|
|
1322
1339
|
egress?: boolean;
|
|
1323
1340
|
irreversibility?: "always" | "never";
|
|
1324
1341
|
}>;
|
|
1342
|
+
/**
|
|
1343
|
+
* design/378 — declare {@link ToolSpec.contentOrigin} on behalf of THIS SERVER'S ENTIRE TOOL SET
|
|
1344
|
+
* (tools a mid-task refresh adds included), with the same authority and the same responsibility a
|
|
1345
|
+
* directly-mounted host tool's own declaration carries.
|
|
1346
|
+
*
|
|
1347
|
+
* WHY THE SEAT EXISTS. Without it the class keys on the MOUNT SHAPE rather than on lineage: a tool
|
|
1348
|
+
* the deployment wrote and runs itself is structurally `"external"` the moment it arrives over the
|
|
1349
|
+
* MCP protocol namespace, so every call marks the session's memory externally exposed. The
|
|
1350
|
+
* per-name channels cannot express the fact either — {@link import("./memory.js").MemorySpecInput.trustedTools}
|
|
1351
|
+
* is a per-REQUEST allowlist keyed on the MINTED name (the host would have to predict the charset
|
|
1352
|
+
* normalization) whose own definition is "the exception channel for UNDECLARED tools", and it says
|
|
1353
|
+
* nothing about tools the server adds later.
|
|
1354
|
+
*
|
|
1355
|
+
* THIS IS A TRUST DECLARATION, not a routing hint. Use it only for servers inside the deployment's
|
|
1356
|
+
* trust boundary — a process, socket or service the deployment itself runs. Declaring a THIRD-PARTY
|
|
1357
|
+
* server means treating its output as content the deployment wrote: the class names are
|
|
1358
|
+
* BOUNDARY-relative, never topological, so neither the transport kind nor the address is evidence
|
|
1359
|
+
* of lineage (a stdio child can be an untrusted package; a loopback URL can be your own service) and
|
|
1360
|
+
* core deliberately does not gate on either. The declaration covers THE PEER THIS ENTRY CONNECTS TO
|
|
1361
|
+
* — authenticating that peer (socket permissions, credentials) is the host's mounting duty.
|
|
1362
|
+
*
|
|
1363
|
+
* SEMANTICS. `"local"` ⇒ invocations no longer mark this session's memory; `"execution"` ⇒ the same,
|
|
1364
|
+
* except that {@link import("./memory.js").MemorySpecInput.execIsExternalContent} can still upgrade
|
|
1365
|
+
* the class for a strict deployment (which is why this seat takes the three-value vocabulary and not
|
|
1366
|
+
* a single "mine" flag — an execution-shaped tool mounted over MCP must stay inside that knob's
|
|
1367
|
+
* reach); `"external"` is an explicit PIN, and pins are not no-ops — a declaration beats the
|
|
1368
|
+
* `trustedTools` allowlist, so writing it forecloses the per-name exemption for this server's tools.
|
|
1369
|
+
* ABSENT ⇒ the pre-378 behavior byte for byte: the protocol namespace classifies the tools
|
|
1370
|
+
* `"external"` (fail-closed). A value outside the vocabulary is refused at the preparation door
|
|
1371
|
+
* (`config.mcp_content_class`), never folded to a class.
|
|
1372
|
+
*
|
|
1373
|
+
* COVERAGE, stated honestly: the class rides this server's own mounted tools. The cross-server
|
|
1374
|
+
* resource faces (ListMcpResourcesTool / ReadMcpResourceTool / ReadMcpResourceDirTool) aggregate
|
|
1375
|
+
* over every connected server in one call, so they stay `"external"` and still mark — over-marking,
|
|
1376
|
+
* the safe direction. A delegated child's pool is a separate static declaration surface
|
|
1377
|
+
* ({@link ToolSpec.agentToolPool} entries carry their own `contentOrigin`): a deployment handing
|
|
1378
|
+
* this server's tools to children mirrors the value there, and not mirroring it over-marks.
|
|
1379
|
+
*
|
|
1380
|
+
* TRUST SOURCE — a DEPLOYMENT-plane key. It redefines where the trust boundary runs, which puts it
|
|
1381
|
+
* on the same authority plane as {@link RunnerDeps} wiring, not on the request plane. Core sees one
|
|
1382
|
+
* `TaskSpec` and cannot tell a deployment-baseline entry from one a request supplied, so any
|
|
1383
|
+
* assembly layer that accepts REQUEST-side MCP entries must reject or strip this key from them:
|
|
1384
|
+
* "allowed to mount a server" is not "allowed to redefine the deployment's trust boundary", and a
|
|
1385
|
+
* caller-supplied `"local"` would otherwise be self-authorization around the session mark. A
|
|
1386
|
+
* single-tenant superuser surface (a host reading its own `--mcp-config` file) IS the deployment
|
|
1387
|
+
* plane and needs no such gate.
|
|
1388
|
+
*/
|
|
1389
|
+
contentOrigin?: ToolContentOrigin;
|
|
1325
1390
|
}
|
|
1326
1391
|
/**
|
|
1327
1392
|
* Definition of one A2A (agent-to-agent protocol) PEER to talk to for the duration of one task.
|
|
@@ -4830,6 +4895,11 @@ export interface ProjectMemoryLoad {
|
|
|
4830
4895
|
*/
|
|
4831
4896
|
export interface EngineNotice {
|
|
4832
4897
|
/** Stable machine-readable family, dot-namespaced. Current families:
|
|
4898
|
+
* - `"config.autocompact_window_clamped"` (design/374 slice 1b) — a model declared
|
|
4899
|
+
* `autoCompactTokens` ABOVE its physical window; the trigger-side geometry was clamped to the
|
|
4900
|
+
* physical window (an autocompact window only ever lowers the trigger) and the bad value is
|
|
4901
|
+
* announced once per prepared task; `detail: { modelId, declaredAutoCompactTokens,
|
|
4902
|
+
* physicalWindow, sessionId }`.
|
|
4833
4903
|
* - `"config.env_timeout_discarded"` — a Bash timeout knob (option or env) held a value that is not
|
|
4834
4904
|
* the value in force; `detail: { knob, raw, usedMs }`.
|
|
4835
4905
|
* - `"config.materialize_env_discarded"` — `SEMA_TOOL_MATERIALIZE_STRATEGY` held a value outside the
|
|
@@ -4997,6 +5067,27 @@ export interface EngineNotice {
|
|
|
4997
5067
|
* `detail: { reason, subagentType?, sessionId? }` — `reason` is the same sentence the waived
|
|
4998
5068
|
* mark would have carried, neutralized/length-bounded (tool and agent-type names are
|
|
4999
5069
|
* host/model-controlled inputs).
|
|
5070
|
+
* - `"memory.content_class_declared"` (design/378) — an `McpServerSpec` entry carries an explicit
|
|
5071
|
+
* {@link McpServerSpec.contentOrigin}: the deployment declared this server's whole tool set to
|
|
5072
|
+
* be inside (or explicitly outside) its trust boundary, so the protocol namespace's structural
|
|
5073
|
+
* `"external"` no longer decides. The AUDIT line for a trust boundary an operator drew by
|
|
5074
|
+
* configuration — which is why it announces on the explicit `"external"` value too: pinning is
|
|
5075
|
+
* not a no-op (a declaration beats the `trustedTools` allowlist, so it forecloses the per-name
|
|
5076
|
+
* exemption for this server's tools). One line per DECLARED ENTRY per prepared task leg, minted
|
|
5077
|
+
* once at preparation and never per call (the declaration's whole effect is that calls stop
|
|
5078
|
+
* marking; narrating each call would trade the saved mark for equal noise). Armed on the SAME
|
|
5079
|
+
* condition as the classification it talks about — a leg with no engine-memory session and no
|
|
5080
|
+
* provenance recorder classifies nothing, so there is no posture to report and no line is
|
|
5081
|
+
* minted. `detail: { server,
|
|
5082
|
+
* contentOrigin, toolCount, execIsExternalContent?, sessionId? }` — `server` is the
|
|
5083
|
+
* host-authored entry name, neutralized/length-bounded; `toolCount` is what this entry actually
|
|
5084
|
+
* mounted (0 for a server that failed to connect — the declaration still stands and is still
|
|
5085
|
+
* disclosed); `execIsExternalContent` rides the `"execution"` value only and reports the strict
|
|
5086
|
+
* knob AS RESOLVED FOR THIS RUN, because that arm's posture depends on it. The message's closing
|
|
5087
|
+
* clause is written per value so it can never describe a posture the run does not have. The
|
|
5088
|
+
* entry's TRANSPORT is deliberately absent: it is a claim about a mutable host-owned object made
|
|
5089
|
+
* long after the dial, and D-10 already rules topology is not evidence of the lineage this line
|
|
5090
|
+
* audits — connection facts live on `MaterializedMcp.statuses` instead.
|
|
5000
5091
|
*
|
|
5001
5092
|
* - `"memory.consolidation_incomplete"` (design/376, LLM consolidation driver) — a driver run
|
|
5002
5093
|
* settled without reaching the fixpoint: `detail` names the stop reason (the closed
|
|
@@ -6070,6 +6161,40 @@ export interface RunnerDeps {
|
|
|
6070
6161
|
* ~20000; set `0` or `Infinity` to disable offloading. Per-tool override via `ToolSpec.offloadThresholdChars`.
|
|
6071
6162
|
*/
|
|
6072
6163
|
toolResultThresholdChars?: number;
|
|
6164
|
+
/**
|
|
6165
|
+
* design/374 — microCompact machine-alignment knobs (EXPERIMENTAL until the slice-3 default
|
|
6166
|
+
* flip; both default OFF so a deployment that never touches this bag runs the pre-374 machine
|
|
6167
|
+
* byte for byte).
|
|
6168
|
+
*
|
|
6169
|
+
* - `machine`: which stale-tool-result clearing machine the request pipeline runs —
|
|
6170
|
+
* `"legacy"` (default; the historical keep-3 / clear-to-budget machine) or `"cc"` (the CC
|
|
6171
|
+
* 2.1.223 rejection-leg form: keep 5, ≥20k minimum-savings gate, one deep clear beyond the
|
|
6172
|
+
* keep window, CC marker bytes). ⚠️ Read the P-form warning on
|
|
6173
|
+
* {@link import("./context-edit.js").ContextEditMachine} before selecting `"cc"`: until the
|
|
6174
|
+
* slice-3 fallback re-ordering ships, the 20k gate sits in front of the only reduction while
|
|
6175
|
+
* the message-dropping guard trim still backstops — opting in is accepting that trade.
|
|
6176
|
+
* - `clearOnRejection` (MC-R, slice 2): on a provider input-too-long rejection, run ONE cheap
|
|
6177
|
+
* deterministic clear over the rejected projection (same cc machine, savings ≥20k or nothing)
|
|
6178
|
+
* and retry inside the turn BEFORE the forced-compaction recovery. Default false (X2: the
|
|
6179
|
+
* machinery lands dark; the default flips together with the machine in slice 3). Independent
|
|
6180
|
+
* of `machine` — an enabled MC-R always clears in the cc form (the rejection arm has no
|
|
6181
|
+
* budget coordinate for the legacy incremental form to stop at). BUDGET ACCOUNTING (design/374
|
|
6182
|
+
* §3.2.1, stated here because it is otherwise invisible to a deployment): a successful MC-R
|
|
6183
|
+
* clear-and-retry SPENDS one attempt of the shared prompt-too-long recovery budget (default 2
|
|
6184
|
+
* attempts per chain), so a chain that clears and is rejected AGAIN has one forced-compaction
|
|
6185
|
+
* attempt left where the knob-off chain nominally had two — the trade costs no effective
|
|
6186
|
+
* compaction pass, because the second forced-compaction call of the off chain is structurally
|
|
6187
|
+
* a no-op whenever the first one landed (the branch leaf is already a compaction entry).
|
|
6188
|
+
*
|
|
6189
|
+
* A declaration outside the closed vocabulary (a `machine` string not in the union, a
|
|
6190
|
+
* non-boolean `clearOnRejection` — JSON/env-derived config the type cannot guard) refuses the
|
|
6191
|
+
* whole prepare loudly (`code: "config.microcompact_invalid"`, no silent re-default): folding it
|
|
6192
|
+
* would run the pre-374 machine while the deployment believes it opted in.
|
|
6193
|
+
*/
|
|
6194
|
+
microCompact?: {
|
|
6195
|
+
machine?: "legacy" | "cc";
|
|
6196
|
+
clearOnRejection?: boolean;
|
|
6197
|
+
};
|
|
6073
6198
|
/**
|
|
6074
6199
|
* Two-phase prefix-cache-break detection (design/31): per turn, fingerprint the prefix and, on a
|
|
6075
6200
|
* confirmed `cacheRead` drop, emit a root-cause finding via `onError(phase:"prompt-cache")`. Cheap
|