@sema-agent/core 7.39.1 → 7.40.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 +92 -0
- package/dist/core/a2a.js +1 -0
- package/dist/core/ask-origin.d.ts +19 -6
- package/dist/core/ask-origin.js +6 -0
- package/dist/core/gate-fold.js +5 -0
- package/dist/core/gate-lanes.js +8 -4
- package/dist/core/hands-band.d.ts +0 -12
- package/dist/core/hooks.d.ts +3 -0
- package/dist/core/hooks.js +4 -1
- package/dist/core/mcp.js +1 -1
- package/dist/core/mode-policy.d.ts +17 -2
- package/dist/core/mode-policy.js +13 -6
- package/dist/core/permission-rule-model.d.ts +29 -0
- package/dist/core/permission-rule-model.js +15 -2
- package/dist/core/permission-rules.d.ts +2 -10
- package/dist/core/permission-rules.js +22 -19
- package/dist/core/runner/branch-writeback.d.ts +38 -0
- package/dist/core/runner/branch-writeback.js +58 -0
- package/dist/core/runner/denial-limit-arms.js +1 -1
- package/dist/core/runner/gate-exit.d.ts +5 -1
- package/dist/core/runner/permission-rule-lanes.js +7 -5
- package/dist/core/runner/persisted-path-rows.js +3 -3
- package/dist/core/runner/prepare-acquire-reconcile.d.ts +9 -2
- package/dist/core/runner/prepare-acquire-reconcile.js +16 -30
- package/dist/core/runner/prepare-file-history.d.ts +2 -2
- package/dist/core/runner/prepare-gate-stations.d.ts +1 -1
- package/dist/core/runner/prepare-gate-stations.js +2 -0
- package/dist/core/runner/prepare-hands-readface.d.ts +1 -1
- package/dist/core/runner/prepare-hands-readface.js +0 -1
- package/dist/core/runner/prepare-policy-chain.js +11 -7
- package/dist/core/runner/prepare-task.js +12 -9
- package/dist/core/runner/prepare-wiring-manifest.d.ts +1 -1
- package/dist/core/runner/prepare-wiring-manifest.js +10 -5
- package/dist/core/runner/retired-runner-deps.js +1 -0
- package/dist/core/tool-policy.d.ts +14 -0
- package/dist/core/tool-policy.js +15 -6
- package/dist/core/tool-spec.d.ts +10 -0
- package/dist/core/tools.js +1 -0
- package/dist/engine/loop/types.d.ts +6 -0
- package/dist/orchestration/workflow.js +1 -1
- package/dist/stores/file/index.d.ts +4 -3
- package/dist/stores/file/session-policy-store.d.ts +19 -11
- package/dist/stores/file/session-policy-store.js +13 -16
- package/dist/stores/file/usage-window-store.d.ts +5 -5
- package/dist/tools/fs/fs-read.d.ts +1 -1
- package/dist/tools/fs/fs-read.js +3 -5
- package/dist/tools/fs/fs-shared.d.ts +0 -27
- package/dist/tools/fs/fs-shared.js +0 -3
- package/dist/tools/fs/index.d.ts +1 -9
- package/dist/tools/fs/index.js +1 -1
- package/package.json +1 -1
- package/wire-changes/core@7.33.6.json +55 -6
- package/wire-changes/core@7.39.2.json +102 -0
- package/wire-changes/core@7.40.0.json +376 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MCP_NAMESPACE, protocolOf } from "./protocol-table.js";
|
|
2
2
|
import { catalogRuleFaceOf, pathTargetOf } from "./tool-registry.js";
|
|
3
3
|
import { indexOfUnescaped, lastIndexOfUnescaped, parsePermissionRule } from "./permission-rule-syntax.js";
|
|
4
|
-
import { PATH_RULE_BASE_LABEL,
|
|
4
|
+
import { PATH_RULE_BASE_LABEL, PATH_RULE_FACES, callPathFaceOf, isUsablePathBase, parseRuleText, pathRuleHeadOf, pathRuleReachOf, programRunReachOf, ruleBasesNeeded } from "./permission-rule-model.js";
|
|
5
5
|
import { pathFamilyOf, pathIdentityOf, spellingOfIdentity } from "../tools/fs/safety.js";
|
|
6
6
|
import { effectivePathTargetOf } from "./effective-path-target.js";
|
|
7
7
|
import { protectivePathTargetOf } from "./tool-registry.js";
|
|
@@ -82,7 +82,6 @@ export const fileToolParamVocabulary = (canonicalTool) => {
|
|
|
82
82
|
const face = catalogRuleFaceOf(canonicalTool);
|
|
83
83
|
return face?.params === undefined || pathTargetOf(canonicalTool) === undefined ? undefined : new Set(face.params);
|
|
84
84
|
};
|
|
85
|
-
export const PATH_DENY_RULE_FACES = Object.freeze({ [READ_RULE_TOOL]: ["read", "write"], Edit: ["write"] });
|
|
86
85
|
export function pathRuleDenyRowOf(rule, bases) {
|
|
87
86
|
const family = pathFamilyOf(bases);
|
|
88
87
|
const tree = bases.root ?? bases.cwd;
|
|
@@ -99,10 +98,6 @@ export function pathRuleDenyRowOf(rule, bases) {
|
|
|
99
98
|
...(head !== undefined ? { head: spellingOfIdentity(head, family) } : {}),
|
|
100
99
|
};
|
|
101
100
|
}
|
|
102
|
-
function callPathFaceOf(req) {
|
|
103
|
-
const target = protectivePathTargetOf(req);
|
|
104
|
-
return target === undefined ? undefined : target.access === "read" ? "read" : "write";
|
|
105
|
-
}
|
|
106
101
|
function countStars(s) {
|
|
107
102
|
let n = 0;
|
|
108
103
|
for (const c of s)
|
|
@@ -218,7 +213,7 @@ function compile(rules, caps, primaryFieldGeneric, ruleFaces, pathBases = {}) {
|
|
|
218
213
|
}
|
|
219
214
|
if (issues.length > 0 && issues[issues.length - 1].rule === text && issues[issues.length - 1].code === "unsupported.path_base")
|
|
220
215
|
continue;
|
|
221
|
-
const faces = r.behavior
|
|
216
|
+
const faces = PATH_RULE_FACES[toolName]?.[r.behavior];
|
|
222
217
|
entry.content[r.behavior].push({ parsed: parsedPath.rule, ruleText: text, ...(r.source !== undefined ? { source: r.source } : {}), ...(faces !== undefined ? { faces } : {}) });
|
|
223
218
|
byTool.set(toolName, entry);
|
|
224
219
|
continue;
|
|
@@ -340,8 +335,15 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
340
335
|
}
|
|
341
336
|
const coveringHit = (lane, toolName) => lane.length === 0 ? undefined : lane.find((r) => namespacedRuleNameCovers(r.ruleName, toolName));
|
|
342
337
|
const hasCovering = namespacedCovering.deny.length > 0 || namespacedCovering.ask.length > 0 || namespacedCovering.allow.length > 0;
|
|
343
|
-
const
|
|
344
|
-
|
|
338
|
+
const faceRules = {
|
|
339
|
+
deny: [...byTool.values()].flatMap((e) => e.content.deny.filter((r) => r.faces !== undefined)),
|
|
340
|
+
ask: [...byTool.values()].flatMap((e) => e.content.ask.filter((r) => r.faces !== undefined)),
|
|
341
|
+
};
|
|
342
|
+
const pathDenyRules = faceRules.deny.map((r) => pathRuleDenyRowOf({ parsed: r.parsed, ruleText: r.ruleText, faces: r.faces }, pathBases));
|
|
343
|
+
const contentLaneOf = (behavior, entry, callFace) => [
|
|
344
|
+
...(entry?.content[behavior].filter((r) => r.faces === undefined) ?? []),
|
|
345
|
+
...(callFace !== undefined ? faceRules[behavior].filter((r) => r.faces.includes(callFace)) : []),
|
|
346
|
+
];
|
|
345
347
|
const commandParam = catalogRuleFaceOf("Bash")?.primaryParams[0] ?? "command";
|
|
346
348
|
const contentHit = (lane, req) => {
|
|
347
349
|
if (lane.length === 0)
|
|
@@ -410,8 +412,8 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
410
412
|
if (coveringDeny)
|
|
411
413
|
return { action: "deny", message: ruleDenial(req, coveringDeny.ruleText, coveringDeny.source) };
|
|
412
414
|
}
|
|
413
|
-
const callFace =
|
|
414
|
-
const denyLane =
|
|
415
|
+
const callFace = faceRules.deny.length > 0 || faceRules.ask.length > 0 ? callPathFaceOf(req) : undefined;
|
|
416
|
+
const denyLane = contentLaneOf("deny", entry, callFace);
|
|
415
417
|
const contentDeny = denyLane.length > 0 ? contentHit(denyLane, req) : undefined;
|
|
416
418
|
if (contentDeny !== undefined && contentDeny.unreadable === undefined) {
|
|
417
419
|
return { action: "deny", message: ruleDenial(req, contentDeny.rule.ruleText, contentDeny.rule.source) };
|
|
@@ -434,23 +436,24 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
434
436
|
if (coveringAsk)
|
|
435
437
|
return { action: "ask", message: ruleAskMessage(coveringAsk.ruleText, coveringAsk.source), matchedAskRule: coveringAsk.ruleText };
|
|
436
438
|
}
|
|
439
|
+
const askLane = contentLaneOf("ask", entry, callFace);
|
|
440
|
+
const contentAsk = askLane.length > 0 ? contentHit(askLane, req) : undefined;
|
|
441
|
+
if (contentAsk !== undefined) {
|
|
442
|
+
if (contentAsk.unreadable !== undefined)
|
|
443
|
+
return unreadableAsk({ rule: contentAsk.rule, unreadable: contentAsk.unreadable }, "content_ask", req);
|
|
444
|
+
return { action: "ask", message: ruleAskMessage(contentAsk.rule.ruleText, contentAsk.rule.source), matchedAskRule: contentAsk.rule.ruleText };
|
|
445
|
+
}
|
|
437
446
|
if (entry) {
|
|
438
|
-
const contentAsk = contentHit(entry.content.ask, req);
|
|
439
|
-
if (contentAsk !== undefined) {
|
|
440
|
-
if (contentAsk.unreadable !== undefined)
|
|
441
|
-
return unreadableAsk({ rule: contentAsk.rule, unreadable: contentAsk.unreadable }, "content_ask", req);
|
|
442
|
-
return { action: "ask", message: ruleAskMessage(contentAsk.rule.ruleText, contentAsk.rule.source), matchedAskRule: contentAsk.rule.ruleText };
|
|
443
|
-
}
|
|
444
447
|
const paramAsk = matchParamRules(entry.param.ask, req.args, caps.maxScalarValueChars);
|
|
445
448
|
if (paramAsk) {
|
|
446
449
|
return { action: "ask", message: ruleAskMessage(paramAsk.ruleText, paramAsk.source), matchedAskRule: paramAsk.ruleText };
|
|
447
450
|
}
|
|
448
451
|
if (entry.bare.allow) {
|
|
449
|
-
return { action: "allow" };
|
|
452
|
+
return { action: "allow", decisionReason: "rule" };
|
|
450
453
|
}
|
|
451
454
|
}
|
|
452
455
|
if (covering && coveringHit(namespacedCovering.allow, req.toolName)) {
|
|
453
|
-
return { action: "allow" };
|
|
456
|
+
return { action: "allow", decisionReason: "rule" };
|
|
454
457
|
}
|
|
455
458
|
if (defaultAction === "allow")
|
|
456
459
|
return { action: "allow" };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { SessionStorage } from "../session.js";
|
|
2
|
+
/** The entries this prepare appended through the watched storage — recorded once a branch is armed (`ids` set). */
|
|
3
|
+
export interface OwnAppends {
|
|
4
|
+
ids: Set<string> | undefined;
|
|
5
|
+
}
|
|
6
|
+
/** The branch a `resumeAt` acquire wrote: the watched storage, the leaf the request found, the leaf it set, and the own appends since. */
|
|
7
|
+
export interface ResumeAtBranch {
|
|
8
|
+
storage: SessionStorage;
|
|
9
|
+
leafBefore: string | null;
|
|
10
|
+
leafSet: string | null;
|
|
11
|
+
own: OwnAppends;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Wrap a storage so a `SessionError("conflict")` from any append/leaf write flips `ref.hit`, and every append
|
|
15
|
+
* that lands is recorded on `own` while a branch is armed. The harness flattens a mid-run storage failure into
|
|
16
|
+
* a generic error (and, since a lost CAS makes every subsequent append fail, even its failure-reporting append
|
|
17
|
+
* fails) — so the conflict code is gone by the time the task result is assembled. Catching it here, at the
|
|
18
|
+
* storage boundary, keeps it recoverable as `TaskResult.errorCode = "conflict"`. Per-task and non-mutating (a
|
|
19
|
+
* Proxy over the shared storage), so it never stacks across tasks on a cached session.
|
|
20
|
+
*/
|
|
21
|
+
export declare function detectConflicts(storage: SessionStorage, ref: {
|
|
22
|
+
hit: boolean;
|
|
23
|
+
}, own: OwnAppends): SessionStorage;
|
|
24
|
+
/**
|
|
25
|
+
* Put a refused prepare's `resumeAt` branch back: the leaf returns to `leafBefore` when it still sits on the
|
|
26
|
+
* branch leaf or on this prepare's own appends above it, and the write is conditional on the leaf just
|
|
27
|
+
* verified. A no-op when the leaf is already there. Throws a sentence naming both leaves when the leaf is
|
|
28
|
+
* not this request's to take back or the write cannot land. `signal` aborted before the write starts ⇒ no
|
|
29
|
+
* write (the caller's bound expired and the view is being dropped); a storage write already in flight cannot
|
|
30
|
+
* be called back — the storage contract carries no cancellation.
|
|
31
|
+
*/
|
|
32
|
+
export declare function writeBranchBack(branch: ResumeAtBranch, sessionId: string, signal?: AbortSignal): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* The session cleanup's write-back leg: {@link writeBranchBack} bounded like every other cleanup leg (a throw or
|
|
35
|
+
* a timeout goes to `report`, never to the caller). A write-back still pending when the bound expires does not
|
|
36
|
+
* START its write afterwards — the view it would write through is being dropped.
|
|
37
|
+
*/
|
|
38
|
+
export declare function settleBranchWriteBack(branch: ResumeAtBranch, sessionId: string, label: string, report: (err: Error) => void): Promise<void>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { isSessionConflict } from "../session.js";
|
|
2
|
+
import { thrownText } from "../thrown-value.js";
|
|
3
|
+
import { settleTeardownLeg } from "./teardown-bounded.js";
|
|
4
|
+
export function detectConflicts(storage, ref, own) {
|
|
5
|
+
const guard = (fn, landed) => async (...args) => {
|
|
6
|
+
try {
|
|
7
|
+
const out = await fn(...args);
|
|
8
|
+
landed(args);
|
|
9
|
+
return out;
|
|
10
|
+
}
|
|
11
|
+
catch (err) {
|
|
12
|
+
if (isSessionConflict(err)) {
|
|
13
|
+
ref.hit = true;
|
|
14
|
+
}
|
|
15
|
+
throw err;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
return new Proxy(storage, {
|
|
19
|
+
get(target, prop, receiver) {
|
|
20
|
+
const value = Reflect.get(target, prop, receiver);
|
|
21
|
+
if (typeof value !== "function") {
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
const bound = value.bind(target);
|
|
25
|
+
if (prop === "appendEntry")
|
|
26
|
+
return guard(bound, (args) => own.ids?.add(args[0].id));
|
|
27
|
+
if (prop === "setLeafId")
|
|
28
|
+
return guard(bound, () => undefined);
|
|
29
|
+
return bound;
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
export async function writeBranchBack(branch, sessionId, signal) {
|
|
34
|
+
let now;
|
|
35
|
+
try {
|
|
36
|
+
now = await branch.storage.getLeafId();
|
|
37
|
+
if (now === branch.leafBefore)
|
|
38
|
+
return;
|
|
39
|
+
const ids = branch.own.ids ?? new Set();
|
|
40
|
+
for (let at = now, steps = 0; at !== branch.leafSet; steps++) {
|
|
41
|
+
if (at === null || steps > ids.size || !ids.has(at))
|
|
42
|
+
throw new Error("another writer moved the leaf past the entries this request wrote");
|
|
43
|
+
at = (await branch.storage.getEntry(at))?.parentId ?? null;
|
|
44
|
+
}
|
|
45
|
+
if (signal?.aborted === true)
|
|
46
|
+
return;
|
|
47
|
+
await branch.storage.setLeafId(branch.leafBefore, { expectedLeafId: now });
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
const shown = (id) => (id === undefined ? "(unreadable)" : id === null ? "(empty)" : `"${id}"`);
|
|
51
|
+
throw new Error(`resumeAt: prepare refused this request but could not put session "${sessionId}" back — its leaf stays at ${shown(now)} (the request found it at ${shown(branch.leafBefore)}), so the next turn continues from there: ${thrownText(err)}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export async function settleBranchWriteBack(branch, sessionId, label, report) {
|
|
55
|
+
const ac = new AbortController();
|
|
56
|
+
await settleTeardownLeg(() => writeBranchBack(branch, sessionId, ac.signal), label, report);
|
|
57
|
+
ac.abort();
|
|
58
|
+
}
|
|
@@ -103,7 +103,7 @@ export async function judgeInheritedClassifier(opts) {
|
|
|
103
103
|
const attributed = (d) => (verdict.classifier !== undefined ? attestClassifierRound(d, verdict.classifier, req) : d);
|
|
104
104
|
if (verdict.kind === "allow") {
|
|
105
105
|
autoMode.denialTracking?.recordAllow();
|
|
106
|
-
return { kind: "allow", result: attributed({ action: "allow", ...(opts.allowRewrite !== undefined ? { updatedInput: opts.allowRewrite } : {}) }) };
|
|
106
|
+
return { kind: "allow", result: attributed({ action: "allow", decisionReason: "classifier", ...(opts.allowRewrite !== undefined ? { updatedInput: opts.allowRewrite } : {}) }) };
|
|
107
107
|
}
|
|
108
108
|
if (verdict.kind === "unavailable") {
|
|
109
109
|
const arm = classifierUnavailableArm({ ask, req, cause: verdict.cause, seat: opts.unavailableSeat, aborted: opts.signal.aborted });
|
|
@@ -214,6 +214,10 @@ export interface GatePass {
|
|
|
214
214
|
hookAsk: Extract<PermissionResult, {
|
|
215
215
|
action: "ask";
|
|
216
216
|
}> | undefined;
|
|
217
|
+
/** borrowed-mutable — the phase-1 hook answered its own `allow` (the exact word): a statement about this call, read by
|
|
218
|
+
* the fold's last station only while the policy left the arguments as the hook saw them. Writer: the dispatch (phase 1).
|
|
219
|
+
* Reader: the fold's unstated station. */
|
|
220
|
+
hookAllowed?: true;
|
|
217
221
|
/** borrowed-mutable — set by a park attempt that FAILED (see `ParkAttemptFailed`). Read at the single deny
|
|
218
222
|
* exit — whatever the fallback chain then decides to say, it says it knowing the park was tried first.
|
|
219
223
|
* Writers: the three park sites. */
|
|
@@ -256,7 +260,7 @@ export interface GatePass {
|
|
|
256
260
|
* policy's answer; then rewritten by every station that changes the verdict (fold, tightens, org, rule
|
|
257
261
|
* lane, stamp, classifier, sandbox, content lane, resolution, edit chain). */
|
|
258
262
|
decision: PermissionResult;
|
|
259
|
-
/** borrowed-mutable — which of the gate's OWN post-fold tightens minted the standing ask, if one did — an
|
|
263
|
+
/** borrowed-mutable — which of the gate's OWN post-fold stations (the tightens and the unstated station) minted the standing ask, if one did — an
|
|
260
264
|
* input of the ask-origin derivation (`AskOriginFacts.tightened`). Written at most once per pass: every
|
|
261
265
|
* tighten fires only over a surviving `allow`, and the first one to fire leaves an `ask` the others do not
|
|
262
266
|
* touch. Recorded by the gate rather than read off `decisionReason` because that member is a policy's to
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { persistedRuleMandateOf, ruleClearanceOf } from "../ask-origin.js";
|
|
2
2
|
import {} from "../hooks.js";
|
|
3
|
-
import { adjudicatePersistedPathRules, adjudicatePersistedRules, laneToolNamesOf, ruleToolGrammarOf, segmentCoverageOf, suggestRulesForCommand } from "../permission-rule-model.js";
|
|
3
|
+
import { adjudicatePersistedPathRules, adjudicatePersistedRules, callPathFaceOf, faceAskSpeaksFor, 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
6
|
import { declaredPathTargetOf, protectivePathTargetOf } from "../tool-registry.js";
|
|
@@ -45,7 +45,7 @@ export function pathRuleLaneAnswer(table, req, ctx) {
|
|
|
45
45
|
const allow = effectivePathTargetOf(req, declaredPathTargetOf(req), bases);
|
|
46
46
|
if (tighten === undefined && allow === undefined)
|
|
47
47
|
return undefined;
|
|
48
|
-
return persistedRuleAnswerOf(adjudicatePersistedPathRules(table, { tool: req.toolName, cwd: ctx.root, sessionId: ctx.sessionId }, { ...(tighten !== undefined ? { tighten } : {}), ...(allow !== undefined ? { allow } : {}) }, bases));
|
|
48
|
+
return persistedRuleAnswerOf(adjudicatePersistedPathRules(table, { tool: req.toolName, cwd: ctx.root, sessionId: ctx.sessionId, face: callPathFaceOf(req) }, { ...(tighten !== undefined ? { tighten } : {}), ...(allow !== undefined ? { allow } : {}) }, bases));
|
|
49
49
|
}
|
|
50
50
|
export function orgRuleLane(cfg) {
|
|
51
51
|
return {
|
|
@@ -127,8 +127,10 @@ export function createPermissionRuleLanes(cfg) {
|
|
|
127
127
|
const personalAdjudicate = async (req) => {
|
|
128
128
|
if (anonymous && !cfg.localOwnerDeclared)
|
|
129
129
|
return undefined;
|
|
130
|
-
const
|
|
131
|
-
if (
|
|
130
|
+
const judged = laneToolNamesOf(req).map((n) => [n, ruleToolGrammarOf(n)]);
|
|
131
|
+
if (!judged.some(([, g]) => g === "path") && faceAskSpeaksFor(callPathFaceOf(req)))
|
|
132
|
+
judged.push([req.toolName, "path"]);
|
|
133
|
+
if (judged.length === 0)
|
|
132
134
|
return undefined;
|
|
133
135
|
let view;
|
|
134
136
|
try {
|
|
@@ -140,7 +142,7 @@ export function createPermissionRuleLanes(cfg) {
|
|
|
140
142
|
if (view.unreadable !== undefined)
|
|
141
143
|
skipped(view.unreadable, view.unreadableSources);
|
|
142
144
|
try {
|
|
143
|
-
const answer = strongestAnswer(
|
|
145
|
+
const answer = strongestAnswer(judged.map(([n, grammar]) => judgeAgainst(view.rules, { ...req, toolName: n }, grammar)));
|
|
144
146
|
return view.unreadable !== undefined && (answer === undefined || "segmentCoverage" in answer) ? { unreadable: true } : answer;
|
|
145
147
|
}
|
|
146
148
|
catch (err) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ownerStoreOf } from "../permission-rule-provider.js";
|
|
2
|
-
import { READ_RULE_TOOL, eligiblePersisted, isUsablePathBase, ruleBasesNeeded } from "../permission-rule-model.js";
|
|
2
|
+
import { PATH_RULE_FACES, READ_RULE_TOOL, eligiblePersisted, isUsablePathBase, ruleBasesNeeded } from "../permission-rule-model.js";
|
|
3
3
|
import { isAbsolutePathForm, pathFamilyOf } from "../../tools/fs/safety.js";
|
|
4
|
-
import {
|
|
4
|
+
import { pathRuleDenyRowOf } from "../permission-rules.js";
|
|
5
5
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
6
6
|
import { DEFAULT_GATE_AWAIT_TIMEOUT_MS, gateWaitFailureOf, withGateTimeout } from "../gate-bounded-await.js";
|
|
7
7
|
import { thrownText } from "../thrown-value.js";
|
|
@@ -34,7 +34,7 @@ export async function persistedPathRowsOf(cfg) {
|
|
|
34
34
|
readDirectories.push(rule.command);
|
|
35
35
|
continue;
|
|
36
36
|
}
|
|
37
|
-
const faces =
|
|
37
|
+
const faces = PATH_RULE_FACES[rule.tool]?.deny;
|
|
38
38
|
if (rule.behavior !== "deny" || faces === undefined || (rule.match !== "path" && rule.match !== "subpath") || !eligiblePersisted(rule, { ...call, tool: rule.tool }, "deny"))
|
|
39
39
|
continue;
|
|
40
40
|
const missing = ruleBasesNeeded(rule).filter((b) => !isUsablePathBase(b === "cwd" ? bases.root : bases[b], family));
|
|
@@ -16,13 +16,15 @@
|
|
|
16
16
|
*
|
|
17
17
|
* Throws pass through unchanged: `resume.session_not_found` (requireExisting fail-loud mapping),
|
|
18
18
|
* the `resume_at.*` target-validation refusals, and — after the bounded re-wake budget — the
|
|
19
|
-
* session conflict itself.
|
|
19
|
+
* session conflict itself. A throw after a `resumeAt` branch landed first writes that branch back
|
|
20
|
+
* (`settleBranchWriteBack`); on success the branch is handed over with the session.
|
|
20
21
|
*/
|
|
21
22
|
import { StoredSession } from "../session.js";
|
|
22
23
|
import type { AcquiredSession, SessionStore } from "../session.js";
|
|
23
24
|
import { type RecoveredOrphan } from "../session-reconcile.js";
|
|
24
25
|
import type { TaskSpec, ToolEffect } from "../types.js";
|
|
25
26
|
import type { PrepareResume } from "./contracts.js";
|
|
27
|
+
import { type ResumeAtBranch } from "./branch-writeback.js";
|
|
26
28
|
export interface PrepareAcquireReconcileInput {
|
|
27
29
|
/** borrowed-mutable (service port) — the deployment's session store. This phase's verbs on it are
|
|
28
30
|
* the acquire/forget half of the session resource protocol: `acquire` per attempt, `forget` to
|
|
@@ -52,8 +54,10 @@ export interface PrepareAcquireReconcileInput {
|
|
|
52
54
|
* absent seat; nothing in the phase's control flow depends on it. Computed by the driver (which
|
|
53
55
|
* holds `deps`) rather than here, keeping this slice's input the narrow Pick its contract says. */
|
|
54
56
|
durableParkGap?: string;
|
|
57
|
+
/** borrowed-readonly — the incident channel for this phase's own cleanup legs: a `resumeAt` branch it could not write back, a forget that did not settle. */
|
|
58
|
+
report?: (err: Error, sessionId: string) => void;
|
|
55
59
|
}
|
|
56
|
-
/** The phase's outputs (four-class form). All
|
|
60
|
+
/** The phase's outputs (four-class form). All six are fresh bindings —
|
|
57
61
|
* the driver destructures them into consts, so a consumer moved ahead of this call is a lexical
|
|
58
62
|
* error, which is the static guarantee the extraction keeps. */
|
|
59
63
|
export interface PrepareAcquireReconcileResult {
|
|
@@ -75,6 +79,9 @@ export interface PrepareAcquireReconcileResult {
|
|
|
75
79
|
* SUCCEEDS (a conflict-losing attempt re-wakes and re-derives the whole set from the fresh
|
|
76
80
|
* branch), and stays empty for a fresh session / a clean one with nothing to reconcile. */
|
|
77
81
|
wakeRecovered: RecoveredOrphan[];
|
|
82
|
+
/** owned (handed over with the session) — the `resumeAt` branch this phase wrote, absent when it wrote
|
|
83
|
+
* none; the driver's throw cleanup writes it back on any later prepare refusal. */
|
|
84
|
+
branch: ResumeAtBranch | undefined;
|
|
78
85
|
}
|
|
79
86
|
/** The B-3 phase body — the acquire+reconcile slice, verbatim (see the module header for the contract). */
|
|
80
87
|
export declare function prepareAcquireReconcile(input: PrepareAcquireReconcileInput): Promise<PrepareAcquireReconcileResult>;
|
|
@@ -1,37 +1,15 @@
|
|
|
1
1
|
import { StoredSession, isSessionConflict } from "../session.js";
|
|
2
2
|
import { reconcileInterruptedSession } from "../session-reconcile.js";
|
|
3
|
+
import { detectConflicts, settleBranchWriteBack } from "./branch-writeback.js";
|
|
4
|
+
import { settleTeardownLeg } from "./teardown-bounded.js";
|
|
3
5
|
const RECONCILE_MAX_RETRIES = 3;
|
|
4
|
-
function detectConflicts(storage, ref) {
|
|
5
|
-
const guard = (fn) => async (...args) => {
|
|
6
|
-
try {
|
|
7
|
-
return await fn(...args);
|
|
8
|
-
}
|
|
9
|
-
catch (err) {
|
|
10
|
-
if (isSessionConflict(err)) {
|
|
11
|
-
ref.hit = true;
|
|
12
|
-
}
|
|
13
|
-
throw err;
|
|
14
|
-
}
|
|
15
|
-
};
|
|
16
|
-
return new Proxy(storage, {
|
|
17
|
-
get(target, prop, receiver) {
|
|
18
|
-
const value = Reflect.get(target, prop, receiver);
|
|
19
|
-
if (typeof value !== "function") {
|
|
20
|
-
return value;
|
|
21
|
-
}
|
|
22
|
-
const bound = value.bind(target);
|
|
23
|
-
return prop === "appendEntry" || prop === "setLeafId"
|
|
24
|
-
? guard(bound)
|
|
25
|
-
: bound;
|
|
26
|
-
},
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
6
|
export async function prepareAcquireReconcile(input) {
|
|
30
7
|
const { sessions, spec, resume, toolEffects } = input;
|
|
31
8
|
let acquired;
|
|
32
9
|
let session;
|
|
33
10
|
let conflictRef;
|
|
34
11
|
let wakeRecovered = [];
|
|
12
|
+
let branch;
|
|
35
13
|
for (let attempt = 0;; attempt++) {
|
|
36
14
|
try {
|
|
37
15
|
acquired = await sessions.acquire(spec.sessionId, spec.requireExistingSession ? { requireExisting: true } : input.placement !== undefined ? { placement: input.placement } : undefined);
|
|
@@ -46,7 +24,8 @@ export async function prepareAcquireReconcile(input) {
|
|
|
46
24
|
throw err;
|
|
47
25
|
}
|
|
48
26
|
conflictRef = { hit: false };
|
|
49
|
-
|
|
27
|
+
const own = { ids: undefined };
|
|
28
|
+
session = new StoredSession(detectConflicts(acquired.session.getStorage(), conflictRef, own));
|
|
50
29
|
if (!spec.sessionId) {
|
|
51
30
|
break;
|
|
52
31
|
}
|
|
@@ -105,7 +84,10 @@ export async function prepareAcquireReconcile(input) {
|
|
|
105
84
|
throw e;
|
|
106
85
|
}
|
|
107
86
|
}
|
|
108
|
-
|
|
87
|
+
const storage = session.getStorage(), leafBefore = await storage.getLeafId(), leafSet = spec.resumeAtMode === "before" ? entry.parentId : spec.resumeAt;
|
|
88
|
+
await storage.setLeafId(leafSet);
|
|
89
|
+
own.ids = new Set();
|
|
90
|
+
branch = { storage, leafBefore, leafSet, own };
|
|
109
91
|
}
|
|
110
92
|
wakeRecovered = (await reconcileInterruptedSession(session, toolEffects, resume?.suspendedBatch)).recovered;
|
|
111
93
|
break;
|
|
@@ -113,13 +95,17 @@ export async function prepareAcquireReconcile(input) {
|
|
|
113
95
|
catch (err) {
|
|
114
96
|
if (isSessionConflict(err) && attempt < RECONCILE_MAX_RETRIES) {
|
|
115
97
|
if (sessions.forget) {
|
|
116
|
-
|
|
98
|
+
const forget = sessions.forget.bind(sessions), id = spec.sessionId;
|
|
99
|
+
await settleTeardownLeg(() => Promise.resolve(forget(id)).catch(() => undefined), "forget (acquire-reconcile re-wake leg)", (e) => input.report?.(e, id));
|
|
117
100
|
}
|
|
101
|
+
branch = undefined;
|
|
118
102
|
continue;
|
|
119
103
|
}
|
|
120
|
-
|
|
104
|
+
if (branch !== undefined)
|
|
105
|
+
await settleBranchWriteBack(branch, acquired.sessionId, "resumeAt branch write-back (acquire-reconcile failure leg)", (e) => input.report?.(e, acquired.sessionId));
|
|
106
|
+
await settleTeardownLeg(() => Promise.resolve(sessions.forget ? sessions.forget(acquired.sessionId) : undefined).catch(() => undefined), "forget (acquire-reconcile failure leg)", (e) => input.report?.(e, acquired.sessionId));
|
|
121
107
|
throw err;
|
|
122
108
|
}
|
|
123
109
|
}
|
|
124
|
-
return { acquired, session, conflictRef, wakeRecovered };
|
|
110
|
+
return { acquired, session, conflictRef, wakeRecovered, branch };
|
|
125
111
|
}
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* boundary seat runtask drives.
|
|
9
9
|
*
|
|
10
10
|
* Runs INSIDE the driver's prepare try-block, after the workspace root is FINAL (`taskRootFinal`) and the
|
|
11
|
-
* env is resolved: every refusal here throws through to the driver's prepare-throw catch
|
|
12
|
-
*
|
|
11
|
+
* env is resolved: every refusal here throws through to the driver's prepare-throw catch (a minted
|
|
12
|
+
* ownedEnv is destroyed there, a resumeAt branch written back, the session view forgotten). The phase acquires
|
|
13
13
|
* no resource of its own — the two seats it returns are closures over the store the deployment owns.
|
|
14
14
|
*
|
|
15
15
|
* The request door (the bad-value doors and every refusal the request decides alone) lives in
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
import type { AgentHarness, AgentTool } from "../../internal/harness.js";
|
|
26
26
|
import type { AskClass } from "../ask-class.js";
|
|
27
27
|
import type { Arbitration, ToolMarkSource } from "../ask-origin.js";
|
|
28
|
-
import type
|
|
28
|
+
import { type PermissionModeController } from "../mode-policy.js";
|
|
29
29
|
import type { AutoModeDecider, AutoModeDenialTracker } from "../auto-mode.js";
|
|
30
30
|
import { type ComplianceCapability } from "../compliance.js";
|
|
31
31
|
import { type HookEnvCapabilities, type HookInvocationIdentity, type Hooks } from "../hooks.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SEND_MESSAGE_TOOL_NAME } from "../../agents/send-message-tool.js";
|
|
2
|
+
import { mountedToolPermissionSeat } from "../mode-policy.js";
|
|
2
3
|
import { complianceCallDenial } from "../compliance.js";
|
|
3
4
|
import { cloneObserverInput, formatHookFeedback, hookSeatExpiredError, runHookSeat, runToolGate } from "../hooks.js";
|
|
4
5
|
import { screenGateOutcome } from "../gate-outcome.js";
|
|
@@ -137,6 +138,7 @@ export function prepareGateStations(input) {
|
|
|
137
138
|
...(autoModeDecider.current !== undefined ? { autoMode: { decider: autoModeDecider.current, ...(autoModeDenialTracking.current !== undefined ? { denialTracking: autoModeDenialTracking.current } : {}), unavailable: classifierUnavailableSeat({ station: "leg", ...(autoModeDecider.current.timeoutMs !== undefined ? { budgetMs: autoModeDecider.current.timeoutMs } : {}), approvalSeat: askReachesPerson(askRoute), onNotice: deps.onNotice, sessionId, runId }) } } : {}),
|
|
138
139
|
permissionMode: () => permissionMode.current,
|
|
139
140
|
...(markSources.size > 0 ? { toolMarkSources: markSources } : {}),
|
|
141
|
+
...(mountedToolPermissionSeat(tools, e.toolName) === "passthrough" ? { permissionSeat: "passthrough" } : {}),
|
|
140
142
|
...(a2aIsolatePeers === true ? { a2aIsolatePeers: true } : {}),
|
|
141
143
|
onModeReleased,
|
|
142
144
|
ancestorArbitrationOf: (toolCallId) => ancestorArbitrations.get(toolCallId),
|
|
@@ -149,7 +149,7 @@ export interface PrepareHandsReadFaceInput {
|
|
|
149
149
|
reversibilityProbes: ReversibilityProbes;
|
|
150
150
|
/** borrowed-readonly — the run's reminder provenance mark (minted/adopted
|
|
151
151
|
* by the driver's adoption ladder); threaded into the hands band so the fs-side engine mints
|
|
152
|
-
* (the Read
|
|
152
|
+
* (the Read dedup/offset/empty reminders, the gh rate-limit hint) stamp the same mark the
|
|
153
153
|
* system-prompt declaration names. */
|
|
154
154
|
reminderMark: string;
|
|
155
155
|
/** borrowed-mutable — the leg's disclosure trigger counters; the hands
|
|
@@ -366,7 +366,6 @@ export async function prepareHandsMount(input) {
|
|
|
366
366
|
...(deps.hands?.commitCoAuthor !== undefined ? { commitCoAuthor: deps.hands.commitCoAuthor } : {}),
|
|
367
367
|
...(deps.hands?.readImageDownsampler !== undefined ? { readImageDownsampler: deps.hands.readImageDownsampler } : {}),
|
|
368
368
|
...(deps.hands?.autoBackgroundOnTimeout !== undefined ? { autoBackgroundOnTimeout: deps.hands.autoBackgroundOnTimeout } : {}),
|
|
369
|
-
...(deps.hands?.readCyberReminder !== undefined ? { readCyberReminder: deps.hands.readCyberReminder } : {}),
|
|
370
369
|
beforeWrite: async (w) => {
|
|
371
370
|
const deploymentGate = deps.hands?.beforeWrite;
|
|
372
371
|
if (deploymentGate) {
|
|
@@ -364,7 +364,8 @@ export async function preparePolicyChain(input) {
|
|
|
364
364
|
if (re.action === "deny")
|
|
365
365
|
return vetoed(re);
|
|
366
366
|
if (re.action === "allow") {
|
|
367
|
-
const
|
|
367
|
+
const stated = re.updatedInput === undefined ? "mode" : re.decisionReason;
|
|
368
|
+
const out = { action: "allow", updatedInput: re.updatedInput !== undefined ? re.updatedInput : editArgs, ...(stated !== undefined ? { decisionReason: stated } : {}) };
|
|
368
369
|
carryEngineAttestations(re, out);
|
|
369
370
|
carryPrecedingSettlement(settledBy, out);
|
|
370
371
|
return out;
|
|
@@ -384,7 +385,7 @@ export async function preparePolicyChain(input) {
|
|
|
384
385
|
re = editAsk;
|
|
385
386
|
const editArbitrated = arbitrateInherited(re, creq.toolCallId, judged);
|
|
386
387
|
if (editArbitrated !== undefined) {
|
|
387
|
-
const out = editArbitrated.action === "allow" ? { action: "allow", updatedInput: editArgs } : editArbitrated;
|
|
388
|
+
const out = editArbitrated.action === "allow" ? { action: "allow", updatedInput: editArgs, decisionReason: "mode" } : editArbitrated;
|
|
388
389
|
carryPrecedingSettlement(settledBy, out);
|
|
389
390
|
return out;
|
|
390
391
|
}
|
|
@@ -408,7 +409,7 @@ export async function preparePolicyChain(input) {
|
|
|
408
409
|
return rr;
|
|
409
410
|
settledBy = rr;
|
|
410
411
|
if (rr.updatedInput === undefined) {
|
|
411
|
-
const out = { action: "allow", updatedInput: editArgs };
|
|
412
|
+
const out = { action: "allow", updatedInput: editArgs, decisionReason: "mode" };
|
|
412
413
|
carryPrecedingSettlement(rr, out);
|
|
413
414
|
return out;
|
|
414
415
|
}
|
|
@@ -461,7 +462,7 @@ export async function preparePolicyChain(input) {
|
|
|
461
462
|
const { ask: inheritedAsk, fallback, mintedHere } = judged;
|
|
462
463
|
if (fallback === undefined && sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq) && judged.classification.row.sandboxAdmission) {
|
|
463
464
|
recordAncestorSandboxAdmission(creq.toolCallId, creq.toolName);
|
|
464
|
-
return { action: "allow" };
|
|
465
|
+
return { action: "allow", decisionReason: "sandbox" };
|
|
465
466
|
}
|
|
466
467
|
const arbitrated = arbitrateInherited(inheritedAsk, creq.toolCallId, judged);
|
|
467
468
|
if (arbitrated !== undefined)
|
|
@@ -496,7 +497,7 @@ export async function preparePolicyChain(input) {
|
|
|
496
497
|
return recheckApprovedEdit(pc.policy, pc.onAsk, creq, resolved.updatedInput, resolved, csignal, pc.autoMode);
|
|
497
498
|
}
|
|
498
499
|
recordInheritedAskGrant(creq.toolCallId, pc.onAsk, resolved.presentedInput, askWaitMs, resolved, askClassMembersOf(judged.classification).classes);
|
|
499
|
-
return { action: "allow" };
|
|
500
|
+
return { action: "allow", decisionReason: "mode" };
|
|
500
501
|
},
|
|
501
502
|
}
|
|
502
503
|
: wrapToolPolicy(pc.policy, async (creq, csignal) => {
|
|
@@ -535,7 +536,7 @@ export async function preparePolicyChain(input) {
|
|
|
535
536
|
const { ask: inheritedAsk, fallback, mintedHere } = judged;
|
|
536
537
|
if (fallback === undefined && sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq) && judged.classification.row.sandboxAdmission) {
|
|
537
538
|
recordAncestorSandboxAdmission(creq.toolCallId, creq.toolName);
|
|
538
|
-
return decision.updatedInput !== undefined ? { action: "allow", updatedInput: decision.updatedInput } : { action: "allow" };
|
|
539
|
+
return decision.updatedInput !== undefined ? { action: "allow", updatedInput: decision.updatedInput, decisionReason: "sandbox" } : { action: "allow", decisionReason: "sandbox" };
|
|
539
540
|
}
|
|
540
541
|
const arbitrated = arbitrateInherited(inheritedAsk.action === "ask" ? { ...inheritedAsk, ...(decision.updatedInput !== undefined ? { updatedInput: decision.updatedInput } : {}) } : inheritedAsk, creq.toolCallId, judged);
|
|
541
542
|
if (arbitrated !== undefined)
|
|
@@ -635,6 +636,7 @@ export async function preparePolicyChain(input) {
|
|
|
635
636
|
const finalReq = { ...req, args: decision.updatedInput };
|
|
636
637
|
let asked;
|
|
637
638
|
const revalidated = {};
|
|
639
|
+
let restated;
|
|
638
640
|
try {
|
|
639
641
|
for (const layer of recheckLayers) {
|
|
640
642
|
const d = refuseOutOfContractDecision(await layer.check(finalReq, signal));
|
|
@@ -653,6 +655,8 @@ export async function preparePolicyChain(input) {
|
|
|
653
655
|
}
|
|
654
656
|
if (d.action === "ask" && asked === undefined)
|
|
655
657
|
asked = d;
|
|
658
|
+
if (d.action === "allow" && restated === undefined && d.decisionReason !== undefined)
|
|
659
|
+
restated = d.decisionReason;
|
|
656
660
|
carryEngineAttestations(d, revalidated);
|
|
657
661
|
}
|
|
658
662
|
}
|
|
@@ -665,7 +669,7 @@ export async function preparePolicyChain(input) {
|
|
|
665
669
|
return surfaced;
|
|
666
670
|
}
|
|
667
671
|
if (decision.action === "allow") {
|
|
668
|
-
const owned = { ...decision };
|
|
672
|
+
const owned = { ...decision, ...(decision.decisionReason === undefined && restated !== undefined ? { decisionReason: restated } : {}) };
|
|
669
673
|
carryEngineAttestations(decision, owned);
|
|
670
674
|
carryEngineAttestations(revalidated, owned);
|
|
671
675
|
return owned;
|
|
@@ -38,6 +38,7 @@ import { preparePromptAssembly } from "./prepare-prompt-assembly.js";
|
|
|
38
38
|
import { limitConfigError, prepareConfigDoors } from "./prepare-config-doors.js";
|
|
39
39
|
import { prepareSafetyScan } from "./prepare-safety-scan.js";
|
|
40
40
|
import { prepareAcquireReconcile } from "./prepare-acquire-reconcile.js";
|
|
41
|
+
import { settleBranchWriteBack } from "./branch-writeback.js";
|
|
41
42
|
import { prepareHandsMount, resolveHandsLessReadFace } from "./prepare-hands-readface.js";
|
|
42
43
|
import { prepareWorkspaceRestore } from "./prepare-workspace-restore.js";
|
|
43
44
|
import { parkContaminationMarker } from "./park-commit.js";
|
|
@@ -291,8 +292,15 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
291
292
|
let shellGatedTools = new Set();
|
|
292
293
|
refuseRequireExistingWithoutSession(spec);
|
|
293
294
|
refuseRegistryScopeMismatch(spec, internals);
|
|
295
|
+
if (model.extraBody !== undefined && Object.prototype.hasOwnProperty.call(model.extraBody, "max_completion_tokens")) {
|
|
296
|
+
const e = new Error(`model.extraBody supplies "max_completion_tokens" — this spelling is engine-owned (the overflow retry ` +
|
|
297
|
+
`adjusts the per-request output cap, and a constant extraBody value would override that adjustment on ` +
|
|
298
|
+
`every retry). Set the cap via limits.maxOutputTokens (per request) or model.maxTokens instead.`);
|
|
299
|
+
e.code = "config.extra_body_output_cap";
|
|
300
|
+
throw e;
|
|
301
|
+
}
|
|
294
302
|
const fileHistoryRequest = resolveFileHistoryRequest(spec, deps);
|
|
295
|
-
const { acquired, session, conflictRef, wakeRecovered } = await prepareAcquireReconcile({ sessions, spec, resume, toolEffects, ...(internals?.sessionPlacement !== undefined ? { placement: internals.sessionPlacement } : {}), ...(() => { const g = durableParkGapFor(deps, spec); return g !== undefined ? { durableParkGap: g } : {}; })() });
|
|
303
|
+
const { acquired, session, conflictRef, wakeRecovered, branch } = await prepareAcquireReconcile({ sessions, spec, resume, toolEffects, report: (e, id) => deps.onError?.(e, { phase: "config", sessionId: id }), ...(internals?.sessionPlacement !== undefined ? { placement: internals.sessionPlacement } : {}), ...(() => { const g = durableParkGapFor(deps, spec); return g !== undefined ? { durableParkGap: g } : {}; })() });
|
|
296
304
|
const sessionId = acquired.sessionId;
|
|
297
305
|
await holdSessionLock?.(sessionId);
|
|
298
306
|
const onceLedger = await loadAnnounceOnceLedger(session, resume?.seed.announcedListings, deps.onError);
|
|
@@ -320,18 +328,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
320
328
|
deps.onError?.(new Error(`offload/tool-result namespace note: this run mounts in registry domain "${internals.registryScope}" but its offloaded tool results live under the TRUST identity "${offloadScope}" (principal${spec.principal === undefined ? " absent → default" : ""}). Before 2.13.0 the registry domain named this namespace too; if a deployment used registryScope as its ref-tenancy boundary, that boundary is now the principal — pass one, or read refs under "${offloadScope}".`), { phase: "config", sessionId });
|
|
321
329
|
}
|
|
322
330
|
defaultTaskRegistry.maybeGc();
|
|
323
|
-
const forgetOnThrow = () =>
|
|
331
|
+
const forgetOnThrow = () => branch === undefined
|
|
332
|
+
? forgetQuietly(sessions, sessionId)
|
|
333
|
+
: settleBranchWriteBack(branch, sessionId, "resumeAt branch write-back (prepare refusal)", (e) => deps.onError?.(e, { phase: "config", sessionId })).then(() => forgetQuietly(sessions, sessionId));
|
|
324
334
|
const extraBodyCollisions = reservedCollisions(model.extraBody, reservedFor(model.api));
|
|
325
335
|
if (extraBodyCollisions.length > 0) {
|
|
326
336
|
deps.onError?.(new Error(`model.extraBody contains reserved key(s) [${extraBodyCollisions.join(", ")}] that core owns and sets itself — they are ignored. Remove them from extraBody.`), { phase: "config", sessionId });
|
|
327
337
|
}
|
|
328
|
-
if (model.extraBody !== undefined && Object.prototype.hasOwnProperty.call(model.extraBody, "max_completion_tokens")) {
|
|
329
|
-
const e = new Error(`model.extraBody supplies "max_completion_tokens" — this spelling is engine-owned (the overflow retry ` +
|
|
330
|
-
`adjusts the per-request output cap, and a constant extraBody value would override that adjustment on ` +
|
|
331
|
-
`every retry). Set the cap via limits.maxOutputTokens (per request) or model.maxTokens instead.`);
|
|
332
|
-
e.code = "config.extra_body_output_cap";
|
|
333
|
-
throw e;
|
|
334
|
-
}
|
|
335
338
|
const { nestedStats, reportUsage, offloadStore, offloadReachableToolsRef, maybeOffload, firstPartyOffload, remoteToolOffload } = prepareOffloadWrappers({ spec, deps, resume, offloadScope, sessionId });
|
|
336
339
|
if (offloadStore && (spec.tools ?? []).some((t) => t.name === OFFLOAD_TOOL_NAME)) {
|
|
337
340
|
const e = new Error(`Tool name "${OFFLOAD_TOOL_NAME}" is reserved when large-result offload is enabled.`);
|
|
@@ -33,7 +33,7 @@ import { type LegWriteProtection } from "./write-protection-station.js";
|
|
|
33
33
|
import { type UsageWindow } from "../usage-window-store.js";
|
|
34
34
|
import type { ToolRoster } from "../tool-roster.js";
|
|
35
35
|
import { type AutoModeArmFact, type WiringManifest, type WiringManifestMcpEntry } from "../wiring-manifest.js";
|
|
36
|
-
import type
|
|
36
|
+
import { type PermissionModeController } from "../mode-policy.js";
|
|
37
37
|
import type { AnnounceOnceSink, Prepared, PrepareResume, RunInternals, UsageGovernance } from "./contracts.js";
|
|
38
38
|
export interface PrepareWiringManifestInput {
|
|
39
39
|
/** borrowed-readonly — the REBOUND spec. Read: `onAsk` (the own seat), `onQuestion` (the question seam), `handsReadOnly` / `tools` (the ungated scan),
|