@sema-agent/core 7.31.1 → 7.32.1

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 (87) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/dist/agents/inherited-tool-pool.d.ts +28 -0
  3. package/dist/agents/inherited-tool-pool.js +15 -0
  4. package/dist/agents/list-agents-tool.d.ts +7 -0
  5. package/dist/agents/list-agents-tool.js +9 -2
  6. package/dist/agents/peer-session-drain.d.ts +2 -0
  7. package/dist/agents/peer-session-drain.js +1 -0
  8. package/dist/agents/send-message-tool.d.ts +17 -0
  9. package/dist/agents/send-message-tool.js +11 -4
  10. package/dist/agents/subagent.d.ts +10 -1
  11. package/dist/agents/subagent.js +24 -8
  12. package/dist/core/ask-origin.d.ts +15 -6
  13. package/dist/core/ask-origin.js +15 -9
  14. package/dist/core/background-shell.d.ts +61 -0
  15. package/dist/core/background-shell.js +14 -0
  16. package/dist/core/checkpoint-store.d.ts +5 -0
  17. package/dist/core/checkpoint-store.js +3 -2
  18. package/dist/core/engine-notice.d.ts +9 -0
  19. package/dist/core/gate-lanes.js +1 -1
  20. package/dist/core/governance-codes.d.ts +1 -1
  21. package/dist/core/governance-codes.js +4 -0
  22. package/dist/core/hooks.d.ts +2 -0
  23. package/dist/core/read-root-grant-screen.d.ts +23 -0
  24. package/dist/core/read-root-grant-screen.js +17 -0
  25. package/dist/core/runner/contracts.d.ts +1 -1
  26. package/dist/core/runner/prepare-artifact.d.ts +12 -7
  27. package/dist/core/runner/prepare-artifact.js +10 -8
  28. package/dist/core/runner/prepare-ask-lane.d.ts +3 -0
  29. package/dist/core/runner/prepare-ask-lane.js +5 -1
  30. package/dist/core/runner/prepare-boundary-parks.js +5 -5
  31. package/dist/core/runner/prepare-delegation-surface.d.ts +18 -0
  32. package/dist/core/runner/prepare-delegation-surface.js +6 -25
  33. package/dist/core/runner/prepare-hands-readface.d.ts +12 -89
  34. package/dist/core/runner/prepare-hands-readface.js +14 -48
  35. package/dist/core/runner/prepare-inherited-gate.d.ts +3 -1
  36. package/dist/core/runner/prepare-inherited-gate.js +2 -1
  37. package/dist/core/runner/prepare-park-ask.js +6 -4
  38. package/dist/core/runner/prepare-policy-chain.js +4 -0
  39. package/dist/core/runner/prepare-task.js +5 -3
  40. package/dist/core/runner/read-root-grant.d.ts +66 -0
  41. package/dist/core/runner/read-root-grant.js +77 -0
  42. package/dist/core/runner/run-notification-lane.d.ts +1 -1
  43. package/dist/core/runner/run-settle-and-teardown.js +3 -11
  44. package/dist/core/runner/runtask.js +1 -1
  45. package/dist/core/runner/send-message-continuation.d.ts +45 -0
  46. package/dist/core/runner/send-message-continuation.js +27 -0
  47. package/dist/core/runner/session-read-file-states.d.ts +91 -0
  48. package/dist/core/runner/session-read-file-states.js +43 -0
  49. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +2 -0
  50. package/dist/core/store-contracts/checkpoint-store-contract.js +19 -0
  51. package/dist/core/task-registry-monitor.js +3 -0
  52. package/dist/core/task-registry-shared.d.ts +17 -4
  53. package/dist/core/task-registry.d.ts +68 -29
  54. package/dist/core/task-registry.js +78 -28
  55. package/dist/core/task-spec.d.ts +5 -3
  56. package/dist/core/tool-policy.d.ts +23 -0
  57. package/dist/core/tool-policy.js +15 -4
  58. package/dist/core/tool-spec.d.ts +2 -0
  59. package/dist/engine/execution-env/node-execution-env.d.ts +13 -3
  60. package/dist/engine/execution-env/node-execution-env.js +26 -0
  61. package/dist/index.d.ts +4 -3
  62. package/dist/index.js +2 -1
  63. package/dist/scenarios/full-body.d.ts +5 -2
  64. package/dist/scenarios/full-body.js +14 -7
  65. package/dist/tools/fs/fs-bash-background-text.d.ts +16 -0
  66. package/dist/tools/fs/fs-bash-background-text.js +22 -0
  67. package/dist/tools/fs/fs-bash.d.ts +6 -0
  68. package/dist/tools/fs/fs-bash.js +9 -27
  69. package/dist/tools/fs/fs-read.d.ts +2 -2
  70. package/dist/tools/fs/fs-search-tools.d.ts +3 -2
  71. package/dist/tools/fs/index.d.ts +8 -1
  72. package/dist/tools/fs/index.js +2 -3
  73. package/dist/tools/fs/repo-map.d.ts +2 -1
  74. package/dist/tools/fs/safety.d.ts +3 -1
  75. package/dist/tools/fs/safety.js +2 -1
  76. package/dist/tools/fs/shell-read-check.d.ts +3 -0
  77. package/dist/tools/fs/shell-read-check.js +1 -1
  78. package/dist/tools/fs/shell-read-establish.d.ts +3 -1
  79. package/dist/tools/fs/shell-read-establish.js +2 -2
  80. package/dist/tools/monitor.d.ts +5 -4
  81. package/dist/tools/monitor.js +5 -4
  82. package/dist/tools/todo.d.ts +3 -3
  83. package/dist/tools/todo.js +5 -4
  84. package/package.json +1 -1
  85. package/test/export-surface.snapshot.json +13 -1
  86. package/wire-changes/core@7.32.0.json +850 -0
  87. package/wire-changes/core@7.32.1.json +78 -0
@@ -10,6 +10,8 @@ import { createShellReadStation } from "../../tools/fs/shell-read-check.js";
10
10
  import { normalizeFileText } from "../../tools/fs/encoding.js";
11
11
  import { wholeFileRecordsFromTranscript } from "./session-file-state-replay.js";
12
12
  import { deliverEngineNotice } from "../types.js";
13
+ import { readRootGrantOver } from "./read-root-grant.js";
14
+ import { sessionSeatAnchorHolds } from "./session-read-file-states.js";
13
15
  import { rebaseWorkspacePath } from "./workspace-path.js";
14
16
  import { RosterBuilder } from "../tool-roster.js";
15
17
  const faceClampAnnouncedSinks = { read: new WeakSet(), write: new WeakSet() };
@@ -31,49 +33,6 @@ export function __resetReadFaceClampAnnouncement() {
31
33
  faceClampConsoleAnnounced.read = false;
32
34
  faceClampConsoleAnnounced.write = false;
33
35
  }
