@sema-agent/core 5.31.0 → 5.33.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 +96 -0
- package/dist/agents/cascade.d.ts +49 -1
- package/dist/agents/cascade.js +2 -2
- package/dist/agents/verify.d.ts +70 -4
- package/dist/agents/verify.js +62 -16
- package/dist/core/checkpoint-store.d.ts +95 -0
- package/dist/core/checkpoint-store.js +40 -0
- package/dist/core/hooks.d.ts +14 -6
- package/dist/core/hooks.js +14 -3
- package/dist/core/memory-engine/file-backend.d.ts +172 -22
- package/dist/core/memory-engine/file-backend.js +877 -79
- package/dist/core/memory-engine/memory-backend-contract.js +33 -0
- package/dist/core/runner/assemble-result.d.ts +7 -0
- package/dist/core/runner/assemble-result.js +1 -1
- package/dist/core/runner/prepare-acquire-reconcile.d.ts +72 -0
- package/dist/core/runner/prepare-acquire-reconcile.js +126 -0
- package/dist/core/runner/prepare-config-doors.d.ts +140 -0
- package/dist/core/runner/prepare-config-doors.js +250 -0
- package/dist/core/runner/prepare-safety-scan.d.ts +53 -0
- package/dist/core/runner/prepare-safety-scan.js +80 -0
- package/dist/core/runner/prepare-task.d.ts +28 -80
- package/dist/core/runner/prepare-task.js +102 -586
- package/dist/core/runner/prepare-workspace-restore.d.ts +102 -0
- package/dist/core/runner/prepare-workspace-restore.js +144 -0
- package/dist/core/runner/runtask.js +8 -2
- package/dist/core/tool-policy.d.ts +25 -0
- package/dist/core/types.d.ts +149 -13
- package/dist/index.d.ts +5 -4
- package/dist/index.js +2 -2
- package/dist/orchestration/workflow-governance.d.ts +6 -4
- package/dist/tools/fs/bash-readonly-classifier.d.ts +9 -3
- package/dist/tools/fs/bash-readonly-classifier.js +4 -1
- package/dist/tools/fs/fs-bash.d.ts +19 -3
- package/dist/tools/fs/fs-bash.js +26 -1
- package/dist/tools/fs/index.d.ts +27 -7
- package/dist/tools/fs/index.js +7 -2
- package/dist/tools/fs/read-deny.d.ts +66 -8
- package/dist/tools/fs/read-deny.js +75 -39
- package/dist/tools/fs/read-face.d.ts +24 -2
- package/dist/tools/fs/read-face.js +9 -0
- package/dist/tools/fs/search.js +2 -0
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@ import { sanitizeCompactionSettings } from "../auto-compaction.js";
|
|
|
7
7
|
import { projectStaleToolResults, resolveStaleToolResultOffload } from "./compaction-call-options.js";
|
|
8
8
|
import { createAutoModeDecider } from "../auto-mode.js";
|
|
9
9
|
import { buildAutoModePrompt, renderAutoModeAction, renderAutoModeWindow } from "../auto-mode-prompt.js";
|
|
10
|
-
import {
|
|
10
|
+
import { resolveTaskModel } from "../roles.js";
|
|
11
11
|
import { primaryActivityArg } from "../arg-summary.js";
|
|
12
12
|
import { materializeMcpTools } from "../mcp.js";
|
|
13
13
|
import { materializeA2aTools } from "../a2a.js";
|
|
@@ -15,7 +15,7 @@ import { Type } from "typebox";
|
|
|
15
15
|
import { Value } from "typebox/value";
|
|
16
16
|
import { uuidv7 } from "../../engine/session/uuid.js";
|
|
17
17
|
import { brainToRuntime } from "../runtime.js";
|
|
18
|
-
import {
|
|
18
|
+
import { hasSessionFork } from "../session.js";
|
|
19
19
|
import { createSubagentWorktreeHelper, forkGovernanceDenial } from "../../agents/subagent.js";
|
|
20
20
|
import { createAgentTranscriptTool, AGENT_TRANSCRIPT_TOOL_NAME } from "../../agents/agent-transcript-tool.js";
|
|
21
21
|
import { createSendMessageTool, SEND_MESSAGE_TOOL_NAME } from "../../agents/send-message-tool.js";
|
|
@@ -31,13 +31,12 @@ import { emitTrace } from "../trace.js";
|
|
|
31
31
|
import { createSessionRulePolicy } from "./session-rule-policy.js";
|
|
32
32
|
import { cloneObserverInput, createHookEnvCapabilities, createPreToolUseConstraintPolicy, formatHookFeedback, persistedRuleMandateOf, runToolGate } from "../hooks.js";
|
|
33
33
|
import { orgRuleVerdictFor } from "../permission-rule-org.js";
|
|
34
|
-
import { reconcileInterruptedSession } from "../session-reconcile.js";
|
|
35
34
|
import { CacheBreakDetector, toolsToFingerprintInputs } from "../cache-break-detector.js";
|
|
36
35
|
import { reservedCollisions, reservedFor } from "../../brain/request-params.js";
|
|
37
|
-
import {
|
|
36
|
+
import { STALL_CONNECT_MS, STALL_FIRST_TOKEN_MS, STALL_IDLE_MS, withBrainCallGuardrail } from "../../brain/timeout.js";
|
|
38
37
|
import { defineTool, isDefineToolProduct } from "../tools.js";
|
|
39
38
|
import { RETIRED_TOOL_NAMES } from "../tool-name-aliases.js";
|
|
40
|
-
import { protocolOf
|
|
39
|
+
import { protocolOf } from "../protocol-table.js";
|
|
41
40
|
import { pathToUri } from "../lsp-protocol.js";
|
|
42
41
|
import { DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, createOffloadPersist, firstPartyOffloadPolicy, InMemoryToolResultStore, RunnerSharedToolResultStore, ScopedToolResultStore, isVolatileOffloadStore, OFFLOAD_TOOL_NAME, createReadToolResultTool, withToolResultOffload, } from "../tool-result-store.js";
|
|
43
42
|
import { OUTPUT_TOOL_NAME, REPORT_FINDINGS_TOOL_NAME, SKILL_CONTENT_MAX_CHARS, SKILL_TOOL_NAME, createOutputTool, createReportBlockedTool, createReportFindingsTool, createSkillTool, normalizeSkills } from "./synthetic-tools.js";
|
|
@@ -50,11 +49,13 @@ import { MEMORY_RECALL_DISCIPLINE } from "../memory-engine/engine.js";
|
|
|
50
49
|
import { classifyToolContentOrigin, contentOriginPollutes, delegationCallIsExternal } from "../memory-engine/content-origin.js";
|
|
51
50
|
import { narrowContentSafety, readCardAttestation } from "../memory-engine/delegation-provenance.js";
|
|
52
51
|
import { composeMemoryBlock } from "../memory.js";
|
|
53
|
-
import { preflightLockedConfig } from "../locked-config.js";
|
|
54
52
|
import { COMPLIANCE_CAPABILITIES, WEB_FETCH_TOOL_NAME, complianceCallDenial, resolveComplianceDenies } from "../compliance.js";
|
|
55
|
-
import { assertRetentionCapability } from "../retention.js";
|
|
56
53
|
import { foldAdmissionFreeze } from "../memory-admission.js";
|
|
57
54
|
import { prepareMemory } from "./prepare-memory.js";
|
|
55
|
+
import { limitConfigError, prepareConfigDoors } from "./prepare-config-doors.js";
|
|
56
|
+
import { NAMESPACED_NAME_SHAPES, prepareSafetyScan } from "./prepare-safety-scan.js";
|
|
57
|
+
import { prepareAcquireReconcile } from "./prepare-acquire-reconcile.js";
|
|
58
|
+
import { prepareWorkspaceRestore, rebaseWorkspacePath, remoteEnvFailureNote, restoreWorkspaceWithRetry } from "./prepare-workspace-restore.js";
|
|
58
59
|
import { defaultPromptProvider, buildEnvironmentContext, buildGitSnapshot, formatLocalDate, isValidTimeZone, PROJECT_CONTEXT_FRAMING } from "../../prompts/default.js";
|
|
59
60
|
import { assemblePrompt } from "../../prompt-assembly/assemble.js";
|
|
60
61
|
import { auditToolCollisions, getToolContract, projectToolManifest } from "../../prompt-assembly/tool-catalog.js";
|
|
@@ -67,18 +68,17 @@ import { capAggregateToolResults } from "../tool-result-budget.js";
|
|
|
67
68
|
import { capAggregateMediaBytes, AGGREGATE_MEDIA_BUDGET_BYTES } from "../media-byte-cap.js";
|
|
68
69
|
import { dropOrphanToolResults, guardBudget, insertTrimNotice, trimToBudget } from "../context-guard.js";
|
|
69
70
|
import { StubExecutionEnv } from "../stub-env.js";
|
|
70
|
-
import { hasDestroy, isIsolated, isRemoteExecutionEnv,
|
|
71
|
-
import { withRetry } from "../with-retry.js";
|
|
71
|
+
import { hasDestroy, isIsolated, isRemoteExecutionEnv, isSuspendable, missingRestoreSurface } from "../remote-env.js";
|
|
72
72
|
import { settleTeardownLeg } from "./teardown-bounded.js";
|
|
73
73
|
import { hasBackgroundShell, sweepBackgroundShells } from "../background-shell.js";
|
|
74
74
|
import { createTaskOutputTool, createTaskStopTool, defaultTaskRegistry } from "../task-registry.js";
|
|
75
75
|
import { createMonitorTool } from "../../tools/monitor.js";
|
|
76
76
|
import { createWorktreeTools } from "../../tools/worktree.js";
|
|
77
|
-
import { applyCompactionToReadFileState,
|
|
77
|
+
import { applyCompactionToReadFileState, bashReversibilityProbe, compileReadDeny, createHandsToolkit, deploymentReadFaceClampNotice, isReadDedupStubResult, resolveReadDenyBuiltins, resolveReadFace, seedReadFileStateFromContext, seedReadFileStateFromTranscript, FULL_SHELL_CONTRACT_ID, HAND_TOOL_EFFECTS, pdfModelCapabilitiesOf } from "../../tools/fs/index.js";
|
|
78
78
|
import { decodeTextBytes } from "../../tools/fs/encoding.js";
|
|
79
79
|
import { ASK_USER_QUESTION_TOOL_NAME, createAskUserQuestionTool, classifyQuestionOutcome, isLiveQuestionFace, validateAskQuestions, } from "../ask-question.js";
|
|
80
80
|
import { createSchedulerTools } from "../../tools/scheduler-tools.js";
|
|
81
|
-
import { createPresentPlanTool, createEnterPlanModeTool, PRESENT_PLAN_TOOL_NAME
|
|
81
|
+
import { createPresentPlanTool, createEnterPlanModeTool, PRESENT_PLAN_TOOL_NAME } from "../present-plan-tool.js";
|
|
82
82
|
import { isSelfOrchestrationActive, selfOrchestrationFailClosedReason } from "../../orchestration/workflow-script-runner.js";
|
|
83
83
|
import { createRunWorkflowTool, RUN_WORKFLOW_TOOL_NAME } from "../../orchestration/run-workflow-tool.js";
|
|
84
84
|
import { resolveWorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js";
|
|
@@ -87,8 +87,8 @@ import { resolveKey } from "../../tools/fs/safety.js";
|
|
|
87
87
|
import { wholeFileRecordsFromTranscript } from "./session-file-state-replay.js";
|
|
88
88
|
import { BINDING_CHECKPOINT_VERSION, mintCheckpointToken, ORG_ADMISSION_CHECKPOINT_VERSION, F012_CHECKPOINT_VERSION, FACE_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, buildRiskDescriptor, debitLedger, encodeAtFidelity, remainingBudgetMicroUsd, resolveCheckpointStore, resolveDeclaredFidelity, samePlainValue, } from "../checkpoint-store.js";
|
|
89
89
|
import { boundInputHashOf } from "../canonical-json.js";
|
|
90
|
-
import { countElicitOptIns,
|
|
91
|
-
import { GLOBAL_USAGE_KEY,
|
|
90
|
+
import { countElicitOptIns, deriveWiringManifest, resolveAskSeamForm, resolveDeclaredDurability, resolveElicitSeam, resolveQuestionSeam } from "../wiring-manifest.js";
|
|
91
|
+
import { GLOBAL_USAGE_KEY, usageRetryAfterMs } from "../usage-window-store.js";
|
|
92
92
|
import { deliverEngineNotice } from "../types.js";
|
|
93
93
|
const announcedMaterializeEnv = new Set();
|
|
94
94
|
export function __resetMaterializeEnvAnnouncements() {
|
|
@@ -97,74 +97,12 @@ export function __resetMaterializeEnvAnnouncements() {
|
|
|
97
97
|
function emitMaterializeEnvNotice(onNotice, message, detail) {
|
|
98
98
|
deliverEngineNotice(onNotice, { code: "config.materialize_env_discarded", message, detail });
|
|
99
99
|
}
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
maxCostUsd: true,
|
|
105
|
-
maxTurns: true,
|
|
106
|
-
maxWalltimeMs: true,
|
|
107
|
-
maxOutputTokens: true,
|
|
108
|
-
approachNotice: true,
|
|
109
|
-
budgetStreamCancel: true,
|
|
110
|
-
degrade: true,
|
|
111
|
-
brainCallGuardrailMs: true,
|
|
112
|
-
};
|
|
113
|
-
const TASK_LIMIT_KEYS = Object.keys(TASK_LIMIT_KEY_DICT);
|
|
114
|
-
const NUMERIC_TASK_LIMIT_KEYS = ["maxTokens", "maxCostUsd", "maxTurns", "maxWalltimeMs", "maxOutputTokens"];
|
|
115
|
-
const RETIRED_TASK_LIMIT_KEYS = {
|
|
116
|
-
timeoutSec: "maxWalltimeMs (milliseconds, not seconds)",
|
|
117
|
-
deadlineNudge: "approachNotice",
|
|
118
|
-
callCapByDeadline: "(retired — no replacement)",
|
|
119
|
-
gracefulFinalize: "(retired — the approach notice is the only end-of-budget prompt)",
|
|
120
|
-
};
|
|
121
|
-
function limitConfigError(code, message) {
|
|
122
|
-
const e = new Error(message);
|
|
123
|
-
e.code = code;
|
|
124
|
-
return e;
|
|
125
|
-
}
|
|
126
|
-
export function resolveTaskLimits(limits) {
|
|
127
|
-
if (limits === undefined)
|
|
128
|
-
return undefined;
|
|
129
|
-
if (typeof limits !== "object" || Array.isArray(limits)) {
|
|
130
|
-
throw limitConfigError("config.limit_invalid", `TaskSpec.limits must be an object (got ${Array.isArray(limits) ? "an array" : typeof limits})`);
|
|
131
|
-
}
|
|
132
|
-
const raw = limits;
|
|
133
|
-
const legal = TASK_LIMIT_KEYS;
|
|
134
|
-
for (const key of Object.keys(raw)) {
|
|
135
|
-
if (legal.includes(key))
|
|
136
|
-
continue;
|
|
137
|
-
const replacement = RETIRED_TASK_LIMIT_KEYS[key];
|
|
138
|
-
throw limitConfigError("config.limit_unknown_key", `TaskSpec.limits.${key} is not a limit this engine reads` +
|
|
139
|
-
(replacement !== undefined ? ` — it was replaced by \`${replacement}\`` : "") +
|
|
140
|
-
`. Legal keys: ${TASK_LIMIT_KEYS.join(", ")}. Refused rather than ignored: a limit that is silently dropped reads to the caller as an armed ceiling.`);
|
|
141
|
-
}
|
|
142
|
-
for (const key of NUMERIC_TASK_LIMIT_KEYS) {
|
|
143
|
-
const value = raw[key];
|
|
144
|
-
if (value === undefined)
|
|
145
|
-
continue;
|
|
146
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
147
|
-
throw limitConfigError("config.limit_invalid", `TaskSpec.limits.${key} must be a finite, non-negative number (got ${String(value)}) — an unevaluable ceiling is not a ceiling, and folding it to a default would run the task under a limit nobody chose.`);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
const notice = raw.approachNotice;
|
|
151
|
-
if (notice !== undefined && notice !== false) {
|
|
152
|
-
if (typeof notice !== "object" || notice === null || Array.isArray(notice)) {
|
|
153
|
-
throw limitConfigError("config.limit_invalid", `TaskSpec.limits.approachNotice must be \`false\` or { at?: [first, second] } (got ${String(notice)})`);
|
|
154
|
-
}
|
|
155
|
-
const at = notice.at;
|
|
156
|
-
if (at !== undefined) {
|
|
157
|
-
const ok = Array.isArray(at) &&
|
|
158
|
-
at.length === 2 &&
|
|
159
|
-
at.every((n) => typeof n === "number" && Number.isFinite(n) && n > 0 && n <= 1) &&
|
|
160
|
-
at[0] <= at[1];
|
|
161
|
-
if (!ok) {
|
|
162
|
-
throw limitConfigError("config.limit_invalid", `TaskSpec.limits.approachNotice.at must be two fractions in (0, 1] with first <= second (got ${JSON.stringify(at)})`);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
return limits;
|
|
100
|
+
const readFaceClampAnnouncedSinks = new WeakSet();
|
|
101
|
+
let readFaceClampConsoleAnnounced = false;
|
|
102
|
+
export function __resetReadFaceClampAnnouncement() {
|
|
103
|
+
readFaceClampConsoleAnnounced = false;
|
|
167
104
|
}
|
|
105
|
+
const DEFAULT_MAX_SUSPENDS = 5;
|
|
168
106
|
const ULTRA_REASONING_TIERS = new Set(["xhigh", "max"]);
|
|
169
107
|
const DEFAULT_RESOURCE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
170
108
|
export const ENV_LIFETIME_SUSPEND_MARGIN_MS = 60_000;
|
|
@@ -197,7 +135,6 @@ function sanitizedTtlMs(ttlMs) {
|
|
|
197
135
|
const ungatedWarnedShapes = new WeakMap();
|
|
198
136
|
const advisedPolicyNames = new WeakMap();
|
|
199
137
|
const ADVISED_KEYS_CAP = 64;
|
|
200
|
-
const NAMESPACED_NAME_SHAPES = PROTOCOL_TABLE.map((ns) => `${ns.prefix}<peer>__<tool>`).join(", ");
|
|
201
138
|
export const DEFAULT_IRREVERSIBLE_SCOPE = "irreversible";
|
|
202
139
|
export function checkpointScopeOf(spec) {
|
|
203
140
|
return spec.durableApproval?.scope || spec.principal || DEFAULT_IRREVERSIBLE_SCOPE;
|
|
@@ -209,16 +146,8 @@ class ParkRefusal extends Error {
|
|
|
209
146
|
}
|
|
210
147
|
}
|
|
211
148
|
export { resolveCheckpointStore } from "../checkpoint-store.js";
|
|
212
|
-
export
|
|
213
|
-
|
|
214
|
-
return /^claude-fable-\d/.test(tail) || /^claude-mythos-5(?!\d)/.test(tail);
|
|
215
|
-
}
|
|
216
|
-
export function resolveModelPromptTraits(model, spec, internals) {
|
|
217
|
-
return {
|
|
218
|
-
promptProfile: spec.promptProfile ?? internals?.promptProfile ?? "simple",
|
|
219
|
-
fableMitigations: isFableFamilyModelId(model.id),
|
|
220
|
-
};
|
|
221
|
-
}
|
|
149
|
+
export { isFableFamilyModelId, resolveModelPromptTraits, resolveTaskLimits } from "./prepare-config-doors.js";
|
|
150
|
+
export { rebaseWorkspacePath, rebaseWorkspacePathAcross } from "./prepare-workspace-restore.js";
|
|
222
151
|
function hasConversationContent(branch) {
|
|
223
152
|
return branch.some((e) => e.type === "message" || e.type === "custom_message" || e.type === "compaction");
|
|
224
153
|
}
|
|
@@ -238,31 +167,6 @@ export function batchContextAt(messages, currentId) {
|
|
|
238
167
|
const completedCallIds = batch.filter((id) => id !== currentId && resolved.has(id));
|
|
239
168
|
return { batchToolCallIds: batch, completedCallIds };
|
|
240
169
|
}
|
|
241
|
-
function detectConflicts(storage, ref) {
|
|
242
|
-
const guard = (fn) => async (...args) => {
|
|
243
|
-
try {
|
|
244
|
-
return await fn(...args);
|
|
245
|
-
}
|
|
246
|
-
catch (err) {
|
|
247
|
-
if (isSessionConflict(err)) {
|
|
248
|
-
ref.hit = true;
|
|
249
|
-
}
|
|
250
|
-
throw err;
|
|
251
|
-
}
|
|
252
|
-
};
|
|
253
|
-
return new Proxy(storage, {
|
|
254
|
-
get(target, prop, receiver) {
|
|
255
|
-
const value = Reflect.get(target, prop, receiver);
|
|
256
|
-
if (typeof value !== "function") {
|
|
257
|
-
return value;
|
|
258
|
-
}
|
|
259
|
-
const bound = value.bind(target);
|
|
260
|
-
return prop === "appendEntry" || prop === "setLeafId"
|
|
261
|
-
? guard(bound)
|
|
262
|
-
: bound;
|
|
263
|
-
},
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
170
|
function raceAbort(p, signal, onAbort) {
|
|
267
171
|
if (signal.aborted)
|
|
268
172
|
return Promise.resolve(onAbort());
|
|
@@ -288,353 +192,19 @@ async function forgetQuietly(sessions, sessionId) {
|
|
|
288
192
|
catch {
|
|
289
193
|
}
|
|
290
194
|
}
|
|
291
|
-
export function rebaseWorkspacePath(p, fromRaw, toRaw) {
|
|
292
|
-
if (p.includes("\\") || fromRaw.includes("\\") || toRaw.includes("\\"))
|
|
293
|
-
return p;
|
|
294
|
-
const stripTrail = (s) => (s.length > 1 && s.endsWith("/") ? stripTrail(s.slice(0, -1)) : s);
|
|
295
|
-
const from = stripTrail(fromRaw);
|
|
296
|
-
const to = stripTrail(toRaw);
|
|
297
|
-
if (p === from || stripTrail(p) === from)
|
|
298
|
-
return to;
|
|
299
|
-
const fromPrefix = from === "/" ? from : `${from}/`;
|
|
300
|
-
if (!p.startsWith(fromPrefix))
|
|
301
|
-
return p;
|
|
302
|
-
const suffix = p.slice(fromPrefix.length).replace(/^\/+/, "");
|
|
303
|
-
return to === "/" ? `${to}${suffix}` : `${to}/${suffix}`;
|
|
304
|
-
}
|
|
305
|
-
function remoteEnvFailureNote(op, error, attempts) {
|
|
306
|
-
return { op, code: error.code, retryable: isRetryableRemoteErrorCode(error.code), attempts, message: error.message };
|
|
307
|
-
}
|
|
308
|
-
const REMOTE_RESTORE_MAX_ATTEMPTS = 2;
|
|
309
|
-
const REMOTE_RESTORE_BACKOFF_MS = 200;
|
|
310
|
-
async function restoreWorkspaceWithRetry(env, snapshotId, options) {
|
|
311
|
-
let attempts = 0;
|
|
312
|
-
const outcome = await withRetry(async (attempt) => {
|
|
313
|
-
attempts = attempt;
|
|
314
|
-
return env.resumeVM(snapshotId, options);
|
|
315
|
-
}, { retryableCodes: RETRYABLE_REMOTE_ERROR_CODES, maxAttempts: REMOTE_RESTORE_MAX_ATTEMPTS, backoffMs: () => REMOTE_RESTORE_BACKOFF_MS }, { ...(options.abortSignal !== undefined ? { signal: options.abortSignal } : {}) });
|
|
316
|
-
return { outcome, attempts };
|
|
317
|
-
}
|
|
318
|
-
export function rebaseWorkspacePathAcross(p, froms, to) {
|
|
319
|
-
for (const from of froms) {
|
|
320
|
-
const out = rebaseWorkspacePath(p, from, to);
|
|
321
|
-
if (out !== p)
|
|
322
|
-
return out;
|
|
323
|
-
}
|
|
324
|
-
return p;
|
|
325
|
-
}
|
|
326
195
|
export async function prepareTask(spec, deps, sessions, resume, internals, runnerSelf) {
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
};
|
|
332
|
-
const promptProfile = resolveModelPromptTraits({ id: "" }, spec, internals).promptProfile;
|
|
333
|
-
if (spec.resumeAt !== undefined) {
|
|
334
|
-
if (resume) {
|
|
335
|
-
const e = new Error("resumeAt cannot be combined with a durable resume");
|
|
336
|
-
e.code = "resume_at.conflicts_resume";
|
|
337
|
-
throw e;
|
|
338
|
-
}
|
|
339
|
-
if (!spec.sessionId) {
|
|
340
|
-
const e = new Error("resumeAt requires a sessionId (the session to branch)");
|
|
341
|
-
e.code = "resume_at.no_session";
|
|
342
|
-
throw e;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
assertReadFaceValue(spec.readFace, "TaskSpec.readFace");
|
|
346
|
-
assertReadFaceValue(deps.readFace, "readFace (deployment seat)");
|
|
347
|
-
if (spec.resumeAtMode !== undefined) {
|
|
348
|
-
if (spec.resumeAt === undefined) {
|
|
349
|
-
const e = new Error(`resumeAtMode "${spec.resumeAtMode}" requires resumeAt (there is no branch target to position against)`);
|
|
350
|
-
e.code = "resume_at.mode_without_target";
|
|
351
|
-
throw e;
|
|
352
|
-
}
|
|
353
|
-
if (spec.resumeAtMode !== "at" && spec.resumeAtMode !== "before") {
|
|
354
|
-
const e = new Error(`resumeAtMode "${String(spec.resumeAtMode)}" is invalid — expected "at" (inclusive branch, the default) or "before" (exclusive branch)`);
|
|
355
|
-
e.code = "resume_at.invalid_mode";
|
|
356
|
-
throw e;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
if (spec.toolMaterializeStrategy !== undefined && spec.toolMaterializeStrategy !== "swap" && spec.toolMaterializeStrategy !== "static") {
|
|
360
|
-
const e = new Error(`toolMaterializeStrategy must be "swap" or "static" (got ${JSON.stringify(spec.toolMaterializeStrategy)}).`);
|
|
361
|
-
e.code = "config.tool_materialize_invalid";
|
|
362
|
-
throw e;
|
|
363
|
-
}
|
|
364
|
-
if (spec.memoryPersistenceCapable !== undefined && typeof spec.memoryPersistenceCapable !== "boolean") {
|
|
365
|
-
const e = new Error(`memoryPersistenceCapable must be a boolean when present (got ${JSON.stringify(spec.memoryPersistenceCapable)}) — a non-boolean would silently read as capable.`);
|
|
366
|
-
e.code = "config.memory_persistence_invalid";
|
|
367
|
-
throw e;
|
|
368
|
-
}
|
|
369
|
-
if (spec.toolMaterializeStrategy === "static" && spec.deferSelfResolve === false) {
|
|
370
|
-
const e = new Error(`toolMaterializeStrategy "static" cannot be combined with deferSelfResolve: false — with the direct-call ` +
|
|
371
|
-
`lane disabled a placeholder is never swapped and never self-resolves, so no deferred tool could ever be ` +
|
|
372
|
-
`called. Use "swap", or leave deferSelfResolve on.`);
|
|
373
|
-
e.code = "config.tool_materialize_unreachable";
|
|
374
|
-
throw e;
|
|
375
|
-
}
|
|
376
|
-
if (resume === undefined && spec.objective.trim().length === 0) {
|
|
377
|
-
const e = new Error("TaskSpec.objective is empty — a task needs an instruction (an empty user message is rejected by strict model endpoints and would fail every later request of the session).");
|
|
378
|
-
e.code = "config.empty_objective";
|
|
379
|
-
throw e;
|
|
380
|
-
}
|
|
381
|
-
const lockedPreflight = preflightLockedConfig(spec, deps);
|
|
382
|
-
assertRetentionCapability({
|
|
383
|
-
policy: deps.retentionPolicy,
|
|
384
|
-
locked: lockedPreflight.lockedKeys.has("retentionPolicy"),
|
|
385
|
-
stores: [
|
|
386
|
-
{ name: "sessionStore", store: sessions },
|
|
387
|
-
{ name: "checkpointStore", store: resolveCheckpointStore(spec, deps) },
|
|
388
|
-
{ name: "toolResultStore", store: deps.toolResultStore },
|
|
389
|
-
],
|
|
390
|
-
});
|
|
391
|
-
const resolvedInteractionPosture = spec.interactionPosture ?? internals?.parentInteractionPosture ?? deps.interactionPosture;
|
|
392
|
-
{
|
|
393
|
-
const interactionPosture = resolvedInteractionPosture;
|
|
394
|
-
const discloseInteractionPostureRefusal = (err) => {
|
|
395
|
-
try {
|
|
396
|
-
deps.onError?.(err, { phase: "config", sessionId: spec.sessionId ?? "(pre-session)", classification: "interaction-posture-refused" });
|
|
397
|
-
}
|
|
398
|
-
catch {
|
|
399
|
-
}
|
|
400
|
-
};
|
|
401
|
-
if (interactionPosture !== undefined && interactionPosture !== "interactive" && interactionPosture !== "headless") {
|
|
402
|
-
const e = new Error(`interactionPosture ${JSON.stringify(interactionPosture)} is not a recognized posture ("interactive" | "headless") — ` +
|
|
403
|
-
`an unevaluable declaration is refused loudly, never folded to either posture.`);
|
|
404
|
-
e.code = "config.interaction_posture";
|
|
405
|
-
discloseInteractionPostureRefusal(e);
|
|
406
|
-
throw e;
|
|
407
|
-
}
|
|
408
|
-
if (interactionPosture === "interactive") {
|
|
409
|
-
const askDoor = resolveAskSeamForm(spec, deps);
|
|
410
|
-
const humanReachable = deriveAskEffective(askDoor.form, "unresolved") === "human_reachable";
|
|
411
|
-
const questionDoor = resolveQuestionSeam(spec, deps);
|
|
412
|
-
const strippedByEngine = internals?.questionFaceStripped === true;
|
|
413
|
-
if (spec.interactiveTools === false) {
|
|
414
|
-
const e = new Error(`interaction posture "interactive" declared together with interactiveTools: false — the hard-headless ` +
|
|
415
|
-
`clamp removes the AskUserQuestion mount, so no content question can ever reach the human this posture ` +
|
|
416
|
-
`promises. Drop one of the two declarations.`);
|
|
417
|
-
e.code = "config.interaction_posture";
|
|
418
|
-
discloseInteractionPostureRefusal(e);
|
|
419
|
-
throw e;
|
|
420
|
-
}
|
|
421
|
-
if (!humanReachable || !(questionDoor.wired || strippedByEngine)) {
|
|
422
|
-
const missing = [];
|
|
423
|
-
if (!humanReachable) {
|
|
424
|
-
missing.push(askDoor.form === "absent"
|
|
425
|
-
? "no onAsk approver is wired (spec.onAsk ?? deps.onAsk is absent — asks would auto-deny or park)"
|
|
426
|
-
: `the resolved onAsk seat is the blanket policy "${askDoor.form}" (${askDoor.provenance ?? "?"}) — a policy setting is not a reachable human`);
|
|
427
|
-
}
|
|
428
|
-
if (!questionDoor.wired && !strippedByEngine) {
|
|
429
|
-
missing.push("no content-question channel (spec.onQuestion ?? deps.onQuestion is absent, and this leg is not an engine-stripped background lane)");
|
|
430
|
-
}
|
|
431
|
-
const e = new Error(`interaction posture "interactive" declared, but this assembly cannot reach a human: ${missing.join("; ")}. ` +
|
|
432
|
-
`Wire the missing seam(s), or drop the posture declaration (absent = no check).`);
|
|
433
|
-
e.code = "config.interaction_posture";
|
|
434
|
-
discloseInteractionPostureRefusal(e);
|
|
435
|
-
throw e;
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
resolveTaskLimits(spec.limits);
|
|
440
|
-
if (spec.resourceSuspend !== undefined) {
|
|
441
|
-
const rsus = spec.resourceSuspend;
|
|
442
|
-
if (typeof rsus.scope !== "string" || rsus.scope === "") {
|
|
443
|
-
throw limitConfigError("config.limit_invalid", `TaskSpec.resourceSuspend.scope must be a non-empty string (got ${String(rsus.scope)}) — it is the multi-tenant isolation key every resource checkpoint is filed under.`);
|
|
444
|
-
}
|
|
445
|
-
for (const key of ["totalBudgetUsd", "totalTokens", "maxSlices", "ttlMs"]) {
|
|
446
|
-
const value = rsus[key];
|
|
447
|
-
if (value === undefined)
|
|
448
|
-
continue;
|
|
449
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
450
|
-
throw limitConfigError("config.limit_invalid", `TaskSpec.resourceSuspend.${key} must be a finite, non-negative number (got ${String(value)}) — an unevaluable allocation is not an allocation, and a NaN here blinds even the validated per-slice window.`);
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
const usageWindows = resolveUsageWindows(deps.usageWindows);
|
|
455
|
-
const brainCallGuardrailRef = {};
|
|
456
|
-
const brainCallGuardrailMs = resolveBrainCallGuardrailMs(spec.limits?.brainCallGuardrailMs ?? deps.brainCallGuardrailMs);
|
|
457
|
-
if (spec.agents !== undefined && spec.agents.length > 0) {
|
|
458
|
-
const pool = spec.tools ?? [];
|
|
459
|
-
if (!pool.some((t) => typeof t.withAgents === "function")) {
|
|
460
|
-
const e = new Error(`TaskSpec.agents was provided but no delegation tool (createSubagentTool) is mounted in spec.tools — the per-task agents could never be offered. Mount the delegation tool, or drop spec.agents.`);
|
|
461
|
-
e.code = "config.agents.no_delegation_tool";
|
|
462
|
-
throw e;
|
|
463
|
-
}
|
|
464
|
-
const perTask = spec.agents;
|
|
465
|
-
spec = { ...spec, tools: pool.map((t) => (typeof t.withAgents === "function" ? t.withAgents(perTask) : t)) };
|
|
466
|
-
}
|
|
467
|
-
const resolvedRole = resolveTaskModel(spec, deps);
|
|
468
|
-
const model = resolvedRole.model;
|
|
469
|
-
const fableMitigations = resolveModelPromptTraits(model, spec, internals).fableMitigations;
|
|
470
|
-
const thinking = spec.thinking ?? resolvedRole.thinking ?? model.defaultThinking;
|
|
471
|
-
const compModel = spec.compactionModel
|
|
472
|
-
? resolveModel(spec.compactionModel, deps.models)
|
|
473
|
-
: roleModelIfSet("summarize", spec, deps);
|
|
474
|
-
const toolEffects = new Map();
|
|
475
|
-
const ownToolNames = new Set((spec.tools ?? []).map((t) => t.name));
|
|
476
|
-
const claimedAliases = new Set();
|
|
477
|
-
const egressTools = new Set();
|
|
478
|
-
const irreversibleTools = new Set();
|
|
479
|
-
const irreversibilityTier = new Map();
|
|
480
|
-
const axisExplicitNegatives = new Map();
|
|
481
|
-
const reversibilityProbes = new Map();
|
|
196
|
+
const doors = prepareConfigDoors({ spec, deps, sessions, resume, internals });
|
|
197
|
+
spec = doors.spec;
|
|
198
|
+
const { toolFaceSnapshot, promptProfile, lockedPreflight, resolvedInteractionPosture, resolvedRole, model, thinking, compModel, fableMitigations, usageWindows, brainCallGuardrailRef, brainCallGuardrailMs } = doors;
|
|
199
|
+
const { toolEffects, egressTools, irreversibleTools, irreversibilityTier, axisExplicitNegatives, reversibilityProbes, ownToolNames } = prepareSafetyScan({ spec, deps });
|
|
482
200
|
let shellGatedBash = false;
|
|
483
201
|
let shellGatedMonitor = false;
|
|
484
|
-
for (const t of spec.tools ?? []) {
|
|
485
|
-
if (t.name.includes("__")) {
|
|
486
|
-
const e = new Error(`Tool name "${t.name}" is invalid: "__" is reserved for the engine's protocol tool namespaces (${NAMESPACED_NAME_SHAPES}) and must not appear in a caller tool name.`);
|
|
487
|
-
e.code = "config.tool_name_invalid";
|
|
488
|
-
throw e;
|
|
489
|
-
}
|
|
490
|
-
if (t.effect) {
|
|
491
|
-
toolEffects.set(t.name, t.effect);
|
|
492
|
-
}
|
|
493
|
-
const tier = t.irreversibility ?? (t.reversibilityProbe ? "maybe" : undefined);
|
|
494
|
-
if (tier !== undefined) {
|
|
495
|
-
irreversibilityTier.set(t.name, tier);
|
|
496
|
-
if (tier === "always" || tier === "maybe")
|
|
497
|
-
irreversibleTools.add(t.name);
|
|
498
|
-
}
|
|
499
|
-
if (t.reversibilityProbe)
|
|
500
|
-
reversibilityProbes.set(t.name, t.reversibilityProbe);
|
|
501
|
-
if (t.egress) {
|
|
502
|
-
if (t.effect !== undefined && t.effect !== "write") {
|
|
503
|
-
const e = new Error(`Tool "${t.name}" declares egress:true with effect:"${t.effect}" — an egress tool (external write) must have effect:"write" (or omit effect; write is the default).`);
|
|
504
|
-
e.code = "config.egress_requires_write_effect";
|
|
505
|
-
throw e;
|
|
506
|
-
}
|
|
507
|
-
egressTools.add(t.name);
|
|
508
|
-
}
|
|
509
|
-
for (const alias of t.aliases ?? []) {
|
|
510
|
-
if (alias === t.name || ownToolNames.has(alias))
|
|
511
|
-
continue;
|
|
512
|
-
if (claimedAliases.has(alias))
|
|
513
|
-
continue;
|
|
514
|
-
claimedAliases.add(alias);
|
|
515
|
-
if (t.effect)
|
|
516
|
-
toolEffects.set(alias, t.effect);
|
|
517
|
-
if (tier !== undefined) {
|
|
518
|
-
irreversibilityTier.set(alias, tier);
|
|
519
|
-
if (tier === "always" || tier === "maybe")
|
|
520
|
-
irreversibleTools.add(alias);
|
|
521
|
-
}
|
|
522
|
-
if (t.reversibilityProbe)
|
|
523
|
-
reversibilityProbes.set(alias, t.reversibilityProbe);
|
|
524
|
-
if (t.egress)
|
|
525
|
-
egressTools.add(alias);
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
toolEffects.set(OFFLOAD_TOOL_NAME, "read");
|
|
529
|
-
if (deps.executionEnvFactory || deps.executionEnv) {
|
|
530
|
-
for (const [name, effect] of Object.entries(HAND_TOOL_EFFECTS))
|
|
531
|
-
toolEffects.set(name, effect);
|
|
532
|
-
if (spec.handsReadOnly === true)
|
|
533
|
-
toolEffects.set("Bash", "read");
|
|
534
|
-
}
|
|
535
|
-
if (spec.enablePlanMode === true) {
|
|
536
|
-
toolEffects.set(PRESENT_PLAN_TOOL_NAME, "read");
|
|
537
|
-
toolEffects.set(ENTER_PLAN_MODE_TOOL_NAME, "read");
|
|
538
|
-
}
|
|
539
202
|
if (spec.requireExistingSession && !spec.sessionId) {
|
|
540
203
|
const e = new Error(`requireExistingSession requires a sessionId — cannot require an existing session without one (design/114 Phase3)`);
|
|
541
204
|
e.code = "resume.session_not_found";
|
|
542
205
|
throw e;
|
|
543
206
|
}
|
|
544
|
-
|
|
545
|
-
let session;
|
|
546
|
-
let conflictRef;
|
|
547
|
-
let wakeRecovered = [];
|
|
548
|
-
let resumeAtBeforeParentId = null;
|
|
549
|
-
for (let attempt = 0;; attempt++) {
|
|
550
|
-
try {
|
|
551
|
-
acquired = await sessions.acquire(spec.sessionId, spec.requireExistingSession ? { requireExisting: true } : undefined);
|
|
552
|
-
}
|
|
553
|
-
catch (err) {
|
|
554
|
-
if (spec.requireExistingSession && err?.code === "not_found") {
|
|
555
|
-
const e = new Error(`requireExistingSession: session "${spec.sessionId}" does not exist — refusing a silent fresh run (design/114 Phase3)`);
|
|
556
|
-
e.code = "resume.session_not_found";
|
|
557
|
-
throw e;
|
|
558
|
-
}
|
|
559
|
-
throw err;
|
|
560
|
-
}
|
|
561
|
-
conflictRef = { hit: false };
|
|
562
|
-
session = new StoredSession(detectConflicts(acquired.session.getStorage(), conflictRef));
|
|
563
|
-
if (!spec.sessionId) {
|
|
564
|
-
break;
|
|
565
|
-
}
|
|
566
|
-
try {
|
|
567
|
-
if (resume) {
|
|
568
|
-
await session.getStorage().setLeafId(resume.leafId);
|
|
569
|
-
}
|
|
570
|
-
else if (spec.resumeAt !== undefined) {
|
|
571
|
-
const entry = await session.getEntry(spec.resumeAt);
|
|
572
|
-
if (!entry) {
|
|
573
|
-
const e = new Error(`resumeAt entry "${spec.resumeAt}" not found in session "${spec.sessionId}"`);
|
|
574
|
-
e.code = "resume_at.not_found";
|
|
575
|
-
throw e;
|
|
576
|
-
}
|
|
577
|
-
if (entry.type !== "message" && entry.type !== "custom_message") {
|
|
578
|
-
const e = new Error(`resumeAt entry "${spec.resumeAt}" is a "${entry.type}" entry; resume-at requires a message boundary`);
|
|
579
|
-
e.code = "resume_at.not_a_message";
|
|
580
|
-
throw e;
|
|
581
|
-
}
|
|
582
|
-
if (entry.type === "message") {
|
|
583
|
-
const msg = entry.message;
|
|
584
|
-
if (msg?.role === "toolResult") {
|
|
585
|
-
const e = new Error(`resumeAt entry "${spec.resumeAt}" is a tool-result (mid-turn); resume-at requires a settled turn boundary (a user message or a finished assistant turn)`);
|
|
586
|
-
e.code = "resume_at.not_a_message";
|
|
587
|
-
throw e;
|
|
588
|
-
}
|
|
589
|
-
if (msg?.role === "assistant" && Array.isArray(msg.content) && msg.content.some((p) => p?.type === "toolCall")) {
|
|
590
|
-
const e = new Error(`resumeAt entry "${spec.resumeAt}" ends a turn mid-tool-call; resume-at requires a settled turn boundary`);
|
|
591
|
-
e.code = "resume_at.not_a_message";
|
|
592
|
-
throw e;
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
if (spec.resumeAtMode === "before") {
|
|
596
|
-
const role = entry.type === "message" ? entry.message.role : undefined;
|
|
597
|
-
if (role !== "user") {
|
|
598
|
-
const kind = entry.type === "message" ? `role "${String(role)}" message` : `"${entry.type}" entry`;
|
|
599
|
-
const e = new Error(`resumeAt entry "${spec.resumeAt}" is a ${kind}; resumeAtMode "before" supports only USER-message targets (the exclusive-rewind shape: the removed message is a user message)`);
|
|
600
|
-
e.code = "resume_at.before_target_not_user";
|
|
601
|
-
throw e;
|
|
602
|
-
}
|
|
603
|
-
let ancestorId = entry.parentId;
|
|
604
|
-
let sawConversationAbove = false;
|
|
605
|
-
while (ancestorId !== null) {
|
|
606
|
-
const ancestor = await session.getEntry(ancestorId);
|
|
607
|
-
if (!ancestor)
|
|
608
|
-
break;
|
|
609
|
-
if (ancestor.type === "message" || ancestor.type === "custom_message") {
|
|
610
|
-
sawConversationAbove = true;
|
|
611
|
-
break;
|
|
612
|
-
}
|
|
613
|
-
ancestorId = ancestor.parentId;
|
|
614
|
-
}
|
|
615
|
-
if (!sawConversationAbove) {
|
|
616
|
-
const e = new Error(`resumeAt entry "${spec.resumeAt}" is the session's first message; resumeAtMode "before" cannot rewind past the session root — start a NEW session instead`);
|
|
617
|
-
e.code = "resume_at.before_root_unsupported";
|
|
618
|
-
throw e;
|
|
619
|
-
}
|
|
620
|
-
resumeAtBeforeParentId = entry.parentId;
|
|
621
|
-
}
|
|
622
|
-
await session.getStorage().setLeafId(spec.resumeAtMode === "before" ? entry.parentId : spec.resumeAt);
|
|
623
|
-
}
|
|
624
|
-
wakeRecovered = (await reconcileInterruptedSession(session, toolEffects, resume?.suspendedBatch)).recovered;
|
|
625
|
-
break;
|
|
626
|
-
}
|
|
627
|
-
catch (err) {
|
|
628
|
-
if (isSessionConflict(err) && attempt < RECONCILE_MAX_RETRIES) {
|
|
629
|
-
if (sessions.forget) {
|
|
630
|
-
await Promise.resolve(sessions.forget(spec.sessionId)).catch(() => undefined);
|
|
631
|
-
}
|
|
632
|
-
continue;
|
|
633
|
-
}
|
|
634
|
-
await Promise.resolve(sessions.forget ? sessions.forget(acquired.sessionId) : undefined).catch(() => undefined);
|
|
635
|
-
throw err;
|
|
636
|
-
}
|
|
637
|
-
}
|
|
207
|
+
const { acquired, session, conflictRef, wakeRecovered, resumeAtBeforeParentId } = await prepareAcquireReconcile({ sessions, spec, resume, toolEffects });
|
|
638
208
|
const sessionId = acquired.sessionId;
|
|
639
209
|
const hostTaskId = spec.taskId ?? sessionId;
|
|
640
210
|
if (compModel !== undefined) {
|
|
@@ -776,7 +346,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
776
346
|
}
|
|
777
347
|
}
|
|
778
348
|
const handsEnabled = ownedEnv !== undefined || deps.executionEnv !== undefined;
|
|
779
|
-
|
|
349
|
+
const taskRootInitial = internals?.isolation === "worktree" || internals?.requestedCwd !== undefined || !deps.rootPath ? executionEnv.cwd : deps.rootPath;
|
|
780
350
|
const abortController = new AbortController();
|
|
781
351
|
if (spec.signal?.aborted)
|
|
782
352
|
abortController.abort();
|
|
@@ -824,108 +394,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
824
394
|
}
|
|
825
395
|
}
|
|
826
396
|
}
|
|
827
|
-
|
|
828
|
-
const rebaseRestoredPath = (p) => restoredRootRebase === undefined ? p : rebaseWorkspacePathAcross(p, restoredRootRebase.from, restoredRootRebase.to);
|
|
829
|
-
if (resume?.workspaceHandle !== undefined) {
|
|
830
|
-
const failResume = (message, cause, note) => {
|
|
831
|
-
const e = new Error(message, cause ? { cause } : undefined);
|
|
832
|
-
e.code = "resume.env_failed";
|
|
833
|
-
if (note !== undefined)
|
|
834
|
-
e.remoteEnvFailure = note;
|
|
835
|
-
throw e;
|
|
836
|
-
};
|
|
837
|
-
const handle = resume.workspaceHandle;
|
|
838
|
-
if (ownedEnv === undefined || !isRemoteExecutionEnv(ownedEnv)) {
|
|
839
|
-
failResume("resume needs a RemoteExecutionEnv from executionEnvFactory to restore the workspace snapshot");
|
|
840
|
-
}
|
|
841
|
-
else if (handle.snapshotId === undefined) {
|
|
842
|
-
if (handle.restoreMode !== "park_only" && ownedEnv.capabilities.suspendable) {
|
|
843
|
-
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?)");
|
|
844
|
-
}
|
|
845
|
-
if (handle.mountPath && handle.mountPath !== taskRootPath) {
|
|
846
|
-
taskRootPath = handle.mountPath;
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
else {
|
|
850
|
-
const restoreSignal = spec.signal
|
|
851
|
-
? AbortSignal.any([abortController.signal, spec.signal])
|
|
852
|
-
: abortController.signal;
|
|
853
|
-
const missingHere = missingRestoreSurface(ownedEnv);
|
|
854
|
-
if (missingHere.length > 0) {
|
|
855
|
-
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.`);
|
|
856
|
-
}
|
|
857
|
-
const { outcome: restored, attempts: restoreAttempts } = await restoreWorkspaceWithRetry(ownedEnv, handle.snapshotId, {
|
|
858
|
-
abortSignal: restoreSignal,
|
|
859
|
-
priorHandle: handle,
|
|
860
|
-
});
|
|
861
|
-
if (!restored.ok) {
|
|
862
|
-
failResume(`resumeVM failed after ${restoreAttempts} attempt(s) (${restored.error.code}): ${restored.error.message}`, restored.error, remoteEnvFailureNote("resumeVM", restored.error, restoreAttempts));
|
|
863
|
-
}
|
|
864
|
-
else {
|
|
865
|
-
const restoredEnv = ownedEnv;
|
|
866
|
-
const canonicalInEnv = async (p) => {
|
|
867
|
-
try {
|
|
868
|
-
const r = await restoredEnv.canonicalPath(p, restoreSignal);
|
|
869
|
-
return r.ok ? r.value : undefined;
|
|
870
|
-
}
|
|
871
|
-
catch {
|
|
872
|
-
return undefined;
|
|
873
|
-
}
|
|
874
|
-
};
|
|
875
|
-
let checkpointedCanonical;
|
|
876
|
-
let sameRootUnderAlias = false;
|
|
877
|
-
if (restored.value.mountPath !== handle.mountPath) {
|
|
878
|
-
checkpointedCanonical = await canonicalInEnv(handle.mountPath);
|
|
879
|
-
const restoredCanonical = await canonicalInEnv(restored.value.mountPath);
|
|
880
|
-
sameRootUnderAlias =
|
|
881
|
-
checkpointedCanonical !== undefined && restoredCanonical !== undefined && checkpointedCanonical === restoredCanonical;
|
|
882
|
-
}
|
|
883
|
-
if (restored.value.mountPath !== handle.mountPath && !sameRootUnderAlias) {
|
|
884
|
-
if (resume.executesApprovedAction === true) {
|
|
885
|
-
failResume(`resumeVM workspace-root divergence with a pending approved action: checkpointed mountPath "${handle.mountPath}" but the restored handle reports "${restored.value.mountPath}" — the approved args are bound to the checkpointed root; refusing to execute them against a moved workspace (adapter should honor priorHandle)`);
|
|
886
|
-
}
|
|
887
|
-
try {
|
|
888
|
-
deps.onError?.(new Error(`resumeVM workspace-root divergence: checkpointed mountPath "${handle.mountPath}" but the restored handle reports "${restored.value.mountPath}" — the resumed task follows the restored root`), { phase: "config", sessionId });
|
|
889
|
-
}
|
|
890
|
-
catch {
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
if (restored.value.mountPath && restored.value.mountPath !== taskRootPath) {
|
|
894
|
-
taskRootPath = restored.value.mountPath;
|
|
895
|
-
}
|
|
896
|
-
if (restored.value.mountPath && restored.value.mountPath !== handle.mountPath && !sameRootUnderAlias) {
|
|
897
|
-
const from = checkpointedCanonical !== undefined && checkpointedCanonical !== handle.mountPath
|
|
898
|
-
? [handle.mountPath, checkpointedCanonical]
|
|
899
|
-
: [handle.mountPath];
|
|
900
|
-
restoredRootRebase = { from, to: restored.value.mountPath };
|
|
901
|
-
try {
|
|
902
|
-
deps.onError?.(new Error(from.length > 1
|
|
903
|
-
? `resumeVM workspace-root rebase accepts both spellings of the checkpointed root (${from.map((f) => `"${f}"`).join(" and ")}) when migrating persisted paths to "${restored.value.mountPath}"`
|
|
904
|
-
: `resumeVM workspace-root rebase is spelling-exact: it matches the checkpointed root "${handle.mountPath}" only, so persisted paths recorded under an equivalent alias of it are NOT migrated to "${restored.value.mountPath}" and stay as written`), { phase: "config", sessionId });
|
|
905
|
-
}
|
|
906
|
-
catch {
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
const init = await ownedEnv.postResumeInit();
|
|
911
|
-
if (!init.ok) {
|
|
912
|
-
failResume(`postResumeInit failed (${init.error.code}): ${init.error.message}`, init.error, remoteEnvFailureNote("postResumeInit", init.error, 1));
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
if (internals?.onWorkspaceResolved !== undefined) {
|
|
917
|
-
try {
|
|
918
|
-
const workspaceEnv = ownedEnv ?? deps.executionEnv;
|
|
919
|
-
internals.onWorkspaceResolved({
|
|
920
|
-
cwd: taskRootPath,
|
|
921
|
-
isolated: internals.isolation === "worktree",
|
|
922
|
-
remote: workspaceEnv !== undefined &&
|
|
923
|
-
(isRemoteExecutionEnv(workspaceEnv) || workspaceEnv.hostLocalPaths === false),
|
|
924
|
-
});
|
|
925
|
-
}
|
|
926
|
-
catch {
|
|
927
|
-
}
|
|
928
|
-
}
|
|
397
|
+
const { taskRootFinal, rebaseRestoredPath } = await prepareWorkspaceRestore({ ownedEnv, deps, spec, internals, resume, taskRootInitial, abortController, sessionId });
|
|
929
398
|
const rewindNotes = [];
|
|
930
399
|
const rewindCaptureRequested = spec.rewindFiles === true;
|
|
931
400
|
if (spec.resumeAt !== undefined && spec.rewindFilesTo !== undefined) {
|
|
@@ -982,7 +451,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
982
451
|
}
|
|
983
452
|
rewindTarget = anchor;
|
|
984
453
|
}
|
|
985
|
-
const restoreRoot =
|
|
454
|
+
const restoreRoot = taskRootFinal;
|
|
986
455
|
const restoreSignal = spec.signal ? AbortSignal.any([abortController.signal, spec.signal]) : abortController.signal;
|
|
987
456
|
const restored = await deps.fileSnapshotStore.restore(sessionId, rewindTarget, executionEnv, restoreRoot, restoreSignal);
|
|
988
457
|
if (!restored.ok) {
|
|
@@ -1027,7 +496,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1027
496
|
planModeRef.active = true;
|
|
1028
497
|
};
|
|
1029
498
|
const subagentRetain = spec.retainSubagentSessions ? new SubagentRetainLedger(spec.retainSubagentSessions) : undefined;
|
|
1030
|
-
const worktreeIsolation = spec.handsReadOnly !== true ? createSubagentWorktreeHelper(executionEnv,
|
|
499
|
+
const worktreeIsolation = spec.handsReadOnly !== true ? createSubagentWorktreeHelper(executionEnv, taskRootFinal) : undefined;
|
|
1031
500
|
const liveInheritedGate = internals?.inheritedGate;
|
|
1032
501
|
const seedInheritedGate = resume?.seed.inheritedGate;
|
|
1033
502
|
const inheritedAncestorRules = (() => {
|
|
@@ -1085,12 +554,15 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1085
554
|
const ownOrgVerdictRef = { current: undefined };
|
|
1086
555
|
const orgAdmissionCheckpointState = () => inheritedAdmittedOrgScopes !== undefined || ownOrgVerdictRef.current !== undefined || orgGovernedProvenance;
|
|
1087
556
|
const faceCheckpointSection = () => {
|
|
1088
|
-
if (resolvedReadFace === undefined) {
|
|
1089
|
-
const seed = resume
|
|
557
|
+
if (resolvedReadFace === undefined && resume !== undefined) {
|
|
558
|
+
const seed = resume.seed.readFace;
|
|
1090
559
|
return seed !== undefined ? { face: seed.face, ...(seed.denyEntries !== undefined ? { denyEntries: seed.denyEntries.map((e) => ({ ...e })) } : {}) } : undefined;
|
|
1091
560
|
}
|
|
1092
|
-
|
|
1093
|
-
|
|
561
|
+
const face = resolvedReadFace ?? handsLessResolvedFace;
|
|
562
|
+
if (face === undefined)
|
|
563
|
+
return undefined;
|
|
564
|
+
if (face === "open" || readDenyAdditionsNormalized.length > 0) {
|
|
565
|
+
return { face, ...(readDenyAdditionsNormalized.length > 0 ? { denyEntries: readDenyAdditionsNormalized.map((e) => ({ ...e })) } : {}) };
|
|
1094
566
|
}
|
|
1095
567
|
return undefined;
|
|
1096
568
|
};
|
|
@@ -1167,6 +639,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1167
639
|
let observersActive = false;
|
|
1168
640
|
let resolvedReadFace;
|
|
1169
641
|
let readDenyAdditionsNormalized = [];
|
|
642
|
+
let handsLessResolvedFace;
|
|
643
|
+
const carrierReadFace = () => resolvedReadFace ?? handsLessResolvedFace;
|
|
1170
644
|
const fullShellReachable = handsEnabled &&
|
|
1171
645
|
!(executionEnv instanceof StubExecutionEnv) &&
|
|
1172
646
|
spec.handsReadOnly !== true &&
|
|
@@ -1181,7 +655,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1181
655
|
...(frozenOnAsk !== undefined ? { onAsk: frozenOnAsk } : {}),
|
|
1182
656
|
...(frozenOnQuestion !== undefined ? { onQuestion: frozenOnQuestion } : {}),
|
|
1183
657
|
...(spec.handsReadOnly === true ? { handsReadOnly: true } : {}),
|
|
1184
|
-
...(
|
|
658
|
+
...(carrierReadFace() === "roots" ? { readFace: "roots" } : {}),
|
|
1185
659
|
...(readDenyAdditionsNormalized.length > 0 ? { readDenyPatterns: Object.freeze(readDenyAdditionsNormalized.map((e) => ({ ...e }))) } : {}),
|
|
1186
660
|
...(spec.interactiveTools === false ? { interactiveTools: false } : {}),
|
|
1187
661
|
oneShot: spec.oneShot,
|
|
@@ -1195,7 +669,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1195
669
|
...(spec.envFacts !== undefined ? { envFacts: { ...spec.envFacts } } : {}),
|
|
1196
670
|
...(spec.memoryPersistenceCapable !== undefined ? { memoryPersistenceCapable: spec.memoryPersistenceCapable } : {}),
|
|
1197
671
|
getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
|
|
1198
|
-
parentCwd:
|
|
672
|
+
parentCwd: taskRootFinal,
|
|
1199
673
|
...(centerAdoption !== undefined ? { centerArtifactDigest: centerAdoption.artifact.artifactDigest } : {}),
|
|
1200
674
|
...(centerAdoption?.sourceRevision !== undefined ? { centerSourceRevision: centerAdoption.sourceRevision } : {}),
|
|
1201
675
|
activeSkillScope: () => skillScope.active(),
|
|
@@ -1386,9 +860,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1386
860
|
...(resolvedInteractionPosture !== undefined ? { parentInteractionPosture: resolvedInteractionPosture } : {}),
|
|
1387
861
|
autoModeReview: () => (autoModeDecider !== undefined ? { decider: autoModeDecider } : undefined),
|
|
1388
862
|
workflowDepth: internals?.workflowDepth,
|
|
1389
|
-
parentCwd:
|
|
863
|
+
parentCwd: taskRootFinal,
|
|
1390
864
|
parentThinking: () => harnessRef.current?.getThinkingLevel() ?? thinking,
|
|
1391
|
-
parentReadFace: () =>
|
|
865
|
+
parentReadFace: () => carrierReadFace(),
|
|
1392
866
|
parentReadDenyPatterns: () => (readDenyAdditionsNormalized.length > 0 ? readDenyAdditionsNormalized : undefined),
|
|
1393
867
|
onNotice: deps.onNotice,
|
|
1394
868
|
parentCheckpointStoreDisabled: spec.checkpointStore === null,
|
|
@@ -1608,7 +1082,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1608
1082
|
let attachmentRootCanonical;
|
|
1609
1083
|
let readDenyMatcher;
|
|
1610
1084
|
if (handsEnabled) {
|
|
1611
|
-
const rootRaw =
|
|
1085
|
+
const rootRaw = taskRootFinal;
|
|
1612
1086
|
const canon = await executionEnv.canonicalPath(rootRaw);
|
|
1613
1087
|
const rootCanonical = canon.ok ? canon.value : rootRaw;
|
|
1614
1088
|
attachmentRootCanonical = rootCanonical;
|
|
@@ -1681,17 +1155,32 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1681
1155
|
}
|
|
1682
1156
|
const seedReadFaceSection = resume?.seed.readFace;
|
|
1683
1157
|
const readDenyAdditions = [...(deps.readDenyPatterns ?? []), ...(spec.readDenyPatterns ?? []), ...(seedReadFaceSection?.denyEntries ?? [])];
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1158
|
+
const readDenyBuiltinCfg = {
|
|
1159
|
+
...(deps.readDenyBuiltinTiers !== undefined ? { tiers: [...deps.readDenyBuiltinTiers] } : {}),
|
|
1160
|
+
...(deps.readDenyBuiltinExclude !== undefined ? { exclude: [...deps.readDenyBuiltinExclude] } : {}),
|
|
1161
|
+
};
|
|
1162
|
+
readDenyMatcher = compileReadDeny(readDenyAdditions, "readDenyPatterns", readDenyBuiltinCfg);
|
|
1163
|
+
readDenyAdditionsNormalized = compileReadDeny(readDenyAdditions, "readDenyPatterns", { tiers: [] }).entries;
|
|
1689
1164
|
let liveReadFace = resolveReadFace({
|
|
1690
1165
|
specReadFace: spec.readFace,
|
|
1691
1166
|
depsReadFace: deps.readFace,
|
|
1692
1167
|
readOnlyMount: handsReadOnly,
|
|
1693
1168
|
orgGoverned: deps.permissionRuleOrg !== undefined,
|
|
1694
1169
|
fullShellReachable,
|
|
1170
|
+
onDeploymentClamp: () => {
|
|
1171
|
+
const sink = deps.onNotice;
|
|
1172
|
+
if (typeof sink === "function") {
|
|
1173
|
+
if (readFaceClampAnnouncedSinks.has(sink))
|
|
1174
|
+
return;
|
|
1175
|
+
readFaceClampAnnouncedSinks.add(sink);
|
|
1176
|
+
}
|
|
1177
|
+
else {
|
|
1178
|
+
if (readFaceClampConsoleAnnounced)
|
|
1179
|
+
return;
|
|
1180
|
+
readFaceClampConsoleAnnounced = true;
|
|
1181
|
+
}
|
|
1182
|
+
deliverEngineNotice(sink, deploymentReadFaceClampNotice());
|
|
1183
|
+
},
|
|
1695
1184
|
});
|
|
1696
1185
|
if (resume !== undefined && (seedReadFaceSection === undefined || seedReadFaceSection.face === "roots"))
|
|
1697
1186
|
liveReadFace = "roots";
|
|
@@ -1708,6 +1197,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1708
1197
|
...(additionalRootsCanonical.length > 0 ? { additionalRoots: additionalRootsCanonical } : {}),
|
|
1709
1198
|
...(additionalReadRootsCanonical.length > 0 ? { additionalReadRoots: additionalReadRootsCanonical } : {}),
|
|
1710
1199
|
...(readDenyAdditions.length > 0 ? { readDenyPatterns: readDenyAdditions } : {}),
|
|
1200
|
+
...(readDenyBuiltinCfg.tiers !== undefined ? { readDenyBuiltinTiers: readDenyBuiltinCfg.tiers } : {}),
|
|
1201
|
+
...(readDenyBuiltinCfg.exclude !== undefined ? { readDenyBuiltinExclude: readDenyBuiltinCfg.exclude } : {}),
|
|
1711
1202
|
readFace: liveReadFace,
|
|
1712
1203
|
includeShell: handsIncludeShell,
|
|
1713
1204
|
readOnly: handsReadOnly,
|
|
@@ -1778,6 +1269,25 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1778
1269
|
}
|
|
1779
1270
|
}
|
|
1780
1271
|
}
|
|
1272
|
+
else {
|
|
1273
|
+
const liveFace = resolveReadFace({
|
|
1274
|
+
specReadFace: spec.readFace,
|
|
1275
|
+
depsReadFace: deps.readFace,
|
|
1276
|
+
readOnlyMount: spec.handsReadOnly === true,
|
|
1277
|
+
orgGoverned: deps.permissionRuleOrg !== undefined,
|
|
1278
|
+
fullShellReachable,
|
|
1279
|
+
});
|
|
1280
|
+
const seed = resume?.seed.readFace;
|
|
1281
|
+
handsLessResolvedFace = resume !== undefined && (seed === undefined || seed.face === "roots") ? "roots" : liveFace;
|
|
1282
|
+
const denyAdditions = [...(deps.readDenyPatterns ?? []), ...(spec.readDenyPatterns ?? []), ...(seed?.denyEntries ?? [])];
|
|
1283
|
+
resolveReadDenyBuiltins({
|
|
1284
|
+
...(deps.readDenyBuiltinTiers !== undefined ? { tiers: deps.readDenyBuiltinTiers } : {}),
|
|
1285
|
+
...(deps.readDenyBuiltinExclude !== undefined ? { exclude: deps.readDenyBuiltinExclude } : {}),
|
|
1286
|
+
});
|
|
1287
|
+
if (denyAdditions.length > 0) {
|
|
1288
|
+
readDenyAdditionsNormalized = compileReadDeny(denyAdditions, "readDenyPatterns", { tiers: [] }).entries;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1781
1291
|
if (backgroundTaskToolsActive || workflowToolsActive) {
|
|
1782
1292
|
toolEffects.set("TaskOutput", "read");
|
|
1783
1293
|
toolEffects.set("TaskStop", "write");
|
|
@@ -1879,7 +1389,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1879
1389
|
};
|
|
1880
1390
|
if (workspaceStateSettle !== undefined)
|
|
1881
1391
|
workspaceStateSettle.restoredWorktreeDir = worktreeSessionRef.current?.worktreeDir;
|
|
1882
|
-
tools.push(...createWorktreeTools(executionEnv, { repoRoot:
|
|
1392
|
+
tools.push(...createWorktreeTools(executionEnv, { repoRoot: taskRootFinal, cwdRef: handsCwdRef, session: worktreeSessionRef }).map((t) => firstPartyOffload(t)));
|
|
1883
1393
|
}
|
|
1884
1394
|
if (offloadStore)
|
|
1885
1395
|
tools.push(createReadToolResultTool(offloadStore));
|
|
@@ -1976,7 +1486,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1976
1486
|
}
|
|
1977
1487
|
const lspManager = spec.lspManager ?? deps.lspManager;
|
|
1978
1488
|
if (lspManager) {
|
|
1979
|
-
const lspRoot =
|
|
1489
|
+
const lspRoot = taskRootFinal;
|
|
1980
1490
|
tools.push(createLspTool(lspManager, { isPathIgnored: gitCheckIgnoreFilter(executionEnv, lspRoot), env: executionEnv }));
|
|
1981
1491
|
}
|
|
1982
1492
|
const lspDiagnostics = spec.lspDiagnostics !== false && lspManager?.diagnostics !== undefined && handsEnabled && spec.handsReadOnly !== true
|
|
@@ -1985,7 +1495,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1985
1495
|
const lspRunIdent = sessionId;
|
|
1986
1496
|
const nudgeLspOnEdit = lspDiagnostics
|
|
1987
1497
|
? (rawPath) => {
|
|
1988
|
-
const baseDir = handsCwdRef?.current ??
|
|
1498
|
+
const baseDir = handsCwdRef?.current ?? taskRootFinal;
|
|
1989
1499
|
const filePath = resolveLspPath(rawPath, baseDir);
|
|
1990
1500
|
lspDiagnostics.fileEdited(lspRunIdent, pathToUri(filePath));
|
|
1991
1501
|
void lspManager
|
|
@@ -2007,7 +1517,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2007
1517
|
spec,
|
|
2008
1518
|
deps,
|
|
2009
1519
|
sessionId,
|
|
2010
|
-
taskRootPath,
|
|
1520
|
+
taskRootPath: taskRootFinal,
|
|
2011
1521
|
memoryWriteGateRef,
|
|
2012
1522
|
writeToolsMounted: tools.some((t) => t.name === "Write") &&
|
|
2013
1523
|
!(toolFaceSnapshot.exclude?.includes("Write") ?? false) &&
|
|
@@ -2096,7 +1606,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2096
1606
|
let loaded = null;
|
|
2097
1607
|
try {
|
|
2098
1608
|
loaded = await Promise.resolve(deps.loadProjectMemory({
|
|
2099
|
-
cwd:
|
|
1609
|
+
cwd: taskRootFinal,
|
|
2100
1610
|
handsEnabled,
|
|
2101
1611
|
isSubagent: isDelegatedNonForkChild(internals),
|
|
2102
1612
|
...(internals?.agentName ? { agentName: internals.agentName } : {}),
|
|
@@ -2263,7 +1773,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2263
1773
|
envFacts.resumeFacts = copy;
|
|
2264
1774
|
}
|
|
2265
1775
|
if (handsEnabled) {
|
|
2266
|
-
envFacts.cwd =
|
|
1776
|
+
envFacts.cwd = taskRootFinal;
|
|
2267
1777
|
if (additionalRootsCanonical.length > 0) {
|
|
2268
1778
|
envFacts.additionalDirectories = [...additionalRootsCanonical];
|
|
2269
1779
|
}
|
|
@@ -3088,7 +2598,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3088
2598
|
if (deps.sessionPolicyStore && spec.sessionId) {
|
|
3089
2599
|
const rules = await deps.sessionPolicyStore.getRules(spec.sessionId, spec.principal);
|
|
3090
2600
|
if (rules) {
|
|
3091
|
-
sessionRulePolicy = createSessionRulePolicy(rules, { env: executionEnv, rootPath:
|
|
2601
|
+
sessionRulePolicy = createSessionRulePolicy(rules, { env: executionEnv, rootPath: taskRootFinal, toolEffects });
|
|
3092
2602
|
ownSessionRulesRef.current = {
|
|
3093
2603
|
sessionId: spec.sessionId,
|
|
3094
2604
|
...(spec.principal !== undefined ? { principal: spec.principal } : {}),
|
|
@@ -3109,13 +2619,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3109
2619
|
catch {
|
|
3110
2620
|
}
|
|
3111
2621
|
}
|
|
3112
|
-
ancestorRulePolicies.push(createSessionRulePolicy(ancestorRules, { env: executionEnv, rootPath:
|
|
2622
|
+
ancestorRulePolicies.push(createSessionRulePolicy(ancestorRules, { env: executionEnv, rootPath: taskRootFinal, toolEffects }));
|
|
3113
2623
|
}
|
|
3114
2624
|
const skillScopePolicy = hasSkillManifest
|
|
3115
2625
|
? createActiveSkillScopePolicy({
|
|
3116
2626
|
scope: skillScope,
|
|
3117
2627
|
env: executionEnv,
|
|
3118
|
-
rootPath:
|
|
2628
|
+
rootPath: taskRootFinal,
|
|
3119
2629
|
toolEffects,
|
|
3120
2630
|
})
|
|
3121
2631
|
: undefined;
|
|
@@ -3281,7 +2791,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3281
2791
|
}
|
|
3282
2792
|
if (provider === undefined)
|
|
3283
2793
|
return undefined;
|
|
3284
|
-
const root =
|
|
2794
|
+
const root = taskRootFinal;
|
|
3285
2795
|
return {
|
|
3286
2796
|
admits: async (req) => {
|
|
3287
2797
|
const anonymous = spec.principal === undefined || spec.principal === "";
|
|
@@ -3901,6 +3411,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3901
3411
|
...riskAxesOf(req.toolName),
|
|
3902
3412
|
...(decision.action === "ask" && decision.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
3903
3413
|
...(decision.action === "ask" && decision.persistedRuleShadowed !== undefined ? { persistedRuleShadowed: decision.persistedRuleShadowed } : {}),
|
|
3414
|
+
...(decision.action === "ask" && decision.probeReason !== undefined ? { probeReason: decision.probeReason } : {}),
|
|
3415
|
+
...(decision.action === "ask" && decision.probeCause !== undefined ? { probeCause: decision.probeCause } : {}),
|
|
3904
3416
|
}, onAsk, abortController.signal);
|
|
3905
3417
|
const waitMs = Math.max(0, now() - t0);
|
|
3906
3418
|
if (resolved.approverUnavailable !== true) {
|
|
@@ -4383,7 +3895,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4383
3895
|
}
|
|
4384
3896
|
};
|
|
4385
3897
|
const suspendAsk = parkLaneArmed && checkpointStore !== undefined
|
|
4386
|
-
? async (req, postHookArgs, safety, liveFaceUnavailable, realApproval, shadowedRule, askDecisionReason) => {
|
|
3898
|
+
? async (req, postHookArgs, safety, liveFaceUnavailable, realApproval, shadowedRule, askDecisionReason, probeReason, probeCause) => {
|
|
4387
3899
|
const syncFirstEligible = req.toolName === ASK_USER_QUESTION_TOOL_NAME ? contentAskRoutable(req.toolCallId) : isLiveApproverSeat(onAsk);
|
|
4388
3900
|
if (syncFirstEligible &&
|
|
4389
3901
|
runtimeCaps?.forceDurableGate !== true &&
|
|
@@ -4489,6 +4001,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4489
4001
|
args: parkedArgs,
|
|
4490
4002
|
safety,
|
|
4491
4003
|
...(shadowedRule !== undefined ? { shadowedRule } : {}),
|
|
4004
|
+
...(probeReason !== undefined ? { probeReason } : {}),
|
|
4005
|
+
...(probeCause !== undefined ? { probeCause } : {}),
|
|
4492
4006
|
shellGated: (req.toolName === "Bash" && shellGatedBash) || (req.toolName === "Monitor" && shellGatedMonitor),
|
|
4493
4007
|
...(effectiveShellGate !== "off" ? { shellGateDoctrine: effectiveShellGate } : {}),
|
|
4494
4008
|
});
|
|
@@ -5035,8 +4549,10 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
5035
4549
|
}
|
|
5036
4550
|
: undefined;
|
|
5037
4551
|
overheadState.promptChars = systemPrompt.length;
|
|
4552
|
+
const effectiveReadFaceObserved = carrierReadFace();
|
|
4553
|
+
const effectiveReadDenyObserved = readDenyAdditionsNormalized.length > 0 ? readDenyAdditionsNormalized.map((e) => ({ ...e })) : undefined;
|
|
5038
4554
|
const preparedHolder = {};
|
|
5039
|
-
const buildPrepared = () => ({ harness, session, sessionId, taskRootPath, model, thinking, compModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, approvalSettledBy, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, suspendRef, suspendProgressRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
4555
|
+
const buildPrepared = () => ({ harness, session, sessionId, taskRootPath: taskRootFinal, model, thinking, compModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, approvalSettledBy, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, suspendRef, suspendProgressRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
5040
4556
|
const prepared = buildPrepared();
|
|
5041
4557
|
preparedHolder.current = prepared;
|
|
5042
4558
|
return prepared;
|