@sema-agent/core 7.4.0 → 7.5.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 (115) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/dist/agents/cascade.d.ts +2 -1
  3. package/dist/agents/peer-notices.d.ts +11 -1
  4. package/dist/agents/peer-session-drain.js +2 -0
  5. package/dist/agents/verify.d.ts +2 -1
  6. package/dist/core/ask-origin.d.ts +130 -0
  7. package/dist/core/ask-origin.js +35 -0
  8. package/dist/core/auto-mode-arming.d.ts +40 -1
  9. package/dist/core/auto-mode-arming.js +51 -3
  10. package/dist/core/auto-mode.d.ts +87 -10
  11. package/dist/core/auto-mode.js +34 -6
  12. package/dist/core/checkpoint-store.d.ts +41 -6
  13. package/dist/core/checkpoint-store.js +8 -0
  14. package/dist/core/hooks.d.ts +63 -19
  15. package/dist/core/hooks.js +37 -16
  16. package/dist/core/mcp.d.ts +47 -55
  17. package/dist/core/mcp.js +89 -31
  18. package/dist/core/park-selfcheck.js +3 -0
  19. package/dist/core/permission-rule-consent.d.ts +2 -11
  20. package/dist/core/permission-rule-consent.js +13 -62
  21. package/dist/core/permission-rule-org.d.ts +70 -54
  22. package/dist/core/permission-rule-org.js +47 -61
  23. package/dist/core/permission-rule-provider.d.ts +162 -0
  24. package/dist/core/permission-rule-provider.js +148 -0
  25. package/dist/core/permission-rule-session.d.ts +23 -19
  26. package/dist/core/permission-rule-session.js +5 -5
  27. package/dist/core/permission-rule-store.d.ts +46 -21
  28. package/dist/core/permission-rule-store.js +13 -6
  29. package/dist/core/permission-rule-sync.d.ts +2 -1
  30. package/dist/core/permission-rule-sync.js +11 -0
  31. package/dist/core/runner/assemble-result.d.ts +3 -2
  32. package/dist/core/runner/checkpoint-scope.d.ts +32 -0
  33. package/dist/core/runner/checkpoint-scope.js +4 -0
  34. package/dist/core/runner/contracts.d.ts +1878 -0
  35. package/dist/core/runner/contracts.js +1 -0
  36. package/dist/core/runner/denial-limit-arms.d.ts +57 -31
  37. package/dist/core/runner/denial-limit-arms.js +42 -17
  38. package/dist/core/runner/derived-route-fallback.d.ts +34 -0
  39. package/dist/core/runner/derived-route-fallback.js +16 -0
  40. package/dist/core/runner/prepare-acquire-reconcile.d.ts +1 -1
  41. package/dist/core/runner/prepare-announce-once.d.ts +83 -0
  42. package/dist/core/runner/prepare-announce-once.js +105 -0
  43. package/dist/core/runner/prepare-caps-and-workflow.d.ts +170 -0
  44. package/dist/core/runner/prepare-caps-and-workflow.js +255 -0
  45. package/dist/core/runner/prepare-config-doors.d.ts +2 -10
  46. package/dist/core/runner/prepare-defer-classify.d.ts +86 -0
  47. package/dist/core/runner/prepare-defer-classify.js +107 -0
  48. package/dist/core/runner/prepare-delegation-surface.d.ts +104 -0
  49. package/dist/core/runner/prepare-delegation-surface.js +144 -0
  50. package/dist/core/runner/prepare-execution-env.d.ts +54 -0
  51. package/dist/core/runner/prepare-execution-env.js +86 -0
  52. package/dist/core/runner/prepare-file-history.d.ts +95 -0
  53. package/dist/core/runner/prepare-file-history.js +383 -0
  54. package/dist/core/runner/prepare-hands-readface.d.ts +6 -8
  55. package/dist/core/runner/prepare-hands-readface.js +3 -3
  56. package/dist/core/runner/prepare-inherited-gate.d.ts +268 -0
  57. package/dist/core/runner/prepare-inherited-gate.js +266 -0
  58. package/dist/core/runner/prepare-listings.d.ts +77 -0
  59. package/dist/core/runner/prepare-listings.js +76 -0
  60. package/dist/core/runner/prepare-lsp.d.ts +55 -0
  61. package/dist/core/runner/prepare-lsp.js +27 -0
  62. package/dist/core/runner/prepare-memory.d.ts +1 -1
  63. package/dist/core/runner/prepare-offload-wrappers.d.ts +62 -0
  64. package/dist/core/runner/prepare-offload-wrappers.js +45 -0
  65. package/dist/core/runner/prepare-permission-rules.d.ts +132 -0
  66. package/dist/core/runner/prepare-permission-rules.js +140 -0
  67. package/dist/core/runner/prepare-project-context.d.ts +131 -0
  68. package/dist/core/runner/prepare-project-context.js +150 -0
  69. package/dist/core/runner/prepare-prompt-inputs.d.ts +138 -0
  70. package/dist/core/runner/prepare-prompt-inputs.js +141 -0
  71. package/dist/core/runner/prepare-protocol-tools.d.ts +91 -0
  72. package/dist/core/runner/prepare-protocol-tools.js +182 -0
  73. package/dist/core/runner/prepare-question-face.d.ts +119 -0
  74. package/dist/core/runner/prepare-question-face.js +83 -0
  75. package/dist/core/runner/prepare-run-refs.d.ts +89 -0
  76. package/dist/core/runner/prepare-run-refs.js +39 -0
  77. package/dist/core/runner/prepare-safety-scan.d.ts +3 -2
  78. package/dist/core/runner/prepare-task.d.ts +11 -1815
  79. package/dist/core/runner/prepare-task.js +138 -2542
  80. package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +111 -0
  81. package/dist/core/runner/prepare-tool-disclosure-mount.js +219 -0
  82. package/dist/core/runner/prepare-wiring-manifest.d.ts +184 -0
  83. package/dist/core/runner/prepare-wiring-manifest.js +240 -0
  84. package/dist/core/runner/prepare-workspace-restore.d.ts +1 -27
  85. package/dist/core/runner/prepare-workspace-restore.js +1 -22
  86. package/dist/core/runner/rollback-stack.d.ts +32 -0
  87. package/dist/core/runner/rollback-stack.js +30 -0
  88. package/dist/core/runner/runtask.d.ts +11 -2
  89. package/dist/core/runner/runtask.js +27 -9
  90. package/dist/core/runner/workspace-path.d.ts +33 -0
  91. package/dist/core/runner/workspace-path.js +22 -0
  92. package/dist/core/sensitive-path-policy.d.ts +16 -0
  93. package/dist/core/sensitive-path-policy.js +1 -1
  94. package/dist/core/tool-policy.d.ts +57 -9
  95. package/dist/core/tool-policy.js +11 -0
  96. package/dist/core/types.d.ts +63 -51
  97. package/dist/core/wiring-manifest.d.ts +40 -3
  98. package/dist/core/wiring-manifest.js +4 -3
  99. package/dist/core/write-protect.d.ts +13 -2
  100. package/dist/core/write-protect.js +58 -29
  101. package/dist/engine/harness/types.d.ts +38 -16
  102. package/dist/engine/harness/types.js +25 -1
  103. package/dist/engine/session/session.d.ts +3 -11
  104. package/dist/index.d.ts +10 -6
  105. package/dist/index.js +9 -5
  106. package/dist/internal/harness.d.ts +1 -0
  107. package/dist/stores/file/adoption/adopt.d.ts +1 -1
  108. package/dist/stores/file/adoption/marker.d.ts +1 -1
  109. package/dist/stores/file/permission-rule-adopt.js +4 -3
  110. package/dist/stores/file/permission-rule-store.d.ts +65 -25
  111. package/dist/stores/file/permission-rule-store.js +215 -37
  112. package/dist/stores/file/task-list-store.d.ts +1 -1
  113. package/dist/tools/fs/read-face.d.ts +1 -1
  114. package/package.json +8 -2
  115. package/test/export-surface.snapshot.json +76 -28
