@sema-agent/core 2.11.0 → 2.13.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.
Files changed (37) hide show
  1. package/dist/agents/agent-transcript-tool.d.ts +1 -0
  2. package/dist/agents/agent-transcript-tool.js +1 -1
  3. package/dist/brain/stream-engine.js +4 -1
  4. package/dist/core/auto-promote.js +2 -1
  5. package/dist/core/checkpoint-store.d.ts +1 -0
  6. package/dist/core/checkpoint-store.js +3 -0
  7. package/dist/core/git-worktree-env.js +5 -0
  8. package/dist/core/mcp.d.ts +3 -0
  9. package/dist/core/mcp.js +91 -7
  10. package/dist/core/remote-env.d.ts +3 -0
  11. package/dist/core/remote-env.js +19 -1
  12. package/dist/core/runner/active-skill-scope.js +34 -6
  13. package/dist/core/runner/assemble-result.d.ts +3 -0
  14. package/dist/core/runner/assemble-result.js +4 -1
  15. package/dist/core/runner/prepare-task.d.ts +4 -0
  16. package/dist/core/runner/prepare-task.js +120 -25
  17. package/dist/core/runner/runtask.js +49 -10
  18. package/dist/core/runner/tool-disclosure.d.ts +1 -0
  19. package/dist/core/runner/tool-disclosure.js +26 -7
  20. package/dist/core/session-store.js +15 -4
  21. package/dist/core/skill-tool-specifier.d.ts +8 -0
  22. package/dist/core/skill-tool-specifier.js +58 -0
  23. package/dist/core/skills-directory.d.ts +1 -1
  24. package/dist/core/skills-directory.js +16 -4
  25. package/dist/core/types.d.ts +11 -5
  26. package/dist/core/with-retry.js +0 -1
  27. package/dist/engine/harness/types.d.ts +1 -1
  28. package/dist/index.d.ts +3 -3
  29. package/dist/index.js +1 -1
  30. package/dist/internal/llm.d.ts +1 -1
  31. package/dist/orchestration/workflow.js +8 -4
  32. package/dist/tools/fs/bash-readonly-classifier.js +38 -6
  33. package/dist/tools/fs/fs-bash.js +14 -0
  34. package/dist/tools/scheduler-tools.js +38 -18
  35. package/dist/tools/web.d.ts +8 -2
  36. package/dist/tools/web.js +46 -17
  37. package/package.json +1 -1
@@ -8,6 +8,7 @@ export interface AgentTranscriptToolOptions {
8
8
  owner?: string;
9
9
  scope?: string;
10
10
  sessionId?: string;
11
+ enrichCtx?: import("../core/tools.js").ToolCtxEnricher;
11
12
  }
12
13
  export declare function createAgentTranscriptTool(opts: AgentTranscriptToolOptions): import("../internal/harness-types.js").AgentTool<Type.TObject<{
13
14
  id: Type.TString;
@@ -118,5 +118,5 @@ export function createAgentTranscriptTool(opts) {
118
118
  details: { type: "agent-transcript", id, steps },
119
119
  };
120
120
  },
121
- });
121
+ }, opts.enrichCtx !== undefined ? { enrichCtx: opts.enrichCtx } : {});
122
122
  }
