@sema-agent/core 7.44.0 → 7.46.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.
Files changed (159) hide show
  1. package/CHANGELOG.md +418 -1
  2. package/dist/agents/extra-tool-names.d.ts +65 -0
  3. package/dist/agents/extra-tool-names.js +124 -0
  4. package/dist/agents/subagent.d.ts +17 -5
  5. package/dist/agents/subagent.js +65 -38
  6. package/dist/agents/tool-filter.d.ts +82 -5
  7. package/dist/agents/tool-filter.js +134 -4
  8. package/dist/core/agent-config-dir.d.ts +20 -0
  9. package/dist/core/agent-config-dir.js +49 -0
  10. package/dist/core/agent-definition.d.ts +2 -2
  11. package/dist/core/allow-entry.d.ts +30 -4
  12. package/dist/core/allow-entry.js +32 -5
  13. package/dist/core/ask-origin.d.ts +23 -4
  14. package/dist/core/ask-origin.js +11 -5
  15. package/dist/core/checkpoint-store.d.ts +41 -4
  16. package/dist/core/decision-receipt.js +1 -1
  17. package/dist/core/effective-path-target.d.ts +143 -10
  18. package/dist/core/effective-path-target.js +113 -9
  19. package/dist/core/engine-notice.d.ts +25 -3
  20. package/dist/core/engine-notice.js +12 -6
  21. package/dist/core/gate-fold.js +3 -0
  22. package/dist/core/gate-lanes.js +11 -3
  23. package/dist/core/gate-outcome.d.ts +5 -0
  24. package/dist/core/gate-outcome.js +2 -0
  25. package/dist/core/governance-codes.d.ts +3 -1
  26. package/dist/core/governance-codes.js +3 -0
  27. package/dist/core/hooks.d.ts +5 -0
  28. package/dist/core/memory-engine/content-origin.d.ts +0 -34
  29. package/dist/core/memory-engine/content-origin.js +9 -4
  30. package/dist/core/memory-engine/layout.d.ts +5 -1
  31. package/dist/core/memory-engine/layout.js +1 -0
  32. package/dist/core/path-rule-binding.d.ts +65 -0
  33. package/dist/core/path-rule-binding.js +99 -0
  34. package/dist/core/permission-rule-model.d.ts +47 -18
  35. package/dist/core/permission-rule-model.js +38 -42
  36. package/dist/core/permission-rule-provider.d.ts +7 -0
  37. package/dist/core/permission-rule-provider.js +10 -3
  38. package/dist/core/permission-rule-store.d.ts +8 -0
  39. package/dist/core/permission-rule-store.js +6 -2
  40. package/dist/core/permission-rule-sync.js +2 -2
  41. package/dist/core/permission-rules.d.ts +22 -36
  42. package/dist/core/permission-rules.js +50 -40
  43. package/dist/core/real-approval.d.ts +9 -6
  44. package/dist/core/real-approval.js +5 -5
  45. package/dist/core/runner/child-tree.d.ts +110 -0
  46. package/dist/core/runner/child-tree.js +107 -0
  47. package/dist/core/runner/construction-doors.d.ts +5 -0
  48. package/dist/core/runner/construction-doors.js +6 -0
  49. package/dist/core/runner/contracts.d.ts +54 -2
  50. package/dist/core/runner/permission-rule-lanes.d.ts +34 -25
  51. package/dist/core/runner/permission-rule-lanes.js +18 -12
  52. package/dist/core/runner/persisted-path-rows.d.ts +31 -7
  53. package/dist/core/runner/persisted-path-rows.js +31 -12
  54. package/dist/core/runner/prepare-ask-lane.d.ts +6 -0
  55. package/dist/core/runner/prepare-ask-lane.js +4 -2
  56. package/dist/core/runner/prepare-config-doors.js +3 -1
  57. package/dist/core/runner/prepare-execution-env.d.ts +5 -2
  58. package/dist/core/runner/prepare-execution-env.js +11 -3
  59. package/dist/core/runner/prepare-gate-stations.d.ts +6 -0
  60. package/dist/core/runner/prepare-gate-stations.js +3 -1
  61. package/dist/core/runner/prepare-hands-readface.d.ts +8 -4
  62. package/dist/core/runner/prepare-hands-readface.js +6 -6
  63. package/dist/core/runner/prepare-inherited-gate.d.ts +8 -1
  64. package/dist/core/runner/prepare-inherited-gate.js +8 -2
  65. package/dist/core/runner/prepare-listings.d.ts +4 -4
  66. package/dist/core/runner/prepare-listings.js +2 -22
  67. package/dist/core/runner/prepare-park-ask.d.ts +5 -0
  68. package/dist/core/runner/prepare-park-ask.js +6 -4
  69. package/dist/core/runner/prepare-policy-chain.d.ts +16 -2
  70. package/dist/core/runner/prepare-policy-chain.js +14 -5
  71. package/dist/core/runner/prepare-suspend-saga.d.ts +10 -0
  72. package/dist/core/runner/prepare-suspend-saga.js +7 -1
  73. package/dist/core/runner/prepare-task.js +41 -18
  74. package/dist/core/runner/prepare-turn-wiring.d.ts +2 -0
  75. package/dist/core/runner/prepare-turn-wiring.js +2 -1
  76. package/dist/core/runner/prepare-workspace-restore.d.ts +3 -0
  77. package/dist/core/runner/prepare-workspace-restore.js +1 -0
  78. package/dist/core/runner/resume-apply.js +17 -3
  79. package/dist/core/runner/resume-checkpoint-screen.js +13 -0
  80. package/dist/core/runner/resume-policy-outcome.js +2 -1
  81. package/dist/core/runner/run-leg.js +5 -1
  82. package/dist/core/runner/run-notification-lane.js +2 -2
  83. package/dist/core/runner/runtask.d.ts +2 -0
  84. package/dist/core/runner/runtask.js +4 -2
  85. package/dist/core/runner/stream-steer-verb.js +2 -2
  86. package/dist/core/runner/sub-agent-deny-lists.d.ts +32 -0
  87. package/dist/core/runner/sub-agent-deny-lists.js +121 -0
  88. package/dist/core/runner/synthetic-tools.d.ts +1 -1
  89. package/dist/core/runner/synthetic-tools.js +3 -2
  90. package/dist/core/runner/write-protection-station.d.ts +3 -2
  91. package/dist/core/runner/write-protection-station.js +1 -0
  92. package/dist/core/runner-deps.d.ts +14 -0
  93. package/dist/core/sensitive-path-policy.d.ts +8 -0
  94. package/dist/core/sensitive-path-policy.js +18 -2
  95. package/dist/core/session-policy-store.d.ts +6 -2
  96. package/dist/core/session-policy-store.js +2 -1
  97. package/dist/core/shell-wrapper-projection.d.ts +11 -0
  98. package/dist/core/shell-wrapper-projection.js +56 -0
  99. package/dist/core/shell-wrapper-table.d.ts +144 -34
  100. package/dist/core/shell-wrapper-table.js +40 -20
  101. package/dist/core/skill-grant.d.ts +10 -2
  102. package/dist/core/skill-grant.js +20 -4
  103. package/dist/core/synthetic-read-verdict.d.ts +3 -0
  104. package/dist/core/synthetic-read-verdict.js +3 -1
  105. package/dist/core/task-notification.d.ts +12 -1
  106. package/dist/core/task-notification.js +13 -1
  107. package/dist/core/task-spec.d.ts +5 -3
  108. package/dist/core/tool-catalog-entries.d.ts +21 -0
  109. package/dist/core/tool-catalog-entries.js +9 -0
  110. package/dist/core/tool-policy.d.ts +45 -12
  111. package/dist/core/tool-policy.js +18 -20
  112. package/dist/core/tool-spec.d.ts +23 -4
  113. package/dist/core/types.d.ts +1 -1
  114. package/dist/core/types.js +1 -1
  115. package/dist/core/write-protect.d.ts +4 -0
  116. package/dist/core/write-protect.js +30 -9
  117. package/dist/index.d.ts +10 -6
  118. package/dist/index.js +9 -5
  119. package/dist/orchestration/run-spec.js +8 -7
  120. package/dist/orchestration/workflow.js +3 -2
  121. package/dist/shell/lex.d.ts +7 -0
  122. package/dist/shell/lex.js +17 -0
  123. package/dist/tools/fs/bash-program-position.d.ts +8 -87
  124. package/dist/tools/fs/bash-program-position.js +33 -69
  125. package/dist/tools/fs/bash-readonly-classifier.d.ts +17 -444
  126. package/dist/tools/fs/bash-readonly-classifier.js +169 -1225
  127. package/dist/tools/fs/index.d.ts +3 -2
  128. package/dist/tools/fs/program-read-model.d.ts +694 -0
  129. package/dist/tools/fs/program-read-model.js +1252 -0
  130. package/dist/tools/fs/program-read-model.rows.d.ts +18 -0
  131. package/dist/tools/fs/program-read-model.rows.js +613 -0
  132. package/dist/tools/fs/read-deny-home.d.ts +19 -1
  133. package/dist/tools/fs/read-deny-home.js +62 -24
  134. package/dist/tools/fs/read-deny.d.ts +54 -14
  135. package/dist/tools/fs/read-deny.js +104 -50
  136. package/dist/tools/fs/removal-targets.d.ts +83 -0
  137. package/dist/tools/fs/removal-targets.js +236 -0
  138. package/dist/tools/fs/safety.d.ts +3 -0
  139. package/dist/tools/fs/safety.js +10 -3
  140. package/dist/tools/fs/shell-read-argv.d.ts +85 -0
  141. package/dist/tools/fs/shell-read-argv.js +386 -0
  142. package/dist/tools/fs/shell-read-bindings.d.ts +273 -0
  143. package/dist/tools/fs/shell-read-bindings.js +746 -0
  144. package/dist/tools/fs/shell-read-check.js +23 -15
  145. package/dist/tools/fs/shell-read-establish.js +79 -5
  146. package/dist/tools/fs/shell-read-judge.d.ts +11 -1
  147. package/dist/tools/fs/shell-read-judge.js +9 -1
  148. package/dist/tools/fs/shell-read-plan.d.ts +31 -7
  149. package/dist/tools/fs/shell-read-plan.js +299 -393
  150. package/dist/tools/fs/shell-read-word.d.ts +29 -26
  151. package/dist/tools/fs/shell-read-word.js +30 -38
  152. package/dist/tools/fs/shell-read-write-targets.d.ts +15 -37
  153. package/dist/tools/fs/shell-read-write-targets.js +83 -136
  154. package/dist/tools/fs/shell-reentry-text.d.ts +38 -6
  155. package/dist/tools/fs/shell-reentry-text.js +90 -53
  156. package/package.json +1 -1
  157. package/test/export-surface.snapshot.json +49 -3
  158. package/wire-changes/core@7.45.0.json +1084 -0
  159. package/wire-changes/core@7.46.0.json +1522 -0