34
- export class SessionReadFileStates {
35
- seats = new Map();
36
- static CAP = 1024;
37
- get(sessionId) {
38
- const seat = this.seats.get(sessionId);
39
- if (seat !== undefined) {
40
- this.seats.delete(sessionId);
41
- this.seats.set(sessionId, seat);
42
- }
43
- return seat;
44
- }
45
- adopt(sessionId, state, anchorLeafId) {
46
- this.seats.delete(sessionId);
47
- this.seats.set(sessionId, { state, anchorLeafId });
48
- while (this.seats.size > SessionReadFileStates.CAP) {
49
- const oldest = this.seats.keys().next().value;
50
- if (oldest === undefined)
51
- break;
52
- this.seats.delete(oldest);
53
- }
54
- }
55
- reanchor(sessionId, state, anchorLeafId) {
56
- const seat = this.seats.get(sessionId);
57
- if (seat !== undefined && seat.state === state)
58
- seat.anchorLeafId = anchorLeafId;
59
- }
60
- }
61
- export async function sessionSeatAnchorHolds(getEntry, anchorLeafId, currentLeafId) {
62
- if (anchorLeafId === null)
63
- return false;
64
- let id = currentLeafId;
65
- for (let steps = 0; id !== null && steps < 100_000; steps++) {
66
- if (id === anchorLeafId)
67
- return true;
68
- const entry = await getEntry(id);
69
- if (entry === null || entry === undefined)
70
- return false;
71
- if (entry.type === "compaction")
72
- return false;
73
- id = entry.parentId;
74
- }
75
- return false;
76
- }
77
36
  function assertStringArraySeat(value, seat, entryLaw) {
78
37
  if (value === undefined)
79
38
  return;
@@ -149,6 +108,8 @@ export function resolveHandsLessReadFace(input) {
149
108
  handsLessResolvedWriteFace,
150
109
  additionalRootsCanonical: [],
151
110
  additionalReadRootsCanonical: [],
111
+ grantReadRoot: undefined,
112
+ artifactStoreRef: undefined,
152
113
  attachmentRootCanonical: undefined,
153
114
  readDenyMatcher: undefined,
154
115
  envHandToolNames: new Set(),
@@ -181,6 +142,8 @@ export async function prepareHandsMount(input) {
181
142
  const shellGatedTools = new Set();
182
143
  const additionalRootsCanonical = [];
183
144
  const additionalReadRootsCanonical = [];
145
+ let grantReadRoot;
146
+ const artifactStoreRef = { current: undefined };
184
147
  let attachmentRootCanonical;
185
148
  let readDenyMatcher;
186
149
  if (handsEnabled) {
@@ -220,6 +183,7 @@ export async function prepareHandsMount(input) {
220
183
  }
221
184
  for (const a of [additionalRootsCanonical, additionalReadRootsCanonical])
222
185
  a.splice(0, a.length, ...new Set(a));
186
+ grantReadRoot = readRootGrantOver({ env: executionEnv, writeRoots: additionalRootsCanonical, readRoots: additionalReadRootsCanonical, artifactStore: artifactStoreRef });
223
187
  const readFileState = new Map((resume?.seed.readFileState ?? []).map(([k, v]) => [rebaseRestoredPath(k), v]));
224
188
  readFileStateForCheckpoint = readFileState;
225
189
  seedContextFiles = async (files) => {
@@ -341,19 +305,18 @@ export async function prepareHandsMount(input) {
341
305
  ...(resolvedReadFace !== undefined ? { face: resolvedReadFace } : {}),
342
306
  });
343
307
  if (handsIncludeShell) {
344
- const readonlyBoundary = handsReadOnly
345
- ? { roots: [rootCanonical, ...additionalRootsCanonical, ...additionalReadRootsCanonical], cwd: rootCanonical, ...denySeat, alsoInside: createShellOverflowSpoolFence(executionEnv) }
346
- : undefined;
308
+ const spoolFence = handsReadOnly ? createShellOverflowSpoolFence(executionEnv) : undefined;
309
+ const readonlyBoundary = () => spoolFence !== undefined ? { roots: [rootCanonical, ...additionalRootsCanonical, ...additionalReadRootsCanonical], cwd: rootCanonical, ...denySeat, alsoInside: spoolFence } : undefined;
347
310
  shellReadStation = createShellReadStation({
348
311
  env: executionEnv,
349
312
  tools: new Set(handsReadOnly ? ["Bash"] : backgroundTaskToolsActive ? ["Bash", "Monitor"] : ["Bash"]),
350
313
  seatOf: () => (handsReadOnly ? "readonly" : effectiveShellGate === "classify" ? "bash_classify" : "bash_off"),
351
- boundaryOf: () => readonlyBoundary ?? shellReadBoundary(),
314
+ boundaryOf: () => readonlyBoundary() ?? shellReadBoundary(), rootsCanonical: true,
352
315
  });
353
316
  }
354
317
  const band = createHandsToolkit(executionEnv, readFileState, rootCanonical, {
355
318
  ...(additionalRootsCanonical.length > 0 ? { additionalRoots: additionalRootsCanonical } : {}),
356
- ...(additionalReadRootsCanonical.length > 0 ? { additionalReadRoots: additionalReadRootsCanonical } : {}),
319
+ additionalReadRoots: additionalReadRootsCanonical,
357
320
  ...(readDenyAdditions.length > 0 ? { readDenyPatterns: readDenyAdditions } : {}),
358
321
  ...(readDenyBuiltinCfg.tiers !== undefined ? { readDenyBuiltinTiers: readDenyBuiltinCfg.tiers } : {}),
359
322
  ...(readDenyBuiltinCfg.exclude !== undefined ? { readDenyBuiltinExclude: readDenyBuiltinCfg.exclude } : {}),
@@ -372,6 +335,7 @@ export async function prepareHandsMount(input) {
372
335
  taskScope,
373
336
  oneShot: spec.oneShot,
374
337
  ...(sessionId !== undefined ? { sessionId } : {}),
338
+ retainBackgroundProcesses: spec.retainBackgroundProcesses === true,
375
339
  ...(internals?.onTaskNotification !== undefined ? { taskNotification: internals.onTaskNotification } : {}),
376
340
  ...(internals?.detachHub !== undefined ? { detachHub: internals.detachHub } : {}),
377
341
  onToolProgress: (body) => input.toolProgressRef.current?.emit(body),
@@ -449,6 +413,8 @@ export async function prepareHandsMount(input) {
449
413
  handsLessResolvedFace,
450
414
  additionalRootsCanonical,
451
415
  additionalReadRootsCanonical,
416
+ grantReadRoot,
417
+ artifactStoreRef,
452
418
  attachmentRootCanonical,
453
419
  readDenyMatcher,
454
420
  envHandToolNames,
@@ -44,7 +44,7 @@ import type { DelegationContentSafety } from "../memory-engine/delegation-proven
44
44
  import { type SessionPermissionRules } from "../session-policy-store.js";
45
45
  import type { SessionStore } from "../session.js";
46
46
  import { type OnAsk } from "../tool-policy.js";
47
- import type { ApproverPosture, RunnerDeps, RuntimeCaps, ShellGateDoctrine, TaskEvent, TaskSpec, ToolExecuteContext } from "../types.js";
47
+ import type { ApproverPosture, RunnerDeps, RuntimeCaps, ShellGateDoctrine, TaskEvent, TaskSpec, ToolExecuteContext, ToolSpec } from "../types.js";
48
48
  import type { ActiveSkillScope } from "./active-skill-scope.js";
49
49
  import type { InheritedGate, Prepared, PrepareResume, RunInternals, StopForDenialLimit, ToolFaceSnapshot } from "./contracts.js";
50
50
  export interface PrepareInheritedGateInput {
@@ -89,6 +89,8 @@ export interface PrepareInheritedGateInput {
89
89
  /** borrowed-readonly — the frozen tool-face snapshot (the ctx forwards exclude / defer / alwaysLoad / restoreGated; the
90
90
  * full-shell predicate reads the exclusion of Bash). */
91
91
  toolFaceSnapshot: ToolFaceSnapshot;
92
+ /** borrowed-readonly — the ctx's `mountsCallerTool` seat: identity membership in the caller's `spec.tools`, pre-gate. */
93
+ mountsCallerTool: (tool: ToolSpec) => boolean;
92
94
  /** borrowed-readonly — the resolved prompt profile (ctx seat). */
93
95
  promptProfile: ToolExecuteContext["promptProfile"];
94
96
  /** borrowed-readonly — the resolved interaction posture (ctx seat; absent ⇒ no key). */
@@ -8,7 +8,7 @@ import { askApproverIdentity, durableMandateSourceOf } from "../tool-policy.js";
8
8
  import { attachRebuiltDenialTrackers, createDenialLimitStop } from "./denial-limit-arms.js";
9
9
  import { asThrownError } from "../thrown-value.js";
10
10
  export function prepareInheritedGate(input) {
11
- const { spec, deps, internals, resume, sessions, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, ownedEnv, lockedPreflight, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, autoModeSeat, approverPostureSeat, handsEnabled, reportUsage, harnessRef, skillScope, forwardEvent, subagentRetain, worktreeIsolation, requestReview, requestStopAfterTurn, enterPlanMode, reminderMark, reminderDisclosureCounts, fileHistoryEnabled, historyScope, historyRoot, historyFs, abortRun } = input;
11
+ const { spec, deps, internals, resume, sessions, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, ownedEnv, lockedPreflight, toolFaceSnapshot, mountsCallerTool, promptProfile, resolvedInteractionPosture, autoModeSeat, approverPostureSeat, handsEnabled, reportUsage, harnessRef, skillScope, forwardEvent, subagentRetain, worktreeIsolation, requestReview, requestStopAfterTurn, enterPlanMode, reminderMark, reminderDisclosureCounts, fileHistoryEnabled, historyScope, historyRoot, historyFs, abortRun } = input;
12
12
  const liveInheritedGate = internals?.inheritedGate;
13
13
  const seedInheritedGate = resume?.seed.inheritedGate;
14
14
  const inheritedAncestorRules = (() => {
@@ -205,6 +205,7 @@ export function prepareInheritedGate(input) {
205
205
  ...(readDenyAdditionsNormalized.length > 0 ? { readDenyPatterns: Object.freeze(readDenyAdditionsNormalized.map((e) => ({ ...e }))) } : {}),
206
206
  ...(spec.interactiveTools === false ? { interactiveTools: false } : {}),
207
207
  oneShot: spec.oneShot,
208
+ mountsCallerTool,
208
209
  parentSpecSeat,
209
210
  ...(internals?.questionFaceStripped === true ? { questionFaceStripped: true } : {}),
210
211
  excludeTools: toolFaceSnapshot.exclude,
@@ -1,7 +1,7 @@
1
1
  import { uuidv7 } from "../../engine/session/uuid.js";
2
2
  import { Value } from "typebox/value";
3
3
  import { ASK_USER_QUESTION_TOOL_NAME, classifyQuestionOutcome, validateAskQuestions } from "../ask-question.js";
4
- import { hasBackgroundShell, sweepBackgroundShells } from "../background-shell.js";
4
+ import { hasBackgroundShell, sweepBackgroundShells, sweepRunBoundaryBackgroundShells } from "../background-shell.js";
5
5
  import { boundInputHashOf } from "../canonical-json.js";
6
6
  import { buildRiskDescriptor, checkpointVersionFor, debitLedger, encodeAtFidelity, mintCheckpointId, mintCheckpointToken, resolveDeclaredFidelity, samePlainValue, } from "../checkpoint-store.js";
7
7
  import { pauseOf } from "../pause-registry.js";
@@ -229,8 +229,8 @@ export function prepareParkAsk(input) {
229
229
  }
230
230
  }
231
231
  else if (parkOnlyRemoteEnv !== undefined) {
232
- if (hasBackgroundShell(parkOnlyRemoteEnv) && spec.retainBackgroundProcesses !== true)
233
- await sweepBackgroundShells(parkOnlyRemoteEnv, defaultTaskRegistry);
232
+ if (hasBackgroundShell(parkOnlyRemoteEnv))
233
+ await sweepRunBoundaryBackgroundShells(parkOnlyRemoteEnv, defaultTaskRegistry, { task: spec.retainBackgroundProcesses === true });
234
234
  remoteHandle = parkOnlyHandle(parkOnlyRemoteEnv);
235
235
  }
236
236
  token = mintCheckpointToken();
@@ -277,8 +277,10 @@ export function prepareParkAsk(input) {
277
277
  }
278
278
  const approvalLedger = debitLedger(priorLedger, liveSpendRef.get?.() ?? { costMicroUsd: 0, tokens: 0, turns: 0, walltimeMs: 0 }, resourceTotal, { countSlice: false });
279
279
  const rowMatchedAskRule = matchedAskRule ?? storedShapeFacts?.matchedAskRule;
280
+ const storedShapeMandated = askMandateMember(storedShapeFacts).mandated === true;
280
281
  const clearanceFacts = {
281
- ...(carry?.mandated === true || askMandateMember(storedShapeFacts).mandated === true ? { mandated: true } : {}),
282
+ ...(carry?.mandated === true || storedShapeMandated ? { mandated: true } : {}),
283
+ ...(carry?.mandate !== undefined && !storedShapeMandated ? { mandate: carry.mandate } : {}),
282
284
  ...(rowRealApproval !== undefined ? { requiresRealApproval: true } : {}),
283
285
  ...(shadowedRule !== undefined ? { persistedRuleShadowed: shadowedRule } : {}),
284
286
  ...(askDecisionReason !== undefined ? { decisionReason: askDecisionReason } : {}),
@@ -13,6 +13,7 @@ import { createActiveSkillScopePolicy } from "./active-skill-scope.js";
13
13
  import { askMandateMember, probeMandatedAsk } from "../ask-origin.js";
14
14
  const ancestorFacts = (ask) => ({ ...(ask ?? {}), ancestorResolved: true });
15
15
  import { headlessDenyAtFold, headlessDenyAtRecheck, inheritedAskCarry, judgeInheritedClassifier, lateAskSettlementObserver, settleDenialLimitFallback } from "./denial-limit-arms.js";
16
+ import { settleReadRootGrant } from "./read-root-grant.js";
16
17
  import { askGrantShapeOf } from "./inherited-ask-grants.js";
17
18
  import { createPermissionRuleLanes, inheritedAskRuleEvidence, createRuleOffersOf } from "./permission-rule-lanes.js";
18
19
  import { createLiveSessionRules, createSessionRulePolicy } from "./session-rule-policy.js";
@@ -360,6 +361,7 @@ export async function preparePolicyChain(input) {
360
361
  ...inheritedAskCarry(judged, isLiveApproverSeat(onAskOf), ancestorTracker),
361
362
  ruleEvidence: inheritedAskEvidence,
362
363
  }, onAskOf, csignal ?? abortController.signal, lateAskSettlementObserver({ toolName: creq.toolName, toolCallId: creq.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice, onError: deps.onError }));
364
+ void settleReadRootGrant(rr.readRootGrant, undefined, { toolName: creq.toolName, toolCallId: creq.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice });
363
365
  settleDenialLimitFallback({ fallback: editFallback, mintedHere: editMintedHere, tracker: ancestorTracker, resolved: rr, headless: headlessDenyAtRecheck, stop: stopForDenialLimit, toolName: creq.toolName, toolCallId: creq.toolCallId });
364
366
  if (rr.action !== "allow")
365
367
  return rr;
@@ -436,6 +438,7 @@ export async function preparePolicyChain(input) {
436
438
  ...inheritedAskCarry(judged, isLiveApproverSeat(pc.onAsk), pc.autoMode?.denialTracking),
437
439
  ruleEvidence: inheritedAskEvidence,
438
440
  }, pc.onAsk, csignal ?? abortController.signal, lateAskSettlementObserver({ toolName: creq.toolName, toolCallId: creq.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice, onError: deps.onError }));
441
+ void settleReadRootGrant(resolved.readRootGrant, undefined, { toolName: creq.toolName, toolCallId: creq.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice });
439
442
  const askWaitMs = Math.max(0, now() - askT0);
440
443
  if (resolved.action === "deny" && resolved.approverUnavailable === true) {
441
444
  if (markInheritedUnavailable(creq.toolCallId))
@@ -506,6 +509,7 @@ export async function preparePolicyChain(input) {
506
509
  ...inheritedAskCarry(judged, isLiveApproverSeat(pc.onAsk), pc.autoMode?.denialTracking),
507
510
  ruleEvidence: inheritedAskEvidence,
508
511
  }, pc.onAsk, csignal ?? abortController.signal, lateAskSettlementObserver({ toolName: creq.toolName, toolCallId: creq.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice, onError: deps.onError }));
512
+ void settleReadRootGrant(resolved.readRootGrant, undefined, { toolName: creq.toolName, toolCallId: creq.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice });
509
513
  const askWaitMs = Math.max(0, now() - askT0);
510
514
  if (resolved.action === "deny" && resolved.approverUnavailable === true) {
511
515
  if (markInheritedUnavailable(creq.toolCallId))
@@ -7,6 +7,7 @@ import { uuidv7 } from "../../engine/session/uuid.js";
7
7
  import { brainToRuntime } from "../runtime.js";
8
8
  import { hasSessionFork } from "../session.js";
9
9
  import { SEND_MESSAGE_TOOL_NAME } from "../../agents/send-message-tool.js";
10
+ import { callerToolMembership } from "../../agents/inherited-tool-pool.js";
10
11
  import { loadAnnounceOnceLedger } from "./announce-once-ledger.js";
11
12
  import { isValidReminderMark, mintReminderMark } from "../reminder-mint.js";
12
13
  import { refuseInvalidApproverPosture } from "../auto-mode-directive.js";
@@ -271,6 +272,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
271
272
  const runId = uuidv7();
272
273
  if (runIdSink !== undefined)
273
274
  runIdSink.runId = runId;
275
+ const mountsCallerTool = callerToolMembership(spec.tools);
274
276
  const doors = prepareConfigDoors({ spec, deps, sessions, resume, internals });
275
277
  spec = doors.spec;
276
278
  const checkpointStore = resolveCheckpointStore(spec, deps), checkpointSeat = checkpointSeatOf(spec, checkpointStore);
@@ -390,7 +392,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
390
392
  let readDenyAdditionsNormalized = [];
391
393
  let handsLessResolvedFace;
392
394
  let handsLessResolvedWriteFace;
393
- const { liveInheritedGate, seedInheritedGate, inheritedAncestorRules, inheritedShellGate, inheritedParentConstraints, autoModeIntent, approverPosture: approverPostureResolved, inheritedAdmittedOrgScopes, priorOwnOrgVerdict, orgGovernedProvenance, effectiveShellGate, ownSessionRulesRef, ancestorSessionRulesRef, ancestorRulesNow, memoryAdmittedOrgScopesRef, ownOrgVerdictRef, orgAdmissionCheckpointState, faceCheckpointSection, faceCheckpointState, writeFaceCheckpointSection, f012CheckpointState, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, frozenOnQuestion, parentSpecSeat, provenanceForChildrenRef, delegationSettlementRef, inheritedGateForChildren, carrierReadFace, carrierWriteFace, fullShellReachable, gateStopRef, stopForDenialLimit, enrichSpecToolCtx } = prepareInheritedGate({ spec, deps, internals, resume, sessions, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, ownedEnv, lockedPreflight, checkpointSeat, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, autoModeSeat, approverPostureSeat, handsEnabled, reportUsage, harnessRef, skillScope, forwardEvent, subagentRetain, worktreeIsolation, requestReview, requestStopAfterTurn, enterPlanMode, reminderMark, reminderDisclosureCounts, fileHistoryEnabled, historyScope, historyRoot, historyFs, abortRun: () => { abortController.abort(); void harness.abort(); }, runtimeCaps: () => runtimeCaps, hooks: () => hooks, preToolUseObservational: () => preToolUseObservational, hookTimeoutMs: () => hookTimeoutMs, hookEnvFace: () => hookEnvFace, autoModeDecider: () => autoModeDecider, autoModeDenialTracking: () => autoModeDenialTracking, autoModeArming: () => autoModeArming, agentForkDenial: () => agentForkDenial, observersActive: () => observersActive, resolvedReadFace: () => resolvedReadFace, resolvedWriteFace: () => resolvedWriteFace, readDenyAdditionsNormalized: () => readDenyAdditionsNormalized, handsLessResolvedFace: () => handsLessResolvedFace, handsLessResolvedWriteFace: () => handsLessResolvedWriteFace, centerAdoption: () => centerAdoptionRef.current, memoryEngineSession: () => memoryEngineSession });
395
+ const { liveInheritedGate, seedInheritedGate, inheritedAncestorRules, inheritedShellGate, inheritedParentConstraints, autoModeIntent, approverPosture: approverPostureResolved, inheritedAdmittedOrgScopes, priorOwnOrgVerdict, orgGovernedProvenance, effectiveShellGate, ownSessionRulesRef, ancestorSessionRulesRef, ancestorRulesNow, memoryAdmittedOrgScopesRef, ownOrgVerdictRef, orgAdmissionCheckpointState, faceCheckpointSection, faceCheckpointState, writeFaceCheckpointSection, f012CheckpointState, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, frozenOnQuestion, parentSpecSeat, provenanceForChildrenRef, delegationSettlementRef, inheritedGateForChildren, carrierReadFace, carrierWriteFace, fullShellReachable, gateStopRef, stopForDenialLimit, enrichSpecToolCtx } = prepareInheritedGate({ spec, deps, internals, resume, sessions, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, ownedEnv, lockedPreflight, checkpointSeat, toolFaceSnapshot, mountsCallerTool, promptProfile, resolvedInteractionPosture, autoModeSeat, approverPostureSeat, handsEnabled, reportUsage, harnessRef, skillScope, forwardEvent, subagentRetain, worktreeIsolation, requestReview, requestStopAfterTurn, enterPlanMode, reminderMark, reminderDisclosureCounts, fileHistoryEnabled, historyScope, historyRoot, historyFs, abortRun: () => { abortController.abort(); void harness.abort(); }, runtimeCaps: () => runtimeCaps, hooks: () => hooks, preToolUseObservational: () => preToolUseObservational, hookTimeoutMs: () => hookTimeoutMs, hookEnvFace: () => hookEnvFace, autoModeDecider: () => autoModeDecider, autoModeDenialTracking: () => autoModeDenialTracking, autoModeArming: () => autoModeArming, agentForkDenial: () => agentForkDenial, observersActive: () => observersActive, resolvedReadFace: () => resolvedReadFace, resolvedWriteFace: () => resolvedWriteFace, readDenyAdditionsNormalized: () => readDenyAdditionsNormalized, handsLessResolvedFace: () => handsLessResolvedFace, handsLessResolvedWriteFace: () => handsLessResolvedWriteFace, centerAdoption: () => centerAdoptionRef.current, memoryEngineSession: () => memoryEngineSession });
394
396
  const { roster, blockedRef, runtimeCaps, runtimeCapsFaulted, complianceDenies, complianceDegraded, agentForkDenial, observersActive, autoModeArmReason, autoModeDecider, autoModeDenialTracking, autoModeArming, selfOrchestrationActive, workflowToolsActive, workflowSizeGuideline } = await prepareCapsAndWorkflow({ spec, deps, internals, session, sessionId, runId, hostTaskId, taskScope, taskRootFinal, model, thinking, lockedPreflight, checkpointSeat, toolEffects, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, harnessRef, frozenOnAsk, parentSpecSeat, carrierReadFace, carrierWriteFace, forwardEvent, inheritedGateForChildren, enrichSpecToolCtx, maybeOffload, requestReview, enterPlanMode, autoModeIntent, peerLaneActive, peerSendMessageBuiltIn, runnerSelf, memoryEngineSession: () => memoryEngineSession, centerAdoption: () => centerAdoptionRef.current, readDenyAdditionsNormalized: () => readDenyAdditionsNormalized });
395
397
  const tools = roster.tools;
396
398
  const pausedRef = {};
@@ -557,7 +559,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
557
559
  `(per-principal entitlement governance, not a misconfiguration — Agent(subagent_type:"fork") will refuse honestly)`), { phase: "config", sessionId });
558
560
  }
559
561
  const { lspDiagnostics, nudgeLspOnEdit, lspRunIdent, managerWired: lspManagerWired } = prepareLsp({ spec, deps, executionEnv, taskRootFinal, handsEnabled, sessionId, handsCwdRef, roster, envHandToolNames });
560
- prepareArtifact({ deps, runId, taskId: hostTaskId, sessionId, agentName: internals?.explicitAgentName, onAsk: frozenOnAsk, executionEnv, rootCanonical: attachmentRootCanonical, additionalRootsCanonical: [...additionalRootsCanonical, ...additionalReadRootsCanonical], readDenyMatcher, resolvedReadFace, handsCwdRef, roster, firstPartyOffload, envHandToolNames });
562
+ prepareArtifact({ deps, runId, taskId: hostTaskId, sessionId, agentName: internals?.explicitAgentName, onAsk: frozenOnAsk, executionEnv, rootCanonical: attachmentRootCanonical, additionalRootsCanonical: () => [...additionalRootsCanonical, ...additionalReadRootsCanonical], readDenyMatcher, resolvedReadFace, handsCwdRef, roster, firstPartyOffload, envHandToolNames, ...(handsReadFace.artifactStoreRef !== undefined ? { artifactStoreRef: handsReadFace.artifactStoreRef } : {}) });
561
563
  if (internals?.delegationProvenance !== undefined && resume !== undefined) {
562
564
  const live = internals.delegationProvenance.ref.current;
563
565
  const seed = resume.seed.delegationProvenance;
@@ -716,7 +718,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
716
718
  return undefined;
717
719
  });
718
720
  }
719
- const { askLane } = prepareAskLane({ gateMachineryActive, abortController, effectivePolicy, budgetSnapshot, handsCwdRef, advertisedWritableDirs, tools, inheritedUnavailableAsks, inheritedAskGrants, onAsk, humanReviewRef, now, ruleOffersOf, askSourceIdentity, riskAxesOf, autoModeDenialTracking, spec, deps, sessionId, runId, hooks, hookTimeoutMs, notifyOwnHookCrash });
721
+ const { askLane } = prepareAskLane({ gateMachineryActive, abortController, effectivePolicy, budgetSnapshot, handsCwdRef, advertisedWritableDirs, tools, inheritedUnavailableAsks, inheritedAskGrants, onAsk, humanReviewRef, now, ruleOffersOf, askSourceIdentity, riskAxesOf, autoModeDenialTracking, spec, deps, sessionId, runId, hooks, hookTimeoutMs, notifyOwnHookCrash, grantReadRoot: handsReadFace.grantReadRoot });
720
722
  const { saga } = prepareSuspendSaga({ gateMachineryActive, abortController, harness, checkpointStore, deps, sessionId, hostTaskId, taskScope, liveSpendRef, resume, nestedStats, internals, activeTools, outputRef, readFileStateForCheckpoint, faceCheckpointSection, writeFaceCheckpointSection, reminderMark, handsCwdRef, worktreeSessionRef, undeliveredContinuationRef, inheritedParentConstraints, seedInheritedGate, ancestorRulesNow, inheritedShellGate, autoModeIntent, approverPosture: approverPostureResolved, inheritedAdmittedOrgScopes, ownOrgVerdictRef, orgGovernedProvenance, announcedListingsRef, gitStatusRef, hookIdentity, placementRootResolved, externalContentTargetActive, remoteEnvFailures, memoryEngineSession, suspendLoopRef, ownedEnv, incompleteSuspendAdapter });
721
723
  const { suspendForResource, suspendForPlatformLimit, suspendForReview } = prepareBoundaryParks({ saga, spec, checkpointStore, abortController, harness, session, sessions, sessionId, deps, priorLedger, maxSlices, maxSuspends, resourceTotal, priorSuspendCount, suspendChainBase, humanReviewRef, liveSpendRef, now, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, pausedRef, remoteEnvFailures, resourceSuspendEligible, durableSuspendInfraReady, incompleteSuspendAdapter });
722
724
  const { parkAsk } = prepareParkAsk({ askLane, saga, spec, deps, sessionId, checkpointStore, toolRosterDeltas: rosterSeat, parkLaneArmed, contentAskRoutable, liveQuestionFace, mountedQuestionTool, contentAskBindings, lateStrandedAnswers, discloseStrandedAnswers, onAsk, runtimeCaps, inheritedUnavailableAsks, basePolicyForResumeEdit, budgetSnapshot, handsCwdRef, advertisedWritableDirs, offloadStore, ownedEnv, incompleteSuspendAdapter, session, sessions, suspendChainBase, maxSuspends, remoteEnvFailures, shellGatedTools, effectiveShellGate, durableApproval, priorLedger, liveSpendRef, resourceTotal, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, ruleOffersOf, now, humanReviewRef, abortController, harness, pausedRef });
@@ -0,0 +1,66 @@
1
+ /** The run's IN-FLIGHT read-root grant: the one place a screened grant ({@link ReadRootGrantScreen}) widens this run's
2
+ * read directories (through the hands phase's {@link GrantReadRoot}, built by {@link readRootGrantOver}), the one
3
+ * composer of the two notices, and the home of the store-isolation rule both root admission points judge by (the
4
+ * Artifact phase's mount and the grant) — machinery both phases reach down for. */
5
+ import type { ExecutionEnv } from "../../internal/harness.js";
6
+ import type { ReadRootGrantCandidate } from "../types.js";
7
+ import type { ReadRootGrantScreen } from "../read-root-grant-screen.js";
8
+ import { type RunnerDeps } from "../types.js";
9
+ /**
10
+ * THE store-isolation rule, one judgment for every root admission point (the Artifact mount over the prepare-time
11
+ * roots, an in-flight read-root grant over the granted root): a root that CONTAINS the local artifact store, lies
12
+ * INSIDE it, or IS it hands the file tools the store's ledger and stored files. Returns the first such root, or
13
+ * undefined.
14
+ *
15
+ * `storePhysical` is the store's physical path on THIS process's filesystem (`physicalPathOfExisting`, resolved
16
+ * once per run by the Artifact phase); a root is the execution env's canonical key. The judgment touches no
17
+ * filesystem — the grant runs after the approver's wait under the call's cut, and a synchronous lookup on a stalled
18
+ * store mount would block the event loop past that cut. The two are compared as identities only when both spell the
19
+ * same family (`within` reads both under the root's family); a root of the other family names a filesystem the store
20
+ * cannot be on, and no claim is made against it.
21
+ */
22
+ export declare function rootAdmittingArtifactStore(storePhysical: string, rootsCanonical: readonly string[]): string | undefined;
23
+ /** The run's cell holding the local artifact store a granted root must never admit (its physical path, as the
24
+ * Artifact phase resolved it): minted with the grant by the hands phase, written by the Artifact phase (the one
25
+ * reader of the hosting seat) once the declaration validates. */
26
+ export interface ArtifactStoreRef {
27
+ current: string | undefined;
28
+ }
29
+ /** Why the run's station applied no grant: the spelling did not resolve (or its lookup was cut), it no longer
30
+ * resolves to itself (a directory swapped for a link while the card waited), or it would admit the local artifact
31
+ * store (the store-isolation rule the Artifact phase judges its mount by). */
32
+ type GrantRefusal = "unresolvable" | "resolves_elsewhere" | "admits_artifact_store";
33
+ /** Widen THIS run's live read roots by one granted candidate: the offered spelling must still be its own canonical
34
+ * form and must not admit the local artifact store, then one deduplicated push; `root` = the spelling held. Nothing
35
+ * is pushed once `signal` has fired (a late lookup never widens). */
36
+ export type GrantReadRoot = (candidate: ReadRootGrantCandidate, signal: AbortSignal) => Promise<{
37
+ ok: true;
38
+ root: string;
39
+ } | {
40
+ ok: false;
41
+ reason: GrantRefusal;
42
+ cause: string;
43
+ }>;
44
+ /** The hands phase's {@link GrantReadRoot} over the run's live root arrays: the offered spelling must still be its own
45
+ * canonical form, must not admit the store in `artifactStore` ({@link rootAdmittingArtifactStore}), then ONE
46
+ * deduplicated push onto `readRoots` (the live read set every read face reads per call); the write roots are only
47
+ * consulted for the dedup, never widened. */
48
+ export declare function readRootGrantOver(seats: {
49
+ env: Pick<ExecutionEnv, "canonicalPath">;
50
+ writeRoots: readonly string[];
51
+ readRoots: string[];
52
+ artifactStore: ArtifactStoreRef;
53
+ }): GrantReadRoot;
54
+ /** Act on one screened grant: `granted` widens through `grant` (absent on a station that cannot widen — an inherited
55
+ * ask, a hands-less leg) and is announced; every other outcome changes nothing and is announced too — never silent.
56
+ * Returns a promise ONLY while a grant is applied, so an answer with no grant costs no extra event-loop turn. */
57
+ export declare function settleReadRootGrant(screen: ReadRootGrantScreen | undefined, grant: GrantReadRoot | undefined, at: {
58
+ toolName: string;
59
+ toolCallId: string;
60
+ sessionId: string;
61
+ runId: string;
62
+ taskId?: string;
63
+ onNotice: RunnerDeps["onNotice"];
64
+ signal?: AbortSignal;
65
+ }): Promise<void> | undefined;
66
+ export {};
@@ -0,0 +1,77 @@
1
+ import { deliverEngineNotice } from "../types.js";
2
+ import { READ_ROOT_CANDIDATE_DIR_MAX } from "../read-root-candidate.js";
3
+ import { DEFAULT_GATE_AWAIT_TIMEOUT_MS } from "../gate-bounded-await.js";
4
+ import { thrownText } from "../thrown-value.js";
5
+ import { pathFamilyOf, withinAnyRoot } from "../../tools/fs/safety.js";
6
+ export function rootAdmittingArtifactStore(storePhysical, rootsCanonical) {
7
+ const storeFamily = pathFamilyOf({ root: storePhysical });
8
+ return rootsCanonical.find((root) => pathFamilyOf({ root }) === storeFamily && (withinAnyRoot([root], storePhysical) || withinAnyRoot([storePhysical], root)));
9
+ }
10
+ export function readRootGrantOver(seats) {
11
+ const { env, writeRoots, readRoots, artifactStore } = seats;
12
+ return async (candidate, signal) => {
13
+ const c = await env.canonicalPath(candidate.dir);
14
+ if (signal.aborted)
15
+ return { ok: false, reason: "unresolvable", cause: "the lookup was cut" };
16
+ if (!c.ok)
17
+ return { ok: false, reason: "unresolvable", cause: `${c.error.code}: ${c.error.message}` };
18
+ if (c.value !== candidate.dir)
19
+ return { ok: false, reason: "resolves_elsewhere", cause: `it resolves to ${c.value}` };
20
+ const store = artifactStore.current;
21
+ if (store !== undefined && rootAdmittingArtifactStore(store, [c.value]) !== undefined)
22
+ return { ok: false, reason: "admits_artifact_store", cause: "the Artifact tool's ledger and stored files stay outside every root the file tools reach" };
23
+ if (!writeRoots.includes(c.value) && !readRoots.includes(c.value))
24
+ readRoots.push(c.value);
25
+ return { ok: true, root: c.value };
26
+ };
27
+ }
28
+ export function settleReadRootGrant(screen, grant, at) {
29
+ if (screen === undefined)
30
+ return undefined;
31
+ const ids = { toolName: at.toolName, toolCallId: at.toolCallId, sessionId: at.sessionId, runId: at.runId, ...(at.taskId !== undefined ? { taskId: at.taskId } : {}) };
32
+ const reject = (reason, offered, requested, why) => deliverEngineNotice(at.onNotice, {
33
+ code: "approval.read_root_grant_rejected",
34
+ message: `the approval for "${at.toolName}" asked to add a read directory, and nothing was added: ${why}. The approval's own verdict is unchanged.`,
35
+ detail: { reason, ...(offered !== undefined ? { offered } : {}), ...(requested !== undefined ? { requested: requested.slice(0, READ_ROOT_CANDIDATE_DIR_MAX) } : {}), ...ids },
36
+ });
37
+ if (screen.verdict === "rejected") {
38
+ const why = {
39
+ on_refusal: "a directory grant rides an approval, and this answer refused the call",
40
+ not_a_string: "the grant is not a string",
41
+ no_candidate: "this approval card offered no directory",
42
+ mismatch: "the grant is not the directory the card offered (it must be sent back exactly as offered)",
43
+ }[screen.reason];
44
+ reject(screen.reason, screen.offered, screen.requested, why);
45
+ return undefined;
46
+ }
47
+ const { candidate } = screen;
48
+ if (grant === undefined) {
49
+ reject("no_read_roots", candidate.dir, candidate.dir, "this run has no read directories to widen");
50
+ return undefined;
51
+ }
52
+ const cut = AbortSignal.any([...(at.signal !== undefined ? [at.signal] : []), AbortSignal.timeout(DEFAULT_GATE_AWAIT_TIMEOUT_MS)]);
53
+ const cutShort = new Promise((resolve) => {
54
+ const onCut = () => resolve({ ok: false, reason: "unresolvable", cause: at.signal?.aborted === true ? "the call ended before it resolved" : `it did not resolve within ${DEFAULT_GATE_AWAIT_TIMEOUT_MS}ms` });
55
+ if (cut.aborted)
56
+ onCut();
57
+ else
58
+ cut.addEventListener("abort", onCut, { once: true });
59
+ });
60
+ const attempt = grant(candidate, cut).catch((err) => ({ ok: false, reason: "unresolvable", cause: thrownText(err) }));
61
+ return Promise.race([attempt, cutShort]).then((applied) => {
62
+ if (!applied.ok) {
63
+ const why = {
64
+ unresolvable: `the directory could not be resolved (${applied.cause})`,
65
+ resolves_elsewhere: `the offered path no longer resolves to itself (${applied.cause})`,
66
+ admits_artifact_store: `the directory would admit the artifact store (${applied.cause})`,
67
+ }[applied.reason];
68
+ reject(applied.reason, candidate.dir, candidate.dir, why);
69
+ return;
70
+ }
71
+ deliverEngineNotice(at.onNotice, {
72
+ code: "approval.read_root_granted",
73
+ message: `the approval for "${at.toolName}" added ${candidate.covers === "exact" ? "the path" : "the directory"} ${candidate.dir} to this run's read directories (reads only; for the rest of this run).`,
74
+ detail: { dir: candidate.dir, ...(candidate.covers !== undefined ? { covers: candidate.covers } : {}), root: applied.root, ...ids },
75
+ });
76
+ });
77
+ }
@@ -3,7 +3,7 @@ import type { PushQueue } from "../push-queue.js";
3
3
  import type { SessionStore } from "../session.js";
4
4
  import { type NotificationInjectionOutcome, type PendingSessionNotifications, type SystemInjectionPriority, type TaskNotificationPayload } from "../task-notification.js";
5
5
  import type { RunnerDeps, TaskEvent, TaskSpec } from "../types.js";
6
- import type { SessionReadFileStates } from "./prepare-hands-readface.js";
6
+ import type { SessionReadFileStates } from "./session-read-file-states.js";
7
7
  import type { NotificationLaneBindings, Prepared, PrepareTaskFn, ResumeRun, RunInternals, RunnerDepsSeat, RunnerSelfSeat, TaskIdRef } from "./contracts.js";
8
8
  export interface RunNotificationLaneInput<R> {
9
9
  /** borrowed-readonly — the task spec: prepare's input, and the principal the peer scope is minted from. */
@@ -2,7 +2,7 @@ import { MAX_CONSECUTIVE_COMPACTION_FAILURES } from "./compaction-knobs.js";
2
2
  import { isSyntheticApiErrorMessage } from "../../internal/harness.js";
3
3
  import { adjudicateDerivedRoute, fallbackToPrimaryNotice, sameRouteIdentity } from "../../brain/route-adjudicator.js";
4
4
  import { isDegenerateCutMessage } from "../../brain/terminal-cause.js";
5
- import { hasBackgroundShell, sweepBackgroundShells } from "../background-shell.js";
5
+ import { hasBackgroundShell, sweepRunBoundaryBackgroundShells } from "../background-shell.js";
6
6
  import { resolveCheckpointStore } from "../checkpoint-store.js";
7
7
  import { hookSeatExpiredError, runHookSeat } from "../hooks.js";
8
8
  import { modelCostToPricing } from "../pricing.js";
@@ -315,17 +315,9 @@ export async function runSettleAndTeardown(input) {
315
315
  retainProcesses: spec.retainBackgroundProcesses === true,
316
316
  }), "taskRegistry.settleKilledForOwner", (e) => runner.deps.onError?.(e, { phase: "config", sessionId: prepared.sessionId }));
317
317
  const bgEnv = prepared.ownedEnv ?? runner.deps.executionEnv;
318
- const retainBgBySpec = spec.retainBackgroundProcesses === true && !ownedEnvDying;
319
- if (bgEnv && hasBackgroundShell(bgEnv) && !retainBgBySpec) {
318
+ if (bgEnv && hasBackgroundShell(bgEnv)) {
320
319
  try {
321
- const keepAlive = ownedEnvDying && bgEnv === prepared.ownedEnv
322
- ? []
323
- : [
324
- ...defaultTaskRegistry.sessionResidentShellIds(prepared.sessionId, bgEnv),
325
- ...defaultTaskRegistry.timeoutResidentShellIds(bgEnv),
326
- ...defaultTaskRegistry.retainedShellIds(bgEnv),
327
- ];
328
- await settleTeardownLeg(() => sweepBackgroundShells(bgEnv, defaultTaskRegistry, keepAlive.length > 0 ? { except: keepAlive } : undefined), "sweepBackgroundShells", (e) => runner.deps.onError?.(e, { phase: "config", sessionId: prepared.sessionId }));
320
+ await settleTeardownLeg(() => sweepRunBoundaryBackgroundShells(bgEnv, defaultTaskRegistry, { task: spec.retainBackgroundProcesses === true, envDying: ownedEnvDying }), "sweepBackgroundShells", (e) => runner.deps.onError?.(e, { phase: "config", sessionId: prepared.sessionId }));
329
321
  }
330
322
  catch (e) {
331
323
  runner.deps.onError?.(e, { phase: "config", sessionId: prepared.sessionId });
@@ -15,7 +15,7 @@ import { PushQueue } from "../push-queue.js";
15
15
  import { TtlSessionStore } from "../session-store.js";
16
16
  import { errorCodeOf } from "./assemble-result.js";
17
17
  import { effectiveDelegationFacts, prepareTask } from "./prepare-task.js";
18
- import { SessionReadFileStates } from "./prepare-hands-readface.js";
18
+ import { SessionReadFileStates } from "./session-read-file-states.js";
19
19
  import { RunnerSharedToolResultStore } from "../tool-result-store.js";
20
20
  import { PAUSE_REGISTRY } from "../pause-registry.js";
21
21
  import { toolPolicyNameSets } from "../tool-policy.js";
@@ -0,0 +1,45 @@
1
+ import type { RunnerDeps, ToolSpec } from "../types.js";
2
+ import type { RunnerSelfSeat } from "./contracts.js";
3
+ import type { SubagentTranscriptTier } from "../wiring-manifest.js";
4
+ /**
5
+ * The tier-3 revival spawner of the SendMessage mount: re-enters the
6
+ * MOUNTED delegation tool (identified by object, never by wire name) through the raw ToolSpec execute +
7
+ * the leg's ctx enricher, with a fresh toolCallId and the trusted `ctx.reviveClaim`, so a revival is the
8
+ * complete spawn chain. `undefined` when no delegation tool is mounted. Module-level: closes over
9
+ * nothing of the leg but the two values it is handed.
10
+ */
11
+ declare function buildTier3ReviveSpawn(delegationForRevive: ToolSpec | undefined, enrichSpecToolCtx: (ctx: import("../types.js").ToolExecuteContext) => import("../types.js").ToolExecuteContext): ((req: {
12
+ row: import("../background-agent-store.js").BackgroundAgentRecord;
13
+ rev: number;
14
+ prompt: string;
15
+ peerSeed?: {
16
+ hopChain: string[];
17
+ };
18
+ }) => Promise<{
19
+ isError?: boolean;
20
+ content: string;
21
+ details?: unknown;
22
+ }>) | undefined;
23
+ /**
24
+ * The tier-3 revival spawner: identify the mounted delegation
25
+ * tool by OBJECT via its `agentListing` marker (filled only by createSubagentTool; a caller's
26
+ * same-named shadow tool never carries it, so the trusted ctx.reviveClaim can never reach
27
+ * one). The call goes through the RAW ToolSpec execute + `enrichSpecToolCtx` (the same
28
+ * enrichment the injection map applies — the AgentTool adapter face forwards only
29
+ * {toolCallId, signal} and would strip the claim), so the revival is the complete spawn
30
+ * chain: notify lane, observer sink, roster, durable store. The request is built by
31
+ * SendMessage AT EXECUTE TIME (ctx-first identity); this closure holds only the tool object
32
+ * reference. Fresh toolCallId per revival (F-11③ — never SendMessage's own call id).
33
+ * `tier` is {@link sendMessageContinuationTier} over the same seats the mount receives.
34
+ */
35
+ export declare function sendMessageContinuationSeats(seats: {
36
+ specTools: readonly ToolSpec[];
37
+ agentStore: RunnerDeps["backgroundAgentStore"];
38
+ mailbox: RunnerDeps["mailboxStore"];
39
+ runner: RunnerSelfSeat;
40
+ enrichSpecToolCtx: (ctx: import("../types.js").ToolExecuteContext) => import("../types.js").ToolExecuteContext;
41
+ }): {
42
+ reviveSpawn: ReturnType<typeof buildTier3ReviveSpawn>;
43
+ tier: SubagentTranscriptTier;
44
+ };
45
+ export {};
@@ -0,0 +1,27 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { sendMessageContinuationTier } from "../../agents/send-message-tool.js";
3
+ function buildTier3ReviveSpawn(delegationForRevive, enrichSpecToolCtx) {
4
+ if (delegationForRevive === undefined)
5
+ return undefined;
6
+ return async (req) => {
7
+ const out = await delegationForRevive.execute({
8
+ description: req.row.description ?? "revived teammate",
9
+ prompt: req.prompt,
10
+ run_in_background: true,
11
+ ...(req.row.agentType !== undefined ? { subagent_type: req.row.agentType } : {}),
12
+ ...(req.row.name !== undefined ? { name: req.row.name } : {}),
13
+ ...(req.row.model !== undefined ? { model: req.row.model } : {}),
14
+ }, enrichSpecToolCtx({ toolCallId: `rv-${randomBytes(8).toString("hex")}`, reviveClaim: { row: req.row, rev: req.rev, ...(req.peerSeed !== undefined ? { peerSeed: req.peerSeed } : {}) } }));
15
+ const o = (typeof out === "string" ? { content: out } : out);
16
+ return {
17
+ content: typeof o.content === "string" ? o.content : JSON.stringify(o.content),
18
+ ...(o.isError === true ? { isError: true } : {}),
19
+ ...(o.details !== undefined ? { details: o.details } : {}),
20
+ };
21
+ };
22
+ }
23
+ export function sendMessageContinuationSeats(seats) {
24
+ const delegationForRevive = seats.specTools.find((t) => t.agentListing !== undefined);
25
+ const reviveSpawn = seats.agentStore !== undefined && seats.mailbox !== undefined ? buildTier3ReviveSpawn(delegationForRevive, seats.enrichSpecToolCtx) : undefined;
26
+ return { reviveSpawn, tier: sendMessageContinuationTier({ agentStore: seats.agentStore, mailbox: seats.mailbox, reviveSpawn, runner: seats.runner }) };
27
+ }