@@ -43,6 +43,9 @@ function emptyAssistant(model) {
43
43
  function isAbortError(err) {
44
44
  return err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError");
45
45
  }
46
+ function isWalltimeCutoff(err) {
47
+ return err instanceof Error && err.message === WALLTIME_CUTOFF_MESSAGE;
48
+ }
46
49
  function sleep(ms, signal) {
47
50
  return new Promise((resolve) => {
48
51
  if (signal?.aborted)
@@ -85,7 +88,7 @@ export function runStreamingBrain(args) {
85
88
  .catch((err) => {
86
89
  const aborted = signal?.aborted === true || isAbortError(err);
87
90
  terminalRetryPhase = "gave_up";
88
- terminalRetryDetail = aborted ? "cancelled while retrying" : "retries exhausted";
91
+ terminalRetryDetail = aborted ? "cancelled while retrying" : isWalltimeCutoff(err) ? "wall-clock deadline reached while retrying" : "retries exhausted";
89
92
  const errorMsg = emptyAssistant(model);
90
93
  errorMsg.stopReason = aborted ? "aborted" : "error";
91
94
  errorMsg.errorMessage = err instanceof Error ? err.message : String(err);
@@ -1,4 +1,5 @@
1
1
  import { extractSymbols } from "../tools/fs/repo-map.js";
2
+ import { parseSkillToolEntry } from "./skill-tool-specifier.js";
2
3
  export function decideAutoPromote(i) {
3
4
  if (i.tripwire.escalate)
4
5
  return "needs_human";
@@ -58,7 +59,7 @@ export function deriveTripwire(artifactText, declared, profileTokens) {
58
59
  const reasons = [];
59
60
  let escalate = false;
60
61
  try {
61
- const allow = new Set(declared.allowTools ?? []);
62
+ const allow = new Set((declared.allowTools ?? []).map((entry) => parseSkillToolEntry(entry).name));
62
63
  const symbols = extractSymbols(artifactText);
63
64
  for (const sym of symbols) {
64
65
  if (!allow.has(sym)) {
@@ -242,6 +242,7 @@ export interface CheckpointSummary {
242
242
  contentKind?: "content_ask";
243
243
  createdAt?: number;
244
244
  toolInput?: string;
245
+ restoreMode?: "snapshot" | "park_only";
245
246
  preview?: unknown;
246
247
  }
247
248
  export declare function summarizeCheckpoint(cp: Checkpoint): CheckpointSummary;
@@ -181,6 +181,9 @@ export function summarizeCheckpoint(cp) {
181
181
  ...(tool !== undefined ? { toolCallId: tool.toolCallId, toolName: tool.toolName } : {}),
182
182
  ...(tool?.toolName === ASK_USER_QUESTION_TOOL_NAME ? { contentKind: "content_ask" } : {}),
183
183
  ...(toolInput !== undefined ? { toolInput } : {}),
184
+ ...(cp.state.workspaceHandle !== undefined
185
+ ? { restoreMode: cp.state.workspaceHandle.restoreMode === "park_only" ? "park_only" : "snapshot" }
186
+ : {}),
184
187
  ...(tool?.preview !== undefined ? { preview: tool.preview } : {}),
185
188
  };
186
189
  }
@@ -28,6 +28,11 @@ export async function addWorktree(baseEnv, opts) {
28
28
  const detail = add.ok ? add.value.stderr || add.value.stdout : String(add.error);
29
29
  throw new Error(`git worktree add failed: ${detail}`);
30
30
  }
31
+ const excludeLine = shq(`/${WORKTREE_PARENT}/`);
32
+ await baseEnv
33
+ .exec(`ex="$(git rev-parse --git-path info/exclude)" && mkdir -p "$(dirname "$ex")" && ` +
34
+ `{ grep -qxF ${excludeLine} "$ex" 2>/dev/null || printf '%s\\n' ${excludeLine} >> "$ex"; }`, { cwd: opts.repoRoot })
35
+ .catch(() => undefined);
31
36
  let inner;
32
37
  try {
33
38
  inner = await opts.rootEnvAt(worktreeDir);
@@ -57,6 +57,7 @@ export interface McpServerStatus {
57
57
  };
58
58
  toolNames?: string[];
59
59
  error?: string;
60
+ transportClosed?: boolean;
60
61
  }
61
62
  export declare const MCP_PREFIX = "mcp__";
62
63
  export declare function resolveMcpDeclaredResultSize(meta: Record<string, unknown> | undefined): number | undefined;
@@ -69,6 +70,8 @@ export declare const MCP_IDLE_TIMEOUT_STDIO_DEFAULT_MS: number;
69
70
  export declare const MCP_IDLE_TIMEOUT_HTTP_DEFAULT_MS: number;
70
71
  export declare function mcpIdleTimeoutMs(kind: "stdio" | "http"): number;
71
72
  export declare function describeMcpSpecErrorCode(code: unknown): string | undefined;
73
+ export declare function collapseMcpErrorPrefix(message: string): string;
74
+ export declare function networkErrorCode(err: unknown, depth?: number): string | undefined;
72
75
  export declare function normalizeMcpName(name: string): string;
73
76
  export declare function clampNameSegment(seg: string, max?: number): string;
74
77
  export * from "./image-downsample.js";
package/dist/core/mcp.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
3
3
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
4
- import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
4
+ import { StreamableHTTPClientTransport, StreamableHTTPError } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
5
5
  import { lstat, mkdir, writeFile } from "node:fs/promises";
6
6
  import { tmpdir } from "node:os";
7
7
  import { join } from "node:path";
@@ -145,8 +145,24 @@ const MCP_SPEC_ERROR_CODE_NAMES = new Map([
145
145
  export function describeMcpSpecErrorCode(code) {
146
146
  return typeof code === "number" ? MCP_SPEC_ERROR_CODE_NAMES.get(code) : undefined;
147
147
  }
148
+ export function collapseMcpErrorPrefix(message) {
149
+ let out = message;
150
+ for (;;) {
151
+ const m = /^MCP error (-?\d+): (?=MCP error \1: )/.exec(out);
152
+ if (m === null)
153
+ return out;
154
+ out = out.slice(m[0].length);
155
+ }
156
+ }
157
+ function collapseMcpErrorStampInPlace(err) {
158
+ if (!(err instanceof McpError))
159
+ return;
160
+ const collapsed = collapseMcpErrorPrefix(err.message);
161
+ if (collapsed !== err.message)
162
+ err.message = collapsed;
163
+ }
148
164
  function namedMcpFailureText(err) {
149
- const detail = err instanceof Error ? err.message : String(err);
165
+ const detail = err instanceof Error ? collapseMcpErrorPrefix(err.message) : String(err);
150
166
  const condition = err instanceof McpError ? describeMcpSpecErrorCode(err.code) : undefined;
151
167
  return condition !== undefined ? `${condition}: ${detail}` : detail;
152
168
  }
@@ -155,6 +171,53 @@ function isTransportLost(err) {
155
171
  return true;
156
172
  return err instanceof Error && /not connected|connection closed/i.test(err.message);
157
173
  }
174
+ const NETWORK_CODES_NEVER_DELIVERED = new Set([
175
+ "ECONNREFUSED",
176
+ "ENOTFOUND",
177
+ "EAI_AGAIN",
178
+ "EHOSTUNREACH",
179
+ "ENETUNREACH",
180
+ "UND_ERR_CONNECT_TIMEOUT",
181
+ ]);
182
+ export function networkErrorCode(err, depth = 0) {
183
+ if (depth > 5 || !(err instanceof Error))
184
+ return undefined;
185
+ const code = err.code;
186
+ if (typeof code === "string" && /^(?:E[A-Z_]+|UND_ERR_[A-Z_]+)$/.test(code))
187
+ return code;
188
+ if (err instanceof AggregateError) {
189
+ for (const inner of err.errors) {
190
+ const found = networkErrorCode(inner, depth + 1);
191
+ if (found !== undefined)
192
+ return found;
193
+ }
194
+ }
195
+ return networkErrorCode(err.cause, depth + 1);
196
+ }
197
+ function describeHttpTransportFailure(err) {
198
+ if (err instanceof McpError)
199
+ return undefined;
200
+ if (err instanceof StreamableHTTPError) {
201
+ const status = typeof err.code === "number" && err.code > 0 ? err.code : undefined;
202
+ return {
203
+ condition: status !== undefined
204
+ ? `its HTTP endpoint answered ${status} instead of an MCP response`
205
+ : "its HTTP endpoint answered something that is not an MCP response",
206
+ delivered: "unknown",
207
+ ...(status !== undefined ? { httpStatus: status } : {}),
208
+ };
209
+ }
210
+ const code = networkErrorCode(err);
211
+ if (code !== undefined) {
212
+ return NETWORK_CODES_NEVER_DELIVERED.has(code)
213
+ ? { condition: `its HTTP endpoint could not be reached (${code})`, delivered: "no" }
214
+ : { condition: `the connection to its HTTP endpoint failed (${code})`, delivered: "unknown" };
215
+ }
216
+ if (err instanceof TypeError && /fetch failed|terminated|network/i.test(err.message)) {
217
+ return { condition: "the HTTP request to its endpoint failed at the network layer", delivered: "unknown" };
218
+ }
219
+ return undefined;
220
+ }
158
221
  function writeEffectWarning(writeEffect) {
159
222
  return writeEffect
160
223
  ? " This tool is write-capable: treat its side effects as POSSIBLY APPLIED and verify the actual state before retrying."
@@ -173,6 +236,7 @@ function rethrowHonestMcpError(err, ctx) {
173
236
  }
174
237
  if (ctx.signal?.aborted)
175
238
  throw err;
239
+ collapseMcpErrorStampInPlace(err);
176
240
  const serverLabel = inlineUntrusted(ctx.server);
177
241
  if (err instanceof McpError && err.code === ErrorCode.RequestTimeout) {
178
242
  const data = err.data;
@@ -193,6 +257,21 @@ function rethrowHonestMcpError(err, ctx) {
193
257
  e.details = { transportLost: true, server: ctx.server };
194
258
  throw e;
195
259
  }
260
+ const httpFailure = describeHttpTransportFailure(err);
261
+ if (httpFailure !== undefined) {
262
+ const detail = err instanceof Error ? err.message : String(err);
263
+ const fenced = `\nThe transport error follows as external/untrusted data:\n${delimitUntrusted(`${ctx.server} transport error`, truncateMcpErrorText(detail))}`;
264
+ const e = new Error(httpFailure.delivered === "no"
265
+ ? `${ctx.what} could not reach MCP server "${serverLabel}": ${httpFailure.condition}. The request was not delivered, so the server did not execute it. This server's tools and resources will keep failing until its endpoint is reachable again — do not retry them; use an alternative if one exists.${fenced}`
266
+ : `${ctx.what} failed at the transport layer of MCP server "${serverLabel}": ${httpFailure.condition}. The request may or may not have executed on the server — the outcome is unknown.${writeEffectWarning(ctx.writeEffect)}${fenced}`, { cause: err });
267
+ e.errorKind = httpFailure.delivered === "no" ? "server_disconnected" : "transport_lost";
268
+ e.details = {
269
+ server: ctx.server,
270
+ ...(httpFailure.delivered === "no" ? {} : { transportLost: true }),
271
+ ...(httpFailure.httpStatus !== undefined ? { httpStatus: httpFailure.httpStatus } : {}),
272
+ };
273
+ throw e;
274
+ }
196
275
  if (err instanceof McpError) {
197
276
  const condition = describeMcpSpecErrorCode(err.code);
198
277
  if (condition !== undefined) {
@@ -546,12 +625,16 @@ export async function materializeMcpTools(specs, principal, onElicit, imageResiz
546
625
  serverInstructions.push({ server: spec.name, text: s.instructions });
547
626
  }
548
627
  const hasInstructions = s.instructions !== undefined;
549
- s.announce.fn = () => {
550
- if (!disposing && hasInstructions)
628
+ const announceClose = () => {
629
+ if (disposing)
630
+ return;
631
+ s.status.transportClosed = true;
632
+ if (hasInstructions)
551
633
  instructionsDelta.pendingRemovals.push(spec.name);
552
634
  };
553
- if (s.health.dead && hasInstructions)
554
- instructionsDelta.pendingRemovals.push(spec.name);
635
+ s.announce.fn = announceClose;
636
+ if (s.health.dead)
637
+ announceClose();
555
638
  if (s.resourceServer)
556
639
  resourceServers.push(s.resourceServer);
557
640
  for (const d of s.dropped)
@@ -703,7 +786,8 @@ async function readDirViaExtension(rs, uri, signal, timeoutMs, watchdog) {
703
786
  if (err instanceof McpError && err.code === ErrorCode.InvalidParams) {
704
787
  if (pages > 0)
705
788
  return { kind: "ok", resources, cursorInvalid: true };
706
- return { kind: classifyDirReadInvalidParams(err.message), detail: err.message };
789
+ const detail = collapseMcpErrorPrefix(err.message);
790
+ return { kind: classifyDirReadInvalidParams(detail), detail };
707
791
  }
708
792
  if (err instanceof McpError && err.code === ErrorCode.MethodNotFound)
709
793
  return { kind: "unsupported" };
@@ -44,6 +44,8 @@ export interface ExecStreamOptions {
44
44
  maxOutputBytes?: number;
45
45
  }
46
46
  export type RemoteExecutionErrorCode = "suspended" | "command_in_flight" | "connect_failed" | "post_resume_failed" | "aborted" | "timeout" | "unsupported" | "auth_transient" | "auth_failed" | "transport_lost" | "unknown";
47
+ export declare const RETRYABLE_REMOTE_ERROR_CODES: readonly RemoteExecutionErrorCode[];
48
+ export declare function isRetryableRemoteErrorCode(code: RemoteExecutionErrorCode): boolean;
47
49
  export declare class RemoteExecutionError extends Error {
48
50
  readonly code: RemoteExecutionErrorCode;
49
51
  constructor(code: RemoteExecutionErrorCode, message: string, cause?: Error);
@@ -101,5 +103,6 @@ export interface ExecutionEnvFactoryContext {
101
103
  export type ExecutionEnvFactory = (ctx: ExecutionEnvFactoryContext) => ExecutionEnv | Promise<ExecutionEnv>;
102
104
  export declare function hasDestroy(env: ExecutionEnv): env is ExecutionEnv & Pick<RemoteExecutionEnv, "destroy">;
103
105
  export declare function isRemoteExecutionEnv(env: ExecutionEnv): env is RemoteExecutionEnv;
106
+ export declare function missingRestoreSurface(env: ExecutionEnv): readonly ("resumeVM" | "postResumeInit")[];
104
107
  export declare function isSuspendable(env: ExecutionEnv): env is RemoteExecutionEnv;
105
108
  export declare function isIsolated(env: ExecutionEnv): boolean;
@@ -1,3 +1,12 @@
1
+ export const RETRYABLE_REMOTE_ERROR_CODES = [
2
+ "auth_transient",
3
+ "connect_failed",
4
+ "timeout",
5
+ "transport_lost",
6
+ ];
7
+ export function isRetryableRemoteErrorCode(code) {
8
+ return RETRYABLE_REMOTE_ERROR_CODES.includes(code);
9
+ }
1
10
  export class RemoteExecutionError extends Error {
2
11
  code;
3
12
  constructor(code, message, cause) {
@@ -18,8 +27,17 @@ export function isRemoteExecutionEnv(env) {
18
27
  typeof e.capabilities === "object" &&
19
28
  e.capabilities !== null);
20
29
  }
30
+ export function missingRestoreSurface(env) {
31
+ const e = env;
32
+ const missing = [];
33
+ if (typeof e.resumeVM !== "function")
34
+ missing.push("resumeVM");
35
+ if (typeof e.postResumeInit !== "function")
36
+ missing.push("postResumeInit");
37
+ return missing;
38
+ }
21
39
  export function isSuspendable(env) {
22
- return isRemoteExecutionEnv(env) && env.capabilities.suspendable;
40
+ return isRemoteExecutionEnv(env) && env.capabilities.suspendable && missingRestoreSurface(env).length === 0;
23
41
  }
24
42
  export function isIsolated(env) {
25
43
  return isRemoteExecutionEnv(env) ? env.capabilities.isolation : false;
@@ -1,5 +1,6 @@
1
1
  import { canonicalizeTarget, fileArgPath } from "../../tools/fs/safety.js";
2
2
  import { canonicalToolName } from "../tool-name-aliases.js";
3
+ import { parseSkillToolEntry, skillSpecifierRejection } from "../skill-tool-specifier.js";
3
4
  export class ActiveSkillScope {
4
5
  frames = [];
5
6
  push(frame) {
@@ -47,12 +48,19 @@ export function createActiveSkillScopePolicy(opts) {
47
48
  };
48
49
  }
49
50
  const manifests = frames.flatMap((f) => (f.kind === "manifest" ? [f.manifest] : []));
50
- let allowed;
51
- for (const m of manifests) {
52
- const here = new Set(m.allowTools.map(canonicalToolName));
53
- allowed = allowed === undefined ? here : new Set([...allowed].filter((t) => here.has(t)));
54
- }
55
- if (!allowed || !allowed.has(toolName)) {
51
+ const perFrame = manifests.map((m) => {
52
+ const byName = new Map();
53
+ for (const raw of m.allowTools) {
54
+ const entry = parseSkillToolEntry(raw);
55
+ const bucket = byName.get(entry.name);
56
+ if (bucket)
57
+ bucket.push(entry);
58
+ else
59
+ byName.set(entry.name, [entry]);
60
+ }
61
+ return { manifest: m, byName };
62
+ });
63
+ if (perFrame.length === 0 || perFrame.some((f) => !f.byName.has(toolName))) {
56
64
  const ids = manifests.map((m) => m.lineageId).join(", ");
57
65
  return {
58
66
  action: "deny",
@@ -60,6 +68,26 @@ export function createActiveSkillScopePolicy(opts) {
60
68
  decisionReason: "safety",
61
69
  };
62
70
  }
71
+ for (const frame of perFrame) {
72
+ const alternatives = frame.byName.get(toolName) ?? [];
73
+ const rejections = [];
74
+ let admitted = false;
75
+ for (const entry of alternatives) {
76
+ const rejection = skillSpecifierRejection(entry, req.args);
77
+ if (rejection === undefined) {
78
+ admitted = true;
79
+ break;
80
+ }
81
+ rejections.push(rejection);
82
+ }
83
+ if (!admitted) {
84
+ return {
85
+ action: "deny",
86
+ reason: `tool "${req.toolName}" is narrowed by skill manifest "${frame.manifest.lineageId}": ${rejections.join("; ")}`,
87
+ decisionReason: "safety",
88
+ };
89
+ }
90
+ }
63
91
  const pathConstrainingActive = manifests.some((m) => m.allowPaths && m.allowPaths.length > 0);
64
92
  if (pathConstrainingActive && !PATH_WRITE_TOOLS.has(toolName)) {
65
93
  const eff = toolEffects?.get(toolName) ?? "write";
@@ -70,6 +70,7 @@ export interface ResultFlags {
70
70
  model?: string;
71
71
  unpricedSpend?: boolean;
72
72
  rewindNotes?: TaskResult["rewindNotes"];
73
+ remoteEnvFailures?: TaskResult["remoteEnvFailures"];
73
74
  abortedForTimeout: boolean;
74
75
  abortedForTurns: boolean;
75
76
  abortedLive?: boolean;
@@ -81,10 +82,12 @@ export interface ResultFlags {
81
82
  suspendRef?: {
82
83
  token: import("../checkpoint-store.js").CheckpointToken;
83
84
  gate: import("../checkpoint-store.js").CheckpointGate;
85
+ restoreMode?: "snapshot" | "park_only";
84
86
  };
85
87
  reviewRef?: {
86
88
  token: import("../checkpoint-store.js").CheckpointToken;
87
89
  gate: import("../checkpoint-store.js").CheckpointGate;
90
+ restoreMode?: "snapshot" | "park_only";
88
91
  };
89
92
  }
90
93
  export declare function errorCodeOf(err: unknown): string | undefined;
@@ -54,6 +54,7 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
54
54
  let salvagedOutput;
55
55
  let checkpointToken;
56
56
  let checkpointGate;
57
+ let workspaceRestoreMode;
57
58
  const isDegenerate = final?.stopReason === "error" && final.errorMessage === DEGENERATE_MESSAGE;
58
59
  const isWalltimeCutoff = final?.stopReason === "error" && final.errorMessage === WALLTIME_CUTOFF_MESSAGE;
59
60
  if (flags.outputInvalid) {
@@ -105,12 +106,14 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
105
106
  status = "suspended";
106
107
  checkpointToken = flags.suspendRef.token;
107
108
  checkpointGate = flags.suspendRef.gate;
109
+ workspaceRestoreMode = flags.suspendRef.restoreMode;
108
110
  }
109
111
  else if (flags.reviewRef) {
110
112
  status = "needs_review";
111
113
  errorCode = "review.pending";
112
114
  checkpointToken = flags.reviewRef.token;
113
115
  checkpointGate = flags.reviewRef.gate;
116
+ workspaceRestoreMode = flags.reviewRef.restoreMode;
114
117
  }
115
118
  else if (flags.abortedLive || final?.stopReason === "aborted") {
116
119
  status = flags.abortedForTimeout ? "timeout" : "failed";
@@ -143,5 +146,5 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
143
146
  void _internalCompaction;
144
147
  if (flags.unpricedSpend)
145
148
  delete publicStats.costMicroUsd;
146
- return { taskId, sessionId, status, ...(flags.model !== undefined ? { model: flags.model } : {}), result: result.trim(), salvagedOutput, blockedReason, errorMessage, errorCode, checkpointToken, checkpointGate, ...(flags.rewindNotes !== undefined && flags.rewindNotes.length > 0 ? { rewindNotes: flags.rewindNotes } : {}), stats: publicStats };
149
+ return { taskId, sessionId, status, ...(flags.model !== undefined ? { model: flags.model } : {}), result: result.trim(), salvagedOutput, blockedReason, errorMessage, errorCode, checkpointToken, checkpointGate, ...(workspaceRestoreMode !== undefined ? { workspaceRestoreMode } : {}), ...(flags.rewindNotes !== undefined && flags.rewindNotes.length > 0 ? { rewindNotes: flags.rewindNotes } : {}), ...(flags.remoteEnvFailures !== undefined && flags.remoteEnvFailures.length > 0 ? { remoteEnvFailures: flags.remoteEnvFailures } : {}), stats: publicStats };
147
150
  }
@@ -127,12 +127,15 @@ export interface Prepared {
127
127
  token?: CheckpointToken;
128
128
  gate?: CheckpointGate;
129
129
  scope?: string;
130
+ restoreMode?: "snapshot" | "park_only";
130
131
  };
131
132
  reviewRef: {
132
133
  token?: CheckpointToken;
133
134
  gate?: CheckpointGate;
134
135
  scope?: string;
136
+ restoreMode?: "snapshot" | "park_only";
135
137
  };
138
+ remoteEnvFailures: NonNullable<TaskResult["remoteEnvFailures"]>;
136
139
  suspendLoopRef: {
137
140
  hit: boolean;
138
141
  };
@@ -340,4 +343,5 @@ export declare function batchContextAt(messages: AgentMessage[], currentId: stri
340
343
  completedCallIds: string[];
341
344
  };
342
345
  export declare function rebaseWorkspacePath(p: string, fromRaw: string, toRaw: string): string;
346
+ export declare function rebaseWorkspacePathAcross(p: string, froms: readonly string[], to: string): string;
343
347
  export declare function prepareTask(spec: TaskSpec, deps: RunnerDeps, sessions: SessionStore, resume?: PrepareResume, internals?: RunInternals, runnerSelf?: Runner): Promise<Prepared>;