@sema-agent/core 5.63.0 → 5.65.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +80 -0
- package/dist/agents/cascade.d.ts +5 -1
- package/dist/agents/cascade.js +6 -1
- package/dist/agents/subagent.js +1 -0
- package/dist/agents/verify.d.ts +5 -1
- package/dist/agents/verify.js +5 -2
- package/dist/core/checkpoint-store.d.ts +100 -5
- package/dist/core/fs-write-gate-policy.d.ts +21 -0
- package/dist/core/fs-write-gate-policy.js +14 -3
- package/dist/core/hooks.d.ts +9 -1
- package/dist/core/hooks.js +1 -1
- package/dist/core/memory-engine/dual-root.js +3 -0
- package/dist/core/memory-engine/engine.d.ts +1 -0
- package/dist/core/memory-engine/engine.js +9 -5
- package/dist/core/memory-engine/types.d.ts +16 -0
- package/dist/core/remote-env.d.ts +34 -2
- package/dist/core/runner/prepare-hands-readface.d.ts +9 -0
- package/dist/core/runner/prepare-hands-readface.js +4 -1
- package/dist/core/runner/prepare-memory.js +1 -1
- package/dist/core/runner/prepare-task.d.ts +31 -0
- package/dist/core/runner/prepare-task.js +53 -18
- package/dist/core/runner/prepare-workspace-restore.js +13 -0
- package/dist/core/runner/runtask.d.ts +18 -0
- package/dist/core/runner/runtask.js +173 -20
- package/dist/core/stub-env.d.ts +4 -0
- package/dist/core/stub-env.js +1 -0
- package/dist/core/task-registry-shared.js +20 -2
- package/dist/core/types.d.ts +104 -11
- package/dist/core/usage-window-store.d.ts +44 -12
- package/dist/core/usage-window-store.js +11 -3
- package/dist/core/workflow-run-store-contract.js +30 -0
- package/dist/core/workflow-run-store.d.ts +39 -1
- package/dist/core/workflow-run-store.js +2 -0
- package/dist/engine/harness/agent-harness.d.ts +8 -3
- package/dist/engine/harness/agent-harness.js +9 -4
- package/dist/engine/harness/types.d.ts +107 -3
- package/dist/engine/loop/agent-loop.js +39 -15
- package/dist/engine/loop/types.d.ts +43 -22
- package/dist/index.d.ts +2 -2
- package/dist/internal/harness-types.d.ts +1 -1
- package/dist/orchestration/run-workflow-tool.d.ts +6 -0
- package/dist/orchestration/run-workflow-tool.js +1 -0
- package/dist/orchestration/workflow-types.d.ts +35 -0
- package/dist/orchestration/workflow-types.js +2 -2
- package/dist/orchestration/workflow.d.ts +6 -0
- package/dist/orchestration/workflow.js +21 -1
- package/dist/prompts/default.d.ts +8 -0
- package/dist/prompts/default.js +3 -0
- package/dist/tools/fs/bash-readonly-classifier.d.ts +44 -1
- package/dist/tools/fs/bash-readonly-classifier.js +132 -5
- package/dist/tools/fs/fs-bash.js +9 -2
- package/dist/tools/fs/fs-write.js +19 -8
- package/dist/tools/fs/index.d.ts +1 -0
- package/dist/tools/fs/index.js +1 -0
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +9 -1
|
@@ -181,6 +181,15 @@ export interface PrepareHandsReadFaceResult {
|
|
|
181
181
|
shellGatedBash: boolean;
|
|
182
182
|
/** write-back — the Monitor twin of the bit above. */
|
|
183
183
|
shellGatedMonitor: boolean;
|
|
184
|
+
/** owned (design/380 rescan C4) — the ENV-HAND tool names, minted AT THE MOUNT: every tool this
|
|
185
|
+
* phase mounted over the ExecutionEnv (the fs/bash band), later grown by the driver's own env
|
|
186
|
+
* mounts (Monitor, the worktree pair — the SAME Set identity, mutated at those mount sites).
|
|
187
|
+
* Consumed by the O9a external-content-target fold: these are the tools whose RESULTS are the
|
|
188
|
+
* target's bytes, so membership must be what actually mounted on the env — never a static table
|
|
189
|
+
* (the wake/resume reconciliation table lists TaskOutput/TaskStop, which are orchestration, and
|
|
190
|
+
* misses Monitor/worktree, which are hands — both directions wrong for this consumer). Empty on
|
|
191
|
+
* a hands-less leg (nothing mounts on the env; the driver's env mounts are hands-gated too). */
|
|
192
|
+
envHandToolNames: Set<string>;
|
|
184
193
|
}
|
|
185
194
|
export declare function resolveHandsLessReadFace(input: PrepareHandsReadFaceInput): PrepareHandsReadFaceResult;
|
|
186
195
|
/** The hands-MOUNT branch — the async half of the phase (see the module header for the contract).
|
|
@@ -71,11 +71,13 @@ export function resolveHandsLessReadFace(input) {
|
|
|
71
71
|
additionalReadRootsCanonical: [],
|
|
72
72
|
attachmentRootCanonical: undefined,
|
|
73
73
|
readDenyMatcher: undefined,
|
|
74
|
+
envHandToolNames: new Set(),
|
|
74
75
|
shellGatedBash: false,
|
|
75
76
|
shellGatedMonitor: false,
|
|
76
77
|
};
|
|
77
78
|
}
|
|
78
79
|
export async function prepareHandsMount(input) {
|
|
80
|
+
const envHandToolNames = new Set();
|
|
79
81
|
const { handsEnabled, executionEnv, taskRootFinal, rebaseRestoredPath, resume, session, sessionId, hostTaskId, taskScope, fullShellReachable, effectiveShellGate, toolFaceSnapshot, model, spec, deps, internals, memoryWriteGateRef, firstPartyOffload, tools, egressTools, irreversibilityTier, irreversibleTools, reversibilityProbes } = input;
|
|
80
82
|
assertStringArraySeat(spec.additionalDirectories, "TaskSpec.additionalDirectories", "strings");
|
|
81
83
|
assertStringArraySeat(spec.additionalReadDirectories, "TaskSpec.additionalReadDirectories", "strings");
|
|
@@ -257,7 +259,7 @@ export async function prepareHandsMount(input) {
|
|
|
257
259
|
deps.onError?.(new Error(`spec.tools ${shadowed.length > 1 ? "entries" : "entry"} ${names} collide${shadowed.length > 1 ? "" : "s"} with the built-in hands tool band — the built-in wins (registered later; the harness tool Map is last-write-wins) and the caller tool never dispatches this task. Rename the caller tool, or don't inject executionEnv if you intend to replace the band.`), { phase: "config", sessionId });
|
|
258
260
|
}
|
|
259
261
|
}
|
|
260
|
-
tools.push(...band.map((t) => firstPartyOffload(t)));
|
|
262
|
+
tools.push(...band.map((t) => (envHandToolNames.add(t.name), firstPartyOffload(t))));
|
|
261
263
|
const shellGate = effectiveShellGate;
|
|
262
264
|
if (shellGate === "off" && !(executionEnv instanceof StubExecutionEnv) && spec.handsReadOnly !== true) {
|
|
263
265
|
deps.onError?.(new Error(`shell gate doctrine is "off" while a real writable shell (Bash) is mounted — no shell safety-axis ` +
|
|
@@ -304,6 +306,7 @@ export async function prepareHandsMount(input) {
|
|
|
304
306
|
additionalReadRootsCanonical,
|
|
305
307
|
attachmentRootCanonical,
|
|
306
308
|
readDenyMatcher,
|
|
309
|
+
envHandToolNames,
|
|
307
310
|
shellGatedBash,
|
|
308
311
|
shellGatedMonitor,
|
|
309
312
|
};
|
|
@@ -314,7 +314,7 @@ export async function prepareMemory(input) {
|
|
|
314
314
|
}
|
|
315
315
|
catch {
|
|
316
316
|
}
|
|
317
|
-
deliverEngineNotice(deps.onNotice, memoryHarvestQuarantinedNotice({ count, moved, escalated, ...(reason !== undefined ? { reason } : {}), sessionId, provenance: input.memoryProvenance, ...(indexOnly ? { indexRolledBack: true } : {}) }));
|
|
317
|
+
deliverEngineNotice(deps.onNotice, memoryHarvestQuarantinedNotice({ count, moved, escalated, ...(report.reportId !== undefined ? { reportId: report.reportId } : {}), ...(reason !== undefined ? { reason } : {}), sessionId, provenance: input.memoryProvenance, ...(indexOnly ? { indexRolledBack: true } : {}) }));
|
|
318
318
|
}
|
|
319
319
|
for (const notice of memoryHoldNotices(report, sessionId))
|
|
320
320
|
deliverEngineNotice(deps.onNotice, notice);
|
|
@@ -1498,6 +1498,27 @@ export interface RunInternals {
|
|
|
1498
1498
|
* after a restart those intermediate sessions are dead ends, and a recovery face enumerating
|
|
1499
1499
|
* "everything under this host session" needs the root anchor, not an alias walk. */
|
|
1500
1500
|
rootSessionId?: string;
|
|
1501
|
+
/**
|
|
1502
|
+
* design/380 O1② — the run tree's PLACEMENT root: the fixed point a target-bound env factory keys
|
|
1503
|
+
* its placement lookup on ({@link import("../remote-env.js").ExecutionEnvFactoryContext.placementRootSessionId}).
|
|
1504
|
+
* A SEPARATE axis from {@link rootSessionId} deliberately: that field means "member of this host
|
|
1505
|
+
* session's DELEGATION tree" and is consumed by the registry access/recovery faces — cascade rungs
|
|
1506
|
+
* and verification legs are intentionally NOT members of that tree (independent cold re-runs in
|
|
1507
|
+
* their own sessions), so widening `rootSessionId` to cover them would corrupt the recovery faces'
|
|
1508
|
+
* reading. This member says only "place me where this session was placed". Producers: the
|
|
1509
|
+
* orchestration entries (`runCascade` / `runWithVerification` family) after their first leg's
|
|
1510
|
+
* sessionId receipt, and — C12 — the delegation/workflow spawn chains, which re-thread a parent's
|
|
1511
|
+
* EXPLICIT value verbatim into child internals (ToolExecuteContext.placementRoot → childInternals;
|
|
1512
|
+
* workflow deps → shared internals base), so every descendant of a placed leg keeps the fixed
|
|
1513
|
+
* point; prepare's mint reads it first (`placementRoot ?? rootSessionId ?? sessionId`). Absent
|
|
1514
|
+
* everywhere else — the delegation lanes' `rootSessionId` fixed point then becomes the placement
|
|
1515
|
+
* root through the middle segment, unchanged. DURABLE since design/380 O1③ (the former C12
|
|
1516
|
+
* residual, fulfilled): the suspend mint stamps the resolved fixed point as
|
|
1517
|
+
* `CheckpointState.placementRootSessionId`, and a bare durable resume restores it into this
|
|
1518
|
+
* member (live internals win when re-supplied; a CONTRADICTING re-supply refuses pre-CAS,
|
|
1519
|
+
* `resume.placement_mismatch`). TRUSTED run-scoped channel (never a {@link TaskSpec} field).
|
|
1520
|
+
*/
|
|
1521
|
+
placementRoot?: string;
|
|
1501
1522
|
/**
|
|
1502
1523
|
* RB-429 — the REGISTRY SCOPE this run's own background row lives in: the domain its registry-facing
|
|
1503
1524
|
* tools (TaskOutput / TaskStop / SendMessage / AgentTranscript / Monitor, and the announce listing)
|
|
@@ -1739,6 +1760,16 @@ export declare function batchContextAt(messages: AgentMessage[], currentId: stri
|
|
|
1739
1760
|
batchToolCallIds: string[];
|
|
1740
1761
|
completedCallIds: string[];
|
|
1741
1762
|
};
|
|
1763
|
+
/**
|
|
1764
|
+
* rescan C5 — the ONE spelling of the placement fields' empty-string discipline: `""` is absence
|
|
1765
|
+
* wearing clothes (the resume entry's principal-rung posture), and every placement read that must
|
|
1766
|
+
* treat it so — the resume rung's two sides (runtask), the restore fold's seed and the suspend
|
|
1767
|
+
* stamp — goes through THIS helper, so three sites cannot drift into three readings. Deliberately
|
|
1768
|
+
* NOT applied to the live `internals.placementRoot`/`rootSessionId` reads of the RESOLUTION fold:
|
|
1769
|
+
* an empty supplied claim there is an assembly error {@link mintPlacementRootSessionId} refuses
|
|
1770
|
+
* loudly on the factory path, and normalizing it away would silently repair what should be loud.
|
|
1771
|
+
*/
|
|
1772
|
+
export declare function placementValueOrAbsent(value: string | undefined): string | undefined;
|
|
1742
1773
|
/**
|
|
1743
1774
|
* Build everything a task run needs (council design/34 ②: a free function with EXPLICIT deps, not a
|
|
1744
1775
|
* Runner method — testable and decoupled). Resolves the model/role/thinking, acquires + reconciles the
|
|
@@ -681,6 +681,35 @@ async function derivedRouteFallsBack(args) {
|
|
|
681
681
|
return false;
|
|
682
682
|
}
|
|
683
683
|
}
|
|
684
|
+
function mintPlacementRootSessionId(placementRootResolved) {
|
|
685
|
+
if (placementRootResolved === "") {
|
|
686
|
+
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.");
|
|
687
|
+
e.code = "config.placement_root_invalid";
|
|
688
|
+
throw e;
|
|
689
|
+
}
|
|
690
|
+
return placementRootResolved;
|
|
691
|
+
}
|
|
692
|
+
function restorePlacementRoot(internals, seedPlacementRoot) {
|
|
693
|
+
if (internals?.placementRoot !== undefined)
|
|
694
|
+
return internals;
|
|
695
|
+
const seed = placementValueOrAbsent(seedPlacementRoot);
|
|
696
|
+
if (seed === undefined)
|
|
697
|
+
return internals;
|
|
698
|
+
return { ...(internals ?? {}), placementRoot: seed };
|
|
699
|
+
}
|
|
700
|
+
export function placementValueOrAbsent(value) {
|
|
701
|
+
return value || undefined;
|
|
702
|
+
}
|
|
703
|
+
function stampPlacementRootSessionId(placementRootResolved) {
|
|
704
|
+
return placementValueOrAbsent(placementRootResolved);
|
|
705
|
+
}
|
|
706
|
+
function refuseRequireExistingWithoutSession(spec) {
|
|
707
|
+
if (spec.requireExistingSession && !spec.sessionId) {
|
|
708
|
+
const e = new Error(`requireExistingSession requires a sessionId — cannot require an existing session without one (design/114 Phase3)`);
|
|
709
|
+
e.code = "resume.session_not_found";
|
|
710
|
+
throw e;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
684
713
|
export async function prepareTask(spec, deps, sessions, resume, internals, runnerSelf) {
|
|
685
714
|
const doors = prepareConfigDoors({ spec, deps, sessions, resume, internals });
|
|
686
715
|
spec = doors.spec;
|
|
@@ -689,15 +718,12 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
689
718
|
const { toolEffects, egressTools, irreversibleTools, irreversibilityTier, axisExplicitNegatives, reversibilityProbes, ownToolNames } = prepareSafetyScan({ spec, deps });
|
|
690
719
|
let shellGatedBash = false;
|
|
691
720
|
let shellGatedMonitor = false;
|
|
692
|
-
|
|
693
|
-
const e = new Error(`requireExistingSession requires a sessionId — cannot require an existing session without one (design/114 Phase3)`);
|
|
694
|
-
e.code = "resume.session_not_found";
|
|
695
|
-
throw e;
|
|
696
|
-
}
|
|
721
|
+
refuseRequireExistingWithoutSession(spec);
|
|
697
722
|
const { acquired, session, conflictRef, wakeRecovered, resumeAtBeforeParentId } = await prepareAcquireReconcile({ sessions, spec, resume, toolEffects, ...(internals?.sessionPlacement !== undefined ? { placement: internals.sessionPlacement } : {}), ...(() => { const g = durableParkGapFor(deps, spec); return g !== undefined ? { durableParkGap: g } : {}; })() });
|
|
698
723
|
const sessionId = acquired.sessionId;
|
|
699
724
|
const hostTaskId = spec.taskId ?? sessionId;
|
|
700
725
|
const delegation = effectiveDelegationFacts(internals, resume?.seed.isDelegatedChild);
|
|
726
|
+
const placementRootResolved = (internals = restorePlacementRoot(internals, resume?.seed.placementRootSessionId))?.placementRoot ?? internals?.rootSessionId ?? sessionId;
|
|
701
727
|
let effectiveCompModel = compModel;
|
|
702
728
|
if (spec.compactionModel === undefined &&
|
|
703
729
|
compModel !== undefined &&
|
|
@@ -789,7 +815,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
789
815
|
try {
|
|
790
816
|
ownedEnv = deps.executionEnvFactory
|
|
791
817
|
? await deps.executionEnvFactory({
|
|
792
|
-
sessionId,
|
|
818
|
+
sessionId, placementRootSessionId: mintPlacementRootSessionId(placementRootResolved),
|
|
793
819
|
taskId: spec.taskId,
|
|
794
820
|
...(internals?.isolation ? { isolation: internals.isolation } : {}),
|
|
795
821
|
...(internals?.parentCwd ? { parentCwd: internals.parentCwd } : {}),
|
|
@@ -803,6 +829,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
803
829
|
throw factoryErr;
|
|
804
830
|
}
|
|
805
831
|
const executionEnv = ownedEnv ?? deps.executionEnv ?? new StubExecutionEnv();
|
|
832
|
+
const externalContentTargetActive = executionEnv.externalContentTarget === true || resume?.seed.externalContentTarget === true;
|
|
806
833
|
if (internals?.requestedCwd !== undefined) {
|
|
807
834
|
const canon = async (p) => {
|
|
808
835
|
const r = await executionEnv.canonicalPath(p);
|
|
@@ -1173,7 +1200,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1173
1200
|
taskId: hostTaskId,
|
|
1174
1201
|
...(internals?.parentTaskId !== undefined ? { parentTaskId: internals.parentTaskId } : {}),
|
|
1175
1202
|
...(internals?.parentSessionId !== undefined ? { parentSessionId: internals.parentSessionId } : {}),
|
|
1176
|
-
...(internals?.rootSessionId !== undefined ? { rootSessionId: internals.rootSessionId } : {}),
|
|
1203
|
+
...(internals?.rootSessionId !== undefined ? { rootSessionId: internals.rootSessionId } : {}), ...(internals?.placementRoot !== undefined ? { placementRoot: internals.placementRoot } : {}),
|
|
1177
1204
|
...(internals?.explicitAgentName !== undefined ? { spawnedAgentName: internals.explicitAgentName } : {}),
|
|
1178
1205
|
...(internals?.peerSelfRef !== undefined ? { peerSelfRef: internals.peerSelfRef } : {}),
|
|
1179
1206
|
...(internals?.peerInboundChainRef !== undefined ? { peerInboundChainRef: internals.peerInboundChainRef } : {}),
|
|
@@ -1348,7 +1375,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1348
1375
|
scope: taskScope,
|
|
1349
1376
|
notifier: deps.workflowCompletionNotifier,
|
|
1350
1377
|
originatingSessionId: sessionId,
|
|
1351
|
-
rootSessionId: internals?.rootSessionId ?? sessionId,
|
|
1378
|
+
rootSessionId: internals?.rootSessionId ?? sessionId, ...(internals?.placementRoot !== undefined ? { placementRoot: internals.placementRoot } : {}),
|
|
1352
1379
|
taskRegistry: defaultTaskRegistry,
|
|
1353
1380
|
taskNotification: internals?.onTaskNotification,
|
|
1354
1381
|
taskOwner: hostTaskId,
|
|
@@ -1596,7 +1623,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1596
1623
|
const memoryWriteGateRef = {};
|
|
1597
1624
|
const handsReadFaceInput = { handsEnabled, executionEnv, taskRootFinal, rebaseRestoredPath, resume, session, sessionId, hostTaskId, taskScope, fullShellReachable, effectiveShellGate, toolFaceSnapshot, model, spec, deps, internals, memoryWriteGateRef, firstPartyOffload, tools, egressTools, irreversibilityTier, irreversibleTools, reversibilityProbes, reminderMark, reminderDisclosureCounts };
|
|
1598
1625
|
const handsReadFace = handsEnabled ? await prepareHandsMount(handsReadFaceInput) : resolveHandsLessReadFace(handsReadFaceInput);
|
|
1599
|
-
const { readFileStateForCheckpoint, seedContextFiles, handsCwdRef, workspaceStateSettle, wsSnapshot, rebaseWsPath, backgroundTaskToolsActive, additionalRootsCanonical, additionalReadRootsCanonical, attachmentRootCanonical, readDenyMatcher } = handsReadFace;
|
|
1626
|
+
const { readFileStateForCheckpoint, seedContextFiles, handsCwdRef, workspaceStateSettle, wsSnapshot, rebaseWsPath, backgroundTaskToolsActive, additionalRootsCanonical, additionalReadRootsCanonical, attachmentRootCanonical, readDenyMatcher, envHandToolNames } = handsReadFace;
|
|
1600
1627
|
resolvedReadFace = handsReadFace.resolvedReadFace;
|
|
1601
1628
|
readDenyAdditionsNormalized = handsReadFace.readDenyAdditionsNormalized;
|
|
1602
1629
|
handsLessResolvedFace = handsReadFace.handsLessResolvedFace;
|
|
@@ -1680,6 +1707,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1680
1707
|
}
|
|
1681
1708
|
if (backgroundTaskToolsActive) {
|
|
1682
1709
|
toolEffects.set("Monitor", "write");
|
|
1710
|
+
envHandToolNames.add("Monitor");
|
|
1683
1711
|
tools.push(firstPartyOffload(createMonitorTool(executionEnv, {
|
|
1684
1712
|
registry: defaultTaskRegistry,
|
|
1685
1713
|
owner: hostTaskId,
|
|
@@ -1715,7 +1743,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1715
1743
|
};
|
|
1716
1744
|
if (workspaceStateSettle !== undefined)
|
|
1717
1745
|
workspaceStateSettle.restoredWorktreeDir = worktreeSessionRef.current?.worktreeDir;
|
|
1718
|
-
tools.push(...createWorktreeTools(executionEnv, { repoRoot: taskRootFinal, cwdRef: handsCwdRef, session: worktreeSessionRef }).map((t) => firstPartyOffload(t)));
|
|
1746
|
+
tools.push(...createWorktreeTools(executionEnv, { repoRoot: taskRootFinal, cwdRef: handsCwdRef, session: worktreeSessionRef }).map((t) => (envHandToolNames.add(t.name), firstPartyOffload(t))));
|
|
1719
1747
|
}
|
|
1720
1748
|
if (offloadStore)
|
|
1721
1749
|
tools.push(createReadToolResultTool(offloadStore));
|
|
@@ -1800,7 +1828,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1800
1828
|
...(spec.principal !== undefined ? { principal: spec.principal } : {}),
|
|
1801
1829
|
...(sessionId !== undefined ? { sessionId } : {}),
|
|
1802
1830
|
requestStopAfterTurn,
|
|
1803
|
-
}));
|
|
1831
|
+
}).map((t) => (envHandToolNames.add(t.name), t)));
|
|
1804
1832
|
}
|
|
1805
1833
|
if (internals?.insideFork !== true &&
|
|
1806
1834
|
runnerSelf &&
|
|
@@ -1814,6 +1842,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1814
1842
|
if (lspManager) {
|
|
1815
1843
|
const lspRoot = taskRootFinal;
|
|
1816
1844
|
tools.push(createLspTool(lspManager, { isPathIgnored: gitCheckIgnoreFilter(executionEnv, lspRoot), env: executionEnv }));
|
|
1845
|
+
envHandToolNames.add("LSP");
|
|
1817
1846
|
}
|
|
1818
1847
|
const lspDiagnostics = spec.lspDiagnostics !== false && lspManager?.diagnostics !== undefined && handsEnabled && spec.handsReadOnly !== true
|
|
1819
1848
|
? lspManager.diagnostics
|
|
@@ -2089,6 +2118,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2089
2118
|
envFacts.sandboxEgress = spec.envFacts.egress;
|
|
2090
2119
|
if (spec.envFacts?.scratchpadDir)
|
|
2091
2120
|
envFacts.scratchpadDir = spec.envFacts.scratchpadDir;
|
|
2121
|
+
if (externalContentTargetActive)
|
|
2122
|
+
envFacts.externalContentTarget = true;
|
|
2092
2123
|
if (resume !== undefined && spec.envFacts?.resumeFacts) {
|
|
2093
2124
|
const rf = spec.envFacts.resumeFacts;
|
|
2094
2125
|
const copy = {};
|
|
@@ -2269,7 +2300,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2269
2300
|
let staticMarkWaiverAnnounced = false;
|
|
2270
2301
|
const effectiveSafety = narrowContentSafety(delegationProvenanceChannel?.contentSafety, memoryEngineSession?.contentSafety);
|
|
2271
2302
|
const trustedTools = effectiveSafety.trustedTools;
|
|
2272
|
-
const execIsExternalContent = effectiveSafety.execIsExternalContent;
|
|
2303
|
+
const execIsExternalContent = effectiveSafety.execIsExternalContent || externalContentTargetActive;
|
|
2273
2304
|
provenanceForChildrenRef.current = { trustedTools: [...trustedTools], execIsExternalContent };
|
|
2274
2305
|
announceDeclaredMcpContentClasses({
|
|
2275
2306
|
entries: lockedPreflight.mcp,
|
|
@@ -2291,12 +2322,14 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2291
2322
|
};
|
|
2292
2323
|
const callerToolNames = new Set((spec.tools ?? []).map((t) => t.name));
|
|
2293
2324
|
const contentOriginWrapped = new WeakSet();
|
|
2294
|
-
const classOf = (t) =>
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2325
|
+
const classOf = (t) => externalContentTargetActive && envHandToolNames.has(t.name)
|
|
2326
|
+
? "external"
|
|
2327
|
+
: classifyToolContentOrigin({
|
|
2328
|
+
declared: t.contentOrigin,
|
|
2329
|
+
isProtocolTool: protocolOf(t.name) !== undefined,
|
|
2330
|
+
isCallerTool: callerToolNames.has(t.name),
|
|
2331
|
+
trusted: trustedTools.has(t.name),
|
|
2332
|
+
});
|
|
2300
2333
|
const delegationByName = new Map();
|
|
2301
2334
|
for (const t of spec.tools ?? []) {
|
|
2302
2335
|
const withFaces = t;
|
|
@@ -3888,6 +3921,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3888
3921
|
gitAnnouncement: gitStatusRef.announced !== undefined ? { ...gitStatusRef.announced } : undefined,
|
|
3889
3922
|
delegationProvenance: internals?.delegationProvenance !== undefined ? { ...internals.delegationProvenance.ref.current } : undefined,
|
|
3890
3923
|
isDelegatedChild: hookIdentity.isDelegatedChild ? true : undefined,
|
|
3924
|
+
placementRootSessionId: stampPlacementRootSessionId(placementRootResolved),
|
|
3925
|
+
externalContentTarget: externalContentTargetActive ? true : undefined,
|
|
3891
3926
|
});
|
|
3892
3927
|
const commitSuspendSaga = async (token, cp, remoteEnv, remoteHandle) => {
|
|
3893
3928
|
if (!checkpointStore)
|
|
@@ -49,6 +49,16 @@ export async function prepareWorkspaceRestore(input) {
|
|
|
49
49
|
throw e;
|
|
50
50
|
};
|
|
51
51
|
const handle = resume.workspaceHandle;
|
|
52
|
+
const refuseIdentityMismatch = (resumedHandle) => {
|
|
53
|
+
if (resumedHandle.provider !== handle.provider) {
|
|
54
|
+
failResume(`resume workspace identity mismatch: the checkpoint records provider "${handle.provider}" but the resumed env reports "${resumedHandle.provider}" — refusing to continue a workspace on a different backend (wire the factory to rebuild the checkpointed lane and re-resume)`);
|
|
55
|
+
}
|
|
56
|
+
if (handle.deviceId !== undefined && resumedHandle.deviceId !== handle.deviceId) {
|
|
57
|
+
failResume(resumedHandle.deviceId === undefined
|
|
58
|
+
? `resume workspace identity mismatch: the checkpoint records deviceId "${handle.deviceId}" but the resumed env reports none — a device-bound workspace cannot be verified on a handle that does not state its device (absence is not a match)`
|
|
59
|
+
: `resume workspace identity mismatch: the checkpoint records deviceId "${handle.deviceId}" but the resumed env reports "${resumedHandle.deviceId}" — refusing to continue on a different physical device`);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
52
62
|
if (ownedEnv === undefined || !isRemoteExecutionEnv(ownedEnv)) {
|
|
53
63
|
failResume("resume needs a RemoteExecutionEnv from executionEnvFactory to restore the workspace snapshot");
|
|
54
64
|
}
|
|
@@ -56,6 +66,7 @@ export async function prepareWorkspaceRestore(input) {
|
|
|
56
66
|
if (handle.restoreMode !== "park_only" && ownedEnv.capabilities.suspendable) {
|
|
57
67
|
failResume("checkpoint workspaceHandle has no snapshotId and is not a park_only handle, but the resumed env is suspendable — refusing to resume on a possibly-unrestored workspace (corrupt checkpoint?)");
|
|
58
68
|
}
|
|
69
|
+
refuseIdentityMismatch(ownedEnv.workspaceHandle());
|
|
59
70
|
if (handle.mountPath && handle.mountPath !== taskRootPath) {
|
|
60
71
|
taskRootPath = handle.mountPath;
|
|
61
72
|
}
|
|
@@ -68,6 +79,7 @@ export async function prepareWorkspaceRestore(input) {
|
|
|
68
79
|
if (missingHere.length > 0) {
|
|
69
80
|
failResume(`the resumed execution env cannot restore a workspace snapshot: its adapter does not implement ${missingHere.join(" or ")}. The checkpoint holds snapshot "${handle.snapshotId}" — wire an adapter that implements the full RemoteExecutionEnv restore surface and re-resume.`);
|
|
70
81
|
}
|
|
82
|
+
refuseIdentityMismatch(ownedEnv.workspaceHandle());
|
|
71
83
|
const { outcome: restored, attempts: restoreAttempts } = await restoreWorkspaceWithRetry(ownedEnv, handle.snapshotId, {
|
|
72
84
|
abortSignal: restoreSignal,
|
|
73
85
|
priorHandle: handle,
|
|
@@ -76,6 +88,7 @@ export async function prepareWorkspaceRestore(input) {
|
|
|
76
88
|
failResume(`resumeVM failed after ${restoreAttempts} attempt(s) (${restored.error.code}): ${restored.error.message}`, restored.error, remoteEnvFailureNote("resumeVM", restored.error, restoreAttempts));
|
|
77
89
|
}
|
|
78
90
|
else {
|
|
91
|
+
refuseIdentityMismatch(restored.value);
|
|
79
92
|
const restoredEnv = ownedEnv;
|
|
80
93
|
const canonicalInEnv = async (p) => {
|
|
81
94
|
try {
|
|
@@ -179,6 +179,24 @@ export declare class Runner {
|
|
|
179
179
|
* observed `already_resolved` and by the FIFO cap — an evicted entry only ever degrades to the explicit
|
|
180
180
|
* re-supply requirement (fail-loud), never fail-open. */
|
|
181
181
|
private readonly parentConstraintRegistry;
|
|
182
|
+
/** rescan C2 — same-process suspended-env reap handles by token: the design/51 fence contract is
|
|
183
|
+
* "only the expire WINNER destroys the paused env", and the terminal resume-preflight arm
|
|
184
|
+
* (resumeStream) can now BE that winner — without this registry the losing stream-side
|
|
185
|
+
* reapSuspended returned early and the paused env this process holds leaked forever. Registered
|
|
186
|
+
* when a suspend publishes its reap handle; retired when the stream's own reap runs, when a
|
|
187
|
+
* resume wins the resolve CAS (the restore path owns the env from there — a later reopen keeps
|
|
188
|
+
* the ORIGINAL stream's closure as the reaping owner, deliberately), and when the terminal arm
|
|
189
|
+
* wins. Bounded by live suspended streams (the closures hold the same handles). */
|
|
190
|
+
private readonly suspendedEnvReaps;
|
|
191
|
+
/** rescan C2 review r3 — SAME-RUNNER claim marks for {@link suspendedEnvReaps}: a token appears
|
|
192
|
+
* here from the moment an in-process actor ATTEMPTS its settling CAS (the terminal preflight's
|
|
193
|
+
* expire; a resume's resolve) until that actor's duty decision is done. The losing stream-side
|
|
194
|
+
* reap preserves a record ONLY while a local claim is in flight — a CROSS-Runner settlement
|
|
195
|
+
* leaves no local claim, so the loser retires the record (drops the strong ref; physical cleanup
|
|
196
|
+
* belongs to the winning owner, unprovable from here) instead of retaining env handles without
|
|
197
|
+
* bound in a long-lived Runner. A stale claim's failure direction is retention (the status quo),
|
|
198
|
+
* never a wrong destroy. */
|
|
199
|
+
private readonly locallyClaimedTokens;
|
|
182
200
|
private static readonly PARENT_CONSTRAINT_REGISTRY_CAP;
|
|
183
201
|
constructor(deps: RunnerDeps);
|
|
184
202
|
/** design/73 §1 v1 — fire `RunnerDeps.onTaskOutcome` through the single swallow-guarded chokepoint
|