@@ -1,7 +1,6 @@
1
1
  import { ownerStoreOf } from "../permission-rule-provider.js";
2
- import { PATH_RULE_FACES, READ_RULE_TOOL, eligiblePersisted, isUsablePathBase, ruleBasesNeeded } from "../permission-rule-model.js";
3
- import { isAbsolutePathForm, pathFamilyOf } from "../../tools/fs/safety.js";
4
- import { pathRuleDenyRowOf } from "../permission-rules.js";
2
+ import { PATH_RULE_FACES, READ_RULE_TOOL, eligiblePersisted } from "../permission-rule-model.js";
3
+ import { bindPathRules, persistedRowAnchors, projectScopeIdentity } from "../path-rule-binding.js";
5
4
  import { inlineUntrusted } from "../untrusted-text.js";
6
5
  import { DEFAULT_GATE_AWAIT_TIMEOUT_MS, gateWaitFailureOf, withGateTimeout } from "../gate-bounded-await.js";
7
6
  import { thrownText } from "../thrown-value.js";
@@ -21,11 +20,11 @@ export async function persistedPathRowsOf(cfg) {
21
20
  }
22
21
  if (view.unreadable !== undefined)
23
22
  skipped(cfg, view.unreadable, view.unreadableSources);
24
- const call = { tool: READ_RULE_TOOL, cwd: cfg.root, sessionId: cfg.sessionId };
25
- const bases = { root: cfg.root, ...(cfg.home !== undefined && isAbsolutePathForm(cfg.home) ? { home: cfg.home } : {}) };
26
- const family = pathFamilyOf(bases);
23
+ cfg.onRowsDropped(view.droppedRows);
24
+ const scopeIdentity = await projectScopeIdentity(view.rules, cfg.rootCanonical, cfg.leads);
25
+ const call = { tool: READ_RULE_TOOL, cwd: cfg.root, sessionId: cfg.sessionId, ...(scopeIdentity !== undefined ? { scopeIdentity } : {}) };
27
26
  const readDirectories = [];
