@sema-agent/core 7.43.0 → 7.45.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 +245 -0
- package/dist/agents/subagent.d.ts +2 -1
- package/dist/agents/subagent.js +33 -14
- package/dist/agents/tool-filter.d.ts +65 -4
- package/dist/agents/tool-filter.js +127 -4
- package/dist/brain/errors.d.ts +23 -2
- package/dist/brain/errors.js +26 -2
- package/dist/brain/stream-engine.d.ts +2 -1
- package/dist/brain/stream-engine.js +10 -9
- package/dist/core/agent-definition.d.ts +2 -2
- package/dist/core/allow-entry.d.ts +25 -1
- package/dist/core/allow-entry.js +29 -4
- package/dist/core/ask-origin.d.ts +23 -4
- package/dist/core/ask-origin.js +10 -4
- package/dist/core/checkpoint-store.d.ts +28 -15
- package/dist/core/checkpoint-store.js +4 -13
- package/dist/core/decision-receipt.js +1 -1
- package/dist/core/effective-path-target.d.ts +7 -6
- package/dist/core/effective-path-target.js +3 -7
- package/dist/core/engine-notice.d.ts +33 -14
- package/dist/core/engine-notice.js +14 -7
- package/dist/core/gate-lanes.js +2 -0
- package/dist/core/gate-outcome.d.ts +5 -0
- package/dist/core/gate-outcome.js +2 -0
- package/dist/core/governance-codes.d.ts +4 -3
- package/dist/core/governance-codes.js +1 -0
- package/dist/core/hooks.d.ts +2 -0
- package/dist/core/memory-engine/content-origin.d.ts +0 -34
- package/dist/core/memory-engine/content-origin.js +9 -4
- package/dist/core/memory-engine/engine.d.ts +2 -2
- package/dist/core/memory-engine/engine.js +7 -14
- package/dist/core/memory-engine/file-backend.d.ts +2 -2
- package/dist/core/memory-engine/file-backend.js +5 -7
- package/dist/core/memory-engine/layout.d.ts +41 -4
- package/dist/core/memory-engine/layout.js +35 -5
- package/dist/core/permission-rules.d.ts +4 -0
- package/dist/core/permission-rules.js +5 -2
- package/dist/core/real-approval.d.ts +9 -6
- package/dist/core/real-approval.js +5 -5
- package/dist/core/runner/child-tree.d.ts +13 -0
- package/dist/core/runner/child-tree.js +3 -0
- package/dist/core/runner/contracts.d.ts +9 -18
- package/dist/core/runner/prepare-ask-lane.d.ts +2 -0
- package/dist/core/runner/prepare-ask-lane.js +2 -2
- package/dist/core/runner/prepare-config-doors.js +3 -1
- package/dist/core/runner/prepare-execution-env.js +2 -1
- package/dist/core/runner/prepare-gate-stations.d.ts +3 -0
- package/dist/core/runner/prepare-gate-stations.js +2 -1
- package/dist/core/runner/prepare-listings.d.ts +4 -4
- package/dist/core/runner/prepare-listings.js +2 -22
- package/dist/core/runner/prepare-park-ask.d.ts +2 -0
- package/dist/core/runner/prepare-park-ask.js +2 -2
- package/dist/core/runner/prepare-policy-chain.d.ts +2 -0
- package/dist/core/runner/prepare-policy-chain.js +4 -2
- package/dist/core/runner/prepare-task.js +10 -6
- package/dist/core/runner/resume-policy-outcome.js +2 -1
- package/dist/core/runner/run-leg.js +1 -1
- package/dist/core/runner/run-notification-lane.js +2 -2
- package/dist/core/runner/runtask.d.ts +2 -0
- package/dist/core/runner/runtask.js +3 -5
- package/dist/core/runner/steer-admission.d.ts +10 -11
- package/dist/core/runner/steer-admission.js +6 -8
- package/dist/core/runner/stream-steer-verb.d.ts +5 -4
- package/dist/core/runner/stream-steer-verb.js +8 -10
- package/dist/core/runner/sub-agent-deny-lists.d.ts +32 -0
- package/dist/core/runner/sub-agent-deny-lists.js +121 -0
- package/dist/core/runner/synthetic-tools.d.ts +1 -1
- package/dist/core/runner/synthetic-tools.js +3 -2
- package/dist/core/session-policy-store.d.ts +6 -2
- package/dist/core/session-policy-store.js +2 -1
- package/dist/core/skill-grant.d.ts +10 -2
- package/dist/core/skill-grant.js +20 -4
- package/dist/core/steer-replay.d.ts +46 -0
- package/dist/core/steer-replay.js +22 -0
- package/dist/core/task-event.d.ts +3 -2
- package/dist/core/task-notification.d.ts +12 -1
- package/dist/core/task-notification.js +13 -1
- package/dist/core/task-spec.d.ts +5 -3
- package/dist/core/task-stream.d.ts +12 -6
- package/dist/core/tool-errors.d.ts +2 -2
- package/dist/core/tool-policy.d.ts +15 -4
- package/dist/core/tool-policy.js +9 -13
- package/dist/core/tool-spec.d.ts +20 -2
- package/dist/core/types.d.ts +2 -2
- package/dist/core/types.js +1 -1
- package/dist/index.d.ts +7 -4
- package/dist/index.js +7 -4
- package/dist/orchestration/workflow-types.d.ts +5 -0
- package/dist/orchestration/workflow.d.ts +3 -2
- package/dist/orchestration/workflow.js +16 -7
- package/dist/tools/fs/removal-targets.d.ts +82 -0
- package/dist/tools/fs/removal-targets.js +236 -0
- package/dist/tools/fs/shell-read-write-targets.d.ts +27 -0
- package/dist/tools/fs/shell-read-write-targets.js +1 -1
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +43 -5
- package/wire-changes/core@7.44.0.json +469 -0
- package/wire-changes/core@7.45.0.json +1084 -0
|
@@ -682,9 +682,9 @@ export declare class FileMemoryEngineBackend implements MemoryBackend {
|
|
|
682
682
|
* let a transient EIO run the unlocked recovery (and its staged sweep) over a live transaction. */
|
|
683
683
|
private txnInFlight;
|
|
684
684
|
/**
|
|
685
|
-
* The act gate for store paths ({@link
|
|
685
|
+
* The act gate for store paths ({@link landsOutsideStore}) over `ops`' writes and deletes — shadow
|
|
686
686
|
* ops name control-plane files, not store paths. Refuses with `memory.target_outside_store` at the
|
|
687
|
-
* first op that would land outside the store, naming the op
|
|
687
|
+
* first op that would land outside the store, naming the op (its path on `detail.path`). Runs over a
|
|
688
688
|
* whole batch before its commit marker (no journal is written; a caller that staged files removes
|
|
689
689
|
* them) and per op at the act itself; past the marker a refusal keeps the journal, which replays
|
|
690
690
|
* once the path resolves inside the store again.
|
|
@@ -8,7 +8,7 @@ import { ambiguousOriginRepresentation, committedDistilledOf, committedOriginOf,
|
|
|
8
8
|
import { convertV1SidecarKeys } from "./delegation-settlement.js";
|
|
9
9
|
import { layerObservationLookup, observedDivergenceNotice } from "./layer-observations.js";
|
|
10
10
|
import { DEFAULT_MAX_ENTRY_DEPTH, MEMORY_INDEX_FILENAME, scanEntryFiles } from "./entry-scan.js";
|
|
11
|
-
import { ControlPlaneCorruptError, CURSORS_FILE, QUARANTINE_DIR, CHALLENGES_FILE, IMPORT_RECEIPTS_DIR, LINEAGE_FILE, SESSION_POLLUTION_DIR, appendChallengeEvents, atomicWriteFileSync, quarantineAndTombstone, captureAndClearLineageForEntries, adoptRootScopeLayout, adoptCanonicalKeyedControlDir, deriveControlPlaneDir, ensureDirExists, enqueueMemoryAnnouncement, importLatchTxnId, importLineageCommitted, importSessionPollution, isContainedIn, permissionRemedyOf, withPermissionRemedy, lineageContributionsOfSession, listSessionPollution, readChallengeEvents, readLineageRecord, registerScope, registeredScopes, releaseImportLatch, layoutNeedsAdoption, resolveMemoryEngineRoot, scopeDirFor, scopeDirName, scopeRegistryShape, stageImportLatch, landingPathOf,
|
|
11
|
+
import { ControlPlaneCorruptError, CURSORS_FILE, QUARANTINE_DIR, CHALLENGES_FILE, IMPORT_RECEIPTS_DIR, LINEAGE_FILE, SESSION_POLLUTION_DIR, appendChallengeEvents, atomicWriteFileSync, quarantineAndTombstone, captureAndClearLineageForEntries, adoptRootScopeLayout, adoptCanonicalKeyedControlDir, deriveControlPlaneDir, ensureDirExists, enqueueMemoryAnnouncement, importLatchTxnId, importLineageCommitted, importSessionPollution, isContainedIn, permissionRemedyOf, withPermissionRemedy, lineageContributionsOfSession, listSessionPollution, readChallengeEvents, readLineageRecord, registerScope, registeredScopes, releaseImportLatch, layoutNeedsAdoption, resolveMemoryEngineRoot, scopeDirFor, scopeDirName, scopeRegistryShape, stageImportLatch, landingPathOf, landsOutsideStore, storeActRefusal, storeRootMovedRefusal, writeAllSync, } from "./layout.js";
|
|
12
12
|
import { screenInboundEntries } from "./data-plane.js";
|
|
13
13
|
import { scanMemoryFileName, scanMemoryWrite } from "./scan.js";
|
|
14
14
|
import { thrownText } from "../thrown-value.js";
|
|
@@ -1404,7 +1404,7 @@ export class FileMemoryEngineBackend {
|
|
|
1404
1404
|
}
|
|
1405
1405
|
}
|
|
1406
1406
|
this.assertTxnLockOwnership(fenceToken);
|
|
1407
|
-
const replayAt =
|
|
1407
|
+
const replayAt = "the journal replay (journal kept)";
|
|
1408
1408
|
this.refuseEscapingTargets(journal.ops, replayAt);
|
|
1409
1409
|
for (const op of journal.ops) {
|
|
1410
1410
|
this.refuseEscapingTargets([op], replayAt);
|
|
@@ -1470,10 +1470,8 @@ export class FileMemoryEngineBackend {
|
|
|
1470
1470
|
if (op.kind !== "write" && op.kind !== "delete")
|
|
1471
1471
|
continue;
|
|
1472
1472
|
for (const path of op.staged === undefined ? [op.target] : [op.target, op.staged]) {
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
continue;
|
|
1476
|
-
throw erasureCodedError(`${TARGET_OUTSIDE_STORE}: ${at} refused a ${op.kind} of ${op.target}${path === op.target ? "" : ` (its staged file ${path})`} — ${escape}; not performed`, TARGET_OUTSIDE_STORE);
|
|
1473
|
+
if (landsOutsideStore(this.physicalRoot, path))
|
|
1474
|
+
throw storeActRefusal(`${path === op.target ? "a" : "the staged file of a"} ${op.kind} at ${at}`, path);
|
|
1477
1475
|
}
|
|
1478
1476
|
}
|
|
1479
1477
|
}
|
|
@@ -1481,7 +1479,7 @@ export class FileMemoryEngineBackend {
|
|
|
1481
1479
|
const now = landingPathOf(this.directoryRoot);
|
|
1482
1480
|
if (now === this.physicalRoot)
|
|
1483
1481
|
return;
|
|
1484
|
-
throw
|
|
1482
|
+
throw storeRootMovedRefusal("backend", this.directoryRoot);
|
|
1485
1483
|
}
|
|
1486
1484
|
sweepStagedFiles() {
|
|
1487
1485
|
const walk = (d, depth) => {
|
|
@@ -259,8 +259,45 @@ export declare function adoptRootScopeLayout(rootPath: string, controlDir: strin
|
|
|
259
259
|
export declare const canonicalize: typeof physicalPathOfExisting;
|
|
260
260
|
/** True iff `child` (canonicalized) is `root` or inside it. */
|
|
261
261
|
export declare function isContainedIn(root: string, child: string): boolean;
|
|
262
|
-
/** The
|
|
262
|
+
/** The codes the store's physical form refuses an act with — the whole family, one closed set: {@link TARGET_OUTSIDE_STORE} and
|
|
263
|
+
* {@link SCOPE_HOME_NOT_DIRECTORY}. Each is a plain `Error` carrying `code` and a `detail` ({@link StoreFormRefusalDetail}). */
|
|
264
|
+
export declare const STORE_FORM_REFUSAL_CODES: readonly ["memory.target_outside_store", "memory.scope_home_not_directory"];
|
|
265
|
+
export type StoreFormRefusalCode = (typeof STORE_FORM_REFUSAL_CODES)[number];
|
|
266
|
+
/** The code an act on a store path is refused with when it would land outside the store ({@link landsOutsideStore}). */
|
|
263
267
|
export declare const TARGET_OUTSIDE_STORE = "memory.target_outside_store";
|
|
268
|
+
/** The code a scope home that is not a real directory is refused with ({@link assertHomePhysicalForm}). */
|
|
269
|
+
export declare const SCOPE_HOME_NOT_DIRECTORY = "memory.scope_home_not_directory";
|
|
270
|
+
/** What a scope home can be besides a real directory (an `lstat` verdict). */
|
|
271
|
+
export declare const SCOPE_HOME_FORMS: readonly ["symlink", "file", "other"];
|
|
272
|
+
export type ScopeHomeForm = (typeof SCOPE_HOME_FORMS)[number];
|
|
273
|
+
/** How a refusal's sentence names each form. */
|
|
274
|
+
declare const SCOPE_HOME_FORM_PHRASE: {
|
|
275
|
+
readonly symlink: "a symbolic link";
|
|
276
|
+
readonly file: "a regular file";
|
|
277
|
+
readonly other: "a special file";
|
|
278
|
+
};
|
|
279
|
+
export type HomeFormPhraseCoversEveryForm = AssertAllKeysHandled<Exclude<ScopeHomeForm, keyof typeof SCOPE_HOME_FORM_PHRASE>>;
|
|
280
|
+
/** What a store-form refusal carries beside its sentence. The ONE host path lives here and never in the
|
|
281
|
+
* sentence; `form` is present on {@link SCOPE_HOME_NOT_DIRECTORY} only. */
|
|
282
|
+
export type StoreFormRefusalDetail = {
|
|
283
|
+
readonly path: string;
|
|
284
|
+
readonly form?: ScopeHomeForm;
|
|
285
|
+
};
|
|
286
|
+
/** A refusal the store's physical form earns: a plain `Error` (never the control-plane-damage class —
|
|
287
|
+
* the engine's own account is intact, the layout it was handed is not the layout it needs) carrying a
|
|
288
|
+
* `code` and a `detail`. */
|
|
289
|
+
export type StoreFormRefusal = Error & {
|
|
290
|
+
code: StoreFormRefusalCode;
|
|
291
|
+
detail: StoreFormRefusalDetail;
|
|
292
|
+
};
|
|
293
|
+
/** Seat 1 — an act the store's physical form refuses: `act` names what was refused (no path), `path` is the
|
|
294
|
+
* target whose directory resolves outside the store ({@link landsOutsideStore}). */
|
|
295
|
+
export declare function storeActRefusal(act: string, path: string): StoreFormRefusal;
|
|
296
|
+
/** Seat 2 — the store root now resolves to another directory than the one the `holder` first opened; `path` is
|
|
297
|
+
* the root's spelling. */
|
|
298
|
+
export declare function storeRootMovedRefusal(holder: "backend" | "engine", path: string): StoreFormRefusal;
|
|
299
|
+
/** Seat 3 — a scope home that is not a real directory. */
|
|
300
|
+
export declare function scopeHomeRefusal(form: ScopeHomeForm, path: string): StoreFormRefusal;
|
|
264
301
|
/** Where an act at `p` lands: the physical path of its normalized spelling (the spelling a child path
|
|
265
302
|
* joined under it carries), a MISSING tail rejoined under the deepest existing directory (what the act
|
|
266
303
|
* creates). Any other resolution fault (EACCES, ELOOP, ENOTDIR) is THROWN as itself: an act that
|
|
@@ -268,8 +305,8 @@ export declare const TARGET_OUTSIDE_STORE = "memory.target_outside_store";
|
|
|
268
305
|
* (a link whose own mode refuses `realpath` is still followed by `unlink` and `rename`). */
|
|
269
306
|
export declare function landingPathOf(p: string): string;
|
|
270
307
|
/**
|
|
271
|
-
*
|
|
272
|
-
* `physicalRoot
|
|
308
|
+
* Whether a write, rename or delete at `target` would physically LAND outside the store, judged against the
|
|
309
|
+
* store's `physicalRoot` (`true` = outside; the refusal is {@link storeActRefusal}). The target's directory, resolved through
|
|
273
310
|
* every link on its existing prefix (a missing tail is what the act creates, under the deepest existing
|
|
274
311
|
* directory), must be the root or inside it. `physicalRoot` is the root's physical form as the store
|
|
275
312
|
* was opened and is not re-resolved here: a store mounted through a link is judged by what the link
|
|
@@ -280,7 +317,7 @@ export declare function landingPathOf(p: string): string;
|
|
|
280
317
|
* keeps the spelling while the act follows the link out. A directory that cannot be resolved throws its
|
|
281
318
|
* fault ({@link landingPathOf}).
|
|
282
319
|
*/
|
|
283
|
-
export declare function
|
|
320
|
+
export declare function landsOutsideStore(physicalRoot: string, target: string): boolean;
|
|
284
321
|
/** mkdir -p convenience used by the engine/backend (0o700 like the file stores). */
|
|
285
322
|
export declare function ensureDirExists(dir: string): void;
|
|
286
323
|
/** The one remedy every surfaced permission refusal carries — `undefined` unless `err` is a write
|
|
@@ -472,11 +472,18 @@ function assertHomePhysicalForm(home) {
|
|
|
472
472
|
return "absent";
|
|
473
473
|
if (st.isDirectory())
|
|
474
474
|
return "dir";
|
|
475
|
-
throw
|
|
475
|
+
throw scopeHomeRefusal(st.isSymbolicLink() ? "symlink" : st.isFile() ? "file" : "other", home);
|
|
476
476
|
}
|
|
477
477
|
function ensureScopeHome(home) {
|
|
478
|
-
if (assertHomePhysicalForm(home) === "absent")
|
|
479
|
-
|
|
478
|
+
if (assertHomePhysicalForm(home) === "absent") {
|
|
479
|
+
try {
|
|
480
|
+
mkdirSync(home, { recursive: true });
|
|
481
|
+
}
|
|
482
|
+
catch (err) {
|
|
483
|
+
assertHomePhysicalForm(home);
|
|
484
|
+
throw err;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
480
487
|
assertHomePhysicalForm(home);
|
|
481
488
|
}
|
|
482
489
|
export function registerScope(memoryDir, controlDir, scope, opts) {
|
|
@@ -724,7 +731,30 @@ export function isContainedIn(root, child) {
|
|
|
724
731
|
const c = canonicalize(child);
|
|
725
732
|
return c === r || c.startsWith(`${r}${sep}`);
|
|
726
733
|
}
|
|
734
|
+
export const STORE_FORM_REFUSAL_CODES = Object.freeze(["memory.target_outside_store", "memory.scope_home_not_directory"]);
|
|
727
735
|
export const TARGET_OUTSIDE_STORE = "memory.target_outside_store";
|
|
736
|
+
export const SCOPE_HOME_NOT_DIRECTORY = "memory.scope_home_not_directory";
|
|
737
|
+
export const SCOPE_HOME_FORMS = Object.freeze(["symlink", "file", "other"]);
|
|
738
|
+
const SCOPE_HOME_FORM_PHRASE = {
|
|
739
|
+
symlink: "a symbolic link",
|
|
740
|
+
file: "a regular file",
|
|
741
|
+
other: "a special file",
|
|
742
|
+
};
|
|
743
|
+
function storeFormRefusal(code, sentence, detail) {
|
|
744
|
+
const e = new Error(`${code}: ${sentence}`);
|
|
745
|
+
e.code = code;
|
|
746
|
+
e.detail = detail;
|
|
747
|
+
return e;
|
|
748
|
+
}
|
|
749
|
+
export function storeActRefusal(act, path) {
|
|
750
|
+
return storeFormRefusal(TARGET_OUTSIDE_STORE, `${act} was refused — its directory resolves outside the store root; not performed`, { path });
|
|
751
|
+
}
|
|
752
|
+
export function storeRootMovedRefusal(holder, path) {
|
|
753
|
+
return storeFormRefusal(TARGET_OUTSIDE_STORE, `the ${holder === "backend" ? "store root" : "memory dir"} now resolves to a different directory than the one this ${holder} ${holder === "backend" ? "opened" : "first mounted"} — refused; open a new ${holder} on the directory you mean`, { path });
|
|
754
|
+
}
|
|
755
|
+
export function scopeHomeRefusal(form, path) {
|
|
756
|
+
return storeFormRefusal(SCOPE_HOME_NOT_DIRECTORY, `a scope home must be a real directory, but this one is ${SCOPE_HOME_FORM_PHRASE[form]} — refused (fail-closed); put the real directory back`, { path, form });
|
|
757
|
+
}
|
|
728
758
|
export function landingPathOf(p) {
|
|
729
759
|
const normalized = resolve(p);
|
|
730
760
|
try {
|
|
@@ -736,9 +766,9 @@ export function landingPathOf(p) {
|
|
|
736
766
|
return canonicalize(normalized);
|
|
737
767
|
}
|
|
738
768
|
}
|
|
739
|
-
export function
|
|
769
|
+
export function landsOutsideStore(physicalRoot, target) {
|
|
740
770
|
const dir = landingPathOf(dirname(target));
|
|
741
|
-
return dir
|
|
771
|
+
return dir !== physicalRoot && !dir.startsWith(`${physicalRoot}${sep}`);
|
|
742
772
|
}
|
|
743
773
|
export function ensureDirExists(dir) {
|
|
744
774
|
if (!existsSync(dir))
|
|
@@ -149,6 +149,10 @@ export interface PermissionRulePolicyOptions {
|
|
|
149
149
|
* own; #644). Facts of the DEPLOYMENT and the task, never of the rule — a pattern is stored as spelled.
|
|
150
150
|
* A rule whose base is absent (or not an absolute path) here is refused at compile time
|
|
151
151
|
* (`unsupported.path_base`, naming the base — {@link ruleBasesNeeded} is the same classification).
|
|
152
|
+
* A call's own target resolves against these bases as given; when they hold neither `root` nor `cwd`,
|
|
153
|
+
* against the request's (`ToolCallRequest.root` / `.cwd`, stamped by the Runner) — never a mix of the two
|
|
154
|
+
* sources. A target neither supplies the base for is unreadable: every path rule able to reach the call
|
|
155
|
+
* answers it with the fail-closed ask (`ruleUnreadable`).
|
|
152
156
|
*/
|
|
153
157
|
pathBases?: PathRuleBases;
|
|
154
158
|
}
|
|
@@ -366,10 +366,13 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
366
366
|
...(callFace !== undefined ? faceRules[behavior].filter((r) => r.faces.includes(callFace)) : []),
|
|
367
367
|
];
|
|
368
368
|
const commandParam = catalogRuleFaceOf("Bash")?.primaryParams[0] ?? "command";
|
|
369
|
+
const policyNamesTree = pathBases.root !== undefined || pathBases.cwd !== undefined;
|
|
370
|
+
const callBasesOf = (req) => policyNamesTree ? pathBases : { ...pathBases, ...(typeof req.root === "string" ? { root: req.root } : {}), ...(typeof req.cwd === "string" ? { cwd: req.cwd } : {}) };
|
|
369
371
|
const contentHit = (lane, req) => {
|
|
370
372
|
if (lane.length === 0)
|
|
371
373
|
return undefined;
|
|
372
|
-
const
|
|
374
|
+
const bases = callBasesOf(req);
|
|
375
|
+
const target = effectivePathTargetOf(req, protectivePathTargetOf(req), bases);
|
|
373
376
|
const command = isPlainObjectArgs(req.args) ? req.args[commandParam] : undefined;
|
|
374
377
|
let unreadable;
|
|
375
378
|
for (const r of lane) {
|
|
@@ -386,7 +389,7 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
386
389
|
unreadable ??= { rule: r, unreadable: target.reason };
|
|
387
390
|
continue;
|
|
388
391
|
}
|
|
389
|
-
const outcome = pathRuleReachOf(r.parsed, target.identity,
|
|
392
|
+
const outcome = pathRuleReachOf(r.parsed, target.identity, bases);
|
|
390
393
|
if (outcome.reach === "reached")
|
|
391
394
|
return { rule: r };
|
|
392
395
|
if (outcome.reach === "unreadable")
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* may still refuse it.
|
|
7
7
|
*/
|
|
8
8
|
import type { Checkpoint } from "./checkpoint-store.js";
|
|
9
|
+
import type { HostDecider } from "./gate-outcome.js";
|
|
9
10
|
import type { OrgGateVerdict } from "./hooks.js";
|
|
10
11
|
import type { AskRequest, PermissionResult, ResolvedAsk } from "./tool-policy.js";
|
|
11
12
|
/** The synchronous seat's refusal of a configuration's allow: one settlement word for both forms
|
|
@@ -16,10 +17,12 @@ export declare function unjudgedAllowRefused(req: AskRequest, by: "blanket" | "p
|
|
|
16
17
|
/** A durable row demands a person when its gate carries the real-approval bit (the gate's own, which every resume
|
|
17
18
|
* check reads; the pending action's `requiresRealApproval` is its display twin). */
|
|
18
19
|
export declare function rowDemandsAPerson(cp: Checkpoint): boolean;
|
|
19
|
-
/** A question a
|
|
20
|
-
*
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
/** A question a decide is held against, in each form a station holds one: the parked row, a policy's re-check of an
|
|
21
|
+
* approver's edit, the organization's CURRENT verdict about the call, and the request an approver was handed (the card a
|
|
22
|
+
* live door answers). */
|
|
23
|
+
export type HeldQuestion = Checkpoint | PermissionResult | OrgGateVerdict | AskRequest;
|
|
24
|
+
/** Whether a decide made by `decidedBy` cannot answer the held question: a question demanding a person is not
|
|
23
25
|
* the policy's to answer. One predicate for every station that holds a decide against such a question — the resume
|
|
24
|
-
* entry (the row, an edit's re-check, the org's current verdict)
|
|
25
|
-
|
|
26
|
+
* entry (the row, an edit's re-check, the org's current verdict), the execution belt (the last two, re-read) and a live
|
|
27
|
+
* door answering the request an approver was handed. */
|
|
28
|
+
export declare function policyCannotAnswer(question: HeldQuestion, decidedBy: HostDecider): boolean;
|
|
@@ -16,11 +16,11 @@ export function rowDemandsAPerson(cp) {
|
|
|
16
16
|
return cp.gate.kind === "irreversible_ask" && cp.gate.realApproval !== undefined;
|
|
17
17
|
}
|
|
18
18
|
function demandsAPerson(question) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return question
|
|
19
|
+
const gate = Reflect.get(question, "gate");
|
|
20
|
+
const verdict = Reflect.get(question, "verdict");
|
|
21
|
+
const row = typeof gate === "object" && gate !== null && rowDemandsAPerson(question);
|
|
22
|
+
const org = Reflect.get(question, "status") === "available" && typeof verdict === "object" && verdict !== null && Reflect.get(verdict, "behavior") === "ask";
|
|
23
|
+
return row || Reflect.get(question, "requiresRealApproval") === true || org;
|
|
24
24
|
}
|
|
25
25
|
export function policyCannotAnswer(question, decidedBy) {
|
|
26
26
|
return decidedBy === "policy" && demandsAPerson(question);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { RunnerDeps } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Whether this deployment can place a child agent in a tree OTHER than the caller's — the one fact behind
|
|
4
|
+
* every request of that shape: an explicit `cwd`, a workflow agent's `isolation: "worktree"`, and the
|
|
5
|
+
* delegation tool's per-call or per-definition `isolation: "worktree"`.
|
|
6
|
+
*
|
|
7
|
+
* Only an `executionEnvFactory` can: it builds one execution env per task, so a child can be rooted
|
|
8
|
+
* wherever the request says. A static `executionEnv` is ONE shared env with ONE cwd — every child that
|
|
9
|
+
* runs on it runs in the caller's root, whatever tree it was asked to work in. A request the deployment
|
|
10
|
+
* cannot honor is refused before the child starts (and, for a worktree, before one is minted); each site
|
|
11
|
+
* keeps its own code and sentence for the refusal, but none decides the condition itself.
|
|
12
|
+
*/
|
|
13
|
+
export declare function deploymentCanRerootChild(deps: Pick<RunnerDeps, "executionEnvFactory">): boolean;
|
|
@@ -31,9 +31,9 @@ import type { MaterializedA2a } from "../a2a.js";
|
|
|
31
31
|
import type { CompactionForkContext, CompactionPhaseDurations, MaybeCompactOptions, RapidRefillState } from "../auto-compaction.js";
|
|
32
32
|
import type { AutoModeArmingRecipe } from "../auto-mode-arming.js";
|
|
33
33
|
import type { AutoModeDecider, AutoModeDenialTracker, DenialLimitFallbackFace } from "../auto-mode.js";
|
|
34
|
-
import type { SettlementKind } from "../gate-outcome.js";
|
|
34
|
+
import type { HostDecider, SettlementKind } from "../gate-outcome.js";
|
|
35
35
|
import type { CacheBreakDetector, ToolFingerprintInput } from "../cache-break-detector.js";
|
|
36
|
-
import type {
|
|
36
|
+
import type { Checkpoint, CheckpointState, CheckpointStore, CheckpointToken, PendingSteerEntry, PlatformLimitReason, ReopenReason, ResourceLedger, ResourceLimitReason, ResumeOutcome, SerializedCheckpointState } from "../checkpoint-store.js";
|
|
37
37
|
import type { ExecutionOutcomeRecordWord } from "../checkpoint-execution-record.js";
|
|
38
38
|
import type { ClearedProjectionLedger, MicroCompactMachine, OccurrenceIndex } from "../context-edit.js";
|
|
39
39
|
import type { HookInvocationIdentity, Hooks, OrgGateVerdict, PermissionDeniedPayload, PostToolBatchCall, PostToolBatchResult, ToolGateInput } from "../hooks.js";
|
|
@@ -391,6 +391,8 @@ export interface Prepared {
|
|
|
391
391
|
/** The per-task logical cwd ref when a real shell is mounted (else undefined). The Runner
|
|
392
392
|
* reads `cwdRef.current` after each tool to detect a `cd` move and emit `workspace_changed`. */
|
|
393
393
|
cwdRef?: CwdRef;
|
|
394
|
+
/** The root the hand tools resolve root-relative spellings against (present with hands; read-only hands included). */
|
|
395
|
+
handsRoot?: string;
|
|
394
396
|
/** The run's TOOL-PROGRESS lane seat, late-bound for the same reason `harnessRef` is (the tool band is
|
|
395
397
|
* built during prepare; the lane needs `pushContent` and `ident`, which exist only after it returns).
|
|
396
398
|
* The driver binds `current` right after `pushContent` exists. Always present on a completed prepare —
|
|
@@ -442,7 +444,7 @@ export interface Prepared {
|
|
|
442
444
|
toolCallId: string;
|
|
443
445
|
toolName: string;
|
|
444
446
|
args: unknown;
|
|
445
|
-
decidedBy:
|
|
447
|
+
decidedBy: HostDecider;
|
|
446
448
|
}) => Promise<{
|
|
447
449
|
readonly refused: string;
|
|
448
450
|
} | {
|
|
@@ -2494,21 +2496,6 @@ export interface RunState {
|
|
|
2494
2496
|
attachmentsInjected: number;
|
|
2495
2497
|
};
|
|
2496
2498
|
}
|
|
2497
|
-
/** One accepted live steer's REPLAY identity, the twin of the parked
|
|
2498
|
-
* queue's `samePendingSteerPayload`. `payload` is the exact string handed to the harness, so the text,
|
|
2499
|
-
* the speaker envelope and the trust framing all fold into it; `trusted` is kept SEPARATELY from those
|
|
2500
|
-
* framed bytes so a text that spells its own `<system-reminder>` wrapper can never read as the trusted
|
|
2501
|
-
* arm's byte-equal twin. `actor` carries the attribution the ledger frame reports. */
|
|
2502
|
-
export interface AcceptedSteerInput {
|
|
2503
|
-
payload: string;
|
|
2504
|
-
trusted: boolean;
|
|
2505
|
-
/** The NORMALIZED priority the call delivered under (absent input ⇒ "next"),
|
|
2506
|
-
* the replay identity's sixth axis: same id + same text at a DIFFERENT tier is not a replay —
|
|
2507
|
-
* a `next` input re-sent as `now` answering "idempotent success" without the interrupt would be
|
|
2508
|
-
* a disposition lie, so it refuses `duplicate_input_id` like any other identity mismatch. */
|
|
2509
|
-
priority: SystemInjectionPriority;
|
|
2510
|
-
actor?: ActorAssertion;
|
|
2511
|
-
}
|
|
2512
2499
|
/**
|
|
2513
2500
|
* Config re-supplied to {@link Runner.resume}. A suspended task's tools / model / policy /
|
|
2514
2501
|
* hooks cannot be reconstructed from a checkpoint token (the session stores neither tool implementations
|
|
@@ -2763,6 +2750,10 @@ export interface RunnerSelfSeat {
|
|
|
2763
2750
|
toolPolicy?: RunnerDeps["toolPolicy"];
|
|
2764
2751
|
hooks?: RunnerDeps["hooks"];
|
|
2765
2752
|
};
|
|
2753
|
+
/** Whether a child task THIS runner runs can be placed in a tree other than the caller's (an explicit cwd, a worktree):
|
|
2754
|
+
* true only when its deps carry an `executionEnvFactory` (live read — `swapDeps` replaces the deps). The delegation tool asks
|
|
2755
|
+
* the runner that will run the child, not the one that ran the parent leg. Absent on a hand-built seat ⇒ read as "cannot say" ⇒ no; a hand-built seat answers for the runner its `runTask` forwards to. */
|
|
2756
|
+
canRerootChild?(): boolean;
|
|
2766
2757
|
/** Fire `RunnerDeps.onTaskOutcome` through the swallow-guarded chokepoint (thin compositions emit at their terminal state). */
|
|
2767
2758
|
emitTaskOutcome(outcome: TaskOutcome): void;
|
|
2768
2759
|
/** Run a task to completion. `internals` is the TRUSTED run-scoped channel (never a TaskSpec field). */
|
|
@@ -28,6 +28,8 @@ export interface PrepareAskLaneInput {
|
|
|
28
28
|
/** borrowed-readonly — the hand's live tracked cwd, or undefined (hands-less). Read per call at RUN time (`current`),
|
|
29
29
|
* never captured: the fs hand tools resolve their relative paths against this very ref. Not written here. */
|
|
30
30
|
handsCwdRef: CwdRef | undefined;
|
|
31
|
+
/** borrowed-readonly — the root the hand tools resolve root-relative spellings against, or undefined (hands-less). */
|
|
32
|
+
handsRoot: string | undefined;
|
|
31
33
|
/** borrowed-readonly — the run's advertised-writable-directory seat, or undefined (hands-less / nothing advertised). `roots` is read at RUN time, never captured: it is the fence's own admitted list and the write gate's exemption domain must be that same list. Not written here. */
|
|
32
34
|
advertisedWritableDirs: Pick<AdvertisedWritableDirs, "roots"> | undefined;
|
|
33
35
|
/** borrowed-readonly — the shared mount roster; read at RUN time by the approval-preview projection (alias-aware
|
|
@@ -59,14 +59,14 @@ function resolveApprovalPreview(tools, toolName, args) {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
export function prepareAskLane(input) {
|
|
62
|
-
const { gateMachineryActive, abortController, effectivePolicy, budgetSnapshot, handsCwdRef, advertisedWritableDirs, tools, inheritedUnavailableAsks, inheritedAskGrants, onAsk, humanReviewRef, now, ruleOffersOf, grantBindingOf, askSourceIdentity, riskAxesOf, autoModeDenialTracking, spec, deps, sessionId, runId, hooks, hookTimeoutMs, notifyOwnHookCrash, grantReadRoot } = input;
|
|
62
|
+
const { gateMachineryActive, abortController, effectivePolicy, budgetSnapshot, handsCwdRef, handsRoot, advertisedWritableDirs, tools, inheritedUnavailableAsks, inheritedAskGrants, onAsk, humanReviewRef, now, ruleOffersOf, grantBindingOf, askSourceIdentity, riskAxesOf, autoModeDenialTracking, spec, deps, sessionId, runId, hooks, hookTimeoutMs, notifyOwnHookCrash, grantReadRoot } = input;
|
|
63
63
|
if (!gateMachineryActive)
|
|
64
64
|
return { askLane: undefined };
|
|
65
65
|
const composedCallSignal = (callSignal) => composeCallSignal(abortController.signal, callSignal);
|
|
66
66
|
const adjudicate = effectivePolicy
|
|
67
67
|
? (req, callSignal) => {
|
|
68
68
|
const signal = composedCallSignal(callSignal);
|
|
69
|
-
return raceAbort(Promise.resolve(effectivePolicy.check({ ...req, budget: budgetSnapshot, ...(handsCwdRef !== undefined ? { cwd: handsCwdRef.current } : {}), ...advertisedWritableRootsSeat(advertisedWritableDirs) }, signal)), signal, () => ({
|
|
69
|
+
return raceAbort(Promise.resolve(effectivePolicy.check({ ...req, budget: budgetSnapshot, ...(handsCwdRef !== undefined ? { cwd: handsCwdRef.current } : {}), ...(handsRoot !== undefined ? { root: handsRoot } : {}), ...advertisedWritableRootsSeat(advertisedWritableDirs) }, signal)), signal, () => ({
|
|
70
70
|
action: "deny",
|
|
71
71
|
message: "policy check aborted (task timed out or cancelled)",
|
|
72
72
|
}));
|
|
@@ -14,6 +14,7 @@ import { applyToolModelGate, assertRestoreGatedToolsValue, modelIdTail } from ".
|
|
|
14
14
|
import { resolveUsageWindows } from "../usage-window-store.js";
|
|
15
15
|
import { deriveAskEffective, resolveAskSeat, resolveQuestionSeam } from "../wiring-manifest.js";
|
|
16
16
|
import { assertPeerPresenceValue } from "../../agents/peer-directory.js";
|
|
17
|
+
import { snapshotDefinitionLists } from "../../agents/tool-filter.js";
|
|
17
18
|
import { TOTAL_TOKENS_REMINDER_MODES } from "./turn-attachments.js";
|
|
18
19
|
const TASK_LIMIT_KEY_DICT = {
|
|
19
20
|
maxTokens: true,
|
|
@@ -359,9 +360,10 @@ export function prepareConfigDoors(input) {
|
|
|
359
360
|
e.code = "config.agents.no_delegation_tool";
|
|
360
361
|
throw e;
|
|
361
362
|
}
|
|
362
|
-
const perTask = spec.agents;
|
|
363
|
+
const perTask = spec.agents.map(snapshotDefinitionLists);
|
|
363
364
|
spec = {
|
|
364
365
|
...spec,
|
|
366
|
+
agents: perTask,
|
|
365
367
|
tools: pool.map((t) => {
|
|
366
368
|
if (typeof t.withAgents !== "function")
|
|
367
369
|
return t;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { StubExecutionEnv } from "../stub-env.js";
|
|
2
2
|
import { hasDestroy } from "../remote-env.js";
|
|
3
3
|
import { announceMalformedCapabilities } from "./env-capability-census.js";
|
|
4
|
+
import { deploymentCanRerootChild } from "./child-tree.js";
|
|
4
5
|
import { settleTeardownLeg } from "./teardown-bounded.js";
|
|
5
6
|
export async function prepareExecutionEnv(input) {
|
|
6
7
|
const { deps, spec, internals, resume, placementRootResolved, sessionId, forgetOnThrow, rollback } = input;
|
|
7
8
|
let ownedEnv;
|
|
8
9
|
let ownedEnvObservedAt;
|
|
9
|
-
if (internals?.requestedCwd !== undefined && deps
|
|
10
|
+
if (internals?.requestedCwd !== undefined && !deploymentCanRerootChild(deps)) {
|
|
10
11
|
await settleTeardownLeg(() => forgetOnThrow(), "forgetOnThrow (cwd-unsupported leg)", (err) => deps.onError?.(err, { phase: "config", sessionId }));
|
|
11
12
|
const e = new Error(`Agent cwd "${internals.requestedCwd}" cannot take effect: this deployment has no executionEnvFactory (a static execution environment cannot be re-rooted per agent). Drop the cwd parameter or deploy a factory.`);
|
|
12
13
|
e.code = "config.cwd_unsupported";
|
|
@@ -37,6 +37,7 @@ import { type WiringManifest } from "../wiring-manifest.js";
|
|
|
37
37
|
import type { WriteProtectionCheck } from "../write-protect.js";
|
|
38
38
|
import type { ShellReadCheck } from "../../tools/fs/shell-read-check.js";
|
|
39
39
|
import type { CwdRef } from "../../tools/fs/fs-shared.js";
|
|
40
|
+
import type { ContainmentFace } from "../../tools/fs/containment-face.js";
|
|
40
41
|
import type { AskLane, GateSeatInputsFor, IrreversibilityTier, ParkAsk, Prepared, ReversibilityProbes, StopForDenialLimit } from "./contracts.js";
|
|
41
42
|
import type { PermissionRuleLanes } from "./permission-rule-lanes.js";
|
|
42
43
|
import { type ToolRosterDeltaSeat } from "../tool-roster.js";
|
|
@@ -167,6 +168,8 @@ export interface PrepareGateStationsInput {
|
|
|
167
168
|
/** borrowed-readonly — the leg's shell READ STATION (`ToolGateInput.shellReadCheck`), mounted by the hands phase, or
|
|
168
169
|
* undefined with no real shell: no station, nothing judged. */
|
|
169
170
|
shellReadCheck: ShellReadCheck | undefined;
|
|
171
|
+
/** borrowed-readonly — the leg's resolved read face (`undefined` with no hands), read per call into the class derivation. */
|
|
172
|
+
readFace: () => ContainmentFace | undefined;
|
|
170
173
|
/** borrowed-readonly — the per-tool irreversibility tier handed to the gate per call. */
|
|
171
174
|
irreversibilityTier: IrreversibilityTier;
|
|
172
175
|
/** borrowed-readonly — the per-tool reversibility probes handed to the gate per call. */
|
|
@@ -46,7 +46,7 @@ export function maybeHumanRejectionHalt(input) {
|
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
48
|
export function prepareGateStations(input) {
|
|
49
|
-
const { askLane, parkAsk, tools, toolRosterDeltas, toolEffects, deps, toolCallGateArmedRef, effectivePolicy, hooks, egressTools, irreversibleTools, spec, complianceDenies, harness, blockedToolCalls, inheritedAskGrants, inheritedUnavailableAsks, foldAskClasses, ancestorSandboxAdmissions, preToolContexts, gateOutcomes, batchHaltRef, blockedTracked, hookIdentity, reminderMark, hostTaskId, sessionId, ownGatePreToolUse, hookTimeoutMs, handsCwdRef, hookEnvFace, writeProtectionCheck, shellReadCheck, irreversibilityTier, reversibilityProbes, abortController, shellGatedTools, autoModeDecider, autoModeDenialTracking, askRoute, permissionMode, toolMarkSources, a2aIsolatePeers, ancestorArbitrations, runId, stopForDenialLimit, permissionRuleLane, permissionRuleOrgLane, questionToolMounted, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, delegation, notifyOwnHookCrash, frameSettlement, seatInputsFor } = input;
|
|
49
|
+
const { askLane, parkAsk, tools, toolRosterDeltas, toolEffects, deps, toolCallGateArmedRef, effectivePolicy, hooks, egressTools, irreversibleTools, spec, complianceDenies, harness, blockedToolCalls, inheritedAskGrants, inheritedUnavailableAsks, foldAskClasses, ancestorSandboxAdmissions, preToolContexts, gateOutcomes, batchHaltRef, blockedTracked, hookIdentity, reminderMark, hostTaskId, sessionId, ownGatePreToolUse, hookTimeoutMs, handsCwdRef, hookEnvFace, writeProtectionCheck, shellReadCheck, readFace, irreversibilityTier, reversibilityProbes, abortController, shellGatedTools, autoModeDecider, autoModeDenialTracking, askRoute, permissionMode, toolMarkSources, a2aIsolatePeers, ancestorArbitrations, runId, stopForDenialLimit, permissionRuleLane, permissionRuleOrgLane, questionToolMounted, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, delegation, notifyOwnHookCrash, frameSettlement, seatInputsFor } = input;
|
|
50
50
|
const gatePreToolUse = ownGatePreToolUse !== undefined ? (name, args, ctx) => ownGatePreToolUse(name, args, { ...ctx, permissionMode: permissionMode.observed }) : undefined;
|
|
51
51
|
const releasesAnnounced = new Map();
|
|
52
52
|
const onModeReleased = (info) => {
|
|
@@ -130,6 +130,7 @@ export function prepareGateStations(input) {
|
|
|
130
130
|
peerMessage: heldBy("gate.peerMessage", PEER_MESSAGE_NAMES, call),
|
|
131
131
|
...(writeProtectionCheck !== undefined ? { writeProtectionCheck } : {}),
|
|
132
132
|
...(shellReadCheck !== undefined ? { shellReadCheck } : {}),
|
|
133
|
+
readFace,
|
|
133
134
|
irreversibility: tierSeat?.value,
|
|
134
135
|
reversibilityProbe: tierSeat?.read(reversibilityProbes),
|
|
135
136
|
abortSignal: abortController.signal,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PrepareTask's LISTINGS phase: the
|
|
3
|
-
*
|
|
2
|
+
* PrepareTask's LISTINGS phase: the sub-agent tool lists' audit (`sub-agent-deny-lists.ts`),
|
|
3
|
+
* the agent-listing announce face read off the
|
|
4
4
|
* delegation tool's roster (with its deferred / excluded / removal-to-zero arms), the two listing
|
|
5
5
|
* opt-out advisories, the announced-listings mirror seeded from the once-per-session ledger, the
|
|
6
6
|
* deferred roster's ToolSearch ride, and the date_change seam. The body is the driver's stretch moved
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* the disclosure mount before it and the context-lane seats after it (a stretch
|
|
11
11
|
* with no await is extracted as a sync function — an `async` wrapper would open a microtask window).
|
|
12
12
|
*
|
|
13
|
-
* Throws pass through unchanged: `config.agent.unknown_tool` (a
|
|
14
|
-
*
|
|
13
|
+
* Throws pass through unchanged: `config.agent.unknown_tool` (a denied-tools entry that names no
|
|
14
|
+
* tool — one judgment for every list a deployment writes, in `sub-agent-deny-lists.ts`).
|
|
15
15
|
*/
|
|
16
16
|
import type { AgentTool } from "../../internal/harness.js";
|
|
17
17
|
import type { TaskSpec } from "../types.js";
|
|
@@ -1,30 +1,10 @@
|
|
|
1
1
|
import { formatLocalDate } from "../../prompts/default.js";
|
|
2
|
+
import { auditSubAgentToolLists } from "./sub-agent-deny-lists.js";
|
|
2
3
|
import { SKILL_TOOL_NAME } from "./synthetic-tools.js";
|
|
3
4
|
import { renderAgentListingDelta } from "./turn-attachments.js";
|
|
4
5
|
export function prepareListings(input) {
|
|
5
6
|
const { spec, tools, onceLedger, sessionId, toolFaceSnapshot, activeTools, resume, skillsListing, listingBudget, listingRideRef, renderWithDate, envFacts, tzValid, userTz, dateAnchor } = input;
|
|
6
|
-
|
|
7
|
-
const known = new Set(tools.flatMap((t) => [t.name, ...(t.aliases ?? [])]));
|
|
8
|
-
for (const def of spec.agents) {
|
|
9
|
-
const unknownAllow = (def.allowTools ?? []).filter((n) => n !== "*" && !known.has(n));
|
|
10
|
-
if (unknownAllow.length > 0) {
|
|
11
|
-
try {
|
|
12
|
-
onceLedger.onError(new Error(`TaskSpec.agents: agent "${def.name}" allows tool(s) ${unknownAllow.join(", ")} not present in this task's assembled roster — likely a typo (the entry would be item-filtered at spawn; the agent stays usable). Advisory only: the delegation pool can differ from this roster, so a tool mounted only on the delegation tool or produced by per-spawn extraTools makes this spurious.`), { phase: "config", sessionId });
|
|
13
|
-
}
|
|
14
|
-
catch {
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
for (const n of def.denyTools ?? []) {
|
|
18
|
-
if (n === "*")
|
|
19
|
-
continue;
|
|
20
|
-
if (!known.has(n)) {
|
|
21
|
-
const e = new Error(`TaskSpec.agents: agent "${def.name}" declares tool "${n}" in its denied tools, but no such tool exists in this deployment — fix the agent's tools list or mount the tool.`);
|
|
22
|
-
e.code = "config.agent.unknown_tool";
|
|
23
|
-
throw e;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
7
|
+
auditSubAgentToolLists({ spec, tools, onceLedger, sessionId });
|
|
28
8
|
const rosterBearingSpec = (spec.tools ?? []).find((t) => t.agentListing !== undefined && t.agentListing.length > 0);
|
|
29
9
|
const rosterFaceDeferred = rosterBearingSpec !== undefined &&
|
|
30
10
|
!(toolFaceSnapshot.exclude?.includes(rosterBearingSpec.name) ?? false) &&
|
|
@@ -94,6 +94,8 @@ export interface PrepareParkAskInput {
|
|
|
94
94
|
/** borrowed-readonly — the hand's live tracked cwd, or undefined; `current` is read at park time for the re-adjudication.
|
|
95
95
|
* Not written here. */
|
|
96
96
|
handsCwdRef: CwdRef | undefined;
|
|
97
|
+
/** borrowed-readonly — the root the live gate stamps beside the cwd ({@link import("./prepare-ask-lane.js").PrepareAskLaneInput.handsRoot}). */
|
|
98
|
+
handsRoot: string | undefined;
|
|
97
99
|
/** borrowed-readonly — the SAME seat the live gate stamps from ({@link import("./prepare-ask-lane.js").PrepareAskLaneInput.advertisedWritableDirs}): a parked call's projection re-adjudication asks one question about one call, so it must ask it against the same admitted list. Read at park time, not written here. */
|
|
98
100
|
advertisedWritableDirs: Pick<AdvertisedWritableDirs, "roots"> | undefined;
|
|
99
101
|
/** borrowed-readonly — the run's offload store, or undefined (a volatile store holding results refuses the park). */
|
|
@@ -27,7 +27,7 @@ const wordlessDemand = (toolName) => {
|
|
|
27
27
|
throw new Error(`a real-approval demand for "${toolName}" reached the park with no origin word — the origin stamp precedes every park`);
|
|
28
28
|
};
|
|
29
29
|
export function prepareParkAsk(input) {
|
|
30
|
-
const { askLane, saga, spec, deps, sessionId, checkpointStore, toolRosterDeltas, parkLaneArmed, contentAskRoutable, liveQuestionFace, mountedQuestionTool, contentAskBindings, lateStrandedAnswers, discloseStrandedAnswers, onAsk, runtimeCaps, inheritedUnavailableAsks, basePolicyForResumeEdit, budgetSnapshot, handsCwdRef, advertisedWritableDirs, offloadStore, ownedEnv, incompleteSuspendAdapter, session, sessions, suspendChainBase, maxSuspends, remoteEnvFailures, shellGatedTools, irreversibilityTier, toolMarkSources, effectiveShellGate, durableApproval, priorLedger, liveSpendRef, resourceTotal, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, ruleOffersOf, grantBindingOf, now, humanReviewRef, abortController, harness, pausedRef } = input;
|
|
30
|
+
const { askLane, saga, spec, deps, sessionId, checkpointStore, toolRosterDeltas, parkLaneArmed, contentAskRoutable, liveQuestionFace, mountedQuestionTool, contentAskBindings, lateStrandedAnswers, discloseStrandedAnswers, onAsk, runtimeCaps, inheritedUnavailableAsks, basePolicyForResumeEdit, budgetSnapshot, handsCwdRef, handsRoot, advertisedWritableDirs, offloadStore, ownedEnv, incompleteSuspendAdapter, session, sessions, suspendChainBase, maxSuspends, remoteEnvFailures, shellGatedTools, irreversibilityTier, toolMarkSources, effectiveShellGate, durableApproval, priorLedger, liveSpendRef, resourceTotal, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, ruleOffersOf, grantBindingOf, now, humanReviewRef, abortController, harness, pausedRef } = input;
|
|
31
31
|
if (askLane === undefined || saga === undefined)
|
|
32
32
|
return { parkAsk: undefined };
|
|
33
33
|
const { composedCallSignal, approvalPreviewOf } = askLane;
|
|
@@ -165,7 +165,7 @@ export function prepareParkAsk(input) {
|
|
|
165
165
|
toolCallId: req.toolCallId,
|
|
166
166
|
budget: budgetSnapshot,
|
|
167
167
|
...callFaceSeat(req.face),
|
|
168
|
-
...(handsCwdRef !== undefined ? { cwd: handsCwdRef.current } : {}),
|
|
168
|
+
...(handsCwdRef !== undefined ? { cwd: handsCwdRef.current } : {}), ...(handsRoot !== undefined ? { root: handsRoot } : {}),
|
|
169
169
|
...advertisedWritableRootsSeat(advertisedWritableDirs),
|
|
170
170
|
}, cutSignal)), cutSignal);
|
|
171
171
|
if (reprojectedRaced.tag === "aborted")
|
|
@@ -140,6 +140,8 @@ export interface PreparePolicyChainInput {
|
|
|
140
140
|
/** borrowed-readonly — LATE-BOUND: this task's own classifier instance (the pass-through test's decider term), read at chain
|
|
141
141
|
* assembly (the arming runs after this phase, and again on a steer into `auto`). */
|
|
142
142
|
autoModeDecider: () => AutoModeDecider | undefined;
|
|
143
|
+
/** borrowed-readonly — the leg's resolved read face (`undefined` with no hands), read per call into the inherited stations' class derivation. */
|
|
144
|
+
readFace: () => import("../../tools/fs/containment-face.js").ContainmentFace | undefined;
|
|
143
145
|
/** borrowed-readonly — the leg's live permission-mode seat: the mode gate policy layer and the inherited stations' arbitration
|
|
144
146
|
* read `current` per call. */
|
|
145
147
|
permissionMode: PermissionModeController;
|
|
@@ -24,7 +24,7 @@ import { createLiveSessionRules, createSessionRulePolicy } from "./session-rule-
|
|
|
24
24
|
import { thrownText } from "../thrown-value.js";
|
|
25
25
|
import { heldBy, heldValues, seatOf, wireOf } from "../tool-name-readings.js";
|
|
26
26
|
export async function preparePolicyChain(input) {
|
|
27
|
-
const { spec, deps, internals, sessionId, runId, hostTaskId, taskRootFinal, additionalRootsCanonical, executionEnv, lockedPreflight, tools, harnessTools, toolEffects, egressTools, irreversibilityTier, axisExplicitNegatives, shellGatedTools, toolMarkSources, envHandToolNames, handsCwdRef, readDenyMatcher, announceRuleStoreSkipped, questionToolMounted, runtimeCaps, inheritedAncestorRules, ancestorSessionRulesRef, inheritedParentConstraints, seedInheritedGate, skillGrants, skillSpecs, allowedToolTables, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, autoModeDecider, permissionMode, scratchpadDir, toolRosterDeltas, stopForDenialLimit, delegation, abortController, now, ownSessionRulesRef } = input;
|
|
27
|
+
const { spec, deps, internals, sessionId, runId, hostTaskId, taskRootFinal, additionalRootsCanonical, executionEnv, lockedPreflight, tools, harnessTools, toolEffects, egressTools, irreversibilityTier, axisExplicitNegatives, shellGatedTools, toolMarkSources, envHandToolNames, handsCwdRef, readDenyMatcher, announceRuleStoreSkipped, questionToolMounted, runtimeCaps, inheritedAncestorRules, ancestorSessionRulesRef, inheritedParentConstraints, seedInheritedGate, skillGrants, skillSpecs, allowedToolTables, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, autoModeDecider, readFace, permissionMode, scratchpadDir, toolRosterDeltas, stopForDenialLimit, delegation, abortController, now, ownSessionRulesRef } = input;
|
|
28
28
|
const ancestorArbitrations = new Map();
|
|
29
29
|
const arbitrateInherited = (ask, toolCallId, judged) => {
|
|
30
30
|
const verdict = arbitrateByMode(permissionMode.current, judged.classification, { origin: judged.origin, ...askMandateMember(ancestorFacts(judged.ask.action === "ask" ? judged.ask : undefined)) });
|
|
@@ -305,11 +305,13 @@ export async function preparePolicyChain(input) {
|
|
|
305
305
|
const grantBindingOf = (call, args) => skillGrantBindingMembers(call, args, skillSpecs, SKILL_TOOL_NAME);
|
|
306
306
|
const inheritedStationFacts = (call) => {
|
|
307
307
|
const seat = seatMarksOf(call);
|
|
308
|
+
const face = readFace();
|
|
308
309
|
return {
|
|
309
310
|
...INHERITED_STATION_FACTS,
|
|
310
311
|
mandateMarks: { egress: seat.egress, shellGated: false, ...(seat.tier === "always" && !seat.shellGated ? { irreversibility: "always" } : {}) },
|
|
311
312
|
...(seat.sources.size > 0 ? { toolMarkSources: seat.sources } : {}),
|
|
312
313
|
...(deps.a2a?.isolatePeers === true ? { a2aIsolatePeers: true } : {}),
|
|
314
|
+
...(face !== undefined ? { readFace: face } : {}),
|
|
313
315
|
};
|
|
314
316
|
};
|
|
315
317
|
const inheritedAskEvidence = inheritedAskRuleEvidence(deps.permissionRuleStore);
|
|
@@ -616,7 +618,7 @@ export async function preparePolicyChain(input) {
|
|
|
616
618
|
...instrumentedChain,
|
|
617
619
|
...(rewriteCapableLayers ? narrowingLayers : []),
|
|
618
620
|
];
|
|
619
|
-
const integrityGuardLayers = [createTranscriptIntegrityPolicy(), createUnverifiableDeletePolicy()];
|
|
621
|
+
const integrityGuardLayers = [createTranscriptIntegrityPolicy(), createUnverifiableDeletePolicy({ removalSeat: { env: executionEnv, additionalRoots: () => additionalRootsCanonical } })];
|
|
620
622
|
const guardedPolicyLayers = rewriteCapableLayers
|
|
621
623
|
? [...integrityGuardLayers, ...policyLayers, modeGateLayer, ...integrityGuardLayers]
|
|
622
624
|
: [...integrityGuardLayers, ...policyLayers, modeGateLayer];
|