@@ -0,0 +1,107 @@
1
+ import { getToolContract } from "../../prompt-assembly/tool-catalog.js";
2
+ import { FULL_SHELL_CONTRACT_ID } from "../../tools/fs/index.js";
3
+ import { ASK_USER_QUESTION_TOOL_NAME } from "../ask-question.js";
4
+ import { MEMORY_ENGINE_TOOL_NAMES } from "../memory-engine/tools.js";
5
+ import { SHARED_MEMORY_TOOL_NAMES } from "../shared-memory/types.js";
6
+ import { TOOL_SEARCH_NAME, classifyDeferred } from "./tool-disclosure.js";
7
+ function explicitlyDeferredMemoryTrio(mounted, roster, deferNames) {
8
+ return mounted ? MEMORY_ENGINE_TOOL_NAMES.filter((n) => roster.some((t) => t.name === n) && (deferNames ?? []).includes(n)) : [];
9
+ }
10
+ function memoryGroupRetractionSet(builtinDeferPairNames, engineTrioInPlay) {
11
+ return new Set([...builtinDeferPairNames, ...(engineTrioInPlay ? MEMORY_ENGINE_TOOL_NAMES : [])]);
12
+ }
13
+ export function prepareDeferClassify(input) {
14
+ const { spec, deps, tools, toolFaceSnapshot, handsEnabled, fullShellReachable, promptProfile, model, mcp, a2a, sharedMemoryPairMounted, memoryEnginePairMounted, memoryRecallSegment, onceLedger, sessionId } = input;
15
+ let memoryBlock = input.memoryBlock;
16
+ if (toolFaceSnapshot.exclude !== undefined && toolFaceSnapshot.exclude.length > 0) {
17
+ const excluded = new Set(toolFaceSnapshot.exclude);
18
+ for (let i = tools.length - 1; i >= 0; i--)
19
+ if (excluded.has(tools[i].name))
20
+ tools.splice(i, 1);
21
+ }
22
+ if (handsEnabled) {
23
+ const fullShellOnRoster = tools.some((t) => t.name === "Bash" && getToolContract(t, "core").contractId === FULL_SHELL_CONTRACT_ID);
24
+ if (fullShellOnRoster !== fullShellReachable) {
25
+ const e = new Error(`internal invariant: fullShellReachable=${String(fullShellReachable)} but the post-exclusion roster ` +
26
+ `${fullShellOnRoster ? "carries" : "does not carry"} the full shell (${FULL_SHELL_CONTRACT_ID}). ` +
27
+ `A new roster-affecting mechanism was added without updating the predicate.`);
28
+ e.code = "internal.full_shell_reachable_mismatch";
29
+ throw e;
30
+ }
31
+ }
32
+ if (promptProfile === "classic") {
33
+ for (let i = 0; i < tools.length; i++) {
34
+ const t = tools[i];
35
+ if (t.descriptionClassic !== undefined)
36
+ tools[i] = { ...t, description: t.descriptionClassic };
37
+ }
38
+ }
39
+ const userToolNames = (spec.tools ?? []).map((t) => t.name);
40
+ const protocolToolNames = [...mcp.tools.map((t) => t.name), ...a2a.tools.map((t) => t.name)];
41
+ const mcpAlwaysLoadNames = mcp.tools
42
+ .filter((t) => t.mcpAlwaysLoad === true && !(toolFaceSnapshot.defer ?? []).includes(t.name))
43
+ .map((t) => t.name);
44
+ const classifyDeferredOverFace = (face, builtinDeferNames) => {
45
+ const deferredSet = classifyDeferred({
46
+ specs: spec.tools ?? [],
47
+ protocolToolNames,
48
+ fullTools: face.filter((t) => userToolNames.includes(t.name) || protocolToolNames.includes(t.name)),
49
+ deferMode: deps.deferMode,
50
+ model,
51
+ deferNames: [...(toolFaceSnapshot.defer ?? []), ...builtinDeferNames].filter((n) => face.some((t) => t.name === n)),
52
+ alwaysLoadNames: [
53
+ ASK_USER_QUESTION_TOOL_NAME,
54
+ ...(toolFaceSnapshot.alwaysLoad ?? []),
55
+ ...mcpAlwaysLoadNames,
56
+ ],
57
+ });
58
+ for (const n of [...deferredSet]) {
59
+ if (!face.some((t) => t.name === n))
60
+ deferredSet.delete(n);
61
+ }
62
+ return deferredSet;
63
+ };
64
+ const sharedMemoryPair = sharedMemoryPairMounted ? SHARED_MEMORY_TOOL_NAMES.filter((n) => tools.some((t) => t.name === n)) : [];
65
+ const memoryEnginePair = explicitlyDeferredMemoryTrio(memoryEnginePairMounted, tools, toolFaceSnapshot.defer);
66
+ const builtinDeferPairNames = [...sharedMemoryPair, ...memoryEnginePair];
67
+ let deferred;
68
+ if (builtinDeferPairNames.length > 0) {
69
+ const withoutPairs = tools.filter((t) => !builtinDeferPairNames.some((n) => n === t.name));
70
+ const d1 = classifyDeferredOverFace(tools, builtinDeferPairNames);
71
+ const d0 = classifyDeferredOverFace(withoutPairs, []);
72
+ const soleCause = d0.size === 0 && d1.size > 0;
73
+ const supportNameTaken = tools.some((t) => t.name === TOOL_SEARCH_NAME || (t.aliases ?? []).includes(TOOL_SEARCH_NAME));
74
+ if (soleCause && supportNameTaken) {
75
+ const retractNames = memoryGroupRetractionSet(builtinDeferPairNames, memoryEnginePair.length > 0);
76
+ for (let i = tools.length - 1; i >= 0; i--) {
77
+ if (retractNames.has(tools[i].name))
78
+ tools.splice(i, 1);
79
+ }
80
+ if (sharedMemoryPair.length > 0) {
81
+ onceLedger.onError(new Error(`Shared memory tools ${SHARED_MEMORY_TOOL_NAMES.join("/")} were NOT mounted: mounting them would inject ` +
82
+ `the "${TOOL_SEARCH_NAME}" tool, whose name this task already declares — the pair mounts together or not at all.`), { phase: "config", sessionId, classification: "shared-memory-not-mounted" });
83
+ }
84
+ if (memoryEnginePair.length > 0) {
85
+ if (memoryBlock !== undefined && memoryRecallSegment !== undefined) {
86
+ const segment = memoryRecallSegment;
87
+ if (memoryBlock === segment)
88
+ memoryBlock = undefined;
89
+ else if (memoryBlock.includes(`\n\n${segment}`))
90
+ memoryBlock = memoryBlock.replace(`\n\n${segment}`, "");
91
+ else if (memoryBlock.startsWith(`${segment}\n\n`))
92
+ memoryBlock = memoryBlock.slice(segment.length + 2);
93
+ }
94
+ onceLedger.onError(new Error(`Memory tools ${MEMORY_ENGINE_TOOL_NAMES.join("/")} were NOT mounted: mounting them would inject ` +
95
+ `the "${TOOL_SEARCH_NAME}" tool, whose name this task already declares — these tools mount together or not at all.`), { phase: "config", sessionId, classification: "memory-tools-not-mounted" });
96
+ }
97
+ deferred = d0;
98
+ }
99
+ else {
100
+ deferred = d1;
101
+ }
102
+ }
103
+ else {
104
+ deferred = classifyDeferredOverFace(tools, []);
105
+ }
106
+ return { deferred, memoryBlock };
107
+ }
@@ -0,0 +1,104 @@
1
+ import type { AgentTool, ExecutionEnv, WorkspaceState } from "../../internal/harness.js";
2
+ import { createSendMessageTool } from "../../agents/send-message-tool.js";
3
+ import { type PeerLaneRefs } from "../../agents/peer-session-drain.js";
4
+ import type { SubagentRetainLedger } from "../../agents/retain-ledger.js";
5
+ import { type WorktreeSessionRef } from "../../tools/worktree.js";
6
+ import type { CwdRef } from "../../tools/fs/index.js";
7
+ import { type ToolResultStore } from "../tool-result-store.js";
8
+ import type { RunnerDeps, TaskSpec, ToolEffect, ToolExecuteContext } from "../types.js";
9
+ import type { PrepareResume, RunInternals, ToolFaceSnapshot } from "./contracts.js";
10
+ /** The runner the SendMessage / AgentTranscript mounts accept — spelled as the factory's own option type so this phase
11
+ * names no runner module of its own (the narrow `RunnerSelfSeat` contract is a design decision still open). */
12
+ type DelegationMountRunner = Parameters<typeof createSendMessageTool>[0]["runner"];
13
+ export interface PrepareDelegationSurfaceInput {
14
+ /** borrowed-readonly — the REBOUND spec. Read: `tools` (the shadow checks and the revival spawner's delegation-tool
15
+ * lookup), `oneShot` (the read/kill faces' completion promises), `retainBackgroundProcesses` (the Monitor receipt),
16
+ * `handsReadOnly` is NOT read here (the hands mount decided the surface). Never mutated. */
17
+ spec: Pick<TaskSpec, "tools" | "oneShot" | "retainBackgroundProcesses">;
18
+ /** borrowed-readonly — the deployment seats the mounts read: `workflowRunStore` / `backgroundAgentStore` / `mailboxStore` (the
19
+ * read/kill faces and the tier-3 revival seams), `rosterStore`, `peerAdmission`, `peerDirectory`, `onNotice`,
20
+ * `onBackgroundChildEvent`, `tracer`. */
21
+ deps: Pick<RunnerDeps, "workflowRunStore" | "backgroundAgentStore" | "mailboxStore" | "rosterStore" | "peerAdmission" | "peerDirectory" | "onNotice" | "onBackgroundChildEvent" | "tracer">;
22
+ /** borrowed-readonly — the trusted spawn-side channel: `parentNotify` (the delegated-child door), the uplink / sibling /
23
+ * identity seats the SendMessage mount forwards, `onTaskNotification`, `onSubagentSpawn`. Never mutated. */
24
+ internals: Pick<RunInternals, "parentNotify" | "parentPeerRef" | "parentRetainLedger" | "explicitAgentName" | "parentTaskId" | "parentSessionId" | "peerSelfRef" | "peerInboundChainRef" | "onTaskNotification" | "onSubagentSpawn"> | undefined;
25
+ /** borrowed-readonly — the resume leg, or undefined: `seed.activeWorktree` seeds the worktree session (checkpoint first). */
26
+ resume: Pick<PrepareResume, "seed"> | undefined;
27
+ /** borrowed-readonly — the acquired session id (the mounts' session axis). */
28
+ sessionId: string;
29
+ /** borrowed-readonly — the per-task identity (the mounts' owner axis). */
30
+ hostTaskId: string;
31
+ /** borrowed-readonly — the registry scope the mounts register in. */
32
+ taskScope: string;
33
+ /** borrowed-readonly — the SETTLED task root (the worktree tools' repoRoot). */
34
+ taskRootFinal: string;
35
+ /** borrowed-readonly — the run's resolved env the Monitor and worktree tools operate. */
36
+ executionEnv: ExecutionEnv;
37
+ /** borrowed-readonly — the trusted Runner self-reference the SendMessage / AgentTranscript mounts execute through; undefined
38
+ * when prepareTask runs standalone (then neither mounts). */
39
+ runnerSelf: DelegationMountRunner | undefined;
40
+ /** borrowed-readonly — the background/workflow door's first half: the hands mount decided a real shell with write hands. */
41
+ backgroundTaskToolsActive: boolean;
42
+ /** borrowed-readonly — the door's second half: the Workflow tool is on the roster. */
43
+ workflowToolsActive: boolean;
44
+ /** borrowed-readonly — the peer lane's mount verdict for this leg. */
45
+ peerLaneActive: boolean;
46
+ /** borrowed-readonly — whether the BUILT-IN SendMessage is on this face (the ListAgents mount's second term). */
47
+ peerSendMessageBuiltIn: boolean;
48
+ /** borrowed-mutable — the lane's late-bound facts; the SendMessage mount hands the SAME object to its peer seats (identity is
49
+ * the contract: the wiring station fills `askEffective` later). Not written here. */
50
+ peerLaneRefs: PeerLaneRefs;
51
+ /** borrowed-mutable — the run's shared roster (`Prepared.tools`, whose JSDoc carries the writer table). Writers here: the
52
+ * eight mounts (TaskOutput, TaskStop, SendMessage, AgentTranscript, ListAgents, Monitor, the worktree pair, the offload
53
+ * reader), every one a push. */
54
+ tools: AgentTool[];
55
+ /** borrowed-mutable — the effect collection (`Prepared.toolEffects`). Writers here: TaskOutput → read, TaskStop / SendMessage
56
+ * (built-in mount only) / Monitor / EnterWorktree / ExitWorktree → write. */
57
+ toolEffects: Map<string, ToolEffect>;
58
+ /** borrowed-mutable — the explicit-negative axis declarations. Writer here: the built-in SendMessage mount's `egress: false`
59
+ * (report-face only). */
60
+ axisExplicitNegatives: Map<string, {
61
+ irreversible?: false;
62
+ egress?: false;
63
+ }>;
64
+ /** borrowed-mutable — the env-hand tool-name set the read posture reads. Writers here: Monitor and the two worktree tools
65
+ * (membership minted at the mount). */
66
+ envHandToolNames: Set<string>;
67
+ /** borrowed-readonly — the frozen tool-face snapshot (`exclude` feeds the ListAgents mount predicate). */
68
+ toolFaceSnapshot: Pick<ToolFaceSnapshot, "exclude">;
69
+ /** borrowed-readonly — the large-result wrapper for first-party mounts. */
70
+ firstPartyOffload: (tool: AgentTool) => AgentTool;
71
+ /** borrowed-readonly — the run's offload store, or undefined when offload is disabled (then no reader mounts and the
72
+ * read/kill faces spill nowhere). */
73
+ offloadStore: ToolResultStore | undefined;
74
+ /** borrowed-readonly — the retain ledger the SendMessage mount continues children through (absent ⇒ honest not-retained). */
75
+ subagentRetain: SubagentRetainLedger | undefined;
76
+ /** borrowed-readonly — the caller-tool ctx enrichment: the revival spawner re-enters the delegation tool through it, and
77
+ * the SendMessage / AgentTranscript mounts activate their ctx-first arms with it. */
78
+ enrichSpecToolCtx: (ctx: ToolExecuteContext) => ToolExecuteContext;
79
+ /** borrowed-mutable — the hands mount's tracked-cwd seat: present ⇔ real shell + write hands; the worktree pair's mount
80
+ * predicate. Not written by this phase, but the tools it mounts over it ARE its writers (design/238 R-3): EnterWorktree and
81
+ * ExitWorktree assign `current` at run time (the hands band's shared cwd then points at the worktree, and back); the
82
+ * Monitor only reads it. The cell must stay the hands mount's own object — a copy would strand those writes. */
83
+ handsCwdRef: CwdRef | undefined;
84
+ /** borrowed-readonly — the workspace-state snapshot (the worktree seed's continuation rung), or undefined. */
85
+ wsSnapshot: Pick<WorkspaceState, "activeWorktree"> | undefined;
86
+ /** borrowed-readonly — the rebase of a snapshot path onto this leg's root. */
87
+ rebaseWsPath: (p: string) => string;
88
+ /** borrowed-readonly — the rebase of a checkpoint path onto this leg's root. */
89
+ rebaseRestoredPath: (p: string) => string;
90
+ /** borrowed-mutable — the workspace-state settle seat; the worktree mount stamps `restoredWorktreeDir` on it once. */
91
+ workspaceStateSettle: {
92
+ restoredWorktreeDir?: string;
93
+ } | undefined;
94
+ }
95
+ export interface PrepareDelegationSurfaceResult {
96
+ /** owned — whether the background/workflow door is open (TaskOutput / TaskStop / AgentTranscript ride it). */
97
+ delegationSurfaceActive: boolean;
98
+ /** borrowed-mutable — the EnterWorktree session's shared serializable state (`Prepared.worktreeSessionRef`), minted iff the
99
+ * worktree pair mounted. Writers: the worktree tools at run time (Enter/Exit); the suspend serializer reads it. */
100
+ worktreeSessionRef: WorktreeSessionRef | undefined;
101
+ }
102
+ /** The M6 phase body — prepareTask's delegation-surface stretch, verbatim (see the module header). */
103
+ export declare function prepareDelegationSurface(input: PrepareDelegationSurfaceInput): PrepareDelegationSurfaceResult;
104
+ export {};
@@ -0,0 +1,144 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { createAgentTranscriptTool, AGENT_TRANSCRIPT_TOOL_NAME } from "../../agents/agent-transcript-tool.js";
3
+ import { createSendMessageTool, SEND_MESSAGE_TOOL_NAME } from "../../agents/send-message-tool.js";
4
+ import { listAgentsMountable, mountListAgents, peerLaneSendMessageSeats } from "../../agents/peer-session-drain.js";
5
+ import { createMonitorTool } from "../../tools/monitor.js";
6
+ import { createWorktreeTools } from "../../tools/worktree.js";
7
+ import { createTaskOutputTool, createTaskStopTool, defaultTaskRegistry } from "../task-registry.js";
8
+ import { createReadToolResultTool } from "../tool-result-store.js";
9
+ import { emitTrace } from "../trace.js";
10
+ import { deliverEngineNotice } from "../types.js";
11
+ function buildTier3ReviveSpawn(delegationForRevive, enrichSpecToolCtx) {
12
+ if (delegationForRevive === undefined)
13
+ return undefined;
14
+ return async (req) => {
15
+ const out = await delegationForRevive.execute({
16
+ description: req.row.description ?? "revived teammate",
17
+ prompt: req.prompt,
18
+ run_in_background: true,
19
+ ...(req.row.agentType !== undefined ? { subagent_type: req.row.agentType } : {}),
20
+ ...(req.row.name !== undefined ? { name: req.row.name } : {}),
21
+ ...(req.row.model !== undefined ? { model: req.row.model } : {}),
22
+ }, enrichSpecToolCtx({ toolCallId: `rv-${randomBytes(8).toString("hex")}`, reviveClaim: { row: req.row, rev: req.rev, ...(req.peerSeed !== undefined ? { peerSeed: req.peerSeed } : {}) } }));
23
+ const o = (typeof out === "string" ? { content: out } : out);
24
+ return {
25
+ content: typeof o.content === "string" ? o.content : JSON.stringify(o.content),
26
+ ...(o.isError === true ? { isError: true } : {}),
27
+ ...(o.details !== undefined ? { details: o.details } : {}),
28
+ };
29
+ };
30
+ }
31
+ export function prepareDelegationSurface(input) {
32
+ const { spec, deps, internals, resume, sessionId, hostTaskId, taskScope, taskRootFinal, executionEnv, runnerSelf, backgroundTaskToolsActive, workflowToolsActive, peerLaneActive, peerSendMessageBuiltIn, peerLaneRefs, tools, toolEffects, axisExplicitNegatives, envHandToolNames, toolFaceSnapshot, firstPartyOffload, offloadStore, subagentRetain, enrichSpecToolCtx, handsCwdRef, wsSnapshot, rebaseWsPath, rebaseRestoredPath, workspaceStateSettle } = input;
33
+ let worktreeSessionRef;
34
+ const delegationSurfaceActive = backgroundTaskToolsActive || workflowToolsActive;
35
+ if (delegationSurfaceActive || internals?.parentNotify !== undefined || peerLaneActive) {
36
+ if (delegationSurfaceActive) {
37
+ toolEffects.set("TaskOutput", "read");
38
+ toolEffects.set("TaskStop", "write");
39
+ tools.push(firstPartyOffload(createTaskOutputTool({ registry: defaultTaskRegistry, owner: hostTaskId, scope: taskScope, sessionId, workflowStore: deps.workflowRunStore, agentStore: deps.backgroundAgentStore, notificationWired: internals?.onTaskNotification !== undefined, oneShot: spec.oneShot, toolResultStore: offloadStore })), firstPartyOffload(createTaskStopTool({ registry: defaultTaskRegistry, owner: hostTaskId, scope: taskScope, sessionId, workflowStore: deps.workflowRunStore, agentStore: deps.backgroundAgentStore })));
40
+ }
41
+ if (runnerSelf && !(spec.tools ?? []).some((t) => t.name === SEND_MESSAGE_TOOL_NAME)) {
42
+ toolEffects.set(SEND_MESSAGE_TOOL_NAME, "write");
43
+ axisExplicitNegatives.set(SEND_MESSAGE_TOOL_NAME, { ...axisExplicitNegatives.get(SEND_MESSAGE_TOOL_NAME), egress: false });
44
+ const delegationForRevive = (spec.tools ?? []).find((t) => t.agentListing !== undefined);
45
+ const reviveSpawn = deps.backgroundAgentStore !== undefined && deps.mailboxStore !== undefined ? buildTier3ReviveSpawn(delegationForRevive, enrichSpecToolCtx) : undefined;
46
+ tools.push(firstPartyOffload(createSendMessageTool({
47
+ runner: runnerSelf,
48
+ registry: defaultTaskRegistry,
49
+ ...(subagentRetain ? { retain: subagentRetain } : {}),
50
+ owner: hostTaskId,
51
+ scope: taskScope,
52
+ ...(sessionId !== undefined ? { sessionId } : {}),
53
+ ...(internals?.onTaskNotification !== undefined ? { notify: internals.onTaskNotification } : {}), ...(spec.oneShot !== undefined ? { oneShot: spec.oneShot } : {}), retrievalToolMounted: delegationSurfaceActive,
54
+ ...(internals?.onSubagentSpawn !== undefined ? { sink: internals.onSubagentSpawn } : {}),
55
+ ...(internals?.parentNotify !== undefined
56
+ ? { uplink: internals.parentNotify, ...(internals.parentPeerRef !== undefined ? { uplinkRecipient: internals.parentPeerRef } : {}) }
57
+ : {}),
58
+ ...(internals?.explicitAgentName !== undefined ? { senderName: internals.explicitAgentName } : {}),
59
+ ...(internals?.parentRetainLedger !== undefined ? { siblingRetain: internals.parentRetainLedger } : {}),
60
+ ...(internals?.parentTaskId !== undefined ? { parentTaskId: internals.parentTaskId } : {}),
61
+ ...(internals?.parentSessionId !== undefined ? { parentSessionId: internals.parentSessionId } : {}),
62
+ enrichCtx: enrichSpecToolCtx,
63
+ ...(deps.rosterStore !== undefined ? { roster: deps.rosterStore } : {}),
64
+ ...(deps.peerAdmission !== undefined ? { admission: deps.peerAdmission } : {}),
65
+ ...(deps.onNotice !== undefined ? { onNotice: deps.onNotice } : {}),
66
+ ...(internals?.peerSelfRef !== undefined ? { peerSelf: internals.peerSelfRef } : {}),
67
+ ...(internals?.peerInboundChainRef !== undefined ? { peerInbound: internals.peerInboundChainRef } : {}),
68
+ ...(deps.onBackgroundChildEvent ? { onBackgroundChildEvent: deps.onBackgroundChildEvent } : {}),
69
+ ...(deps.backgroundAgentStore !== undefined ? { agentStore: deps.backgroundAgentStore } : {}),
70
+ ...(deps.mailboxStore !== undefined ? { mailbox: deps.mailboxStore } : {}),
71
+ ...(reviveSpawn !== undefined ? { reviveSpawn } : {}),
72
+ ...(peerLaneActive ? peerLaneSendMessageSeats({ peerDirectory: deps.peerDirectory, sessionId, scope: taskScope, ...(internals?.explicitAgentName !== undefined ? { name: internals.explicitAgentName } : {}), refs: peerLaneRefs, listingMounted: listAgentsMountable({ exclude: toolFaceSnapshot.exclude, specTools: spec.tools ?? [] }) }) : {}),
73
+ onNotifyError: (f) => emitTrace(deps.tracer, () => ({ kind: "observer.notify_failed", version: 1, taskId: hostTaskId, site: f.site, message: f.error.message, ts: Date.now() })),
74
+ onTranscriptIntegrityGap: (handle, scope) => deliverEngineNotice(deps.onNotice, {
75
+ code: "delegation.transcript_integrity",
76
+ message: `delegation transcript integrity: agent ${handle}'s durable row binds a transcript session the session store attests is gone — the declared transcript durability is being contradicted (check the session store wiring/retention)`,
77
+ detail: { handle, ...(scope !== undefined ? { scope } : {}) },
78
+ }),
79
+ })));
80
+ if (delegationSurfaceActive && !(spec.tools ?? []).some((t) => t.name === AGENT_TRANSCRIPT_TOOL_NAME)) {
81
+ tools.push(firstPartyOffload(createAgentTranscriptTool({
82
+ runner: runnerSelf,
83
+ registry: defaultTaskRegistry,
84
+ agentStore: deps.backgroundAgentStore,
85
+ owner: hostTaskId,
86
+ scope: taskScope,
87
+ ...(sessionId !== undefined ? { sessionId } : {}),
88
+ enrichCtx: enrichSpecToolCtx,
89
+ onTranscriptIntegrityGap: (handle, scope) => deliverEngineNotice(deps.onNotice, {
90
+ code: "delegation.transcript_integrity",
91
+ message: `delegation transcript integrity: agent ${handle}'s durable row binds a transcript session the session store attests is gone — the declared transcript durability is being contradicted (check the session store wiring/retention)`,
92
+ detail: { handle, ...(scope !== undefined ? { scope } : {}) },
93
+ }),
94
+ })));
95
+ }
96
+ }
97
+ }
98
+ const listAgents = peerLaneActive && peerSendMessageBuiltIn && listAgentsMountable({ exclude: toolFaceSnapshot.exclude, specTools: spec.tools ?? [] }) ? mountListAgents({ peerDirectory: deps.peerDirectory, sessionId, scope: taskScope, hostTaskId, ...(internals?.parentTaskId !== undefined ? { parentTaskId: internals.parentTaskId } : {}), ...(internals?.parentSessionId !== undefined ? { parentSessionId: internals.parentSessionId } : {}), registry: defaultTaskRegistry, ...(deps.rosterStore !== undefined ? { roster: deps.rosterStore } : {}), specTools: spec.tools ?? [], toolEffects }) : undefined;
99
+ if (listAgents !== undefined)
100
+ tools.push(firstPartyOffload(listAgents));
101
+ if (backgroundTaskToolsActive) {
102
+ toolEffects.set("Monitor", "write");
103
+ envHandToolNames.add("Monitor");
104
+ tools.push(firstPartyOffload(createMonitorTool(executionEnv, {
105
+ registry: defaultTaskRegistry,
106
+ owner: hostTaskId,
107
+ scope: taskScope,
108
+ ...(sessionId !== undefined ? { sessionId } : {}),
109
+ ...(internals?.onTaskNotification !== undefined ? { onTaskNotification: internals.onTaskNotification } : {}),
110
+ ...(handsCwdRef !== undefined ? { cwdRef: handsCwdRef } : {}),
111
+ ...(offloadStore !== undefined ? { toolResultStore: offloadStore } : {}),
112
+ ...(spec.retainBackgroundProcesses === true ? { retainBackgroundProcesses: true } : {}),
113
+ })));
114
+ }
115
+ if (handsCwdRef !== undefined) {
116
+ toolEffects.set("EnterWorktree", "write");
117
+ toolEffects.set("ExitWorktree", "write");
118
+ worktreeSessionRef = {
119
+ ...(resume?.seed.activeWorktree
120
+ ? {
121
+ current: {
122
+ ...resume.seed.activeWorktree,
123
+ worktreeDir: rebaseRestoredPath(resume.seed.activeWorktree.worktreeDir),
124
+ originalCwd: rebaseRestoredPath(resume.seed.activeWorktree.originalCwd),
125
+ },
126
+ }
127
+ : wsSnapshot?.activeWorktree !== undefined
128
+ ? {
129
+ current: {
130
+ ...wsSnapshot.activeWorktree,
131
+ worktreeDir: rebaseWsPath(wsSnapshot.activeWorktree.worktreeDir),
132
+ originalCwd: rebaseWsPath(wsSnapshot.activeWorktree.originalCwd),
133
+ },
134
+ }
135
+ : {}),
136
+ };
137
+ if (workspaceStateSettle !== undefined)
138
+ workspaceStateSettle.restoredWorktreeDir = worktreeSessionRef.current?.worktreeDir;
139
+ tools.push(...createWorktreeTools(executionEnv, { repoRoot: taskRootFinal, cwdRef: handsCwdRef, session: worktreeSessionRef }).map((t) => (envHandToolNames.add(t.name), firstPartyOffload(t))));
140
+ }
141
+ if (offloadStore)
142
+ tools.push(createReadToolResultTool(offloadStore));
143
+ return { delegationSurfaceActive, worktreeSessionRef };
144
+ }
@@ -0,0 +1,54 @@
1
+ import type { ExecutionEnv } from "../../internal/harness.js";
2
+ import type { RunnerDeps, TaskSpec } from "../types.js";
3
+ import type { PrepareResume, RollbackStack, RunInternals } from "./contracts.js";
4
+ /**
5
+ * The ONE reference to a factory-minted env, shared by the driver's detach guard and the rollback entry
6
+ * this phase registers. `current` is the minted env, or `undefined` when the run has none / after the
7
+ * driver detached an aliasing one. Written by: this phase (once, at the mint) and the driver's worktree
8
+ * guard (the detach). Read by: the rollback entry (at unwind), and the driver, which settles it into a
9
+ * plain `ownedEnv` binding once the guard has passed.
10
+ */
11
+ export interface OwnedEnvSeat {
12
+ current: ExecutionEnv | undefined;
13
+ }
14
+ export interface PrepareExecutionEnvInput {
15
+ /** borrowed-readonly — the deployment's env seats: the per-task factory (wins), the static env, the
16
+ * declared root fence, and the incident sink for the bounded teardown legs. */
17
+ deps: Pick<RunnerDeps, "executionEnvFactory" | "executionEnv" | "rootPath" | "onError">;
18
+ /** borrowed-readonly — the REBOUND spec; only the caller's task id rides into the factory ctx. */
19
+ spec: Pick<TaskSpec, "taskId">;
20
+ /** borrowed-readonly — the trusted spawn-side requests: the explicit cwd (a contract), the isolation
21
+ * request and the parent's cwd (both threaded to the factory ctx). */
22
+ internals: Pick<RunInternals, "requestedCwd" | "isolation" | "parentCwd"> | undefined;
23
+ /** borrowed-readonly — the resume leg, read only to refuse an explicit cwd that a checkpoint workspace
24
+ * restore would overrule. */
25
+ resume: Pick<PrepareResume, "workspaceHandle"> | undefined;
26
+ /** borrowed-readonly — the run tree's placement fixed point, resolved ONCE by the driver; the factory
27
+ * door refuses an empty value loudly before the factory is asked. */
28
+ placementRootResolved: string;
29
+ /** borrowed-readonly — the acquired session id (the factory ctx's identity, and the incident phase tag). */
30
+ sessionId: string;
31
+ /** borrowed-readonly — the driver's session-view forget leg; every refusal here runs it, bounded, before
32
+ * throwing (the acquired view must not outlive a prepare the caller never gets a handle to). */
33
+ forgetOnThrow: () => Promise<void>;
34
+ /** borrowed-mutable — the driver-owned prepare-failure stack; this phase pushes ONE entry (the minted
35
+ * env's destroy, read through the seat) and nothing else touches it here. */
36
+ rollback: RollbackStack;
37
+ }
38
+ export interface PrepareExecutionEnvResult {
39
+ /** borrowed-readonly — the run's resolved env: the minted one, else the static one, else the no-I/O stub. */
40
+ executionEnv: ExecutionEnv;
41
+ /** ownership-ref — the seat holding the factory-minted env (`current` undefined when none was minted).
42
+ * The exception-path protocol stays with the driver (D-8 案①): its worktree guard may detach through
43
+ * this seat before throwing; the rollback entry registered here reads it at unwind. */
44
+ ownedEnvSeat: OwnedEnvSeat;
45
+ /** borrowed-readonly — epoch ms at which the factory-minted env was obtained (the lifetime anchor of
46
+ * last resort); `undefined` for a static env or no env. */
47
+ ownedEnvObservedAt: number | undefined;
48
+ /** borrowed-readonly — whether a real fs env is present (a minted env, or a static one). */
49
+ handsEnabled: boolean;
50
+ /** borrowed-readonly — the INITIAL task root (the workspace-restore phase may re-root it and returns the
51
+ * settled root under the NEW name `taskRootFinal`). */
52
+ taskRootInitial: string;
53
+ }
54
+ export declare function prepareExecutionEnv(input: PrepareExecutionEnvInput): Promise<PrepareExecutionEnvResult>;
@@ -0,0 +1,86 @@
1
+ import { StubExecutionEnv } from "../stub-env.js";
2
+ import { hasDestroy } from "../remote-env.js";
3
+ import { settleTeardownLeg } from "./teardown-bounded.js";
4
+ export async function prepareExecutionEnv(input) {
5
+ const { deps, spec, internals, resume, placementRootResolved, sessionId, forgetOnThrow, rollback } = input;
6
+ let ownedEnv;
7
+ let ownedEnvObservedAt;
8
+ if (internals?.requestedCwd !== undefined && deps.executionEnvFactory === undefined) {
9
+ await settleTeardownLeg(() => forgetOnThrow(), "forgetOnThrow (cwd-unsupported leg)", (err) => deps.onError?.(err, { phase: "config", sessionId }));
10
+ 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.`);
11
+ e.code = "config.cwd_unsupported";
12
+ throw e;
13
+ }
14
+ if (internals?.requestedCwd !== undefined && resume?.workspaceHandle !== undefined) {
15
+ await settleTeardownLeg(() => forgetOnThrow(), "forgetOnThrow (cwd-conflicts-restore leg)", (err) => deps.onError?.(err, { phase: "config", sessionId }));
16
+ throw cwdConflictsRestoreError(internals.requestedCwd);
17
+ }
18
+ try {
19
+ ownedEnv = deps.executionEnvFactory
20
+ ? await deps.executionEnvFactory({
21
+ sessionId, placementRootSessionId: mintPlacementRootSessionId(placementRootResolved),
22
+ taskId: spec.taskId,
23
+ ...(internals?.isolation ? { isolation: internals.isolation } : {}),
24
+ ...(internals?.parentCwd ? { parentCwd: internals.parentCwd } : {}),
25
+ })
26
+ : undefined;
27
+ if (ownedEnv !== undefined)
28
+ ownedEnvObservedAt = Date.now();
29
+ }
30
+ catch (factoryErr) {
31
+ await settleTeardownLeg(() => forgetOnThrow(), "forgetOnThrow (env-factory-throw leg)", (err) => deps.onError?.(err, { phase: "config", sessionId }));
32
+ throw factoryErr;
33
+ }
34
+ const ownedEnvSeat = { current: ownedEnv };
35
+ if (ownedEnv !== undefined) {
36
+ rollback.push(function ownedEnvDestroy() {
37
+ const env = ownedEnvSeat.current;
38
+ return env !== undefined && hasDestroy(env) ? env.destroy() : undefined;
39
+ });
40
+ }
41
+ const executionEnv = ownedEnv ?? deps.executionEnv ?? new StubExecutionEnv();
42
+ if (internals?.requestedCwd !== undefined) {
43
+ const canon = async (p) => {
44
+ const r = await executionEnv.canonicalPath(p);
45
+ return r.ok ? r.value : p;
46
+ };
47
+ const want = await canon(internals.requestedCwd);
48
+ const rejectCwd = async (message, code) => {
49
+ await settleTeardownLeg(() => forgetOnThrow(), "forgetOnThrow (cwd-reject leg)", (err) => deps.onError?.(err, { phase: "config", sessionId }));
50
+ await settleTeardownLeg(() => (ownedEnv && hasDestroy(ownedEnv) ? ownedEnv.destroy() : undefined), "ownedEnv.destroy (cwd-reject leg)", (err) => deps.onError?.(err, { phase: "config", sessionId }));
51
+ const e = new Error(message);
52
+ e.code = code;
53
+ throw e;
54
+ };
55
+ if (deps.rootPath !== undefined) {
56
+ const root = await canon(deps.rootPath);
57
+ const sep = root.includes("\\") && !root.includes("/") ? "\\" : "/";
58
+ const rootPrefix = root.endsWith(sep) ? root : root + sep;
59
+ if (want !== root && !want.startsWith(rootPrefix)) {
60
+ await rejectCwd(`Agent cwd "${internals.requestedCwd}" is outside this deployment's declared root "${deps.rootPath}" — the cwd must be located within the deployment root.`, "config.cwd_outside_root");
61
+ }
62
+ }
63
+ const got = await canon(executionEnv.cwd);
64
+ if (want !== got) {
65
+ await rejectCwd(`Agent cwd "${internals.requestedCwd}" was not honored by the execution environment (it rooted at "${executionEnv.cwd}") — failing loud instead of running the agent in the wrong tree.`, "config.cwd_not_honored");
66
+ }
67
+ }
68
+ const handsEnabled = ownedEnv !== undefined || deps.executionEnv !== undefined;
69
+ const taskRootInitial = internals?.isolation === "worktree" || internals?.requestedCwd !== undefined || !deps.rootPath ? executionEnv.cwd : deps.rootPath;
70
+ return { executionEnv, ownedEnvSeat, ownedEnvObservedAt, handsEnabled, taskRootInitial };
71
+ }
72
+ function cwdConflictsRestoreError(requestedCwd) {
73
+ const e = new Error(`RunInternals.requestedCwd ("${requestedCwd}") cannot be combined with a checkpoint workspace restore — ` +
74
+ `the restored workspace's own mount path is authoritative for the task root, so a requested cwd on this leg ` +
75
+ `would be ignored (or worse, probed against an unrestored environment). Drop requestedCwd on resume legs.`);
76
+ e.code = "config.cwd_conflicts_restore";
77
+ return e;
78
+ }
79
+ function mintPlacementRootSessionId(placementRootResolved) {
80
+ if (placementRootResolved === "") {
81
+ const e = new Error("placement root resolved EMPTY (internals.placementRoot / internals.rootSessionId carries an empty string) — an empty fixed point cannot key a placement lookup; fix the spawning lane instead of defaulting around it.");
82
+ e.code = "config.placement_root_invalid";
83
+ throw e;
84
+ }
85
+ return placementRootResolved;
86
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * design/390 §1.2 M2 — prepareTask's file-history phase, verbatim from the driver: the DV-14 first-touch
3
+ * policy door, the `RunnerDeps.fileHistoryStore` bad-value door, the retired `rewindFiles` two-sense
4
+ * split, the restoreFiles / resumeAt / rewindFilesTo combination gates, the run's history coordinates
5
+ * (canonical root, recording scope, filesystem identity), the fork carry, the child-scope refusal, the
6
+ * rewind RESTORE itself (anchor translation in "before" mode, the per-file ledger, partial-restore
7
+ * policy), the `trackFileEdit` hook the hands band calls before every mutation lane, and the turn-start
8
+ * boundary seat runtask drives.
9
+ *
10
+ * Runs INSIDE the driver's prepare try-block, after the workspace root is FINAL (`taskRootFinal`) and the
11
+ * env is resolved: every refusal here throws through to the driver's prepare-throw catch exactly as
12
+ * before the move (a minted ownedEnv is destroyed there, the session view forgotten). The phase acquires
13
+ * no resource of its own — the two seats it returns are closures over the store the deployment owns.
14
+ *
15
+ * The module-level helpers below the phase (`restoredAbsPathsOf`, `resolveWiredFileHistoryStore`,
16
+ * the refusal's epoch-tail wording helper, `adoptForkedFileHistory` with its per-store carry memo,
17
+ * `createFileHistoryBoundarySeat`, `resolveFileHistoryCoordinates` / `resolveFileHistoryScope` /
18
+ * `fileHistoryFilesystemIdentity`, `childScopeRewindRefusal`) moved with it unchanged; the two the test
19
+ * suite reaches through the driver are re-exported from there.
20
+ */
21
+ import type { ExecutionEnv } from "../../internal/harness.js";
22
+ import type { StoredSession } from "../../engine/session/session.js";
23
+ import type { RunnerDeps, TaskResult, TaskSpec, TrackFileEditHook } from "../types.js";
24
+ import type { FileHistoryBoundarySeat, RunInternals } from "./contracts.js";
25
+ export interface PrepareFileHistoryInput {
26
+ /** borrowed-readonly — the deployment seats this phase reads: the first-touch failure policy and the
27
+ * history store (both validated HERE, once), the incident sink, and the retired-epoch diagnostic probe. */
28
+ deps: Pick<RunnerDeps, "onTrackFailure" | "fileHistoryStore" | "onError" | "legacyRewindBoundaryProbe">;
29
+ /** borrowed-readonly — the REBOUND spec's rewind request fields (the retired `rewindFiles` spelling is
30
+ * read off the same object as an unknown key) and the caller's cancel signal for the restore. */
31
+ spec: Pick<TaskSpec, "resumeAt" | "resumeAtMode" | "restoreFiles" | "rewindFilesTo" | "acceptPartialRestore" | "signal">;
32
+ /** borrowed-readonly — the trusted lineage a delegating parent handed this run (absent for a root run
33
+ * and for a host's own fork), read for the recording-scope decision only. */
34
+ internals: Pick<RunInternals, "fileHistoryLineage"> | undefined;
35
+ /** borrowed-readonly — the run's resolved env (owned or static or the stub); every history verb runs
36
+ * against ITS filesystem ops. Never rebound or destroyed here. */
37
+ executionEnv: ExecutionEnv;
38
+ /** borrowed-readonly — the FINAL task root (the workspace-restore phase's output binding). */
39
+ taskRootFinal: string;
40
+ /** borrowed-readonly — the "real fs env" fact (`ownedEnv !== undefined || deps.executionEnv !== undefined`). */
41
+ handsEnabled: boolean;
42
+ /** borrowed-readonly — the acquired session id: the run's OWN history scope and the restore's session key. */
43
+ sessionId: string;
44
+ /** borrowed-readonly — the acquired session, read for the fork lineage (`getMetadata`) and the "before"
45
+ * anchor walk (`getEntry`); never written here. */
46
+ session: Pick<StoredSession, "getEntry" | "getMetadata">;
47
+ /** borrowed-readonly — §E18 "before" mode: the validated target's parentId (the acquire phase's output);
48
+ * `null` outside "before" mode. */
49
+ resumeAtBeforeParentId: string | null;
50
+ /** borrowed-readonly — the run's abort controller (driver-minted); only its `signal` is read, composed
51
+ * with `spec.signal` for the restore. */
52
+ abortController: AbortController;
53
+ }
54
+ export interface PrepareFileHistoryResult {
55
+ /** borrowed-readonly — the hook the hands band calls before every mutation lane; `undefined` when the
56
+ * machinery is not live (no store, or no real fs env). */
57
+ trackFileEdit: TrackFileEditHook | undefined;
58
+ /** borrowed-readonly — the turn-start boundary seat runtask drives; `undefined` when not live or when
59
+ * this run records into its ROOT session's scope (a child mints no boundaries). */
60
+ fileHistoryBoundary: FileHistoryBoundarySeat | undefined;
61
+ /** borrowed-readonly — the absolute paths the restore MUTATED (empty when no restore ran), for the
62
+ * read-face phase's read-state invalidation. */
63
+ restoredFilePaths: string[];
64
+ /** borrowed-mutable — the non-fatal rewind disclosures (`TaskResult.rewindNotes`). Mutation owners after
65
+ * this call: none — every push happens inside this phase; the driver only reads it into `Prepared`. */
66
+ rewindNotes: NonNullable<TaskResult["rewindNotes"]>;
67
+ /** borrowed-readonly — whether the file-history machinery is LIVE for this run (store wired AND hands). */
68
+ fileHistoryEnabled: boolean;
69
+ /** borrowed-readonly — the scope this run's first-touch records land in (own session, or the root
70
+ * session's for a same-tree delegated child). */
71
+ historyScope: string;
72
+ /** borrowed-readonly — the canonical history root every history verb keys against. */
73
+ historyRoot: string;
74
+ /** borrowed-readonly — the filesystem identity of the env (the lineage's second tree coordinate). */
75
+ historyFs: string;
76
+ }
77
+ export declare function prepareFileHistory(input: PrepareFileHistoryInput): Promise<PrepareFileHistoryResult>;
78
+ /** The ONE reading of {@link RunInternals.fileHistoryLineage} (see {@link resolveFileHistoryCoordinates}):
79
+ * the lineage's scope iff BOTH tree coordinates match; otherwise the run's own session. */
80
+ export declare function resolveFileHistoryScope(lineage: RunInternals["fileHistoryLineage"], historyRoot: string, historyFs: string, sessionId: string): string;
81
+ /**
82
+ * WHICH filesystem an env is a view of — the second coordinate of tree identity for the file-history
83
+ * lineage, read from what the env's MINTER states rather than inferred from a path:
84
+ * · a remote workspace names itself through its {@link WorkspaceHandle} (provider + sandbox, and the
85
+ * device lane's id when stamped) — two runs on one sandbox share a tree, two sandboxes never do;
86
+ * · an env that DECLARES its paths host-local (`hostLocalPaths: true`), or is the host adapter itself
87
+ * (a {@link NodeExecutionEnv}, which the #211 seam names as the host-local default), is the
88
+ * control-plane host's filesystem — every such env is one tree;
89
+ * · anything else — an env declaring `hostLocalPaths: false`, or an undeclared custom adapter — is
90
+ * attested to nothing, so it is its OWN tree: a per-instance token, which still matches when the
91
+ * child literally holds the parent's env object and never matches a fresh per-task mint.
92
+ * A remote handle that cannot be read (an env not yet connected) falls to the per-instance arm for
93
+ * the same reason: unattested is not shared.
94
+ */
95
+ export declare function fileHistoryFilesystemIdentity(env: ExecutionEnv): string;