pi-subagents 0.61.0 → 0.63.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 +52 -1
- package/docs/agents.md +11 -6
- package/docs/configuration.md +31 -5
- package/docs/extension-api.md +2 -2
- package/docs/models.md +5 -5
- package/docs/observability.md +5 -2
- package/docs/tool-reference.md +3 -3
- package/install.mjs +0 -1
- package/package.json +1 -1
- package/skills/pi-subagents/references/execution-controls.md +3 -4
- package/skills/pi-subagents/references/management-authoring-rpc.md +1 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +3 -3
- package/src/agents/agent-management.ts +41 -4
- package/src/agents/agent-serializer.ts +3 -0
- package/src/agents/agents.ts +120 -124
- package/src/agents/runtime-agent-registry.ts +5 -1
- package/src/api/preflight.ts +4 -0
- package/src/api/shared-types.ts +3 -0
- package/src/extension/config.ts +20 -0
- package/src/extension/public-execution.ts +1 -0
- package/src/extension/schemas.ts +6 -2
- package/src/extension/tool-description.ts +1 -1
- package/src/inspectors/herdr/inspector-runner.ts +19 -13
- package/src/runs/background/active-async-capacity.ts +26 -8
- package/src/runs/background/async-execution.ts +100 -23
- package/src/runs/background/async-resume.ts +6 -2
- package/src/runs/background/async-status.ts +18 -2
- package/src/runs/background/notify.ts +13 -1
- package/src/runs/background/process-terminal.ts +16 -0
- package/src/runs/background/run-status.ts +22 -2
- package/src/runs/background/scheduled-runs.ts +63 -6
- package/src/runs/background/steering.ts +4 -1
- package/src/runs/background/subagent-runner.ts +44 -9
- package/src/runs/background/wait-completions.ts +13 -0
- package/src/runs/background/wait-tool.ts +1 -7
- package/src/runs/foreground/execution.ts +14 -7
- package/src/runs/foreground/subagent-executor.ts +38 -5
- package/src/runs/shared/acceptance.ts +85 -18
- package/src/runs/shared/capability-ceiling.ts +1 -0
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/lane-metadata.ts +24 -3
- package/src/runs/shared/parallel-handoff.ts +4 -0
- package/src/runs/shared/parallel-utils.ts +2 -6
- package/src/runs/shared/permissions.ts +1 -1
- package/src/runs/shared/pi-args.ts +32 -14
- package/src/runs/shared/pi-spawn.ts +69 -35
- package/src/runs/shared/structured-output.ts +33 -6
- package/src/runs/shared/subagent-prompt-runtime.ts +20 -3
- package/src/runs/shared/task-intent.ts +21 -7
- package/src/runs/shared/tool-timeout.ts +1 -1
- package/src/runs/shared/worktree.ts +467 -63
- package/src/shared/atomic-json.ts +3 -1
- package/src/shared/fork-context.ts +0 -12
- package/src/shared/fork-session-cwd.ts +27 -0
- package/src/shared/launch-contract.ts +3 -0
- package/src/shared/types.ts +32 -1
- package/src/shared/utils.ts +18 -7
- package/src/slash/slash-commands.ts +1 -1
- package/src/slash/subagents-admin.ts +26 -12
- package/src/tui/fleet-status.ts +61 -2
- package/src/tui/fleet.ts +12 -7
- package/src/tui/render.ts +222 -14
- package/src/workflows/workflow-checklist.ts +441 -0
|
@@ -420,6 +420,18 @@ export function buildCompletionDetails(result: CompletionNotification): Subagent
|
|
|
420
420
|
const handoffPath = typeof parallelHandoff?.path === "string" ? parallelHandoff.path : undefined;
|
|
421
421
|
const rawRunId = typeof result.runId === "string" ? result.runId : typeof result.id === "string" ? result.id : undefined;
|
|
422
422
|
const workflowRunId = (result.mode === "workflow" || agent === "workflow") && rawRunId ? rawRunId : undefined;
|
|
423
|
+
const directChild = !workflowRunId && result.results?.length === 1 ? result.results[0]! : undefined;
|
|
424
|
+
const directStructuredPreview = directChild
|
|
425
|
+
? childInlinePreview(directChild).preview
|
|
426
|
+
: undefined;
|
|
427
|
+
const directSummary = summary.trim();
|
|
428
|
+
const directAgent = typeof directChild?.agent === "string" ? directChild.agent : agent;
|
|
429
|
+
const directNoOutputSummary = directChild && (!directSummary
|
|
430
|
+
|| directSummary === "(no output)"
|
|
431
|
+
|| (directAgent && directSummary === `${directAgent}:\n(no output)`));
|
|
432
|
+
const resultPreview = directStructuredPreview && directNoOutputSummary
|
|
433
|
+
? `Structured output:\n${directStructuredPreview}`
|
|
434
|
+
: summary;
|
|
423
435
|
const childRuns = result.results?.flatMap((child) => {
|
|
424
436
|
const runId = typeof child.runId === "string" && child.runId.trim() ? child.runId.trim() : undefined;
|
|
425
437
|
const workflowKey = typeof child.workflowKey === "string" && child.workflowKey.trim() ? child.workflowKey.trim() : undefined;
|
|
@@ -468,7 +480,7 @@ export function buildCompletionDetails(result: CompletionNotification): Subagent
|
|
|
468
480
|
...(scheduleOrigin ? { scheduleOrigin } : {}),
|
|
469
481
|
...(result.source ? { source: result.source } : {}),
|
|
470
482
|
...(taskInfo ? { taskInfo } : {}),
|
|
471
|
-
resultPreview
|
|
483
|
+
resultPreview,
|
|
472
484
|
...(typeof result.durationMs === "number" ? { durationMs: result.durationMs } : {}),
|
|
473
485
|
...(handoffPath ? { handoffPath } : {}),
|
|
474
486
|
...(workflowRunId ? { workflowRunId } : {}),
|
|
@@ -115,6 +115,22 @@ export function writeProcessTerminalCandidate(asyncDir: string, candidate: Proce
|
|
|
115
115
|
writePrivateAtomicJson(processTerminalCandidatePath(asyncDir), candidate);
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
/** Establish ownership before authorizing a runner to launch any child process. */
|
|
119
|
+
export function initializeProcessTerminal(asyncDir: string, runId: string, runnerProcessInstanceId: string): void {
|
|
120
|
+
writeProcessTerminalCandidate(asyncDir, {
|
|
121
|
+
version: 1,
|
|
122
|
+
runId,
|
|
123
|
+
runnerProcessInstanceId,
|
|
124
|
+
writers: {},
|
|
125
|
+
});
|
|
126
|
+
writeAtomicJson(processTerminalPath(asyncDir), {
|
|
127
|
+
version: 1,
|
|
128
|
+
state: "pending",
|
|
129
|
+
runId,
|
|
130
|
+
runnerProcessInstanceId,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
118
134
|
export function markProcessTerminalCandidateLeaseRelease(asyncDir: string, token: string, acknowledged: boolean): void {
|
|
119
135
|
const candidate = readProcessTerminalCandidate(asyncDir);
|
|
120
136
|
if (!candidate || candidate.revivalLeaseToken !== token) return;
|
|
@@ -28,6 +28,8 @@ import { formatRunFanoutBudget, getRunFanoutBudgetSnapshot, readRunFanoutBudgetD
|
|
|
28
28
|
import { workflowGraphStageNodes } from "../shared/workflow-graph.ts";
|
|
29
29
|
import { getExternalJobProvider } from "../../api/external-job-provider.ts";
|
|
30
30
|
import { formatTimeoutRecoveryLines } from "../shared/mutation-evidence.ts";
|
|
31
|
+
import { formatWorkflowChecklistText, projectWorkflowChecklist } from "../../workflows/workflow-checklist.ts";
|
|
32
|
+
import { validHostStepNodes } from "../shared/host-step-status.ts";
|
|
31
33
|
|
|
32
34
|
interface RunStatusParams {
|
|
33
35
|
action?: string;
|
|
@@ -74,7 +76,7 @@ function formatWorkflowDebug(status: AsyncStatus): string[] {
|
|
|
74
76
|
status.lane ? `Lane: ${status.lane.key}${status.lane.mode ? ` (${status.lane.mode})` : ""}` : undefined,
|
|
75
77
|
].filter((line): line is string => line !== undefined);
|
|
76
78
|
for (const [index, step] of (status.steps ?? []).entries()) {
|
|
77
|
-
lines.push(` ${index + 1}. key ${step.workflowKey ?? "n/a"} · ${runStatusStepDisplayName(step)} · ${step.status} · async ${step.async === undefined ? "unknown" : step.async ? "yes" : "no"}${step.runId ? ` · run ${step.runId}` : ""}${step.lane ? ` · lane ${step.lane.key}` : ""}${step.worktreePath ? ` · worktree ${step.worktreePath} · branch ${step.branch ?? "unknown"}` : ""}`);
|
|
79
|
+
lines.push(` ${index + 1}. key ${step.workflowKey ?? "n/a"} · ${runStatusStepDisplayName(step)} · ${step.status} · async ${step.async === undefined ? "unknown" : step.async ? "yes" : "no"}${step.runId ? ` · run ${step.runId}` : ""}${step.lane ? ` · lane ${step.lane.key}` : ""}${step.worktreePath ? ` · worktree ${step.worktreePath} · branch ${step.branch ?? "unknown"}${step.provider ? ` · provider ${step.provider}` : ""}` : ""}`);
|
|
78
80
|
}
|
|
79
81
|
return lines;
|
|
80
82
|
}
|
|
@@ -522,6 +524,14 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
522
524
|
status.mode === "workflow" && workflowReturnPreview !== undefined ? `Return: ${workflowReturnPreview}` : undefined,
|
|
523
525
|
status.mode === "workflow" && workflowEmitPreview !== undefined ? `Latest emit: ${workflowEmitPreview}` : undefined,
|
|
524
526
|
`Progress: ${progressLabel}`,
|
|
527
|
+
...(status.mode === "workflow" ? formatWorkflowChecklistText(projectWorkflowChecklist({
|
|
528
|
+
graph: status.workflowGraph,
|
|
529
|
+
steps: status.steps,
|
|
530
|
+
hostSteps: validHostStepNodes(status.workflowGraph),
|
|
531
|
+
preflight: status.preflight,
|
|
532
|
+
trace: status.workflow?.trace,
|
|
533
|
+
now: status.lastUpdate ?? status.endedAt ?? Date.now(),
|
|
534
|
+
}), "", { includeItems: false }) : []),
|
|
525
535
|
status.pendingAppends ? `Pending appends: ${status.pendingAppends}` : undefined,
|
|
526
536
|
`Started: ${started}`,
|
|
527
537
|
`Updated: ${updated}`,
|
|
@@ -551,6 +561,9 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
551
561
|
const display = runStatusStepDisplayName(step);
|
|
552
562
|
const phase = step.phase ? `[${step.phase}] ` : "";
|
|
553
563
|
lines.push(`${stepLineLabel(status, index)}: ${phase}${display} ${step.status}${modelText}${stepActivityText ? `, ${stepActivityText}` : ""}${steeringSuffix}${acceptanceText}${budgetText}${errorText}`);
|
|
564
|
+
const structuredOutputPreview = step.structuredOutput === undefined ? undefined : formatWorkflowJsonPreview(step.structuredOutput, 4_000);
|
|
565
|
+
if (structuredOutputPreview !== undefined) lines.push(` Structured output: ${structuredOutputPreview}`);
|
|
566
|
+
if (step.structuredOutputPath) lines.push(` Structured output path: ${step.structuredOutputPath}`);
|
|
554
567
|
lines.push(...formatTimeoutRecoveryLines(step.timeoutRecovery, " "));
|
|
555
568
|
if (step.runner?.type === "external-cli") {
|
|
556
569
|
const runner = normalizeExternalCliRunnerStatus(step.runner);
|
|
@@ -670,7 +683,14 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
670
683
|
if (data.parallelHandoff?.path) lines.push(`Parallel handoff: ${data.parallelHandoff.path}`);
|
|
671
684
|
const children = Array.isArray(data.results) ? data.results : data.agent ? [{ agent: data.agent, sessionFile: data.sessionFile }] : [];
|
|
672
685
|
lines.push(...formatTimeoutRecoveryLines(data.timeoutRecovery, " "));
|
|
673
|
-
for (const child of children
|
|
686
|
+
for (const [index, child] of children.entries()) {
|
|
687
|
+
const structuredOutput = (child as { structuredOutput?: unknown }).structuredOutput;
|
|
688
|
+
const structuredOutputPreview = structuredOutput === undefined ? undefined : formatWorkflowJsonPreview(structuredOutput, 4_000);
|
|
689
|
+
if (structuredOutputPreview !== undefined) lines.push(` Structured output${children.length > 1 ? ` (${index + 1})` : ""}: ${structuredOutputPreview}`);
|
|
690
|
+
const structuredOutputPath = (child as { structuredOutputPath?: unknown }).structuredOutputPath;
|
|
691
|
+
if (typeof structuredOutputPath === "string" && structuredOutputPath.trim()) lines.push(` Structured output path${children.length > 1 ? ` (${index + 1})` : ""}: ${structuredOutputPath}`);
|
|
692
|
+
lines.push(...formatTimeoutRecoveryLines(child.timeoutRecovery, " "));
|
|
693
|
+
}
|
|
674
694
|
lines.push(formatResumeGuidance(runId, children, data.sessionFile, { stopped: status === "stopped" }));
|
|
675
695
|
if (data.summary) lines.push("", data.summary);
|
|
676
696
|
const workflowChildren = parseWorkflowChildSummary((data as unknown as Record<string, unknown>).workflowChildren);
|
|
@@ -52,6 +52,8 @@ export interface ScheduleRecord {
|
|
|
52
52
|
catchUp: "none" | "latest";
|
|
53
53
|
timeoutMs?: number;
|
|
54
54
|
paused: boolean;
|
|
55
|
+
sessionOnly?: boolean;
|
|
56
|
+
ownerSessionFile?: string;
|
|
55
57
|
createdAt: string;
|
|
56
58
|
updatedAt: string;
|
|
57
59
|
activeRunId?: string;
|
|
@@ -72,6 +74,8 @@ export interface ScheduleRunRecord {
|
|
|
72
74
|
error?: string;
|
|
73
75
|
}
|
|
74
76
|
|
|
77
|
+
type PublicScheduleRecord = Omit<ScheduleRecord, "ownerSessionFile">;
|
|
78
|
+
|
|
75
79
|
type ScheduledRunManagerDeps = {
|
|
76
80
|
config: ExtensionConfig;
|
|
77
81
|
launch(params: SubagentParamsLike, ctx: ExtensionContext, signal: AbortSignal): Promise<AgentToolResult<Details>>;
|
|
@@ -295,6 +299,8 @@ function parseSchedule(value: unknown, file: string): ScheduleRecord {
|
|
|
295
299
|
} else if (record.trigger.kind === "interval") {
|
|
296
300
|
if (typeof record.trigger.every !== "string" || typeof record.trigger.everyMs !== "number" || typeof record.trigger.anchorAt !== "string" || typeof record.trigger.nextRunAt !== "string") throw new Error(`Schedule record '${file}' has an invalid interval trigger.`);
|
|
297
301
|
} else throw new Error(`Schedule record '${file}' has an unsupported trigger.`);
|
|
302
|
+
if (record.sessionOnly !== undefined && typeof record.sessionOnly !== "boolean") throw new Error(`Schedule record '${file}' has invalid sessionOnly.`);
|
|
303
|
+
if (record.sessionOnly === true && (typeof record.ownerSessionFile !== "string" || !record.ownerSessionFile.trim())) throw new Error(`Schedule record '${file}' is session-only but has no owner session file.`);
|
|
298
304
|
return { ...record, target: parseScheduleTarget(record.target, file) } as ScheduleRecord;
|
|
299
305
|
}
|
|
300
306
|
|
|
@@ -399,13 +405,19 @@ function duePlannedAt(schedule: ScheduleRecord, now: number): number | undefined
|
|
|
399
405
|
}
|
|
400
406
|
|
|
401
407
|
function textResult(text: string, schedules?: ScheduleRecord[], runs?: ScheduleRunRecord[], isError = false): AgentToolResult<Details> {
|
|
408
|
+
const publicSchedules = schedules?.map(publicScheduleRecord);
|
|
402
409
|
return {
|
|
403
410
|
content: [{ type: "text", text }],
|
|
404
411
|
...(isError ? { isError: true } : {}),
|
|
405
|
-
details: { mode: "management", results: [], schedules: { ...(
|
|
412
|
+
details: { mode: "management", results: [], schedules: { ...(publicSchedules ? { records: publicSchedules } : {}), ...(runs ? { runs } : {}) } },
|
|
406
413
|
};
|
|
407
414
|
}
|
|
408
415
|
|
|
416
|
+
function publicScheduleRecord(schedule: ScheduleRecord): PublicScheduleRecord {
|
|
417
|
+
const { ownerSessionFile: _ownerSessionFile, ...rest } = schedule;
|
|
418
|
+
return rest;
|
|
419
|
+
}
|
|
420
|
+
|
|
409
421
|
function targetLabel(target: ScheduleTarget): string {
|
|
410
422
|
const preview = previewSimpleWorkflowRun(target.workflowScript);
|
|
411
423
|
return preview?.agent ? `workflowScript -> agent ${preview.agent}` : "workflowScript (dynamic)";
|
|
@@ -450,6 +462,32 @@ function snapshotContext(ctx: ExtensionContext, cwd: string): ExtensionContext {
|
|
|
450
462
|
return { ...ctx, cwd, sessionManager };
|
|
451
463
|
}
|
|
452
464
|
|
|
465
|
+
/**
|
|
466
|
+
* 规范化会话文件路径, 兼容 Windows 路径大小写差异.
|
|
467
|
+
*
|
|
468
|
+
* @param value 会话文件路径
|
|
469
|
+
* @returns 规范化后的路径, 空值时返回 undefined
|
|
470
|
+
*/
|
|
471
|
+
function normalizedSessionFile(value: string | undefined): string | undefined {
|
|
472
|
+
if (!value || !value.trim()) return undefined;
|
|
473
|
+
const normalized = path.normalize(path.resolve(value));
|
|
474
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* 判断 Schedule 是否属于当前 Pi 会话.
|
|
479
|
+
*
|
|
480
|
+
* @param schedule Schedule 记录
|
|
481
|
+
* @param ctx 当前 Pi 会话上下文
|
|
482
|
+
* @returns 是否允许当前会话执行该 Schedule
|
|
483
|
+
*/
|
|
484
|
+
function scheduleBelongsToSession(schedule: ScheduleRecord, ctx: ExtensionContext): boolean {
|
|
485
|
+
if (schedule.sessionOnly !== true) return true;
|
|
486
|
+
const ownerSessionFile = normalizedSessionFile(schedule.ownerSessionFile);
|
|
487
|
+
const currentSessionFile = normalizedSessionFile(ctx.sessionManager.getSessionFile());
|
|
488
|
+
return ownerSessionFile !== undefined && ownerSessionFile === currentSessionFile;
|
|
489
|
+
}
|
|
490
|
+
|
|
453
491
|
export function listScheduledRunSummaries(cwd: string, root?: string): ScheduleRecord[] {
|
|
454
492
|
return new ScheduleStore(scheduledRunStorePath(cwd, undefined, root), root === undefined ? path.resolve(cwd) : undefined).list();
|
|
455
493
|
}
|
|
@@ -560,6 +598,10 @@ export class ScheduledRunManager {
|
|
|
560
598
|
if (params.catchUp !== undefined && params.catchUp !== "none" && params.catchUp !== "latest") return textResult("catchUp must be 'none' or 'latest'.", undefined, undefined, true);
|
|
561
599
|
if (params.missionId !== undefined || params.mission !== undefined || params.missionUpdate !== undefined || params.missionStatus !== undefined || params.missionScope !== undefined) return textResult("Mission attachment is deferred from this first schedule slice.", undefined, undefined, true);
|
|
562
600
|
if (params.on !== undefined || params.timezone !== undefined || every === "day" || every === "week" || every === "month" || every === "year") return textResult("Calendar schedules are deferred from this first safe slice. Use a fixed interval such as every:'24h' or every:'7d'.", undefined, undefined, true);
|
|
601
|
+
const sessionOnly = params.sessionOnly === true;
|
|
602
|
+
if (sessionOnly && params.cwd !== undefined && !samePath(params.cwd, ctx.cwd)) return textResult("sessionOnly schedules cannot use an explicit cross-project cwd.", undefined, undefined, true);
|
|
603
|
+
const ownerSessionFile = sessionOnly ? ctx.sessionManager.getSessionFile() : undefined;
|
|
604
|
+
if (sessionOnly && !ownerSessionFile) return textResult("sessionOnly schedules require a persisted current session.", undefined, undefined, true);
|
|
563
605
|
const sessionId = ctx.sessionManager.getSessionId() ?? "unknown";
|
|
564
606
|
if (this.deps.resolveCapabilityCeiling?.(sessionId)) return textResult("Cannot persist a schedule while a capability ceiling is active.", undefined, undefined, true);
|
|
565
607
|
const pendingCount = store.list().filter(hasPendingScheduleWork).length;
|
|
@@ -587,24 +629,25 @@ export class ScheduledRunManager {
|
|
|
587
629
|
catchUp: params.catchUp ?? "latest",
|
|
588
630
|
...(params.timeoutMs === undefined ? {} : { timeoutMs: params.timeoutMs }),
|
|
589
631
|
paused: false,
|
|
632
|
+
...(sessionOnly ? { sessionOnly: true, ownerSessionFile: path.resolve(ownerSessionFile!) } : {}),
|
|
590
633
|
createdAt: timestamp(now),
|
|
591
634
|
updatedAt: timestamp(now),
|
|
592
635
|
};
|
|
593
636
|
store.write(schedule);
|
|
594
637
|
store.appendEvent(schedule, "schedule.created");
|
|
595
638
|
this.arm(schedule, store);
|
|
596
|
-
return textResult(`Created schedule ${id}.\nName: ${schedule.name}\nTrigger: ${at ? `at ${at}` : `every ${every}`}\nNext: ${schedule.trigger.nextRunAt}\nTarget: ${targetLabel(schedule.target)}`, [schedule]);
|
|
639
|
+
return textResult(`Created schedule ${id}.\nName: ${schedule.name}\nTrigger: ${at ? `at ${at}` : `every ${every}`}\nSession only: ${schedule.sessionOnly === true ? "yes" : "no"}\nNext: ${schedule.trigger.nextRunAt}\nTarget: ${targetLabel(schedule.target)}`, [schedule]);
|
|
597
640
|
}
|
|
598
641
|
|
|
599
642
|
private list(): AgentToolResult<Details> {
|
|
600
643
|
const schedules = this.requireStore().list().sort((a, b) => (a.trigger.nextRunAt ?? "").localeCompare(b.trigger.nextRunAt ?? ""));
|
|
601
644
|
if (!schedules.length) return textResult("No project schedules.", []);
|
|
602
|
-
return textResult([`Project schedules: ${schedules.length}`, ...schedules.map((item) => `- ${item.id} | ${item.paused ? "paused" : item.activeRunId ? "running" : "scheduled"} | ${item.trigger.nextRunAt ?? "no next run"} | ${item.name}`)].join("\n"), schedules);
|
|
645
|
+
return textResult([`Project schedules: ${schedules.length}`, ...schedules.map((item) => `- ${item.id} | ${item.paused ? "paused" : item.activeRunId ? "running" : "scheduled"} | ${item.trigger.nextRunAt ?? "no next run"} | ${item.sessionOnly === true ? "session-only" : "project"} | ${item.name}`)].join("\n"), schedules);
|
|
603
646
|
}
|
|
604
647
|
|
|
605
648
|
private show(params: SubagentParamsLike): AgentToolResult<Details> {
|
|
606
649
|
const schedule = this.resolve(params);
|
|
607
|
-
return textResult([`Schedule: ${schedule.id}`, `Name: ${schedule.name}`, `State: ${schedule.paused ? "paused" : schedule.activeRunId ? "running" : "scheduled"}`, `Target: ${targetLabel(schedule.target)}`, `CWD: ${shortenPath(schedule.cwd)}`, `Next: ${schedule.trigger.nextRunAt ?? "none"}`, `Catch up: ${schedule.catchUp}`, schedule.activeRunId ? `Active run: ${schedule.activeRunId}` : undefined].filter(Boolean).join("\n"), [schedule]);
|
|
650
|
+
return textResult([`Schedule: ${schedule.id}`, `Name: ${schedule.name}`, `State: ${schedule.paused ? "paused" : schedule.activeRunId ? "running" : "scheduled"}`, `Session only: ${schedule.sessionOnly === true ? "yes" : "no"}`, `Target: ${targetLabel(schedule.target)}`, `CWD: ${shortenPath(schedule.cwd)}`, `Next: ${schedule.trigger.nextRunAt ?? "none"}`, `Catch up: ${schedule.catchUp}`, schedule.activeRunId ? `Active run: ${schedule.activeRunId}` : undefined].filter(Boolean).join("\n"), [schedule]);
|
|
608
651
|
}
|
|
609
652
|
|
|
610
653
|
private history(params: SubagentParamsLike): AgentToolResult<Details> {
|
|
@@ -628,13 +671,18 @@ export class ScheduledRunManager {
|
|
|
628
671
|
private async runManual(params: SubagentParamsLike): Promise<AgentToolResult<Details>> {
|
|
629
672
|
const store = this.requireStore();
|
|
630
673
|
const schedule = this.resolve(params);
|
|
674
|
+
const context = this.requireContext(store);
|
|
675
|
+
if (!scheduleBelongsToSession(schedule, context)) {
|
|
676
|
+
return textResult(`Skipped schedule ${schedule.id}: current session is not its owner.`, [schedule]);
|
|
677
|
+
}
|
|
631
678
|
const run = await this.launch(store, schedule, this.now(), "manual", false);
|
|
632
679
|
return textResult(`Manual schedule run ${run.id}: ${run.state}${run.asyncId ? ` (async ${run.asyncId})` : ""}.`, [store.get(schedule.id)], [run], run.state === "failed_launch");
|
|
633
680
|
}
|
|
634
681
|
|
|
635
682
|
private async runDue(): Promise<AgentToolResult<Details>> {
|
|
636
683
|
const store = this.requireStore();
|
|
637
|
-
const
|
|
684
|
+
const context = this.requireContext(store);
|
|
685
|
+
const due = store.list().filter((schedule) => scheduleBelongsToSession(schedule, context) && !schedule.paused && nextRunAt(schedule) !== undefined && nextRunAt(schedule)! <= this.now());
|
|
638
686
|
const runs: ScheduleRunRecord[] = [];
|
|
639
687
|
for (const schedule of due) {
|
|
640
688
|
const planned = duePlannedAt(schedule, this.now())!;
|
|
@@ -659,6 +707,7 @@ export class ScheduledRunManager {
|
|
|
659
707
|
}
|
|
660
708
|
|
|
661
709
|
private restoreOne(store: ScheduleStore, schedule: ScheduleRecord, notBefore?: number, rearm = true): void {
|
|
710
|
+
if (!scheduleBelongsToSession(schedule, this.requireContext(store))) return;
|
|
662
711
|
if (schedule.activeRunId) {
|
|
663
712
|
const run = store.history(schedule.id).find((item) => item.id === schedule.activeRunId);
|
|
664
713
|
if (run?.state === "running" && run.asyncId) this.observedAsyncIds.add(run.asyncId);
|
|
@@ -737,6 +786,7 @@ export class ScheduledRunManager {
|
|
|
737
786
|
// is then nothing to run and nothing to re-arm.
|
|
738
787
|
const schedule = store.find(id);
|
|
739
788
|
if (!schedule) return;
|
|
789
|
+
if (!scheduleBelongsToSession(schedule, this.requireContext(store))) return;
|
|
740
790
|
const planned = duePlannedAt(schedule, this.now());
|
|
741
791
|
if (planned === undefined || schedule.paused) return;
|
|
742
792
|
if (planned > this.now()) return this.arm(schedule, store);
|
|
@@ -857,13 +907,20 @@ export class ScheduledRunManager {
|
|
|
857
907
|
private selectProject(cwd: string, ctx: ExtensionContext): void {
|
|
858
908
|
const projectCwd = path.resolve(cwd);
|
|
859
909
|
const root = scheduledRunStorePath(projectCwd, undefined, this.deps.storeRoot);
|
|
860
|
-
|
|
910
|
+
const isBoundContext = path.resolve(ctx.cwd) === projectCwd;
|
|
911
|
+
const previousContext = this.contexts.get(root);
|
|
912
|
+
const contextChanged = isBoundContext
|
|
913
|
+
&& previousContext !== undefined
|
|
914
|
+
&& normalizedSessionFile(previousContext.sessionManager.getSessionFile()) !== normalizedSessionFile(ctx.sessionManager.getSessionFile());
|
|
915
|
+
if (isBoundContext) this.contexts.set(root, snapshotContext(ctx, projectCwd));
|
|
861
916
|
else if (!this.contexts.has(root)) throw new Error(`Cannot use project '${projectCwd}' until that project has been opened in this runtime.`);
|
|
862
917
|
let store = this.stores.get(root);
|
|
863
918
|
if (!store) {
|
|
864
919
|
store = new ScheduleStore(root, this.deps.storeRoot === undefined ? projectCwd : undefined);
|
|
865
920
|
this.stores.set(root, store);
|
|
866
921
|
this.restore(store);
|
|
922
|
+
} else if (contextChanged) {
|
|
923
|
+
this.restore(store);
|
|
867
924
|
}
|
|
868
925
|
this.store = store;
|
|
869
926
|
}
|
|
@@ -23,7 +23,10 @@ export function steeringMessagePreview(message: string): string {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export function steeringReceipt(message: string, receipt: string): string {
|
|
26
|
-
|
|
26
|
+
const preview = steeringMessagePreview(message);
|
|
27
|
+
const longestFence = Math.max(2, ...[...preview.matchAll(/`{3,}/g)].map((match) => match[0]!.length));
|
|
28
|
+
const fence = "`".repeat(longestFence + 1);
|
|
29
|
+
return `${receipt}\n\nMessage sent:\n${fence}text\n${preview}\n${fence}`;
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
export function createSteeringStatus(): SteeringStatus {
|
|
@@ -77,9 +77,10 @@ import {
|
|
|
77
77
|
} from "../shared/parallel-utils.ts";
|
|
78
78
|
import { applyThinkingSuffix, buildPiArgs, cleanupTempDir, deriveForkPromptCacheKey, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan, type SubagentTaskDelivery } from "../shared/pi-args.ts";
|
|
79
79
|
import { deriveChildSessionName } from "../../shared/child-session-name.ts";
|
|
80
|
+
import { alignForkedSessionCwd } from "../../shared/fork-session-cwd.ts";
|
|
80
81
|
import { readRuntimeAcknowledgedExtensions } from "../shared/runtime-acknowledged-extensions.ts";
|
|
81
82
|
import { outputEntryFromAsyncResult, resolveOutputReferences } from "../shared/chain-outputs.ts";
|
|
82
|
-
import { createStructuredOutputRuntime, MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput, readStructuredOutputAcceptanceReport } from "../shared/structured-output.ts";
|
|
83
|
+
import { clearStructuredOutputCaptures, createStructuredOutputRuntime, MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput, readStructuredOutputAcceptanceReport } from "../shared/structured-output.ts";
|
|
83
84
|
import { formatMidToolExitError, formatProcessSignalError, isOrdinaryToolForMidToolExit, isUnexplainedProcessSignal } from "../shared/process-signal.ts";
|
|
84
85
|
import { readChildToolDiagnosticError } from "../shared/tool-availability.ts";
|
|
85
86
|
import { buildTimeoutRecoverySummary, collectTrackedMutationEvidence, snapshotTrackedMutations } from "../shared/mutation-evidence.ts";
|
|
@@ -123,6 +124,7 @@ import {
|
|
|
123
124
|
findWorktreeTaskCwdConflict,
|
|
124
125
|
formatWorktreeDiffSummary,
|
|
125
126
|
formatWorktreeTaskCwdConflict,
|
|
127
|
+
WORKTREE_AGENT_CWD_PLACEHOLDER,
|
|
126
128
|
type WorktreeSetup,
|
|
127
129
|
} from "../shared/worktree.ts";
|
|
128
130
|
import { findModelInfo, resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
@@ -130,7 +132,7 @@ import { assertThinkingWithinCeiling, decodeThinkingCeiling, SUBAGENT_THINKING_C
|
|
|
130
132
|
import { launchBindingDigest } from "../../shared/launch-contract.ts";
|
|
131
133
|
import { writeInitialProgressFile } from "../../shared/settings.ts";
|
|
132
134
|
import { resolveSubagentIntercomTarget } from "../../intercom/intercom-bridge.ts";
|
|
133
|
-
import { acceptanceFailureMessage, aggregateAcceptanceReport, buildSkippedAcceptanceLedger, evaluateAcceptance, formatAcceptancePrompt, resolveEffectiveAcceptance, stripAcceptanceReport } from "../shared/acceptance.ts";
|
|
135
|
+
import { acceptanceFailureMessage, aggregateAcceptanceReport, buildSkippedAcceptanceLedger, evaluateAcceptance, formatAcceptancePrompt, resolveAcceptanceReportMode, resolveEffectiveAcceptance, stripAcceptanceReport } from "../shared/acceptance.ts";
|
|
134
136
|
import { attachContractProjections, isAgentContractV1 } from "../shared/agent-contract.ts";
|
|
135
137
|
import { waitForImportedAsyncRoot } from "./chain-root-attachment.ts";
|
|
136
138
|
import { normalizeExtensionBindings } from "../shared/extension-bindings.ts";
|
|
@@ -184,6 +186,8 @@ interface SubagentRunConfig {
|
|
|
184
186
|
worktreeSetupHook?: string;
|
|
185
187
|
worktreeSetupHookTimeoutMs?: number;
|
|
186
188
|
worktreeBaseDir?: string;
|
|
189
|
+
worktreeProvider?: import("../../shared/types.ts").WorktreeProvider;
|
|
190
|
+
worktreeBranchPrefix?: string;
|
|
187
191
|
controlConfig?: ResolvedControlConfig;
|
|
188
192
|
controlIntercomTarget?: string;
|
|
189
193
|
childIntercomTargets?: Array<string | undefined>;
|
|
@@ -1397,7 +1401,7 @@ async function runSingleStepInner(
|
|
|
1397
1401
|
}
|
|
1398
1402
|
|
|
1399
1403
|
const effectiveStructuredOutput = step.structuredOutput ?? (step.structuredOutputSchema
|
|
1400
|
-
? createStructuredOutputRuntime(step.structuredOutputSchema, path.join(path.dirname(ctx.outputFile), "structured-output"))
|
|
1404
|
+
? createStructuredOutputRuntime(step.structuredOutputSchema, path.join(path.dirname(ctx.outputFile), "structured-output"), { acceptanceReport: resolveAcceptanceReportMode(step.acceptanceInput) })
|
|
1401
1405
|
: undefined);
|
|
1402
1406
|
const placeholderRegex = new RegExp(ctx.placeholder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g");
|
|
1403
1407
|
let task = step.task.replace(placeholderRegex, () => ctx.previousOutput);
|
|
@@ -1407,6 +1411,7 @@ async function runSingleStepInner(
|
|
|
1407
1411
|
if (!step.runner) {
|
|
1408
1412
|
resolvedTaskToolPlan = resolvePiLaunchToolPlan(omitUndefinedProperties({
|
|
1409
1413
|
tools: step.tools,
|
|
1414
|
+
excludeTools: step.excludeTools,
|
|
1410
1415
|
allowNestedSubagents: step.allowNestedSubagents,
|
|
1411
1416
|
extensions: step.extensions,
|
|
1412
1417
|
subagentOnlyExtensions: step.subagentOnlyExtensions,
|
|
@@ -1627,6 +1632,9 @@ async function runSingleStepInner(
|
|
|
1627
1632
|
const effectiveCwd = step.cwd ?? ctx.cwd;
|
|
1628
1633
|
const cwdError = preflightLaunchCwd(step.requestedCwd ?? effectiveCwd, effectiveCwd);
|
|
1629
1634
|
if (cwdError) return { agent: step.agent, output: cwdError, error: cwdError, exitCode: 1, context: step.context };
|
|
1635
|
+
if (step.context === "fork" && step.sessionFile && fs.existsSync(step.sessionFile)) {
|
|
1636
|
+
alignForkedSessionCwd(step.sessionFile, effectiveCwd);
|
|
1637
|
+
}
|
|
1630
1638
|
|
|
1631
1639
|
const candidates = step.modelCandidates !== undefined
|
|
1632
1640
|
? step.modelCandidates.length > 0 ? step.modelCandidates : [undefined]
|
|
@@ -1681,10 +1689,9 @@ async function runSingleStepInner(
|
|
|
1681
1689
|
}));
|
|
1682
1690
|
const outputSnapshot = captureSingleOutputSnapshot(step.outputPath);
|
|
1683
1691
|
if (effectiveStructuredOutput) {
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
// Missing/stale structured-output files are handled after the child exits.
|
|
1692
|
+
const cleanupError = clearStructuredOutputCaptures(effectiveStructuredOutput);
|
|
1693
|
+
if (cleanupError) {
|
|
1694
|
+
return omitUndefinedProperties({ agent: step.agent, output: cleanupError, error: cleanupError, exitCode: 1, context: step.context });
|
|
1688
1695
|
}
|
|
1689
1696
|
}
|
|
1690
1697
|
const watchdogConfig = resolveWatchdogConfig(step.cwd ?? ctx.cwd);
|
|
@@ -1712,6 +1719,7 @@ async function runSingleStepInner(
|
|
|
1712
1719
|
inheritSkills: step.inheritSkills,
|
|
1713
1720
|
requireReadTool: Boolean(step.skills?.length),
|
|
1714
1721
|
tools: step.tools,
|
|
1722
|
+
excludeTools: step.excludeTools,
|
|
1715
1723
|
allowNestedSubagents: step.allowNestedSubagents,
|
|
1716
1724
|
extensions: step.extensions,
|
|
1717
1725
|
subagentOnlyExtensions: step.subagentOnlyExtensions,
|
|
@@ -1761,6 +1769,7 @@ async function runSingleStepInner(
|
|
|
1761
1769
|
if (step.definitionDigest) {
|
|
1762
1770
|
const toolPlan = resolvedTaskToolPlan ?? resolvePiLaunchToolPlan(omitUndefinedProperties({
|
|
1763
1771
|
tools: step.tools,
|
|
1772
|
+
excludeTools: step.excludeTools,
|
|
1764
1773
|
allowNestedSubagents: step.allowNestedSubagents,
|
|
1765
1774
|
extensions: step.extensions,
|
|
1766
1775
|
subagentOnlyExtensions: step.subagentOnlyExtensions,
|
|
@@ -1793,6 +1802,7 @@ async function runSingleStepInner(
|
|
|
1793
1802
|
inheritSkills: step.inheritSkills,
|
|
1794
1803
|
skills: step.skills,
|
|
1795
1804
|
tools: toolPlan.effectiveToolAllowlist,
|
|
1805
|
+
...(toolPlan.excludeTools.length > 0 ? { excludeTools: toolPlan.excludeTools } : {}),
|
|
1796
1806
|
extensions: toolPlan.extensionArgs,
|
|
1797
1807
|
mcpDirectTools: toolPlan.effectiveMcpTools,
|
|
1798
1808
|
...(step.outputPath ? { outputPath: step.outputPath } : {}),
|
|
@@ -2332,6 +2342,8 @@ function requiredStatusStep(statusPayload: RunnerStatusPayload, index: number):
|
|
|
2332
2342
|
function setStatusWorktreeReference(statusStep: RunnerStatusStep, worktree: WorktreeSetup["worktrees"][number]): void {
|
|
2333
2343
|
statusStep.worktreePath = worktree.path;
|
|
2334
2344
|
statusStep.branch = worktree.branch;
|
|
2345
|
+
if (worktree.provider) statusStep.provider = worktree.provider;
|
|
2346
|
+
if (worktree.naming) statusStep.naming = worktree.naming;
|
|
2335
2347
|
}
|
|
2336
2348
|
|
|
2337
2349
|
function markParallelGroupSetupFailure(input: {
|
|
@@ -2412,6 +2424,18 @@ function markParallelGroupRunning(input: {
|
|
|
2412
2424
|
}));
|
|
2413
2425
|
}
|
|
2414
2426
|
|
|
2427
|
+
function bindWorktreeCwd(step: SubagentStep, worktreeCwd: string): SubagentStep {
|
|
2428
|
+
const bind = <T extends string | null | undefined>(value: T): T => value === null || value === undefined ? value : value.replaceAll(WORKTREE_AGENT_CWD_PLACEHOLDER, worktreeCwd) as T;
|
|
2429
|
+
return {
|
|
2430
|
+
...step,
|
|
2431
|
+
task: bind(step.task) ?? step.task,
|
|
2432
|
+
...(step.systemPrompt !== undefined ? { systemPrompt: bind(step.systemPrompt) } : {}),
|
|
2433
|
+
...(step.outputPath !== undefined ? { outputPath: bind(step.outputPath) } : {}),
|
|
2434
|
+
...(step.launchBindingTask !== undefined ? { launchBindingTask: bind(step.launchBindingTask) } : {}),
|
|
2435
|
+
...(step.requestedCwd !== undefined ? { requestedCwd: bind(step.requestedCwd) } : {}),
|
|
2436
|
+
};
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2415
2439
|
function prepareParallelTaskRun(
|
|
2416
2440
|
task: SubagentStep,
|
|
2417
2441
|
cwd: string,
|
|
@@ -2420,8 +2444,9 @@ function prepareParallelTaskRun(
|
|
|
2420
2444
|
): { taskForRun: SubagentStep; taskCwd: string } {
|
|
2421
2445
|
if (!worktreeSetup) return { taskForRun: task, taskCwd: cwd };
|
|
2422
2446
|
const { cwd: _taskCwd, ...taskForRun } = task;
|
|
2447
|
+
const boundTask = bindWorktreeCwd(taskForRun, worktreeSetup.worktrees[taskIndex]!.agentCwd);
|
|
2423
2448
|
return {
|
|
2424
|
-
taskForRun,
|
|
2449
|
+
taskForRun: boundTask,
|
|
2425
2450
|
taskCwd: worktreeSetup.worktrees[taskIndex]!.agentCwd,
|
|
2426
2451
|
};
|
|
2427
2452
|
}
|
|
@@ -4468,6 +4493,10 @@ async function runSubagent(
|
|
|
4468
4493
|
try {
|
|
4469
4494
|
worktreeSetup = createWorktrees(cwd, `${id}-s${stepIndex}`, group.parallel.length, omitUndefinedProperties({
|
|
4470
4495
|
agents: group.parallel.map((task) => task.agent),
|
|
4496
|
+
labels: group.parallel.map((task) => task.lane?.key ?? config.workflowKey ?? task.outputName ?? task.label),
|
|
4497
|
+
tasks: group.parallel.map((task) => task.task),
|
|
4498
|
+
provider: config.worktreeProvider,
|
|
4499
|
+
branchPrefix: config.worktreeBranchPrefix,
|
|
4471
4500
|
setupHook: config.worktreeSetupHook
|
|
4472
4501
|
? omitUndefinedProperties({ hookPath: config.worktreeSetupHook, timeoutMs: config.worktreeSetupHookTimeoutMs })
|
|
4473
4502
|
: undefined,
|
|
@@ -4900,6 +4929,10 @@ async function runSubagent(
|
|
|
4900
4929
|
try {
|
|
4901
4930
|
singleWorktreeSetup = createWorktrees(cwd, `${id}-s${stepIndex}`, 1, omitUndefinedProperties({
|
|
4902
4931
|
agents: [seqStep.agent],
|
|
4932
|
+
labels: [seqStep.lane?.key ?? config.workflowKey ?? seqStep.outputName ?? seqStep.label],
|
|
4933
|
+
tasks: [seqStep.task],
|
|
4934
|
+
provider: config.worktreeProvider,
|
|
4935
|
+
branchPrefix: config.worktreeBranchPrefix,
|
|
4903
4936
|
setupHook: config.worktreeSetupHook
|
|
4904
4937
|
? omitUndefinedProperties({ hookPath: config.worktreeSetupHook, timeoutMs: config.worktreeSetupHookTimeoutMs })
|
|
4905
4938
|
: undefined,
|
|
@@ -4954,7 +4987,9 @@ async function runSubagent(
|
|
|
4954
4987
|
}));
|
|
4955
4988
|
|
|
4956
4989
|
flushPendingStepSteers(flatIndex);
|
|
4957
|
-
const executionStep = singleWorktreeSetup
|
|
4990
|
+
const executionStep = singleWorktreeSetup
|
|
4991
|
+
? bindWorktreeCwd({ ...seqStep, cwd: singleCwd }, singleCwd)
|
|
4992
|
+
: seqStep;
|
|
4958
4993
|
let singleResult: Awaited<ReturnType<typeof runSingleStepWithTimeout>>;
|
|
4959
4994
|
try {
|
|
4960
4995
|
singleResult = await runSingleStepWithTimeout(executionStep, compactOptional<SingleStepContext>({
|
|
@@ -42,6 +42,15 @@ function errorMessage(error: unknown): string {
|
|
|
42
42
|
return error instanceof Error ? error.message : String(error);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
const STRUCTURED_OUTPUT_INLINE_LIMIT_BYTES = 4 * 1024;
|
|
46
|
+
|
|
47
|
+
export function projectStructuredOutput(value: unknown): unknown {
|
|
48
|
+
if (value === undefined) return undefined;
|
|
49
|
+
const serialized = JSON.stringify(value);
|
|
50
|
+
if (typeof serialized !== "string") throw new Error("Structured output must be JSON-serializable");
|
|
51
|
+
return Buffer.byteLength(serialized, "utf8") <= STRUCTURED_OUTPUT_INLINE_LIMIT_BYTES ? JSON.parse(serialized) : undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
45
54
|
/**
|
|
46
55
|
* Project a terminal result payload into the slim shape that is safe to surface in
|
|
47
56
|
* tool_result details: run identity, per-child outcome, and the artifact trail.
|
|
@@ -65,6 +74,8 @@ export function toWaitCompletion(data: Record<string, unknown>, runId: string):
|
|
|
65
74
|
const sessionFile = asNonEmptyString(child.sessionFile);
|
|
66
75
|
const error = asNonEmptyString(child.error);
|
|
67
76
|
const model = asNonEmptyString(child.model);
|
|
77
|
+
const structuredOutput = projectStructuredOutput(child.structuredOutput);
|
|
78
|
+
const structuredOutputPath = asNonEmptyString(child.structuredOutputPath);
|
|
68
79
|
const contextOverflow = child.contextOverflow === true;
|
|
69
80
|
const timeoutRecovery = projectTimeoutRecovery(child.timeoutRecovery);
|
|
70
81
|
return [{
|
|
@@ -74,6 +85,8 @@ export function toWaitCompletion(data: Record<string, unknown>, runId: string):
|
|
|
74
85
|
...(sessionFile ? { sessionFile } : {}),
|
|
75
86
|
...(typeof child.success === "boolean" ? { success: child.success } : {}),
|
|
76
87
|
...(outputState ? { outputState } : {}),
|
|
88
|
+
...(structuredOutput !== undefined ? { structuredOutput } : {}),
|
|
89
|
+
...(structuredOutputPath ? { structuredOutputPath } : {}),
|
|
77
90
|
...(error ? { error } : {}),
|
|
78
91
|
...(model ? { model } : {}),
|
|
79
92
|
...(contextOverflow ? { contextOverflow: true } : {}),
|
|
@@ -23,7 +23,7 @@ Ordinary async subagent runs already notify this session natively when they comp
|
|
|
23
23
|
• { stopOnAttention: false } — for blocking waits only, keep waiting through idle or long-thinking attention; supervisor/contact requests still stop the wait.
|
|
24
24
|
• { timeoutMs: 600000 } — stop waiting after N ms; active work keeps running. Omitted values use waitTool.defaultTimeoutMs, then 30 minutes. Window expiry returns a non-error window_elapsed result with active work identities.
|
|
25
25
|
|
|
26
|
-
Non-blocking subscriptions are visible in subagent status and differ from disabling waitTool: waitTool.enabled=false returns immediately without registering any future wake. Provider jobs are session-scoped and identified exactly, so replacing one job with another cannot hide a completion. Provider extensions must be explicitly loaded in this process. In a child agent, keep \`bg_wait\`
|
|
26
|
+
Non-blocking subscriptions are visible in subagent status and differ from disabling waitTool: waitTool.enabled=false returns immediately without registering any future wake. Provider jobs are session-scoped and identified exactly, so replacing one job with another cannot hide a completion. Provider extensions must be explicitly loaded in this process. In a child agent, keep \`bg_wait\` in the child tool allowlist and load each provider through the agent's extensions or subagentOnlyExtensions; this tool never loads providers or grants tools itself.${enabled ? "" : "\n\nConfigured behavior: bg_wait is disabled by config.waitTool or PI_SUBAGENT_WAIT_TOOL_ENABLED and returns immediately without blocking."}`;
|
|
27
27
|
const execute: ToolDefinition<typeof SubagentWaitParams, Details>["execute"] = async (_id, params, signal, onUpdate, ctx) => finalizeToolResult(await waitForSubagents(params, signal, {
|
|
28
28
|
state,
|
|
29
29
|
events: pi.events,
|
|
@@ -40,10 +40,4 @@ Non-blocking subscriptions are visible in subagent status and differ from disabl
|
|
|
40
40
|
execute,
|
|
41
41
|
};
|
|
42
42
|
pi.registerTool(primaryTool);
|
|
43
|
-
pi.registerTool({
|
|
44
|
-
...primaryTool,
|
|
45
|
-
name: "subagent_wait",
|
|
46
|
-
label: "Subagent Wait (deprecated)",
|
|
47
|
-
description: "Deprecated compatibility alias for `bg_wait`. Use `bg_wait` for background, provider, or detached work without a native completion notification. It has the same parameters and behavior.",
|
|
48
|
-
});
|
|
49
43
|
}
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
|
-
import { existsSync
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
7
7
|
import * as path from "node:path";
|
|
8
8
|
import type { Message } from "@earendil-works/pi-ai";
|
|
9
9
|
import { discoverAgents, formatUnknownAgentError, unknownAgentDiagnosticContext, type AgentConfig } from "../../agents/agents.ts";
|
|
10
10
|
import { appendAgentRefinementOverlay } from "../../agents/agent-refinements.ts";
|
|
11
|
-
import { alignForkedSessionCwd } from "../../shared/fork-
|
|
11
|
+
import { alignForkedSessionCwd } from "../../shared/fork-session-cwd.ts";
|
|
12
12
|
import {
|
|
13
13
|
ensureArtifactsDir,
|
|
14
14
|
formatOutputArtifactContent,
|
|
@@ -73,7 +73,7 @@ import { readRuntimeAcknowledgedExtensions } from "../shared/runtime-acknowledge
|
|
|
73
73
|
import { assertAgentAllowedByCapabilityCeiling, decodeSubagentCapabilityCeiling, intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV } from "../shared/capability-ceiling.ts";
|
|
74
74
|
import { resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
75
75
|
import { assertThinkingWithinCeiling, decodeThinkingCeiling, intersectThinkingCeilings, SUBAGENT_THINKING_CEILING_ENV } from "../../shared/thinking-ceiling.ts";
|
|
76
|
-
import { MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput, readStructuredOutputAcceptanceReport } from "../shared/structured-output.ts";
|
|
76
|
+
import { clearStructuredOutputCaptures, MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput, readStructuredOutputAcceptanceReport } from "../shared/structured-output.ts";
|
|
77
77
|
import { formatMidToolExitError, formatProcessSignalError, isOrdinaryToolForMidToolExit, isUnexplainedProcessSignal } from "../shared/process-signal.ts";
|
|
78
78
|
import { readChildToolDiagnosticError } from "../shared/tool-availability.ts";
|
|
79
79
|
import { buildTimeoutRecoverySummary, collectTrackedMutationEvidence, snapshotTrackedMutations } from "../shared/mutation-evidence.ts";
|
|
@@ -411,6 +411,7 @@ async function runSingleAttempt(
|
|
|
411
411
|
inheritSkills: agent.inheritSkills,
|
|
412
412
|
requireReadTool: Boolean(shared.resolvedSkillNames?.length),
|
|
413
413
|
tools: agent.tools,
|
|
414
|
+
excludeTools: agent.excludeTools,
|
|
414
415
|
allowNestedSubagents: agent.allowNestedSubagents,
|
|
415
416
|
extensions: agent.extensions,
|
|
416
417
|
subagentOnlyExtensions: agent.subagentOnlyExtensions,
|
|
@@ -464,6 +465,7 @@ async function runSingleAttempt(
|
|
|
464
465
|
cwd: options.cwd ?? runtimeCwd,
|
|
465
466
|
requireReadTool: Boolean(shared.resolvedSkillNames?.length),
|
|
466
467
|
structuredOutput: Boolean(options.structuredOutput),
|
|
468
|
+
excludeTools: agent.excludeTools,
|
|
467
469
|
fast: options.fast ?? agent.fast,
|
|
468
470
|
model: modelArg,
|
|
469
471
|
modelCandidates: shared.modelCandidates,
|
|
@@ -520,6 +522,7 @@ async function runSingleAttempt(
|
|
|
520
522
|
inheritSkills: agent.inheritSkills,
|
|
521
523
|
skills: shared.resolvedSkillNames ?? [],
|
|
522
524
|
tools: toolPlan.effectiveToolAllowlist,
|
|
525
|
+
...(toolPlan.excludeTools.length > 0 ? { excludeTools: toolPlan.excludeTools } : {}),
|
|
523
526
|
extensions: toolPlan.extensionArgs,
|
|
524
527
|
mcpDirectTools: toolPlan.effectiveMcpTools,
|
|
525
528
|
...(options.outputPath ? { outputPath: options.outputPath } : {}),
|
|
@@ -551,10 +554,14 @@ async function runSingleAttempt(
|
|
|
551
554
|
}, options.context);
|
|
552
555
|
const startTime = Date.now();
|
|
553
556
|
if (options.structuredOutput) {
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
557
|
+
const cleanupError = clearStructuredOutputCaptures(options.structuredOutput);
|
|
558
|
+
if (cleanupError) {
|
|
559
|
+
cleanupTempDir(tempDir);
|
|
560
|
+
result.exitCode = 1;
|
|
561
|
+
result.error = cleanupError;
|
|
562
|
+
result.finalOutput = cleanupError;
|
|
563
|
+
result.progressSummary = { toolCount: 0, tokens: 0, durationMs: Date.now() - startTime };
|
|
564
|
+
return result;
|
|
558
565
|
}
|
|
559
566
|
}
|
|
560
567
|
const controlConfig = options.controlConfig ?? DEFAULT_CONTROL_CONFIG;
|