28
- const denyRows = [];
27
+ const denySpecs = [];
29
28
  for (const rule of view.rules) {
30
29
  if (rule.status !== "live")
31
30
  continue;
@@ -37,12 +36,15 @@ export async function persistedPathRowsOf(cfg) {
37
36
  const faces = PATH_RULE_FACES[rule.tool]?.deny;
38
37
  if (rule.behavior !== "deny" || faces === undefined || (rule.match !== "path" && rule.match !== "subpath") || !eligiblePersisted(rule, { ...call, tool: rule.tool }, "deny"))
39
38
  continue;
40
- const missing = ruleBasesNeeded(rule).filter((b) => !isUsablePathBase(b === "cwd" ? bases.root : bases[b], family));
41
- if (missing.length > 0) {
42
- cfg.onDisclosure(`the persisted deny rule ${inlineUntrusted(rule.rule, 200)} needs a ${missing.join(" / ")} base this task does not declare — the shell's file commands and the search tools do not judge it on this leg`);
39
+ denySpecs.push({ parsed: rule, ruleText: rule.rule, faces, rule });
40
+ }
41
+ const denyRows = [];
42
+ for (const row of bindPathRules(denySpecs, (spec) => persistedRowAnchors(spec.rule.scope, cfg.chainTop))) {
43
+ if (row.unanchored !== undefined) {
44
+ cfg.onDisclosure(`the persisted deny rule ${inlineUntrusted(row.rule, 200)} needs a ${row.unanchored.join(" / ")} base this task does not declare — the shell's file commands and the search tools do not judge it on this leg`);
43
45
  continue;
44
46
  }
45
- denyRows.push(pathRuleDenyRowOf({ parsed: rule, ruleText: rule.rule, faces }, bases));
47
+ denyRows.push(row);
46
48
  }
47
49
  return { readDirectories, denyRows };
48
50
  }
@@ -68,6 +70,20 @@ export function ruleStoreUnreadableNotice(onNotice, ids) {
68
70
  deliverEngineNotice(onNotice, { code: "permission.rule_store_unavailable", message: SKIPPED_STORE_SENTENCE[at](which), detail: { at, sessionId: ids.sessionId, runId: ids.runId } });
69
71
  };
70
72
  }
73
+ export function ruleRowsDroppedNotice(onNotice, ids) {
74
+ let sent = false;
75
+ return (rows) => {
76
+ if (sent || rows === undefined || rows.length === 0)
77
+ return;
78
+ sent = true;
79
+ const one = rows.length === 1;
80
+ deliverEngineNotice(onNotice, {
81
+ code: "permission.rule_row_dropped",
82
+ message: `${rows.length} of your persisted permission rule rows ${one ? "is" : "are"} scoped to a project directory that is not written as a complete absolute path, so ${one ? "it takes" : "they take"} no part in this run's checks — a deny or ask row among them protects nothing. Rewrite ${one ? "it" : "them"} with the project's full path, or remove ${one ? "it" : "them"} (this notice's detail names ${one ? "it" : "them"}).`,
83
+ detail: { rows: rows.slice(0, 8).map((r) => ({ rule: inlineUntrusted(r.rule, 200), scopeRoot: inlineUntrusted(r.scopeRoot, 200) })), count: rows.length, sessionId: ids.sessionId, runId: ids.runId },
84
+ });
85
+ };
86
+ }
71
87
  function readsPersistedPathRows(provider, principal, localOwnerDeclared) {
72
88
  return provider.partitions.durable && ((principal !== undefined && principal !== "") || localOwnerDeclared);
73
89
  }
@@ -81,9 +97,12 @@ export function persistedPathRowsOfTask(t) {
81
97
  principal: t.principal,
82
98
  sessionId: t.sessionId,
83
99
  root: t.root,
84
- home: t.home,
100
+ rootCanonical: t.rootCanonical,
101
+ leads: t.leads,
102
+ chainTop: t.chainTop,
85
103
  onDisclosure: (message) => emitTrace(t.deps.tracer, () => ({ kind: "permission.rule_store_unreadable", version: 1, taskId: t.taskId, message, ts: Date.now() })),
86
104
  onUnread: t.onUnread,
105
+ onRowsDropped: t.onRowsDropped,
87
106
  signal: AbortSignal.any([...t.signals]),
88
107
  });
89
108
  }
@@ -1,3 +1,4 @@
1
+ import type { CallTargetStamp } from "../effective-path-target.js";
1
2
  import type { AgentTool } from "../../internal/harness.js";
2
3
  import type { AdvertisedWritableDirs } from "./advertised-writable-dirs.js";
3
4
  import type { AutoModeDenialTracker } from "../auto-mode.js";
