pi-subagents 0.58.0 → 0.60.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 +102 -0
- package/docs/agents.md +5 -3
- package/docs/configuration.md +4 -4
- package/docs/extension-api.md +1 -1
- package/docs/models.md +24 -1
- package/docs/observability.md +1 -1
- package/docs/tool-reference.md +93 -5
- package/docs/workflows.md +94 -3
- package/package.json +3 -1
- package/prompts/review-loop.md +2 -2
- package/skills/council-mode/SKILL.md +48 -243
- package/skills/council-mode/references/pass-contracts.md +150 -0
- package/skills/pi-subagents/SKILL.md +87 -37
- package/skills/pi-subagents/references/constraints-and-recipes.md +29 -233
- package/skills/pi-subagents/references/execution-controls.md +52 -7
- package/skills/pi-subagents/references/management-authoring-rpc.md +3 -4
- package/skills/pi-subagents/references/multi-lane-orchestration.md +13 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +54 -26
- package/skills/pi-subagents/references/review-and-validation.md +73 -0
- package/src/agents/agent-management.ts +159 -42
- package/src/agents/agent-serializer.ts +4 -2
- package/src/agents/agents.ts +67 -26
- package/src/agents/runtime-agent-registry.ts +9 -16
- package/src/api/background-work.ts +5 -1
- package/src/api/delegation.ts +0 -7
- package/src/api/preflight.ts +6 -9
- package/src/api/shared-types.ts +2 -0
- package/src/extension/fanout-child.ts +5 -3
- package/src/extension/index.ts +51 -23
- package/src/extension/public-execution.ts +16 -2
- package/src/extension/schemas.ts +36 -10
- package/src/extension/tool-description.ts +24 -5
- package/src/intercom/result-intercom.ts +2 -0
- package/src/profiles/profiles.ts +5 -6
- package/src/runs/background/active-async-capacity.ts +2 -2
- package/src/runs/background/async-execution.ts +52 -37
- package/src/runs/background/async-job-tracker.ts +17 -12
- package/src/runs/background/async-resume.ts +23 -25
- package/src/runs/background/async-status-snapshot.ts +23 -261
- package/src/runs/background/async-status.ts +110 -8
- package/src/runs/background/chain-append.ts +6 -3
- package/src/runs/background/chain-root-attachment.ts +60 -8
- package/src/runs/background/control-channel.ts +3 -2
- package/src/runs/background/fleet-view.ts +21 -11
- package/src/runs/background/notify.ts +158 -6
- package/src/runs/background/result-files.ts +2 -1
- package/src/runs/background/result-watcher.ts +2 -0
- package/src/runs/background/resume-guidance.ts +1 -1
- package/src/runs/background/retained-children.ts +1 -1
- package/src/runs/background/run-status.ts +30 -9
- package/src/runs/background/scheduled-runs.ts +86 -7
- package/src/runs/background/stale-run-reconciler.ts +10 -4
- package/src/runs/background/steering.ts +4 -14
- package/src/runs/background/subagent-runner.ts +351 -358
- package/src/runs/background/subagent-wait.ts +68 -12
- package/src/runs/background/terminal-run-index.ts +1 -1
- package/src/runs/background/wait-completions.ts +25 -1
- package/src/runs/background/wait-config.ts +23 -9
- package/src/runs/background/wait-tool.ts +9 -2
- package/src/runs/foreground/async-steering-action.ts +2 -2
- package/src/runs/foreground/execution.ts +125 -122
- package/src/runs/foreground/foreground-control.ts +3 -0
- package/src/runs/foreground/foreground-history.ts +1 -0
- package/src/runs/foreground/subagent-executor.ts +576 -240
- package/src/runs/foreground/workflow-detach-reconcile.ts +99 -200
- package/src/runs/shared/abort-recovery.ts +119 -0
- package/src/runs/shared/async-status-projection.ts +597 -0
- package/src/runs/shared/background-process-options.ts +9 -0
- package/src/runs/shared/child-identity.ts +19 -4
- package/src/runs/shared/child-launch-plan.ts +151 -0
- package/src/runs/shared/completion-evidence.ts +89 -0
- package/src/runs/shared/completion-guard.ts +1 -1
- package/src/runs/shared/dynamic-fanout.ts +2 -2
- package/src/runs/shared/host-step-status.ts +230 -0
- package/src/runs/shared/lane-metadata.ts +105 -0
- package/src/runs/shared/mcp-config-sources.ts +42 -6
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +93 -143
- package/src/runs/shared/mcp-direct-tool-grant.ts +194 -0
- package/src/runs/shared/model-fallback.ts +9 -2
- package/src/runs/shared/mutation-evidence.ts +52 -3
- package/src/runs/shared/nested-events.ts +6 -2
- package/src/runs/shared/nested-render.ts +7 -3
- package/src/runs/shared/parallel-handoff.ts +419 -7
- package/src/runs/shared/parallel-utils.ts +7 -0
- package/src/runs/shared/pi-args.ts +67 -3
- package/src/runs/shared/single-output.ts +72 -22
- package/src/runs/shared/subagent-prompt-runtime.ts +33 -6
- package/src/runs/shared/workflow-graph.ts +15 -0
- package/src/runs/shared/worktree-cleanup-plan.ts +847 -0
- package/src/runs/shared/worktree.ts +18 -0
- package/src/shared/child-session-name.ts +46 -0
- package/src/shared/extension-context.ts +24 -0
- package/src/shared/formatters.ts +5 -2
- package/src/shared/launch-contract.ts +1 -1
- package/src/shared/settings.ts +9 -103
- package/src/shared/types.ts +232 -30
- package/src/shared/utils.ts +35 -55
- package/src/slash/delegation-adapters.ts +1 -8
- package/src/slash/delegation-request.ts +0 -4
- package/src/slash/slash-bridge.ts +1 -2
- package/src/slash/slash-commands.ts +369 -90
- package/src/slash/slash-live-state.ts +22 -11
- package/src/tui/fleet-status.ts +134 -75
- package/src/tui/fleet.ts +11 -5
- package/src/tui/render-helpers.ts +31 -0
- package/src/tui/render.ts +895 -121
- package/src/watchdog/change-signature.ts +40 -1
- package/src/watchdog/turn-delta.ts +1 -1
- package/src/workflows/chat-progress.ts +6 -3
- package/src/workflows/host-command.ts +235 -0
- package/src/workflows/scripted-workflow.ts +503 -38
- package/src/workflows/workflow-child-summary.ts +9 -5
- package/src/workflows/workflow-preflight.ts +270 -0
- package/src/workflows/workflow-receipt.ts +43 -4
- package/src/workflows/workflow-settlement.ts +246 -0
- package/src/runs/shared/turn-budget.ts +0 -98
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
1
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
3
|
import * as fs from "node:fs";
|
|
3
4
|
import * as path from "node:path";
|
|
@@ -11,6 +12,8 @@ import type { SubagentParamsLike } from "../foreground/subagent-executor.ts";
|
|
|
11
12
|
import { validateExecutionAcceptance } from "../shared/acceptance.ts";
|
|
12
13
|
import type { ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
13
14
|
import { previewSimpleWorkflowRun } from "../../workflows/scripted-workflow.ts";
|
|
15
|
+
import { resolveGitRepositoryIdentity } from "../../workflows/chat-progress.ts";
|
|
16
|
+
import { getConfigDirName } from "../../shared/utils.ts";
|
|
14
17
|
|
|
15
18
|
export const SCHEDULED_RUN_ACTIONS = [
|
|
16
19
|
"schedule.create",
|
|
@@ -142,11 +145,83 @@ function validateScheduleId(id: string): string {
|
|
|
142
145
|
return id;
|
|
143
146
|
}
|
|
144
147
|
|
|
148
|
+
function normalizedComparisonPath(value: string): string {
|
|
149
|
+
const absolute = path.resolve(value);
|
|
150
|
+
if (process.platform !== "win32") return absolute;
|
|
151
|
+
let normalized = absolute;
|
|
152
|
+
try { normalized = fs.realpathSync.native(absolute); } catch {}
|
|
153
|
+
normalized = normalized.replaceAll("/", "\\");
|
|
154
|
+
if (normalized.startsWith("\\\\?\\UNC\\")) normalized = `\\\\${normalized.slice(8)}`;
|
|
155
|
+
else if (normalized.startsWith("\\\\?\\")) normalized = normalized.slice(4);
|
|
156
|
+
normalized = path.win32.normalize(normalized).toLowerCase();
|
|
157
|
+
if (normalized.length > 3) normalized = normalized.replace(/[\\]+$/, "");
|
|
158
|
+
return normalized;
|
|
159
|
+
}
|
|
160
|
+
|
|
145
161
|
function pathWithin(root: string, candidate: string): boolean {
|
|
146
|
-
const relative = path.relative(root, candidate);
|
|
162
|
+
const relative = path.relative(normalizedComparisonPath(root), normalizedComparisonPath(candidate));
|
|
147
163
|
return relative === "" || (relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
|
|
148
164
|
}
|
|
149
165
|
|
|
166
|
+
function resolveGitCommonDirForCheckout(checkoutRoot: string): string | undefined {
|
|
167
|
+
const gitPath = path.join(checkoutRoot, ".git");
|
|
168
|
+
try {
|
|
169
|
+
const stat = fs.statSync(gitPath);
|
|
170
|
+
if (stat.isDirectory()) return fs.realpathSync.native(gitPath);
|
|
171
|
+
if (!stat.isFile()) return undefined;
|
|
172
|
+
const match = /^gitdir:[ \t]*([^\r\n]+)$/i.exec(fs.readFileSync(gitPath, "utf-8").trim());
|
|
173
|
+
return match?.[1] ? fs.realpathSync.native(path.resolve(checkoutRoot, match[1])) : undefined;
|
|
174
|
+
} catch {
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function samePath(left: string, right: string): boolean {
|
|
180
|
+
return normalizedComparisonPath(left) === normalizedComparisonPath(right);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function resolveTrustedGitConfigRoot(checkoutRoot: string, commonDir: string): string | undefined {
|
|
184
|
+
const resolvedCommonDir = resolveGitCommonDirForCheckout(checkoutRoot);
|
|
185
|
+
if (!resolvedCommonDir || !samePath(resolvedCommonDir, commonDir)) return undefined;
|
|
186
|
+
const configRoot = path.join(checkoutRoot, getConfigDirName());
|
|
187
|
+
try {
|
|
188
|
+
const resolved = fs.realpathSync.native(configRoot);
|
|
189
|
+
return fs.statSync(resolved).isDirectory() && pathWithin(checkoutRoot, resolved) ? resolved : undefined;
|
|
190
|
+
} catch {
|
|
191
|
+
return undefined;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function registeredGitWorktreeRoots(projectCwd: string): string[] {
|
|
196
|
+
const result = spawnSync("git", ["-C", projectCwd, "worktree", "list", "--porcelain"], { encoding: "utf-8", windowsHide: true });
|
|
197
|
+
if (result.status !== 0 || typeof result.stdout !== "string") return [];
|
|
198
|
+
const roots: string[] = [];
|
|
199
|
+
for (const line of result.stdout.split(/\r?\n/)) {
|
|
200
|
+
if (!line.startsWith("worktree ")) continue;
|
|
201
|
+
try { roots.push(fs.realpathSync.native(line.slice("worktree ".length).trim())); } catch {}
|
|
202
|
+
}
|
|
203
|
+
return roots;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function resolveSharedGitConfigRoot(projectCwd: string): string | undefined {
|
|
207
|
+
const repository = resolveGitRepositoryIdentity(projectCwd);
|
|
208
|
+
if (!repository) return undefined;
|
|
209
|
+
let projectConfigRoot: string;
|
|
210
|
+
try {
|
|
211
|
+
projectConfigRoot = fs.realpathSync.native(path.join(projectCwd, getConfigDirName()));
|
|
212
|
+
} catch {
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
// Git may report a separate-git-dir primary checkout as its common git
|
|
216
|
+
// directory, not as the checkout root. Without a registered checkout root,
|
|
217
|
+
// do not infer a shared config path from that unprovable layout.
|
|
218
|
+
for (const checkoutRoot of new Set(registeredGitWorktreeRoots(projectCwd))) {
|
|
219
|
+
const resolved = resolveTrustedGitConfigRoot(checkoutRoot, repository.commonDir);
|
|
220
|
+
if (resolved && samePath(resolved, projectConfigRoot)) return resolved;
|
|
221
|
+
}
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
|
|
150
225
|
function assertScheduleRoot(root: string, projectCwd: string | undefined, create: boolean): void {
|
|
151
226
|
if (!projectCwd) {
|
|
152
227
|
if (create) fs.mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
@@ -154,7 +229,7 @@ function assertScheduleRoot(root: string, projectCwd: string | undefined, create
|
|
|
154
229
|
}
|
|
155
230
|
let projectPath: string;
|
|
156
231
|
try {
|
|
157
|
-
projectPath = fs.realpathSync(projectCwd);
|
|
232
|
+
projectPath = fs.realpathSync.native(projectCwd);
|
|
158
233
|
} catch (error) {
|
|
159
234
|
if (!create && (error as NodeJS.ErrnoException).code === "ENOENT") return;
|
|
160
235
|
throw error;
|
|
@@ -165,10 +240,14 @@ function assertScheduleRoot(root: string, projectCwd: string | undefined, create
|
|
|
165
240
|
if (parent === existing) break;
|
|
166
241
|
existing = parent;
|
|
167
242
|
}
|
|
168
|
-
|
|
243
|
+
const existingPath = fs.realpathSync.native(existing);
|
|
244
|
+
const sharedGitConfigRoot = pathWithin(projectPath, existingPath) ? undefined : resolveSharedGitConfigRoot(projectCwd);
|
|
245
|
+
const isTrustedPath = (candidate: string): boolean => pathWithin(projectPath, candidate)
|
|
246
|
+
|| (sharedGitConfigRoot !== undefined && pathWithin(sharedGitConfigRoot, candidate));
|
|
247
|
+
if (!isTrustedPath(existingPath)) throw new Error(`Project schedule root '${root}' resolves outside the real project.`);
|
|
169
248
|
if (!create) return;
|
|
170
249
|
fs.mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
171
|
-
if (!
|
|
250
|
+
if (!isTrustedPath(fs.realpathSync.native(root))) throw new Error(`Project schedule root '${root}' resolves outside the real project.`);
|
|
172
251
|
}
|
|
173
252
|
|
|
174
253
|
function scheduleDir(root: string, id: string, create = false, projectCwd?: string): string {
|
|
@@ -182,9 +261,9 @@ function scheduleDir(root: string, id: string, create = false, projectCwd?: stri
|
|
|
182
261
|
if (!create) return dir;
|
|
183
262
|
fs.mkdirSync(dir, { mode: 0o700 });
|
|
184
263
|
}
|
|
185
|
-
const rootPath = fs.realpathSync(root);
|
|
186
|
-
const dirPath = fs.realpathSync(dir);
|
|
187
|
-
if (dirPath
|
|
264
|
+
const rootPath = fs.realpathSync.native(root);
|
|
265
|
+
const dirPath = fs.realpathSync.native(dir);
|
|
266
|
+
if (!samePath(dirPath, path.join(rootPath, id))) throw new Error(`Schedule path '${dir}' escapes the project schedule root.`);
|
|
188
267
|
return dir;
|
|
189
268
|
}
|
|
190
269
|
|
|
@@ -8,6 +8,7 @@ import { DIRS, type AsyncParallelGroupStatus, type AsyncStatus, type NestedRunSu
|
|
|
8
8
|
import { resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
9
9
|
import { normalizeParallelGroups } from "./parallel-groups.ts";
|
|
10
10
|
import { nestedSummaryFromAsyncStatus, projectNestedEvents, resolveNestedAsyncDir, writeNestedEvent, type NestedRoute } from "../shared/nested-events.ts";
|
|
11
|
+
import { assertWorkflowGraphHostSteps } from "../shared/host-step-status.ts";
|
|
11
12
|
|
|
12
13
|
export type PidLiveness = "alive" | "dead" | "unknown";
|
|
13
14
|
|
|
@@ -24,6 +25,7 @@ interface StartedRunMetadata {
|
|
|
24
25
|
parallelGroups?: AsyncParallelGroupStatus[];
|
|
25
26
|
startedAt?: number;
|
|
26
27
|
sessionFile?: string;
|
|
28
|
+
sessionName?: string;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
interface ReconcileAsyncRunOptions {
|
|
@@ -90,6 +92,7 @@ function appendJsonlBestEffort(filePath: string, payload: object): void {
|
|
|
90
92
|
|
|
91
93
|
interface ResultChildOutcome {
|
|
92
94
|
agent?: string;
|
|
95
|
+
sessionName?: string;
|
|
93
96
|
success?: boolean;
|
|
94
97
|
error?: string;
|
|
95
98
|
sessionFile?: string;
|
|
@@ -101,7 +104,7 @@ interface ResultChildOutcome {
|
|
|
101
104
|
}
|
|
102
105
|
|
|
103
106
|
interface ResultRepairData {
|
|
104
|
-
state: "complete" | "failed" | "paused" | "stopped" | "rejected";
|
|
107
|
+
state: "complete" | "failed" | "partial" | "paused" | "stopped" | "rejected";
|
|
105
108
|
results?: ResultChildOutcome[];
|
|
106
109
|
}
|
|
107
110
|
|
|
@@ -117,7 +120,7 @@ function regularFileExists(filePath: string): boolean {
|
|
|
117
120
|
function readResultRepairData(resultPath: string): ResultRepairData | undefined {
|
|
118
121
|
try {
|
|
119
122
|
const data = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as { success?: boolean; state?: string; exitCode?: number; results?: unknown };
|
|
120
|
-
const state = data.success ? "complete" : data.state === "stopped" ? "stopped" : data.state === "rejected" ? "rejected" : data.state === "paused" || data.exitCode === 0 ? "paused" : "failed";
|
|
123
|
+
const state = data.success ? "complete" : data.state === "stopped" ? "stopped" : data.state === "rejected" ? "rejected" : data.state === "partial" ? "partial" : data.state === "paused" || data.exitCode === 0 ? "paused" : "failed";
|
|
121
124
|
const results = Array.isArray(data.results)
|
|
122
125
|
? data.results.map((entry, index) => {
|
|
123
126
|
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return {};
|
|
@@ -157,8 +160,9 @@ function terminalStatusFromResult(status: AsyncStatus, resultPath: string, now:
|
|
|
157
160
|
endedAt: step.endedAt ?? now,
|
|
158
161
|
durationMs: step.startedAt !== undefined && step.durationMs === undefined ? Math.max(0, now - step.startedAt) : step.durationMs,
|
|
159
162
|
exitCode: step.exitCode ?? (state === "complete" || state === "paused" ? 0 : 1),
|
|
160
|
-
error: state === "failed" || state === "stopped" ? step.error ?? child?.error : step.error,
|
|
163
|
+
error: state === "failed" || state === "partial" || state === "stopped" ? step.error ?? child?.error : step.error,
|
|
161
164
|
stopped: state === "stopped" ? true : step.stopped,
|
|
165
|
+
sessionName: step.sessionName ?? child?.sessionName,
|
|
162
166
|
sessionFile: step.sessionFile ?? child?.sessionFile,
|
|
163
167
|
model,
|
|
164
168
|
thinking,
|
|
@@ -254,6 +258,7 @@ function buildFailedRepair(status: AsyncStatus, asyncDir: string, now: number, r
|
|
|
254
258
|
summary: message,
|
|
255
259
|
results: repairedSteps.map((step) => ({
|
|
256
260
|
agent: step.agent,
|
|
261
|
+
...(step.sessionName ? { sessionName: step.sessionName } : {}),
|
|
257
262
|
output: step.status === "complete" || step.status === "completed" ? "" : message,
|
|
258
263
|
error: step.status === "complete" || step.status === "completed" ? undefined : step.error ?? message,
|
|
259
264
|
success: step.status === "complete" || step.status === "completed",
|
|
@@ -290,7 +295,7 @@ function writeFailedRepair(asyncDir: string, status: AsyncStatus, resultPath: st
|
|
|
290
295
|
}
|
|
291
296
|
|
|
292
297
|
function terminal(state: AsyncStatus["state"]): boolean {
|
|
293
|
-
return state === "complete" || state === "failed" || state === "paused" || state === "stopped" || state === "rejected";
|
|
298
|
+
return state === "complete" || state === "failed" || state === "partial" || state === "paused" || state === "stopped" || state === "rejected";
|
|
294
299
|
}
|
|
295
300
|
|
|
296
301
|
function* nestedRuns(children: NestedRunSummary[] | undefined): Generator<NestedRunSummary> {
|
|
@@ -353,6 +358,7 @@ export function reconcileAsyncRun(asyncDir: string, options: ReconcileAsyncRunOp
|
|
|
353
358
|
const startedStatus = !status && options.startedRun ? buildStartedStatus(asyncDir, options.startedRun, now) : undefined;
|
|
354
359
|
const effectiveStatus = status ?? startedStatus;
|
|
355
360
|
if (!effectiveStatus) return { status: null, repaired: false };
|
|
361
|
+
assertWorkflowGraphHostSteps(effectiveStatus.workflowGraph, path.join(asyncDir, "status.json"), effectiveStatus.runId);
|
|
356
362
|
const statusPath = path.join(asyncDir, "status.json");
|
|
357
363
|
for (const [index, step] of (effectiveStatus.steps ?? []).entries()) {
|
|
358
364
|
const stepRecord = step as Record<string, unknown>;
|
|
@@ -4,7 +4,6 @@ import { writePrivateAtomicJson } from "../../shared/atomic-json.ts";
|
|
|
4
4
|
import type {
|
|
5
5
|
AsyncStatus,
|
|
6
6
|
ResolvedToolBudget,
|
|
7
|
-
ResolvedTurnBudget,
|
|
8
7
|
SteerActionResult,
|
|
9
8
|
SteeringRecoveryDescriptor,
|
|
10
9
|
SteeringRequestStatus,
|
|
@@ -195,26 +194,17 @@ export function readSteeringStatus(asyncDir: string): SteeringStatus | undefined
|
|
|
195
194
|
}
|
|
196
195
|
|
|
197
196
|
export function remainingSteeringRecoveryLimits(
|
|
198
|
-
descriptor: Pick<SteeringRecoveryDescriptor, "absoluteDeadlineAt" | "
|
|
199
|
-
status: Pick<AsyncStatus, "
|
|
197
|
+
descriptor: Pick<SteeringRecoveryDescriptor, "absoluteDeadlineAt" | "initialToolBudget">,
|
|
198
|
+
status: Pick<AsyncStatus, "toolBudget" | "toolCount">,
|
|
200
199
|
now = Date.now(),
|
|
201
|
-
): { timeoutMs?: number; absoluteDeadlineAt?: number;
|
|
202
|
-
const limits: { timeoutMs?: number; absoluteDeadlineAt?: number;
|
|
200
|
+
): { timeoutMs?: number; absoluteDeadlineAt?: number; toolBudget?: ResolvedToolBudget } {
|
|
201
|
+
const limits: { timeoutMs?: number; absoluteDeadlineAt?: number; toolBudget?: ResolvedToolBudget } = {};
|
|
203
202
|
if (descriptor.absoluteDeadlineAt !== undefined) {
|
|
204
203
|
const timeoutMs = descriptor.absoluteDeadlineAt - now;
|
|
205
204
|
if (timeoutMs <= 0) throw new Error("Source run has no remaining deadline budget; it remains paused.");
|
|
206
205
|
limits.timeoutMs = timeoutMs;
|
|
207
206
|
limits.absoluteDeadlineAt = descriptor.absoluteDeadlineAt;
|
|
208
207
|
}
|
|
209
|
-
if (descriptor.initialTurnBudget) {
|
|
210
|
-
const consumed = status.turnBudget?.turnCount ?? status.turnCount ?? 0;
|
|
211
|
-
const totalRemaining = descriptor.initialTurnBudget.maxTurns + descriptor.initialTurnBudget.graceTurns - consumed;
|
|
212
|
-
if (totalRemaining <= 0) throw new Error("Source run has no remaining turn budget; it remains paused.");
|
|
213
|
-
const softRemaining = Math.max(0, descriptor.initialTurnBudget.maxTurns - consumed);
|
|
214
|
-
limits.turnBudget = softRemaining > 0
|
|
215
|
-
? { maxTurns: softRemaining, graceTurns: totalRemaining - softRemaining }
|
|
216
|
-
: { maxTurns: 1, graceTurns: totalRemaining - 1 };
|
|
217
|
-
}
|
|
218
208
|
if (descriptor.initialToolBudget) {
|
|
219
209
|
const consumed = status.toolBudget?.toolCount ?? status.toolCount ?? 0;
|
|
220
210
|
const hard = descriptor.initialToolBudget.hard - consumed;
|