@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.
- package/CHANGELOG.md +69 -0
- package/dist/agents/cascade.d.ts +2 -1
- package/dist/agents/peer-notices.d.ts +11 -1
- package/dist/agents/peer-session-drain.js +2 -0
- package/dist/agents/verify.d.ts +2 -1
- package/dist/core/ask-origin.d.ts +130 -0
- package/dist/core/ask-origin.js +35 -0
- package/dist/core/auto-mode-arming.d.ts +40 -1
- package/dist/core/auto-mode-arming.js +51 -3
- package/dist/core/auto-mode.d.ts +87 -10
- package/dist/core/auto-mode.js +34 -6
- package/dist/core/checkpoint-store.d.ts +41 -6
- package/dist/core/checkpoint-store.js +8 -0
- package/dist/core/hooks.d.ts +63 -19
- package/dist/core/hooks.js +37 -16
- package/dist/core/mcp.d.ts +47 -55
- package/dist/core/mcp.js +89 -31
- package/dist/core/park-selfcheck.js +3 -0
- package/dist/core/permission-rule-consent.d.ts +2 -11
- package/dist/core/permission-rule-consent.js +13 -62
- package/dist/core/permission-rule-org.d.ts +70 -54
- package/dist/core/permission-rule-org.js +47 -61
- package/dist/core/permission-rule-provider.d.ts +162 -0
- package/dist/core/permission-rule-provider.js +148 -0
- package/dist/core/permission-rule-session.d.ts +23 -19
- package/dist/core/permission-rule-session.js +5 -5
- package/dist/core/permission-rule-store.d.ts +46 -21
- package/dist/core/permission-rule-store.js +13 -6
- package/dist/core/permission-rule-sync.d.ts +2 -1
- package/dist/core/permission-rule-sync.js +11 -0
- package/dist/core/runner/assemble-result.d.ts +3 -2
- package/dist/core/runner/checkpoint-scope.d.ts +32 -0
- package/dist/core/runner/checkpoint-scope.js +4 -0
- package/dist/core/runner/contracts.d.ts +1878 -0
- package/dist/core/runner/contracts.js +1 -0
- package/dist/core/runner/denial-limit-arms.d.ts +57 -31
- package/dist/core/runner/denial-limit-arms.js +42 -17
- package/dist/core/runner/derived-route-fallback.d.ts +34 -0
- package/dist/core/runner/derived-route-fallback.js +16 -0
- package/dist/core/runner/prepare-acquire-reconcile.d.ts +1 -1
- package/dist/core/runner/prepare-announce-once.d.ts +83 -0
- package/dist/core/runner/prepare-announce-once.js +105 -0
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +170 -0
- package/dist/core/runner/prepare-caps-and-workflow.js +255 -0
- package/dist/core/runner/prepare-config-doors.d.ts +2 -10
- package/dist/core/runner/prepare-defer-classify.d.ts +86 -0
- package/dist/core/runner/prepare-defer-classify.js +107 -0
- package/dist/core/runner/prepare-delegation-surface.d.ts +104 -0
- package/dist/core/runner/prepare-delegation-surface.js +144 -0
- package/dist/core/runner/prepare-execution-env.d.ts +54 -0
- package/dist/core/runner/prepare-execution-env.js +86 -0
- package/dist/core/runner/prepare-file-history.d.ts +95 -0
- package/dist/core/runner/prepare-file-history.js +383 -0
- package/dist/core/runner/prepare-hands-readface.d.ts +6 -8
- package/dist/core/runner/prepare-hands-readface.js +3 -3
- package/dist/core/runner/prepare-inherited-gate.d.ts +268 -0
- package/dist/core/runner/prepare-inherited-gate.js +266 -0
- package/dist/core/runner/prepare-listings.d.ts +77 -0
- package/dist/core/runner/prepare-listings.js +76 -0
- package/dist/core/runner/prepare-lsp.d.ts +55 -0
- package/dist/core/runner/prepare-lsp.js +27 -0
- package/dist/core/runner/prepare-memory.d.ts +1 -1
- package/dist/core/runner/prepare-offload-wrappers.d.ts +62 -0
- package/dist/core/runner/prepare-offload-wrappers.js +45 -0
- package/dist/core/runner/prepare-permission-rules.d.ts +132 -0
- package/dist/core/runner/prepare-permission-rules.js +140 -0
- package/dist/core/runner/prepare-project-context.d.ts +131 -0
- package/dist/core/runner/prepare-project-context.js +150 -0
- package/dist/core/runner/prepare-prompt-inputs.d.ts +138 -0
- package/dist/core/runner/prepare-prompt-inputs.js +141 -0
- package/dist/core/runner/prepare-protocol-tools.d.ts +91 -0
- package/dist/core/runner/prepare-protocol-tools.js +182 -0
- package/dist/core/runner/prepare-question-face.d.ts +119 -0
- package/dist/core/runner/prepare-question-face.js +83 -0
- package/dist/core/runner/prepare-run-refs.d.ts +89 -0
- package/dist/core/runner/prepare-run-refs.js +39 -0
- package/dist/core/runner/prepare-safety-scan.d.ts +3 -2
- package/dist/core/runner/prepare-task.d.ts +11 -1815
- package/dist/core/runner/prepare-task.js +138 -2542
- package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +111 -0
- package/dist/core/runner/prepare-tool-disclosure-mount.js +219 -0
- package/dist/core/runner/prepare-wiring-manifest.d.ts +184 -0
- package/dist/core/runner/prepare-wiring-manifest.js +240 -0
- package/dist/core/runner/prepare-workspace-restore.d.ts +1 -27
- package/dist/core/runner/prepare-workspace-restore.js +1 -22
- package/dist/core/runner/rollback-stack.d.ts +32 -0
- package/dist/core/runner/rollback-stack.js +30 -0
- package/dist/core/runner/runtask.d.ts +11 -2
- package/dist/core/runner/runtask.js +27 -9
- package/dist/core/runner/workspace-path.d.ts +33 -0
- package/dist/core/runner/workspace-path.js +22 -0
- package/dist/core/sensitive-path-policy.d.ts +16 -0
- package/dist/core/sensitive-path-policy.js +1 -1
- package/dist/core/tool-policy.d.ts +57 -9
- package/dist/core/tool-policy.js +11 -0
- package/dist/core/types.d.ts +63 -51
- package/dist/core/wiring-manifest.d.ts +40 -3
- package/dist/core/wiring-manifest.js +4 -3
- package/dist/core/write-protect.d.ts +13 -2
- package/dist/core/write-protect.js +58 -29
- package/dist/engine/harness/types.d.ts +38 -16
- package/dist/engine/harness/types.js +25 -1
- package/dist/engine/session/session.d.ts +3 -11
- package/dist/index.d.ts +10 -6
- package/dist/index.js +9 -5
- package/dist/internal/harness.d.ts +1 -0
- package/dist/stores/file/adoption/adopt.d.ts +1 -1
- package/dist/stores/file/adoption/marker.d.ts +1 -1
- package/dist/stores/file/permission-rule-adopt.js +4 -3
- package/dist/stores/file/permission-rule-store.d.ts +65 -25
- package/dist/stores/file/permission-rule-store.js +215 -37
- package/dist/stores/file/task-list-store.d.ts +1 -1
- package/dist/tools/fs/read-face.d.ts +1 -1
- package/package.json +8 -2
- 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;
|