@@ -28,6 +29,11 @@ export interface PrepareAskLaneInput {
28
29
  /** borrowed-readonly — the hand's live tracked cwd, or undefined (hands-less). Read per call at RUN time (`current`),
29
30
  * never captured: the fs hand tools resolve their relative paths against this very ref. Not written here. */
30
31
  handsCwdRef: CwdRef | undefined;
32
+ /** borrowed-readonly — the root the hand tools resolve root-relative spellings against, or undefined (hands-less). */
33
+ handsRoot: string | undefined;
34
+ /** borrowed-readonly — the leg's target-reading stamp ({@link CallTargetStamp}), or undefined (hands-less): the request a
35
+ * policy judges carries a reading of its own args. Called at RUN time; a seat with nothing to stamp answers undefined. */
36
+ stampCallTarget: CallTargetStamp | undefined;
31
37
  /** 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
38
  advertisedWritableDirs: Pick<AdvertisedWritableDirs, "roots"> | undefined;
33
39
  /** borrowed-readonly — the shared mount roster; read at RUN time by the approval-preview projection (alias-aware
@@ -59,14 +59,16 @@ 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, stampCallTarget, 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
+ const stampedReq = () => ({ ...req, budget: budgetSnapshot, ...(handsCwdRef !== undefined ? { cwd: handsCwdRef.current } : {}), ...(handsRoot !== undefined ? { root: handsRoot } : {}), ...advertisedWritableRootsSeat(advertisedWritableDirs) });
70
+ const withReading = stampCallTarget?.(stampedReq(), signal);
71
+ return raceAbort(withReading === undefined ? Promise.resolve(effectivePolicy.check(stampedReq(), signal)) : withReading.then((r) => effectivePolicy.check(r, signal)), signal, () => ({
70
72
  action: "deny",
71
73
  message: "policy check aborted (task timed out or cancelled)",
72
74
  }));
@@ -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,6 +1,6 @@
1
1
  import type { ExecutionEnv } from "../../internal/harness.js";
2
2
  import type { RunnerDeps, TaskSpec } from "../types.js";
3
- import type { PrepareResume, RollbackStack, RunInternals } from "./contracts.js";
3
+ import type { ChildPlacement, PrepareResume, RollbackStack, RunInternals } from "./contracts.js";
4
4
  /**
5
5
  * The ONE reference to a factory-minted env, shared by the driver's detach guard and the rollback entry
6
6
  * this phase registers. `current` is the minted env, or `undefined` when the run has none / after the
@@ -19,7 +19,7 @@ export interface PrepareExecutionEnvInput {
19
19
  spec: Pick<TaskSpec, "taskId">;
20
20
  /** borrowed-readonly — the trusted spawn-side requests: the explicit cwd (a contract), the isolation
21
21
  * request and the parent's cwd (both threaded to the factory ctx). */
22
- internals: Pick<RunInternals, "requestedCwd" | "isolation" | "parentCwd"> | undefined;
22
+ internals: Pick<RunInternals, "requestedCwd" | "isolation" | "parentCwd" | "callerRoot" | "worktreeOffered"> | undefined;
23
23
  /** borrowed-readonly — the resume leg, read only to refuse an explicit cwd that a checkpoint workspace
24
24
  * restore would overrule. */
25
25
  resume: Pick<PrepareResume, "workspaceHandle"> | undefined;
@@ -50,5 +50,8 @@ export interface PrepareExecutionEnvResult {
50
50
  /** borrowed-readonly — the INITIAL task root (the workspace-restore phase may re-root it and returns the
51
51
  * settled root under the NEW name `taskRootFinal`). */
52
52
  taskRootInitial: string;
53
+ /** borrowed-readonly — where a CHILD landed against where its caller asked ({@link childPlacementOnLeg}); absent on a leg no
54
+ * delegation caller placed (no `callerRoot`). */
55
+ placement: ChildPlacement | undefined;
53
56
  }
54
57
  export declare function prepareExecutionEnv(input: PrepareExecutionEnvInput): Promise<PrepareExecutionEnvResult>;
@@ -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 { childPlacementOnLeg, deploymentCanRerootChild, worktreeNotHonored } 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.executionEnvFactory === undefined) {
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";
@@ -68,8 +69,15 @@ export async function prepareExecutionEnv(input) {
68
69
  }
69
70
  }
70
71
  const handsEnabled = ownedEnv !== undefined || deps.executionEnv !== undefined;
71
- const taskRootInitial = internals?.isolation === "worktree" || internals?.requestedCwd !== undefined || !deps.rootPath ? executionEnv.cwd : deps.rootPath;
72
- return { executionEnv, ownedEnvSeat, ownedEnvObservedAt, handsEnabled, taskRootInitial };
72
+ const taskRootInitial = internals?.isolation === "worktree" || internals?.requestedCwd !== undefined || internals?.worktreeOffered === true || !deps.rootPath ? executionEnv.cwd : deps.rootPath;
73
+ const placement = internals?.callerRoot === undefined ? undefined : await childPlacementOnLeg(executionEnv, { ...internals, callerRoot: internals.callerRoot }, taskRootInitial);
74
+ if (internals?.worktreeOffered === true && placement === "caller_tree") {
75
+ await settleTeardownLeg(() => forgetOnThrow(), "forgetOnThrow (worktree-not-honored leg)", (err) => deps.onError?.(err, { phase: "config", sessionId }));
76
+ await settleTeardownLeg(() => (ownedEnv && hasDestroy(ownedEnv) ? ownedEnv.destroy() : undefined), "ownedEnv.destroy (worktree-not-honored leg)", (err) => deps.onError?.(err, { phase: "config", sessionId }));
77
+ ownedEnvSeat.current = undefined;
78
+ throw worktreeNotHonored(executionEnv.cwd);
79
+ }
80
+ return { executionEnv, ownedEnvSeat, ownedEnvObservedAt, handsEnabled, taskRootInitial, placement };
73
81
  }
