@sema-agent/core 7.35.1 → 7.36.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 +85 -0
- package/dist/brain/anthropic.js +6 -6
- package/dist/brain/errors.d.ts +1 -14
- package/dist/brain/errors.js +2 -6
- package/dist/brain/open-responses.js +7 -7
- package/dist/brain/openai.js +7 -7
- package/dist/brain/provider-echo-shape.d.ts +26 -0
- package/dist/brain/provider-echo-shape.js +12 -0
- package/dist/brain/stream-engine.d.ts +2 -1
- package/dist/brain/stream-engine.js +28 -14
- package/dist/brain/tool-call-id.d.ts +27 -15
- package/dist/brain/tool-call-id.js +43 -0
- package/dist/core/ask-question.d.ts +0 -30
- package/dist/core/ask-question.js +3 -2
- package/dist/core/checkpoint-store.d.ts +5 -3
- package/dist/core/checkpoint-store.js +2 -1
- package/dist/core/context-edit.d.ts +4 -0
- package/dist/core/context-edit.js +5 -2
- package/dist/core/engine-notice.d.ts +7 -0
- package/dist/core/gate-lanes.js +12 -10
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +2 -0
- package/dist/core/hooks.d.ts +3 -0
- package/dist/core/hooks.js +4 -2
- package/dist/core/mode-policy.js +2 -1
- package/dist/core/permission-rule-model.d.ts +2 -2
- package/dist/core/permission-rule-model.js +2 -2
- package/dist/core/permission-rules.js +2 -5
- package/dist/core/runner/active-skill-scope.js +5 -5
- package/dist/core/runner/decide-continuation.js +2 -1
- package/dist/core/runner/permission-rule-lanes.d.ts +3 -0
- package/dist/core/runner/permission-rule-lanes.js +7 -6
- package/dist/core/runner/prepare-context-lane.d.ts +4 -0
- package/dist/core/runner/prepare-context-lane.js +7 -5
- package/dist/core/runner/prepare-gate-stations.js +17 -12
- package/dist/core/runner/prepare-park-ask.d.ts +2 -0
- package/dist/core/runner/prepare-park-ask.js +7 -6
- package/dist/core/runner/prepare-policy-chain.d.ts +2 -0
- package/dist/core/runner/prepare-policy-chain.js +10 -9
- package/dist/core/runner/prepare-safety-scan.js +9 -2
- package/dist/core/runner/prepare-task.js +3 -3
- package/dist/core/runner/resume-apply.js +2 -1
- package/dist/core/runner/run-attachment-seats.js +3 -1
- package/dist/core/runner/run-harness-handlers.js +6 -1
- package/dist/core/runner/run-leg.js +11 -9
- package/dist/core/runner/run-recovery-lanes.js +2 -0
- package/dist/core/runner/session-rule-policy.js +5 -4
- package/dist/core/runner/tool-call-id-fold-seat.d.ts +13 -0
- package/dist/core/runner/tool-call-id-fold-seat.js +31 -0
- package/dist/core/sensitive-path-policy.js +2 -2
- package/dist/core/side-query.d.ts +5 -1
- package/dist/core/side-query.js +5 -1
- package/dist/core/task-event.d.ts +2 -1
- package/dist/core/tool-name-readings.d.ts +366 -0
- package/dist/core/tool-name-readings.js +136 -0
- package/dist/core/tool-policy.d.ts +5 -5
- package/dist/core/tool-policy.js +15 -13
- package/dist/core/tool-registry.d.ts +1 -31
- package/dist/core/tool-registry.js +2 -22
- package/dist/core/tool-result-budget.d.ts +3 -0
- package/dist/core/tool-result-budget.js +5 -3
- package/dist/core/tool-roster.d.ts +30 -9
- package/dist/core/tool-roster.js +45 -5
- package/dist/engine/llm/types.d.ts +4 -0
- package/dist/tools/fs/file-read-check.d.ts +1 -1
- package/dist/tools/fs/file-read-check.js +4 -3
- package/dist/tools/fs/shell-read-check.d.ts +15 -1
- package/dist/tools/fs/shell-read-check.js +5 -2
- package/package.json +2 -1
- package/wire-changes/core@7.35.2.json +61 -0
- package/wire-changes/core@7.36.0.json +288 -0
|
@@ -5,7 +5,7 @@ import { defineTool, errorResult } from "./tools.js";
|
|
|
5
5
|
import { delimitUntrusted, inlineUntrusted } from "./untrusted-text.js";
|
|
6
6
|
import { toolFace } from "./tool-catalog-entries.js";
|
|
7
7
|
import { thrownText } from "./thrown-value.js";
|
|
8
|
-
import {
|
|
8
|
+
import { heldBy } from "./tool-name-readings.js";
|
|
9
9
|
export const ASK_USER_QUESTION_TOOL_NAME = "AskUserQuestion";
|
|
10
10
|
export function classifyQuestionOutcome(outcome) {
|
|
11
11
|
let unavailable;
|
|
@@ -132,10 +132,11 @@ export function validateAskQuestions(questions) {
|
|
|
132
132
|
}
|
|
133
133
|
return undefined;
|
|
134
134
|
}
|
|
135
|
+
const QUESTION_TOOL_NAMES = new Set([ASK_USER_QUESTION_TOOL_NAME]);
|
|
135
136
|
export function createDurableQuestionPolicy() {
|
|
136
137
|
return {
|
|
137
138
|
check(req) {
|
|
138
|
-
if (!
|
|
139
|
+
if (!heldBy("policy.durableQuestion", QUESTION_TOOL_NAMES, req)) {
|
|
139
140
|
return { action: "allow" };
|
|
140
141
|
}
|
|
141
142
|
return { action: "ask", message: "AskUserQuestion: awaiting a human answer (durable)" };
|
|
@@ -868,9 +868,11 @@ export type PendingAction = {
|
|
|
868
868
|
*/
|
|
869
869
|
family?: import("./tool-face.js").ToolFamily;
|
|
870
870
|
pathTarget?: import("./tool-face.js").ToolRosterPathTarget;
|
|
871
|
-
/** The names
|
|
872
|
-
*
|
|
873
|
-
*
|
|
871
|
+
/** The names the parked call answered to, copied from the face the gate judged it under (`ToolCallFace.names`: the
|
|
872
|
+
* model's spelling first, then the wire name and the other aliases that resolve to it; a row minted before the
|
|
873
|
+
* spelling led carries the wire name first) — the pre-CAS recheck judges an edit against rules written on any of
|
|
874
|
+
* them, as the live gate judged the original, and the resume re-mints its face under the spelling while it still
|
|
875
|
+
* resolves to the row's tool. Absent on a row minted before it ⇒ the row's `toolName` alone. */
|
|
874
876
|
names?: readonly string[];
|
|
875
877
|
/**
|
|
876
878
|
* #438 (additive; no checkpoint-version bump — the `reminderMark` precedent: an optional field an
|
|
@@ -9,6 +9,7 @@ import { renderUntrustedCommandText, stripFormatCharacters } from "./permission-
|
|
|
9
9
|
import { redactSecrets } from "./untrusted-egress.js";
|
|
10
10
|
import { ASK_USER_QUESTION_TOOL_NAME } from "./ask-question.js";
|
|
11
11
|
import { isPermissionMode } from "./ask-origin.js";
|
|
12
|
+
import { wireOf } from "./tool-name-readings.js";
|
|
12
13
|
export function mintCheckpointToken() {
|
|
13
14
|
return randomBytes(16).toString("hex");
|
|
14
15
|
}
|
|
@@ -290,7 +291,7 @@ export function summarizeCheckpoint(cp) {
|
|
|
290
291
|
...(cp.sourceTaskId !== undefined ? { sourceTaskId: cp.sourceTaskId } : {}),
|
|
291
292
|
...(cp.principal !== undefined ? { principal: cp.principal } : {}),
|
|
292
293
|
...(tool !== undefined ? { toolCallId: tool.toolCallId, toolName: tool.toolName } : {}),
|
|
293
|
-
...(tool
|
|
294
|
+
...(tool !== undefined && wireOf("park.contentKindProjection", tool) === ASK_USER_QUESTION_TOOL_NAME ? { contentKind: "content_ask" } : {}),
|
|
294
295
|
...(toolInput !== undefined ? { toolInput } : {}),
|
|
295
296
|
...(cp.state.workspaceHandle !== undefined
|
|
296
297
|
? { restoreMode: cp.state.workspaceHandle.restoreMode === "park_only" ? "park_only" : "snapshot" }
|
|
@@ -171,6 +171,9 @@ export interface ContextEditOptions {
|
|
|
171
171
|
* registry state are compaction's job, not a blanking marker's.
|
|
172
172
|
*/
|
|
173
173
|
compactableTools?: ReadonlySet<string>;
|
|
174
|
+
/** The wire name a result's recorded tool name resolves to on the leg's roster (a result is clearable by the tool the
|
|
175
|
+
* call ran, not the spelling the model used). Absent ⇒ the recorded name. */
|
|
176
|
+
toolNameOf?: (recorded: string) => string;
|
|
174
177
|
/**
|
|
175
178
|
* design/374 (RB-212 durable half): the PRE-CAP counterpart array of `messages`, position-aligned
|
|
176
179
|
* (the pipeline's aggregate caps replace in place, never drop). When present, the machine
|
|
@@ -316,6 +319,7 @@ export declare function planRejectionClears(messages: AgentMessage[], opts: {
|
|
|
316
319
|
keyOf: OccurrenceIndex["keyOf"];
|
|
317
320
|
offload?: ContextEditOptions["offload"];
|
|
318
321
|
compactableTools?: ReadonlySet<string>;
|
|
322
|
+
toolNameOf?: (recorded: string) => string;
|
|
319
323
|
keepRecentToolResults?: number;
|
|
320
324
|
}): RejectionClearPlan;
|
|
321
325
|
/**
|
|
@@ -5,6 +5,7 @@ import { physicalWindowOf } from "./context-settings.js";
|
|
|
5
5
|
import { PERSISTED_OUTPUT_PREFIX, offloadPagebackHint } from "./tool-result-store.js";
|
|
6
6
|
import { assertNoRetiredKeys } from "./retired-keys.js";
|
|
7
7
|
import { compactableToolNames } from "./tool-registry.js";
|
|
8
|
+
import { wireOf } from "./tool-name-readings.js";
|
|
8
9
|
const CC_CLEARED_MARKER = "[Old tool result content cleared]";
|
|
9
10
|
const CC_CLEARED_EXTENDED_PREFIX = "[Old tool result content cleared —";
|
|
10
11
|
const refNote = (ref) => offloadPagebackHint(ref, "cleared");
|
|
@@ -47,6 +48,7 @@ function resolveKeepRecentToolResults(value, defaultValue) {
|
|
|
47
48
|
return Math.max(MIN_KEEP_RECENT_TOOL_RESULTS, value);
|
|
48
49
|
}
|
|
49
50
|
const COMPACTABLE_TOOLS = compactableToolNames();
|
|
51
|
+
const compactableResult = (compactable, recorded, toolNameOf) => compactable.has(wireOf("compaction.compactableResult", { toolName: toolNameOf !== undefined ? toolNameOf(recorded) : recorded }));
|
|
50
52
|
function soleMarkerText(m) {
|
|
51
53
|
const content = m.content;
|
|
52
54
|
if (!Array.isArray(content) || content.length !== 1)
|
|
@@ -127,6 +129,7 @@ const CONTEXT_EDIT_OPTION_KEYS = new Set([
|
|
|
127
129
|
"anchoredTotalTokens",
|
|
128
130
|
"charsPerToken",
|
|
129
131
|
"compactableTools",
|
|
132
|
+
"toolNameOf",
|
|
130
133
|
"clearSource",
|
|
131
134
|
]);
|
|
132
135
|
const RETIRED_CONTEXT_EDIT_OPTION_KEYS = {
|
|
@@ -159,7 +162,7 @@ export function clearStaleToolResults(messages, opts) {
|
|
|
159
162
|
}
|
|
160
163
|
const keep = resolveKeepRecentToolResults(opts.keepRecentToolResults, CC_DEFAULT_KEEP_RECENT_TOOL_RESULTS);
|
|
161
164
|
const compactable = opts.compactableTools ?? COMPACTABLE_TOOLS;
|
|
162
|
-
const candidates = messages.flatMap((m, i) => isToolResult(m) && compactable
|
|
165
|
+
const candidates = messages.flatMap((m, i) => isToolResult(m) && compactableResult(compactable, m.toolName, opts.toolNameOf) ? [{ idx: i, target: m }] : []);
|
|
163
166
|
const beyondKeep = candidates.slice(0, Math.max(0, candidates.length - keep));
|
|
164
167
|
const clearable = beyondKeep.filter(({ target }) => !isCleared(target));
|
|
165
168
|
if (clearable.length === 0) {
|
|
@@ -223,7 +226,7 @@ function occurrenceGroupId(m) {
|
|
|
223
226
|
export function planRejectionClears(messages, opts) {
|
|
224
227
|
const keep = resolveKeepRecentToolResults(opts.keepRecentToolResults, CC_DEFAULT_KEEP_RECENT_TOOL_RESULTS);
|
|
225
228
|
const compactable = opts.compactableTools ?? COMPACTABLE_TOOLS;
|
|
226
|
-
const candidates = messages.flatMap((m) => (isToolResult(m) && compactable
|
|
229
|
+
const candidates = messages.flatMap((m) => (isToolResult(m) && compactableResult(compactable, m.toolName, opts.toolNameOf) ? [m] : []));
|
|
227
230
|
const beyondKeep = candidates.slice(0, Math.max(0, candidates.length - keep));
|
|
228
231
|
const clearable = beyondKeep.flatMap((m) => {
|
|
229
232
|
if (isCleared(m))
|
|
@@ -251,6 +251,13 @@ export interface EngineNotice {
|
|
|
251
251
|
* (requested, reported) PAIR — a catalog fact, not a turn fact. Audience `operator`;
|
|
252
252
|
* `detail: { requested, reported, sessionId, runId }`. Absent ⇒ the provider echoed the requested id
|
|
253
253
|
* or named nothing — one reading, not two.
|
|
254
|
+
* - `"route.tool_call_id_folded"` (#1207) — the provider named a tool call with an id outside the shape the
|
|
255
|
+
* engine keys calls under (visible ASCII, 1–128, no `~`), so the brain keyed it under a deterministic fold
|
|
256
|
+
* (`ToolCall.id`) and keeps the provider's spelling beside it (`ToolCall.wireId`), handed back only to the
|
|
257
|
+
* provider. Advisory: the run is unchanged. ONCE PER RUN, on the first folded call — a gateway fact, not a
|
|
258
|
+
* call fact. Audience `operator`; `detail: { toolCallId, rawLength, reason, sessionId, runId }` —
|
|
259
|
+
* `toolCallId` the engine id, `reason` `"charset"` (a control byte, a space, a non-ASCII unit or `~`) or
|
|
260
|
+
* `"length"` (admitted characters, over 128); never the raw bytes. Absent ⇒ no call of the run was folded.
|
|
254
261
|
* - `"config.read_face_deployment_clamped"` (#237) — a deployment-wide `readFace: "open"` is not
|
|
255
262
|
* in force beside a read-only (verifier) mount: it clamps to "roots" without throwing
|
|
256
263
|
* (stricter-wins; the clamp verdict stands, only its occurrence was undisclosed). Announced
|
package/dist/core/gate-lanes.js
CHANGED
|
@@ -11,13 +11,15 @@ import { CD_GIT_COMPOUND_SENTENCE, changesDirectoryBeforeGit, readOnlyShellVerdi
|
|
|
11
11
|
import { readShellCommand } from "./shell-lexer.js";
|
|
12
12
|
import { COMMAND_RULE_TOOL } from "./permission-rule-model.js";
|
|
13
13
|
import { catalogRuleFaceOf } from "./tool-registry.js";
|
|
14
|
-
import {
|
|
14
|
+
import { callFaceSeat } from "./tool-roster.js";
|
|
15
|
+
import { heldBy, wireOf } from "./tool-name-readings.js";
|
|
15
16
|
import { modeAcceptsFileCommand } from "./mode-policy.js";
|
|
16
17
|
import { ORG_ADJUDICATION_TIMEOUT_MS, ORG_RULE_DECISION_REASON, ORG_UNAVAILABLE_DECISION_REASON, settleOrgVerdictWithin } from "./permission-rule-org.js";
|
|
17
18
|
import { exitGate } from "./runner/gate-exit.js";
|
|
18
19
|
import { traceHookCrash } from "./gate-crash-trace.js";
|
|
19
20
|
import { createGateDeadline, awaitWriteProtectionJudgment, withGateTimeout } from "./gate-bounded-await.js";
|
|
20
21
|
import { thrownText } from "./thrown-value.js";
|
|
22
|
+
const QUESTION_TOOL_NAMES = new Set([ASK_USER_QUESTION_TOOL_NAME]);
|
|
21
23
|
function isPlainOwnRecord(x) {
|
|
22
24
|
try {
|
|
23
25
|
if (x === null || typeof x !== "object" || Array.isArray(x))
|
|
@@ -170,7 +172,7 @@ export async function runGateLanes(pass) {
|
|
|
170
172
|
writeProtectionUnresolved: pass.writeProtectionUnresolved === true,
|
|
171
173
|
});
|
|
172
174
|
const askOriginFacts = (org, ruleCallUnreadable) => ({
|
|
173
|
-
contentQuestion:
|
|
175
|
+
contentQuestion: heldBy("lanes.contentQuestionClass", QUESTION_TOOL_NAMES, pass.req),
|
|
174
176
|
markedUnresolvable: markedUnresolvable(),
|
|
175
177
|
org,
|
|
176
178
|
...(ruleCallUnreadable ? { ruleCallUnreadable } : {}),
|
|
@@ -195,7 +197,7 @@ export async function runGateLanes(pass) {
|
|
|
195
197
|
const applyOrgLayer = async (current, args) => {
|
|
196
198
|
if (input.orgRules === undefined)
|
|
197
199
|
return { decision: current, org: undefined };
|
|
198
|
-
if (
|
|
200
|
+
if (wireOf("lanes.orgContentAskExemption", pass.req) === ASK_USER_QUESTION_TOOL_NAME && input.orgRules.contentAskToolMounted === true)
|
|
199
201
|
return { decision: current, org: undefined };
|
|
200
202
|
let decided = current;
|
|
201
203
|
let pending;
|
|
@@ -271,7 +273,7 @@ export async function runGateLanes(pass) {
|
|
|
271
273
|
return await settleOrgVerdictWithin(pendingHit.then(normalizePersistedRuleHit).catch(() => ({ unreadable: true })), { unreadable: true }, { ...(input.abortSignal !== undefined ? { signal: input.abortSignal } : {}), timeoutMs: ORG_ADJUDICATION_TIMEOUT_MS });
|
|
272
274
|
};
|
|
273
275
|
const laneReq = { ...pass.req, ...(pass.policyRewrite !== undefined ? { args: pass.policyRewrite } : {}) };
|
|
274
|
-
const laneAnswer = input.persistedRules !== undefined && pass.req
|
|
276
|
+
const laneAnswer = input.persistedRules !== undefined && wireOf("lanes.persistedLaneSkip", pass.req) !== ASK_USER_QUESTION_TOOL_NAME ? await readPersistedRules(laneReq) : undefined;
|
|
275
277
|
{
|
|
276
278
|
const tightened = applyPersistedTightening(pass.decision, laneAnswer);
|
|
277
279
|
pass.decision = tightened.decision;
|
|
@@ -299,7 +301,7 @@ export async function runGateLanes(pass) {
|
|
|
299
301
|
pass.decision.action === "ask" &&
|
|
300
302
|
pass.decision.requiresRealApproval !== true &&
|
|
301
303
|
pass.decision.decisionReason !== "hook" &&
|
|
302
|
-
pass.req
|
|
304
|
+
wireOf("lanes.allowLayerQuestionSkip", pass.req) !== ASK_USER_QUESTION_TOOL_NAME &&
|
|
303
305
|
!markedUnresolvable();
|
|
304
306
|
if (input.persistedRules && allowLayerMayClear()) {
|
|
305
307
|
const answer = laneAnswer ?? {};
|
|
@@ -356,7 +358,7 @@ export async function runGateLanes(pass) {
|
|
|
356
358
|
classifyStanding(stamped, originFacts);
|
|
357
359
|
pass.decision = stampAskOrigin(stamped, originFacts);
|
|
358
360
|
}
|
|
359
|
-
if (allowLayerMayClear() && pass.decision.action === "ask" && ruleMayClear() && pass.probeBoundaryDeclined !== true && pass.req
|
|
361
|
+
if (allowLayerMayClear() && pass.decision.action === "ask" && ruleMayClear() && pass.probeBoundaryDeclined !== true && wireOf("lanes.readOnlyShellAllow", pass.req) === COMMAND_RULE_TOOL) {
|
|
360
362
|
const commandParam = catalogRuleFaceOf(COMMAND_RULE_TOOL)?.primaryParams[0] ?? "command";
|
|
361
363
|
const args = pass.req.args;
|
|
362
364
|
const command = typeof args === "object" && args !== null && !Array.isArray(args) ? args[commandParam] : undefined;
|
|
@@ -435,7 +437,7 @@ export async function runGateLanes(pass) {
|
|
|
435
437
|
}
|
|
436
438
|
if (input.sandboxAdmission !== undefined &&
|
|
437
439
|
pass.decision.action === "ask" &&
|
|
438
|
-
pass.req
|
|
440
|
+
wireOf("lanes.sandboxQuestionSkip", pass.req) !== ASK_USER_QUESTION_TOOL_NAME &&
|
|
439
441
|
input.isMarkedUnresolvable?.(input.event.toolCallId) !== true &&
|
|
440
442
|
pass.decision.requiresRealApproval !== true &&
|
|
441
443
|
pass.decision.decisionReason !== "hook" &&
|
|
@@ -463,7 +465,7 @@ export async function runGateLanes(pass) {
|
|
|
463
465
|
if (pass.decision.action === "ask" && input.permissionMode !== undefined && pass.askClassification !== undefined) {
|
|
464
466
|
const standing = pass.decision;
|
|
465
467
|
const mode = input.permissionMode();
|
|
466
|
-
const fileCommandByWord = modeAcceptsFileCommand(mode,
|
|
468
|
+
const fileCommandByWord = modeAcceptsFileCommand(mode, wireOf("lanes.modeFileCommand", pass.req), pass.policyRewrite ?? pass.currentInput);
|
|
467
469
|
const verdict = arbitrateByMode(mode, pass.askClassification, { ...(standing.origin !== undefined ? { origin: standing.origin } : {}), ...(standing.mandate !== undefined ? { mandate: standing.mandate } : {}) }, { fileCommand: pass.shellWritesPlaced === true && fileCommandByWord });
|
|
468
470
|
if (verdict.kind === "ask" && fileCommandByWord && pass.shellWritesPlaced !== true && pass.shellUnplacedOptions !== undefined && pass.askClassification.classes.size === 1 && pass.askClassification.classes.has("ordinary")) {
|
|
469
471
|
pass.decision = { ...standing, message: fileCommandOptionsSentence(pass.shellUnplacedOptions.program) };
|
|
@@ -528,7 +530,7 @@ export async function runGateLanes(pass) {
|
|
|
528
530
|
return { suspend: suspended, preToolContext: pass.preToolContext };
|
|
529
531
|
}
|
|
530
532
|
}
|
|
531
|
-
if (pass.decision.action === "ask" && pass.req
|
|
533
|
+
if (pass.decision.action === "ask" && wireOf("lanes.contentAskRoute", pass.req) === ASK_USER_QUESTION_TOOL_NAME) {
|
|
532
534
|
const outcome = input.resolveContentAsk !== undefined ? await (callSignal !== undefined ? input.resolveContentAsk(pass.req, callSignal) : input.resolveContentAsk(pass.req)) : { kind: "unavailable", parkDeclined: false };
|
|
533
535
|
if (outcome.kind === "answered") {
|
|
534
536
|
pass.decision = { action: "allow", updatedInput: outcome.presentedInput };
|
|
@@ -692,7 +694,7 @@ export async function runGateLanes(pass) {
|
|
|
692
694
|
break;
|
|
693
695
|
}
|
|
694
696
|
let editRuleCallUnreadable = false;
|
|
695
|
-
if (input.persistedRules !== undefined &&
|
|
697
|
+
if (input.persistedRules !== undefined && wireOf("lanes.persistedEditSkip", pass.req) !== ASK_USER_QUESTION_TOOL_NAME) {
|
|
696
698
|
const editLane = await readPersistedRules({ toolName, args: editArgs, toolCallId, ...(pass.req.face !== undefined ? { face: pass.req.face } : {}) });
|
|
697
699
|
editRuleCallUnreadable = ruleCallUnreadableOf(editLane);
|
|
698
700
|
const editTightened = applyPersistedTightening(recheck, editLane);
|
|
@@ -135,7 +135,7 @@ export type NoticeAudience = "user" | "operator";
|
|
|
135
135
|
* the union's catalog ({@link EngineNotice.code}). The groups below say only which family a row joins,
|
|
136
136
|
* so the catalog is not written a second time here and the two cannot drift.
|
|
137
137
|
*/
|
|
138
|
-
export declare const ENGINE_NOTICE_CODES: readonly ["config.autocompact_window_clamped", "config.env_timeout_discarded", "config.materialize_env_discarded", "config.models_swapped", "config.context_settings_swapped", "config.read_face_swapped", "config.read_face_deployment_clamped", "config.write_face_swapped", "config.write_face_deployment_clamped", "config.tool_model_gate_removed", "config.tool_model_gate_unknown_class", "config.tool_model_gate_env_invalid", "config.artifact_host_invalid", "config.task_root_not_canonical", "config.write_protection_target_view_absent", "config.write_protection_unresolved", "config.execution_env_capability_invalid", "config.tool_card_undeclared", "config.tool_face_undeclared", "config.tool_face_invalid", "config.forward_sink_failed", "config.durable_gate_unavailable", "config.peer_admission_out_of_range", "config.peer_lane_unmounted", "config.secret_env_scrubbed", "config.permission_mode_unavailable", "permission.ask_released_by_mode", "permission.read_deny_unapplied", "permission.rule_store_unavailable", "resume.legacy_mode_seed_migrated", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "classifier.fallback", "classifier.role_defaulted", "classifier.contract_broken", "classifier.unavailable", "checkpoint.execution_outcome_unrecorded", "delegation.transcript_integrity", "delegation.ask_unresolvable", "mcp.injection_dropped", "mcp.revocation_probe_failed", "mcp.server_redialed", "workflow.governance_key_stripped", "workflow.agent_option_ignored", "memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.content_class_declared", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "memory.consolidation_recommended", "memory.consolidation_committed", "memory.consolidation_conflict", "memory.consolidation_incomplete", "memory.consolidation_refused", "memory.consolidation_withheld", "memory.restore_withheld", "memory.project_scope_absent", "route.fallback_to_primary", "route.classifier_unroutable", "route.base_url_changed_key_unchanged", "route.response_model_mismatch", "task.user_steer_undrained", "task.user_followup_undrained", "steering.parked_input_blocked", "task.turn_interrupted", "task.halt_unconsumed", "task.late_approval", "task.interrupt_unconsumed", "approval.read_root_granted", "approval.read_root_grant_rejected", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "skills.listing_truncated", "agents.listing_truncated", "tool_result.offload_put_failed"];
|
|
138
|
+
export declare const ENGINE_NOTICE_CODES: readonly ["config.autocompact_window_clamped", "config.env_timeout_discarded", "config.materialize_env_discarded", "config.models_swapped", "config.context_settings_swapped", "config.read_face_swapped", "config.read_face_deployment_clamped", "config.write_face_swapped", "config.write_face_deployment_clamped", "config.tool_model_gate_removed", "config.tool_model_gate_unknown_class", "config.tool_model_gate_env_invalid", "config.artifact_host_invalid", "config.task_root_not_canonical", "config.write_protection_target_view_absent", "config.write_protection_unresolved", "config.execution_env_capability_invalid", "config.tool_card_undeclared", "config.tool_face_undeclared", "config.tool_face_invalid", "config.forward_sink_failed", "config.durable_gate_unavailable", "config.peer_admission_out_of_range", "config.peer_lane_unmounted", "config.secret_env_scrubbed", "config.permission_mode_unavailable", "permission.ask_released_by_mode", "permission.read_deny_unapplied", "permission.rule_store_unavailable", "resume.legacy_mode_seed_migrated", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "classifier.fallback", "classifier.role_defaulted", "classifier.contract_broken", "classifier.unavailable", "checkpoint.execution_outcome_unrecorded", "delegation.transcript_integrity", "delegation.ask_unresolvable", "mcp.injection_dropped", "mcp.revocation_probe_failed", "mcp.server_redialed", "workflow.governance_key_stripped", "workflow.agent_option_ignored", "memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.content_class_declared", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "memory.consolidation_recommended", "memory.consolidation_committed", "memory.consolidation_conflict", "memory.consolidation_incomplete", "memory.consolidation_refused", "memory.consolidation_withheld", "memory.restore_withheld", "memory.project_scope_absent", "route.fallback_to_primary", "route.classifier_unroutable", "route.base_url_changed_key_unchanged", "route.response_model_mismatch", "route.tool_call_id_folded", "task.user_steer_undrained", "task.user_followup_undrained", "steering.parked_input_blocked", "task.turn_interrupted", "task.halt_unconsumed", "task.late_approval", "task.interrupt_unconsumed", "approval.read_root_granted", "approval.read_root_grant_rejected", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "skills.listing_truncated", "agents.listing_truncated", "tool_result.offload_put_failed"];
|
|
139
139
|
/** A code this engine mints (see {@link ENGINE_NOTICE_CODES}). NOT the type of
|
|
140
140
|
* `EngineNotice.code`, which stays `string` — a host forwarding its own notices through the same
|
|
141
141
|
* sink is a supported shape, and narrowing that field would break it. */
|
|
@@ -178,6 +178,7 @@ export const ENGINE_NOTICE_CODES = [
|
|
|
178
178
|
"route.classifier_unroutable",
|
|
179
179
|
"route.base_url_changed_key_unchanged",
|
|
180
180
|
"route.response_model_mismatch",
|
|
181
|
+
"route.tool_call_id_folded",
|
|
181
182
|
"task.user_steer_undrained",
|
|
182
183
|
"task.user_followup_undrained",
|
|
183
184
|
"steering.parked_input_blocked",
|
|
@@ -269,6 +270,7 @@ const NOTICE_AUDIENCE_TABLE = {
|
|
|
269
270
|
"route.classifier_unroutable": "operator",
|
|
270
271
|
"route.base_url_changed_key_unchanged": "operator",
|
|
271
272
|
"route.response_model_mismatch": "operator",
|
|
273
|
+
"route.tool_call_id_folded": "operator",
|
|
272
274
|
"skills.listing_truncated": "operator",
|
|
273
275
|
"agents.listing_truncated": "operator",
|
|
274
276
|
"tool_result.offload_put_failed": "operator",
|
package/dist/core/hooks.d.ts
CHANGED
|
@@ -474,6 +474,9 @@ export interface PostToolUseFailureResult {
|
|
|
474
474
|
/** One resolved call in a postToolBatch payload (CC PostToolBatchToolCall shape, thin projection —
|
|
475
475
|
* `response` is a bounded text digest of the result, never the full details object). */
|
|
476
476
|
export interface PostToolBatchCall {
|
|
477
|
+
/** The name the model spelled on the tool_use block (an alias stays an alias) — the reference's PostToolBatch payload
|
|
478
|
+
* names each block the same way, while the per-call post hooks receive the resolved tool's wire name. Correlate
|
|
479
|
+
* the two faces by `toolCallId`. */
|
|
477
480
|
toolName: string;
|
|
478
481
|
input: unknown;
|
|
479
482
|
toolCallId: string;
|
package/dist/core/hooks.js
CHANGED
|
@@ -11,6 +11,7 @@ import { isAskOrigin } from "./ask-origin.js";
|
|
|
11
11
|
import { inlineUntrusted } from "./untrusted-text.js";
|
|
12
12
|
export { formatHookFeedback } from "./reminder-mint.js";
|
|
13
13
|
import { createSafeNotifier } from "./safe-notify.js";
|
|
14
|
+
import { wireOf } from "./tool-name-readings.js";
|
|
14
15
|
export const DEFAULT_HOOK_TIMEOUT_MS = 600_000;
|
|
15
16
|
export const MAX_HOOK_TIMEOUT_MS = 2_147_483_647;
|
|
16
17
|
const POST_ABORT_GRACE_MS = 1_000;
|
|
@@ -247,9 +248,10 @@ export async function runToolGate(input) {
|
|
|
247
248
|
const { event, preToolUse, adjudicate, resolveAsk, suspendAsk } = input;
|
|
248
249
|
const { toolCallId, toolName } = event;
|
|
249
250
|
const callSignal = input.callSignal;
|
|
251
|
+
const screenedName = wireOf("gate.preToolUse", event);
|
|
250
252
|
const hookCtx = (seatSignal) => ({
|
|
251
253
|
toolCallId,
|
|
252
|
-
toolName,
|
|
254
|
+
toolName: screenedName,
|
|
253
255
|
...(seatSignal !== undefined ? { signal: seatSignal } : {}),
|
|
254
256
|
...(() => {
|
|
255
257
|
const c = input.trackedCwd?.();
|
|
@@ -284,7 +286,7 @@ export async function runToolGate(input) {
|
|
|
284
286
|
};
|
|
285
287
|
const screening = preToolUse !== undefined
|
|
286
288
|
? {
|
|
287
|
-
screen: (args) => runHookSeat("preToolUse", seatBound(true), (sig) => preToolUse(
|
|
289
|
+
screen: (args) => runHookSeat("preToolUse", seatBound(true), (sig) => preToolUse(screenedName, args, hookCtx(sig))),
|
|
288
290
|
screened: screenPreToolUseResult,
|
|
289
291
|
crashReason: preToolUseCrashReason,
|
|
290
292
|
unansweredReason: preToolUseUnansweredBlock,
|
package/dist/core/mode-policy.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createFsWriteGatePolicy } from "./fs-write-gate-policy.js";
|
|
2
2
|
import { isProtectedWrite } from "./tool-registry.js";
|
|
3
3
|
import { rosterEntryNamed } from "./tool-roster.js";
|
|
4
|
+
import { wireOf } from "./tool-name-readings.js";
|
|
4
5
|
import { clampPermissionMode } from "./ask-origin.js";
|
|
5
6
|
import { argvOf, lexShellText } from "../shell/lex.js";
|
|
6
7
|
import { COMMAND_RULE_TOOL } from "./permission-rule-model.js";
|
|
@@ -42,7 +43,7 @@ const ENGINE_ROSTER_SOURCES = new Set(["builtin", "synthetic"]);
|
|
|
42
43
|
export function planRefusesWrite(req, roster, readOnlyDeclared) {
|
|
43
44
|
if (isProtectedWrite(req))
|
|
44
45
|
return true;
|
|
45
|
-
const row = roster === undefined ? undefined : rosterEntryNamed(roster, req
|
|
46
|
+
const row = roster === undefined ? undefined : rosterEntryNamed(roster, wireOf("mode.planRosterRow", req));
|
|
46
47
|
return row !== undefined && !ENGINE_ROSTER_SOURCES.has(row.source) && row.effect !== "read" && !(row.effectProvenance === "default" && readOnlyDeclared?.(row.name) === true);
|
|
47
48
|
}
|
|
48
49
|
const planAsk = (message) => ({ action: "ask", message, decisionReason: "mode" });
|
|
@@ -141,8 +141,8 @@ export declare const READ_RULE_TOOL = "Read";
|
|
|
141
141
|
* catalog's declarations, exactly as the protective fences' covered set does.
|
|
142
142
|
*/
|
|
143
143
|
export declare function ruleToolGrammarOf(toolName: string): "command" | "path" | undefined;
|
|
144
|
-
/** The names the persisted grammar judges a call under: every
|
|
145
|
-
* name,
|
|
144
|
+
/** The names the persisted grammar judges a call under: every name the call answers to (the model's spelling, the wire
|
|
145
|
+
* name, the other aliases that resolve to it) the grammar speaks for, so a stored `Bash(…)` or `Edit(…)` row reaches a
|
|
146
146
|
* tool that answers to that name under another wire name, as a policy's rule does. Empty ⇒ the grammar speaks for none. */
|
|
147
147
|
export declare function laneToolNamesOf(req: Pick<import("./tool-policy.js").ToolCallRequest, "toolName" | "face">): readonly string[];
|
|
148
148
|
/**
|
|
@@ -4,7 +4,7 @@ import { compileReadDeny } from "../tools/fs/read-deny.js";
|
|
|
4
4
|
import { carriesShellRedirection, parseLeadingCommandName, splitShellCompoundSegments } from "../tools/fs/bash-readonly-classifier.js";
|
|
5
5
|
import { inlineUntrusted } from "./untrusted-text.js";
|
|
6
6
|
import { pathTargetOf } from "./tool-registry.js";
|
|
7
|
-
import {
|
|
7
|
+
import { heldValues } from "./tool-name-readings.js";
|
|
8
8
|
import { carriesDotSegment, isAbsoluteForFamily, isIdentityDir, isPathIdentity, isShellRootedSpellingUnmapped, isUncIdentity, pathFamilyOf, pathIdentityOf } from "../tools/fs/safety.js";
|
|
9
9
|
import { readShellCommand } from "./shell-lexer.js";
|
|
10
10
|
import { readOnlyLineObjection, readOnlyShellVerdict } from "./read-only-shell.js";
|
|
@@ -32,7 +32,7 @@ export function ruleToolGrammarOf(toolName) {
|
|
|
32
32
|
return pathTargetOf(toolName) !== undefined ? "path" : undefined;
|
|
33
33
|
}
|
|
34
34
|
export function laneToolNamesOf(req) {
|
|
35
|
-
return
|
|
35
|
+
return heldValues("policy.persistedGrammar", { get: (n) => (ruleToolGrammarOf(n) !== undefined ? n : undefined) }, req);
|
|
36
36
|
}
|
|
37
37
|
export const MAX_RULE_TEXT_CHARS = 512;
|
|
38
38
|
export const BARE_INTERPRETER_NAMES = new Set([
|
|
@@ -6,7 +6,7 @@ import { pathFamilyOf, pathIdentityOf, spellingOfIdentity } from "../tools/fs/sa
|
|
|
6
6
|
import { effectivePathTargetOf } from "./effective-path-target.js";
|
|
7
7
|
import { protectivePathTargetOf } from "./tool-registry.js";
|
|
8
8
|
import { ruleDenialSentence } from "./rule-denial-sentence.js";
|
|
9
|
-
import {
|
|
9
|
+
import { heldValues } from "./tool-name-readings.js";
|
|
10
10
|
export { parsePermissionRule };
|
|
11
11
|
export const BASH_GENERIC_PARAMS = new Set(catalogRuleFaceOf("Bash")?.params ?? []);
|
|
12
12
|
const DEFAULT_CAPS = {
|
|
@@ -381,10 +381,7 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
381
381
|
requiresRealApproval: true,
|
|
382
382
|
});
|
|
383
383
|
const entryFor = (req) => {
|
|
384
|
-
const entries =
|
|
385
|
-
const e = byTool.get(n);
|
|
386
|
-
return e !== undefined ? [e] : [];
|
|
387
|
-
});
|
|
384
|
+
const entries = heldValues("policy.permissionRules", byTool, req);
|
|
388
385
|
if (entries.length <= 1)
|
|
389
386
|
return entries[0];
|
|
390
387
|
const bare = {};
|
|
@@ -3,7 +3,7 @@ import { pathTargetValue } from "../tool-registry.js";
|
|
|
3
3
|
import { pathTargetBaseOf } from "../effective-path-target.js";
|
|
4
4
|
import { PATH_WRITE_TOOLS, isWithin } from "./session-rule-policy.js";
|
|
5
5
|
import { parseSkillToolEntry, skillSpecifierRejection } from "../skill-tool-specifier.js";
|
|
6
|
-
import {
|
|
6
|
+
import { heldValues, wireOf } from "../tool-name-readings.js";
|
|
7
7
|
export class ActiveSkillScope {
|
|
8
8
|
frames = [];
|
|
9
9
|
push(frame) {
|
|
@@ -23,7 +23,7 @@ export function createActiveSkillScopePolicy(opts) {
|
|
|
23
23
|
const { scope, env, rootPath, toolEffects } = opts;
|
|
24
24
|
return {
|
|
25
25
|
async check(req, signal) {
|
|
26
|
-
const
|
|
26
|
+
const entriesFor = (byName) => heldValues("policy.skillScope", byName, req).flat();
|
|
27
27
|
const frames = scope.active();
|
|
28
28
|
if (frames.length === 0)
|
|
29
29
|
return { action: "allow" };
|
|
@@ -48,7 +48,7 @@ export function createActiveSkillScopePolicy(opts) {
|
|
|
48
48
|
}
|
|
49
49
|
return { manifest: m, byName };
|
|
50
50
|
});
|
|
51
|
-
if (perFrame.length === 0 || perFrame.some((f) =>
|
|
51
|
+
if (perFrame.length === 0 || perFrame.some((f) => entriesFor(f.byName).length === 0)) {
|
|
52
52
|
const ids = manifests.map((m) => m.lineageId).join(", ");
|
|
53
53
|
return {
|
|
54
54
|
action: "deny",
|
|
@@ -57,7 +57,7 @@ export function createActiveSkillScopePolicy(opts) {
|
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
59
|
for (const frame of perFrame) {
|
|
60
|
-
const alternatives =
|
|
60
|
+
const alternatives = entriesFor(frame.byName);
|
|
61
61
|
const rejections = [];
|
|
62
62
|
let admitted = false;
|
|
63
63
|
for (const entry of alternatives) {
|
|
@@ -80,7 +80,7 @@ export function createActiveSkillScopePolicy(opts) {
|
|
|
80
80
|
const declaredTarget = req.face?.pathTarget;
|
|
81
81
|
const skillScopeEligible = declaredTarget !== undefined && declaredTarget.skillScopeEligible === true;
|
|
82
82
|
if (pathConstrainingActive && !skillScopeEligible) {
|
|
83
|
-
const eff = toolEffects?.get(req
|
|
83
|
+
const eff = toolEffects?.get(wireOf("policy.skillScope.effect", req)) ?? "write";
|
|
84
84
|
if (eff !== "read") {
|
|
85
85
|
const ids = manifests.filter((m) => m.allowPaths?.length).map((m) => m.lineageId).join(", ");
|
|
86
86
|
return {
|
|
@@ -3,6 +3,7 @@ import { boundInputHashOf } from "../canonical-json.js";
|
|
|
3
3
|
import { CheckpointError } from "../checkpoint-store.js";
|
|
4
4
|
import { formatHookFeedback } from "../hooks.js";
|
|
5
5
|
import { delimitUntrusted, REVIEWER_NOTE_MAX_BODY } from "../untrusted-text.js";
|
|
6
|
+
import { wireOf } from "../tool-name-readings.js";
|
|
6
7
|
export function resumeDecisionWasNegative(resume) {
|
|
7
8
|
const o = resume.outcome;
|
|
8
9
|
return o.decision === "reject" || o.decision === "deny";
|
|
@@ -49,7 +50,7 @@ export function sameWinner(incoming, persisted) {
|
|
|
49
50
|
(persisted.reason === undefined || incoming.reason === persisted.reason));
|
|
50
51
|
}
|
|
51
52
|
export function pendingContentAskCallId(cp) {
|
|
52
|
-
return cp.pendingAction.kind === "tool_approval" && cp.pendingAction
|
|
53
|
+
return cp.pendingAction.kind === "tool_approval" && wireOf("resume.contentAskRow", cp.pendingAction) === ASK_USER_QUESTION_TOOL_NAME
|
|
53
54
|
? cp.pendingAction.toolCallId
|
|
54
55
|
: undefined;
|
|
55
56
|
}
|
|
@@ -241,6 +241,9 @@ export declare function createRuleOffersOf(cfg: {
|
|
|
241
241
|
shellGatedTools: {
|
|
242
242
|
has(name: string): boolean;
|
|
243
243
|
};
|
|
244
|
+
toolMarkSources: {
|
|
245
|
+
get(name: string): ReadonlySet<string> | undefined;
|
|
246
|
+
};
|
|
244
247
|
taskRoot: string | undefined;
|
|
245
248
|
/** The live tracked-cwd ref (read per call — the relative-cd resolution base). */
|
|
246
249
|
cwdRef: {
|
|
@@ -3,7 +3,8 @@ import {} from "../hooks.js";
|
|
|
3
3
|
import { adjudicatePersistedPathRules, adjudicatePersistedRules, laneToolNamesOf, ruleToolGrammarOf, segmentCoverageOf, suggestRulesForCommand } from "../permission-rule-model.js";
|
|
4
4
|
import { isAbsolutePathForm } from "../../tools/fs/safety.js";
|
|
5
5
|
import { effectivePathTargetOf } from "../effective-path-target.js";
|
|
6
|
-
import {
|
|
6
|
+
import { declaredPathTargetOf, protectivePathTargetOf } from "../tool-registry.js";
|
|
7
|
+
import { heldBy, seatOf, wireOf } from "../tool-name-readings.js";
|
|
7
8
|
import { orgRuleVerdictFor } from "../permission-rule-org.js";
|
|
8
9
|
import { ownerStoreOf } from "../permission-rule-provider.js";
|
|
9
10
|
import { thrownText } from "../thrown-value.js";
|
|
@@ -92,7 +93,7 @@ export function createPermissionRuleLanes(cfg) {
|
|
|
92
93
|
const org = provider.partitions.org
|
|
93
94
|
? {
|
|
94
95
|
adjudicate: async (req) => {
|
|
95
|
-
if (
|
|
96
|
+
if (wireOf("offer.orgQuestionExemption", req) === cfg.questionToolName && cfg.questionToolMounted)
|
|
96
97
|
return { status: "available" };
|
|
97
98
|
let resolution;
|
|
98
99
|
try {
|
|
@@ -158,16 +159,16 @@ export function createPermissionRuleLanes(cfg) {
|
|
|
158
159
|
}
|
|
159
160
|
export function createRuleOffersOf(cfg) {
|
|
160
161
|
return (call, args, ask) => {
|
|
161
|
-
const toolName = call
|
|
162
|
+
const toolName = wireOf("offer.laneTool", call);
|
|
162
163
|
if (!cfg.laneArmed || toolName !== PERSISTED_RULE_TOOL)
|
|
163
164
|
return {};
|
|
164
165
|
if ((cfg.principal === undefined || cfg.principal === "") && !cfg.localOwnerDeclared)
|
|
165
166
|
return {};
|
|
166
|
-
const tierSeat =
|
|
167
|
+
const tierSeat = seatOf("offer.marks", cfg.irreversibilityTier, call);
|
|
167
168
|
const closedDoor = ruleClearanceOf(ask, {
|
|
168
|
-
egress:
|
|
169
|
+
egress: heldBy("offer.marks.egress", cfg.egressTools, call),
|
|
169
170
|
irreversibility: tierSeat?.value,
|
|
170
|
-
shellGated: tierSeat
|
|
171
|
+
shellGated: tierSeat?.shellGated(cfg.shellGatedTools, cfg.toolMarkSources) === true,
|
|
171
172
|
}).word;
|
|
172
173
|
if (closedDoor !== undefined)
|
|
173
174
|
return { ruleOffersAbsence: closedDoor };
|
|
@@ -24,6 +24,7 @@ import { type RunnerDeps } from "../types.js";
|
|
|
24
24
|
import type { Prepared, PreparedMicroCompact } from "./contracts.js";
|
|
25
25
|
import { type GitStatusLaneRef } from "./git-status-frame.js";
|
|
26
26
|
import type { ReadFileState } from "../../tools/fs/safety.js";
|
|
27
|
+
import { type ToolRosterDeltaSeat } from "../tool-roster.js";
|
|
27
28
|
export interface PrepareContextLaneInput {
|
|
28
29
|
/** borrowed-readonly — the task model: the budgets, the trigger-window geometry and `charsPerToken` are read off it. */
|
|
29
30
|
model: Model;
|
|
@@ -39,6 +40,8 @@ export interface PrepareContextLaneInput {
|
|
|
39
40
|
hostTaskId: string;
|
|
40
41
|
/** borrowed-readonly — the run's offload store, or undefined (the caps' and the machine's persist seat). */
|
|
41
42
|
offloadStore: ToolResultStore | undefined;
|
|
43
|
+
/** borrowed-readonly — the leg's live roster: a recorded tool name is judged as the wire name it resolves to. */
|
|
44
|
+
toolRosterDeltas: ToolRosterDeltaSeat;
|
|
42
45
|
/** borrowed-readonly — the doors' RESOLVED microCompact knob (immutable values, never the deps bag). */
|
|
43
46
|
microCompactKnob: {
|
|
44
47
|
machine: MicroCompactMachine;
|
|
@@ -135,6 +138,7 @@ export interface GuardChainArgs {
|
|
|
135
138
|
offloadStore: import("../tool-result-store.js").ToolResultStore | undefined;
|
|
136
139
|
sessionId: string;
|
|
137
140
|
taskId: string;
|
|
141
|
+
toolNameOf?: (recorded: string) => string;
|
|
138
142
|
deps: RunnerDeps;
|
|
139
143
|
trimPressureRef: {
|
|
140
144
|
droppedMessages: boolean;
|
|
@@ -8,8 +8,10 @@ import { emitTrace } from "../trace.js";
|
|
|
8
8
|
import { deliverEngineNotice } from "../types.js";
|
|
9
9
|
import { applyGitFrameGuard } from "./git-status-frame.js";
|
|
10
10
|
import { applyContextShrinkToReadFileState } from "../../tools/fs/index.js";
|
|
11
|
+
import { rosterToolName } from "../tool-roster.js";
|
|
11
12
|
export function prepareContextLane(input) {
|
|
12
|
-
const { model, deps, sessionId, runId, hostTaskId, offloadStore, microCompactKnob, harness, batchHaltRef, gitStatusRef, postureFrameRef, requestLossyRef, trimPressureRef, readFileState } = input;
|
|
13
|
+
const { model, deps, sessionId, runId, hostTaskId, offloadStore, toolRosterDeltas, microCompactKnob, harness, batchHaltRef, gitStatusRef, postureFrameRef, requestLossyRef, trimPressureRef, readFileState } = input;
|
|
14
|
+
const toolNameOf = (recorded) => rosterToolName(toolRosterDeltas.current, recorded);
|
|
13
15
|
const contextOverlay = input.contextOverlay ?? (() => undefined);
|
|
14
16
|
const requestGeometry = () => {
|
|
15
17
|
const current = harness.getModel();
|
|
@@ -27,7 +29,7 @@ export function prepareContextLane(input) {
|
|
|
27
29
|
const replay = replayClearedProjection(healed, microCompact.ledger);
|
|
28
30
|
const replayed = replay.messages;
|
|
29
31
|
const capped = await capAggregateToolResults(replayed, {
|
|
30
|
-
store: offloadStore,
|
|
32
|
+
store: offloadStore, toolNameOf,
|
|
31
33
|
sessionId,
|
|
32
34
|
onCapped: (info) => pendingProjectionObservations.push(() => emitTrace(deps.tracer, () => ({
|
|
33
35
|
kind: "tool_result.capped",
|
|
@@ -49,7 +51,7 @@ export function prepareContextLane(input) {
|
|
|
49
51
|
});
|
|
50
52
|
let ccPass;
|
|
51
53
|
const edited = microCompact.machine === "off" ? mediaCapped : clearStaleToolResults(mediaCapped, {
|
|
52
|
-
budgetTokens: editAt,
|
|
54
|
+
budgetTokens: editAt, toolNameOf,
|
|
53
55
|
clearSource: replayed,
|
|
54
56
|
onCleared: (pass) => { ccPass = pass; },
|
|
55
57
|
anchoredTotalTokens: estimateContextTokens(mediaCapped, cpt).tokens,
|
|
@@ -67,7 +69,7 @@ export function prepareContextLane(input) {
|
|
|
67
69
|
}
|
|
68
70
|
const chain = await runGuardChain({
|
|
69
71
|
edited, replayed, index: replay.index, microCompact, guardAt, charsPerToken: cpt, offloadStore,
|
|
70
|
-
sessionId, taskId: hostTaskId, deps, trimPressureRef, recheck, signal,
|
|
72
|
+
sessionId, taskId: hostTaskId, deps, trimPressureRef, recheck, signal, toolNameOf,
|
|
71
73
|
pendingProjectionObservations,
|
|
72
74
|
...(readFileState !== undefined ? { readFileState } : {}),
|
|
73
75
|
});
|
|
@@ -221,7 +223,7 @@ export async function runGuardChain(args) {
|
|
|
221
223
|
if (microCompact.machine === "off" && anchoredWorking > guardAt) {
|
|
222
224
|
let blockingPass;
|
|
223
225
|
const blockingEdited = clearStaleToolResults(working, {
|
|
224
|
-
budgetTokens: guardAt,
|
|
226
|
+
budgetTokens: guardAt, toolNameOf: args.toolNameOf,
|
|
225
227
|
clearSource: args.replayed,
|
|
226
228
|
onCleared: (pass) => { blockingPass = pass; },
|
|
227
229
|
anchoredTotalTokens: anchoredWorking,
|