74
82
  function cwdConflictsRestoreError(requestedCwd) {
75
83
  const e = new Error(`RunInternals.requestedCwd ("${requestedCwd}") cannot be combined with a checkpoint workspace restore — ` +
@@ -24,6 +24,7 @@
24
24
  * gate outcome; nothing here changes a word, a source or an order — the stations are moved, not re-adjudicated.
25
25
  */
26
26
  import type { AgentHarness, AgentTool } from "../../internal/harness.js";
27
+ import type { CallTargetStamp } from "../effective-path-target.js";
27
28
  import type { AskClass } from "../ask-class.js";
28
29
  import type { Arbitration, ToolMarkSource } from "../ask-origin.js";
29
30
  import { type PermissionModeController } from "../mode-policy.js";
@@ -37,6 +38,7 @@ import { type WiringManifest } from "../wiring-manifest.js";
37
38
  import type { WriteProtectionCheck } from "../write-protect.js";
38
39
  import type { ShellReadCheck } from "../../tools/fs/shell-read-check.js";
39
40
  import type { CwdRef } from "../../tools/fs/fs-shared.js";
41
+ import type { ContainmentFace } from "../../tools/fs/containment-face.js";
40
42
  import type { AskLane, GateSeatInputsFor, IrreversibilityTier, ParkAsk, Prepared, ReversibilityProbes, StopForDenialLimit } from "./contracts.js";
41
43
  import type { PermissionRuleLanes } from "./permission-rule-lanes.js";
42
44
  import { type ToolRosterDeltaSeat } from "../tool-roster.js";
@@ -151,6 +153,8 @@ export interface PrepareGateStationsInput {
151
153
  /** borrowed-readonly — the hand's live tracked cwd, or undefined; `current` is read per screening / per post seat. Not
152
154
  * written here. */
153
155
  handsCwdRef: CwdRef | undefined;
156
+ /** borrowed-readonly — the leg's target-reading stamp, or undefined (hands-less): the gate stamps its request with it. */
157
+ stampCallTarget: CallTargetStamp | undefined;
154
158
  /** borrowed-readonly — the leg's read-deny table in force, read at a release: the active built-in tiers and the rows
155
159
  * beside them (the deployment's and the task's patterns, the read-face rule rows). Empty ⇒ no table. */
156
160
  readDenyInForce: () => {
@@ -167,6 +171,8 @@ export interface PrepareGateStationsInput {
167
171
  /** borrowed-readonly — the leg's shell READ STATION (`ToolGateInput.shellReadCheck`), mounted by the hands phase, or
168
172
  * undefined with no real shell: no station, nothing judged. */
169
173
  shellReadCheck: ShellReadCheck | undefined;
174
+ /** borrowed-readonly — the leg's resolved read face (`undefined` with no hands), read per call into the class derivation. */
175
+ readFace: () => ContainmentFace | undefined;
170
176
  /** borrowed-readonly — the per-tool irreversibility tier handed to the gate per call. */
171
177
  irreversibilityTier: IrreversibilityTier;
172
178
  /** 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, stampCallTarget, 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) => {
@@ -120,6 +120,7 @@ export function prepareGateStations(input) {
120
120
  ...(gatePreToolUse !== undefined ? { preToolUse: gatePreToolUse } : {}),
121
121
  hookTimeoutMs,
122
122
  ...(handsCwdRef !== undefined ? { trackedCwd: () => handsCwdRef.current } : {}),
123
+ ...(stampCallTarget !== undefined ? { stampCallTarget } : {}),
123
124
  ...(hookEnvFace !== undefined ? { hookEnv: hookEnvFace } : {}),
124
125
  toolFaceOf: (name) => toolCallFaceOf(toolRosterDeltas.current, name === e.toolName ? called.toolName : name),
125
126
  adjudicate,
@@ -130,6 +131,7 @@ export function prepareGateStations(input) {
130
131
  peerMessage: heldBy("gate.peerMessage", PEER_MESSAGE_NAMES, call),
131
132
  ...(writeProtectionCheck !== undefined ? { writeProtectionCheck } : {}),
132
133
  ...(shellReadCheck !== undefined ? { shellReadCheck } : {}),
134
+ readFace,
133
135
  irreversibility: tierSeat?.value,
134
136
  reversibilityProbe: tierSeat?.read(reversibilityProbes),
135
137
  abortSignal: abortController.signal,
@@ -1,6 +1,7 @@
1
1
  import { type BeforeWriteHook, type CwdRef, type PathRuleDenyRow, type ContainmentFace } from "../../tools/fs/index.js";
2
2
  import { type ReadFileState } from "../../tools/fs/safety.js";
3
3
  import { type PlacedReadDenyMatcher } from "../../tools/fs/read-deny-home.js";
4
+ import type { HeadLeads } from "../path-rule-binding.js";
4
5
  import { type ShellReadAdmission, type ShellReadCheck } from "../../tools/fs/shell-read-check.js";
5
6
  import type { ShellReadBoundary } from "../../tools/fs/shell-read-judge.js";
6
7
  import type { RunnerDeps, ShellGateDoctrine, TaskSpec } from "../types.js";
@@ -54,9 +55,12 @@ export interface PrepareHandsReadFaceInput {
54
55
  /** borrowed-readonly — the registry scope (`internals?.registryScope ?? spec.principal ??
55
56
  * "default"`, computed in the driver); the band's task-registry scope. */
56
57
  taskScope: string;
57
- /** borrowed-readonly — the deny tables' permission-rule rows: the path deny rules of the leg's caller policy and of every
58
- * ancestor's (`toolPolicyPathDenyRules`); joined to the read face's matcher and to the shell station's write face. */
59
- pathDenyRules: readonly PathRuleDenyRow[];
58
+ /** borrowed-readonly — the deny tables' permission-rule rows: the face-keyed path deny rules of the leg's caller policy and
59
+ * of every ancestor's, each bound where it was installed (`toolPolicyPathRules`), and the person's deny rows; joined to the
60
+ * read face's matcher and to the shell station's write face. */
61
+ denyRuleRows: readonly PathRuleDenyRow[];
62
+ /** borrowed-readonly — the leg's one resolution of where rule heads lead (the rows' twins, the same the gate's lanes read). */
63
+ headLeads: HeadLeads;
60
64
  /** borrowed-readonly — the two judges a shell command's write targets meet for the mode's file-command placement: the
61
65
  * leg's write-protection judge (read per judgment — it is compiled at the manifest phase, before any gate is mounted)
62
66
  * and the chain's write-target guard (`toolPolicyWriteTargetGuard`). Absent ⇒ neither (a standalone phase drive). */
@@ -93,7 +97,7 @@ export interface PrepareHandsReadFaceInput {
93
97
  * object (R-4: `deps.onError?.()` keeps its receiver; a new deps read must widen this Pick).
94
98
  * `readDenyBuiltinTiers`/`readDenyBuiltinExclude` are SNAPSHOTTED before the first await that
95
99
  * precedes the toolkit compile (the caller retains these arrays by reference). */
96
- deps: Pick<RunnerDeps, "onError" | "onNotice" | "tracer" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude" | "readFace" | "writeFace" | "permissionRuleStore" | "hands">;
100
+ deps: Pick<RunnerDeps, "onError" | "onNotice" | "tracer" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude" | "agentConfigDir" | "readFace" | "writeFace" | "permissionRuleStore" | "hands">;
97
101
  /** borrowed-readonly — the trusted spawn-side seats the band forwards: the finished-command
98
102
  * notification lane, the mid-flight detach hub, and the Runner's per-session
99
103
  * read-state seats (absent on a standalone prepare ⇒ the transcript-replay rung covers alone). */
@@ -2,9 +2,9 @@ import { emitTrace } from "../trace.js";
2
2
  import { StubExecutionEnv } from "../stub-env.js";
3
3
  import { hasBackgroundShell } from "../background-shell.js";
4
4
  import { defaultTaskRegistry } from "../task-registry.js";
5
- import { bashReversibilityProbe, activeReadDenyBuiltinTiers, readDenyBuiltinConfigOf, compileReadDeny, compileWriteDeny, withRuleRowsWhereTheyLead, readDenySeat, createHandsToolkit, deploymentFaceClampNotice, pdfModelCapabilitiesOf, inheritSessionReadFileState, backgroundOutputReadExemption, resolveContainmentFace, seedReadFileStateFromContext, seedReadFileStateFromTranscript, } from "../../tools/fs/index.js";
5
+ import { bashReversibilityProbe, activeReadDenyBuiltinTiers, readDenyBuiltinConfigOf, compileReadDeny, compileWriteDeny, rowsWhereTheyLead, readDenySeat, createHandsToolkit, deploymentFaceClampNotice, pdfModelCapabilitiesOf, inheritSessionReadFileState, backgroundOutputReadExemption, resolveContainmentFace, seedReadFileStateFromContext, seedReadFileStateFromTranscript, } from "../../tools/fs/index.js";
6
6
  import { resolveKey, sha256 } from "../../tools/fs/safety.js";
7
- import { placeReadDenyAtHome, whereItsAnsweredPrefixLeads } from "../../tools/fs/read-deny-home.js";
7
+ import { placeReadDenyAtHome } from "../../tools/fs/read-deny-home.js";
8
8
  import { createShellOverflowSpoolFence } from "../../tools/fs/fs-shared.js";
9
9
  import { createShellReadStation } from "../../tools/fs/shell-read-check.js";
10
10
  import { composeReadChecks, createFileReadStation } from "../../tools/fs/file-read-check.js";
@@ -220,8 +220,8 @@ export async function prepareHandsMount(input) {
220
220
  const seedReadFaceSection = resume?.seed.readFace;
221
221
  const readDenyAdditions = [...(deps.readDenyPatterns ?? []), ...(spec.readDenyPatterns ?? []), ...(seedReadFaceSection?.denyEntries ?? [])];
222
222
  const readDenyBuiltinCfg = readDenyBuiltinConfigOf({ tiers: deps.readDenyBuiltinTiers, exclude: deps.readDenyBuiltinExclude });
223
- const pathDenyRules = await withRuleRowsWhereTheyLead(input.pathDenyRules, (head) => whereItsAnsweredPrefixLeads(executionEnv, head));
224
- const readDenyPlacement = await placeReadDenyAtHome(executionEnv, (homes, linked) => compileReadDeny(readDenyAdditions, "readDenyPatterns", readDenyBuiltinCfg, pathDenyRules, homes, linked));
223
+ const denyRuleRows = rowsWhereTheyLead(input.denyRuleRows, await input.headLeads.resolve(input.denyRuleRows));
224
+ const readDenyPlacement = await placeReadDenyAtHome(executionEnv, (homes, linked) => compileReadDeny(readDenyAdditions, "readDenyPatterns", readDenyBuiltinCfg, denyRuleRows, homes, linked), undefined, undefined, deps.agentConfigDir);
225
225
  readDenyMatcher = readDenyPlacement.matcher;
226
226
  readDenyBuiltinTiersActive = activeReadDenyBuiltinTiers(readDenyBuiltinCfg);
227
227
  readDenyAdditionsNormalized = compileReadDeny(readDenyAdditions, "readDenyPatterns", { tiers: [] }).entries;
@@ -291,7 +291,7 @@ export async function prepareHandsMount(input) {
291
291
  }
292
292
  : undefined;
293
293
  const denySeat = readDenySeat(readDenyMatcher);
294
- const writeDeny = compileWriteDeny(pathDenyRules);
294
+ const writeDeny = compileWriteDeny(denyRuleRows);
295
295
  const writeDenySeat = writeDeny.ruleRows.length > 0 ? { writeDenyMatch: (p) => writeDeny.matchPath(p)?.pattern ?? null } : {};
296
296
  const shellReadBoundary = () => ({
297
297
  roots: [rootCanonical, ...additionalRootsCanonical, ...additionalReadRootsCanonical],
@@ -334,7 +334,7 @@ export async function prepareHandsMount(input) {
334
334
  ...(additionalRootsCanonical.length > 0 ? { additionalRoots: additionalRootsCanonical } : {}),
335
335
  additionalReadRoots: additionalReadRootsCanonical,
336
336
  ...(readDenyAdditions.length > 0 ? { readDenyPatterns: readDenyAdditions } : {}),
337
- ...(pathDenyRules.length > 0 ? { pathDenyRules } : {}),
337
+ ...(denyRuleRows.length > 0 ? { pathDenyRules: denyRuleRows } : {}),
338
338
  readDenyHomes: readDenyPlacement.homes,
339
339
  readDenyHomeLinks: readDenyPlacement.linked,
340
340
  ...(readDenyBuiltinCfg.tiers !== undefined ? { readDenyBuiltinTiers: readDenyBuiltinCfg.tiers } : {}),
@@ -43,7 +43,8 @@ import type { OwnOrgAdmissionVerdict } from "../memory-admission.js";
43
43
  import type { DelegationContentSafety } from "../memory-engine/delegation-provenance.js";
44
44
  import { type SessionPermissionRules } from "../session-policy-store.js";
45
45
  import type { SessionStore } from "../session.js";
46
- import { type OnAsk } from "../tool-policy.js";
46
+ import { type OnAsk, type ToolPolicy } from "../tool-policy.js";
47
+ import type { PathRuleBases } from "../permission-rule-model.js";
47
48
  import type { PermissionMode, RunnerDeps, RuntimeCaps, ShellGateDoctrine, TaskEvent, TaskSpec, ToolExecuteContext, ToolSpec } from "../types.js";
48
49
  import { PermissionModeController } from "../mode-policy.js";
49
50
  import { type SkillGrantTable } from "../skill-grant.js";
@@ -89,6 +90,12 @@ export interface PrepareInheritedGateInput {
89
90
  ownedEnv: ExecutionEnv | undefined;
90
91
  /** borrowed-readonly — the frozen preflight; only `toolPolicy` (the RESOLVED caller policy the children's chain carries). */
91
92
  lockedPreflight: Pick<LockedPreflight, "toolPolicy">;
93
+ /** borrowed-readonly — the chain's top leg's anchors as this leg reads them (handed on to children), and how this leg
94
+ * installs the policies its chain hands it (`chainPolicyOnLeg`). */
95
+ pathRuleAnchors: {
96
+ readonly chainTop: PathRuleBases;
97
+ readonly onLeg: (policy: ToolPolicy, position: number) => ToolPolicy;
98
+ };
92
99
  /** borrowed-readonly — the frozen tool-face snapshot (the ctx forwards exclude / defer / alwaysLoad / restoreGated; the
93
100
  * full-shell predicate reads the exclusion of Bash). */
94
101
  toolFaceSnapshot: ToolFaceSnapshot;
@@ -5,6 +5,7 @@ import { createPreToolUseConstraintPolicy } from "../hooks.js";
5
5
  import { continuesLineage } from "../session-policy-store.js";
6
6
  import { StubExecutionEnv } from "../stub-env.js";
7
7
  import { askApproverIdentity, durableMandateSourceOf } from "../tool-policy.js";
8
+ import { chainAnchorRecords } from "./child-tree.js";
8
9
  import { deliverEngineNotice } from "../types.js";
9
10
  import { PermissionModeController, modeRequestAfterResume } from "../mode-policy.js";
10
11
  import { SessionModeSeats } from "./session-mode-seats.js";
@@ -13,7 +14,7 @@ import { frozenSkillGrants } from "../skill-grant.js";
13
14
  import { attachRebuiltDenialTrackers, createDenialLimitStop } from "./denial-limit-arms.js";
14
15
  import { asThrownError } from "../thrown-value.js";
15
16
  export function prepareInheritedGate(input) {
16
- const { spec, deps, askSeat, internals, resume, sessions, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, ownedEnv, lockedPreflight, toolFaceSnapshot, mountsCallerTool, promptProfile, resolvedInteractionPosture, permissionModeSeat, handsEnabled, reportUsage, harnessRef, skillGrants, forwardEvent, subagentRetain, worktreeIsolation, requestReview, requestStopAfterTurn, enterPlanMode, reminderMark, reminderDisclosureCounts, fileHistoryEnabled, historyScope, historyRoot, historyFs, abortRun } = input;
17
+ const { spec, deps, askSeat, internals, resume, sessions, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, ownedEnv, lockedPreflight, pathRuleAnchors, toolFaceSnapshot, mountsCallerTool, promptProfile, resolvedInteractionPosture, permissionModeSeat, handsEnabled, reportUsage, harnessRef, skillGrants, forwardEvent, subagentRetain, worktreeIsolation, requestReview, requestStopAfterTurn, enterPlanMode, reminderMark, reminderDisclosureCounts, fileHistoryEnabled, historyScope, historyRoot, historyFs, abortRun } = input;
17
18
  const liveInheritedGate = internals?.inheritedGate;
18
19
  const seedInheritedGate = resume?.seed.inheritedGate;
19
20
  const inheritedAncestorRules = (() => {
@@ -35,7 +36,11 @@ export function prepareInheritedGate(input) {
35
36
  })();
36
37
  const modeSeed = recordedModeSeed(seedInheritedGate);
37
38
  const inheritedShellGate = liveInheritedGate?.shellGate ?? modeSeed.shellGate;
38
- const chainView = askSeat.reach === "none" ? liveInheritedGate?.parentConstraints?.map((pc) => (typeof pc.onAsk === "function" ? ((({ onAsk: _person, ...rest }) => rest)(pc)) : pc)) : liveInheritedGate?.parentConstraints;
39
+ const askView = askSeat.reach === "none" ? liveInheritedGate?.parentConstraints?.map((pc) => (typeof pc.onAsk === "function" ? ((({ onAsk: _person, ...rest }) => rest)(pc)) : pc)) : liveInheritedGate?.parentConstraints;
40
+ const chainView = askView?.map((pc, position) => {
41
+ const policy = pathRuleAnchors.onLeg(pc.policy, position);
42
+ return policy === pc.policy ? pc : { ...pc, policy };
43
+ });
39
44
  const inheritedParentConstraints = attachRebuiltDenialTrackers(chainView, deps.autoMode?.denialLimit);
40
45
  const parentSeat = liveInheritedGate?.permissionModeCeiling;
41
46
  const ceiling = parentSeat ?? liveInheritedGate?.permissionMode;
@@ -186,6 +191,7 @@ export function prepareInheritedGate(input) {
186
191
  permissionMode: permissionMode.current,
187
192
  permissionModeCeiling: legModeCeiling,
188
193
  ...(parentConstraints.length > 0 ? { parentConstraints } : {}),
194
+ ...chainAnchorRecords(pathRuleAnchors.chainTop, parentConstraints),
189
195
  admittedOrgScopes: memoryAdmittedOrgScopesRef.current,
190
196
  ...(orgGovernedProvenance ? { orgAdmissionGoverned: true } : {}),
191
197
  };
@@ -1,6 +1,6 @@
1
1
  /**
2
- * PrepareTask's LISTINGS phase: the per-task agent definitions' tool-name audit
3
- * (deny entries fail loud, allow entries advise), the agent-listing announce face read off the
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 deny entry naming a tool this
14
- * deployment does not mount — a typo there would silently guard nothing).
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
- if (spec.agents !== undefined && spec.agents.length > 0) {
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) &&
@@ -20,6 +20,7 @@
20
20
  * closures, so the orchestrator grows no branch.
21
21
  */
22
22
  import type { AgentHarness, AgentMessage, AgentTool, ExecutionEnv } from "../../internal/harness.js";
23
+ import type { CallTargetStamp } from "../effective-path-target.js";
23
24
  import { type OnQuestion } from "../ask-question.js";
24
25
  import { type CheckpointStore } from "../checkpoint-store.js";
25
26
  import type { GrantBindingOf } from "../skill-grant-binding.js";
@@ -94,6 +95,10 @@ export interface PrepareParkAskInput {
94
95
  /** borrowed-readonly — the hand's live tracked cwd, or undefined; `current` is read at park time for the re-adjudication.
95
96
  * Not written here. */
96
97
  handsCwdRef: CwdRef | undefined;
98
+ /** borrowed-readonly — the root the live gate stamps beside the cwd ({@link import("./prepare-ask-lane.js").PrepareAskLaneInput.handsRoot}). */
99
+ handsRoot: string | undefined;
100
+ /** borrowed-readonly — the live gate's target-reading stamp ({@link import("./prepare-ask-lane.js").PrepareAskLaneInput.stampCallTarget}). */
101
+ stampCallTarget: CallTargetStamp | undefined;
97
102
  /** 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
103
  advertisedWritableDirs: Pick<AdvertisedWritableDirs, "roots"> | undefined;
99
104
  /** 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, stampCallTarget, 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;
@@ -159,15 +159,17 @@ export function prepareParkAsk(input) {
159
159
  if (!presented.ok) {
160
160
  throw new ParkRefusal(`the stored form of "${req.toolName}"'s arguments could not be presented for re-adjudication (${describeThrown(presented.cause)})`, { cause: presented.cause });
161
161
  }
162
- const reprojectedRaced = await raceSettlementAgainstSignal(Promise.resolve(basePolicyForResumeEdit.check({
162
+ const reprojectedReq = () => ({
163
163
  toolName: req.toolName,
164
164
  args: presented.value,
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
- }, cutSignal)), cutSignal);
170
+ });
171
+ const withReading = stampCallTarget?.(reprojectedReq(), cutSignal);
172
+ const reprojectedRaced = await raceSettlementAgainstSignal(withReading === undefined ? Promise.resolve(basePolicyForResumeEdit.check(reprojectedReq(), cutSignal)) : withReading.then((r) => basePolicyForResumeEdit.check(r, cutSignal)), cutSignal);
171
173
  if (reprojectedRaced.tag === "aborted")
172
174
  return undefined;
173
175
  if (reprojectedRaced.tag === "threw")
@@ -27,6 +27,8 @@ import { type Hooks } from "../hooks.js";
27
27
  import type { LockedPreflight } from "../locked-config.js";
28
28
  import type { SessionPermissionRules } from "../session-policy-store.js";
29
29
  import { type OnAsk, type ToolPolicy } from "../tool-policy.js";
30
+ import type { PathRuleBases } from "../permission-rule-model.js";
31
+ import type { HeadLeads } from "../path-rule-binding.js";
30
32
  import type { RunnerDeps, RuntimeCaps, TaskSpec, ToolEffect } from "../types.js";
31
33
  import type { CwdRef } from "../../tools/fs/fs-shared.js";
32
34
  import type { ReadDenyMatcher } from "../../tools/fs/read-deny.js";
@@ -65,9 +67,17 @@ export interface PreparePolicyChainInput {
65
67
  additionalRootsCanonical: readonly string[];
66
68
  /** borrowed-readonly — the run's resolved env (path rules resolve in it; the sandbox declaration is read off it). */
67
69
  executionEnv: ExecutionEnv;
68
- /** borrowed-readonly — the frozen preflight: `toolPolicy` (the caller slot) and `basePolicyForResumeEdit` (the lock-aware
69
- * resume-edit override). */
70
+ /** borrowed-readonly — the frozen preflight as installed on this leg: `toolPolicy` (the caller slot) and
71
+ * `basePolicyForResumeEdit` (the lock-aware resume-edit override), their path rules anchored where this leg installed them. */
70
72
  lockedPreflight: Pick<LockedPreflight, "toolPolicy" | "basePolicyForResumeEdit">;
73
+ /** borrowed-readonly — the path rules' anchors on this leg: `leg` (this leg's root and environment home — where this
74
+ * leg's own lists are installed) and `chainTop` (the top leg's: the person's global / session rows and every `~/`). */
75
+ pathRuleAnchors: {
76
+ readonly leg: PathRuleBases;
77
+ readonly chainTop: PathRuleBases;
78
+ };
79
+ /** borrowed-readonly — the leg's one resolution of where rule heads lead (the rows' twins). */
80
+ headLeads: HeadLeads;
71
81
  /** borrowed-readonly — the run's ONE tool roster, read only here (the name audit's universe: names + aliases). */
72
82
  tools: ReadonlyArray<AgentTool>;
73
83
  /** borrowed-readonly — the harness list (placeholders keep their deferred names), the audit universe's second half. */
@@ -96,6 +106,8 @@ export interface PreparePolicyChainInput {
96
106
  readDenyMatcher: ReadDenyMatcher | undefined;
97
107
  /** borrowed-readonly — the run's one notice for a person's rule store that could not be read (`ruleStoreUnreadableNotice`). */
98
108
  announceRuleStoreSkipped: (at: "prepare" | "call") => void;
109
+ /** borrowed-readonly — the run's one notice for rows the person's store served that were left out (`ruleRowsDroppedNotice`). */
110
+ announceRuleRowsDropped: (rows: import("../permission-rule-provider.js").EffectivePermissionRules["droppedRows"]) => void;
99
111
  /** borrowed-readonly — whether the question tool mounted (the lanes' question-tool exclusion). */
100
112
  questionToolMounted: boolean;
101
113
  /** borrowed-readonly — the resolved per-principal entitlements; only `forceDurableGate` (the mandate-park facility and
@@ -140,6 +152,8 @@ export interface PreparePolicyChainInput {
140
152
  /** borrowed-readonly — LATE-BOUND: this task's own classifier instance (the pass-through test's decider term), read at chain
141
153
  * assembly (the arming runs after this phase, and again on a steer into `auto`). */
142
154
  autoModeDecider: () => AutoModeDecider | undefined;
155
+ /** borrowed-readonly — the leg's resolved read face (`undefined` with no hands), read per call into the inherited stations' class derivation. */
156
+ readFace: () => import("../../tools/fs/containment-face.js").ContainmentFace | undefined;
143
157
  /** borrowed-readonly — the leg's live permission-mode seat: the mode gate policy layer and the inherited stations' arbitration
144
158
  * read `current` per call. */
145
159
  permissionMode: PermissionModeController;