pi-subagents 0.62.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 +28 -0
- package/docs/agents.md +5 -4
- package/docs/configuration.md +28 -2
- package/docs/models.md +5 -5
- package/docs/observability.md +4 -1
- package/docs/tool-reference.md +2 -2
- package/package.json +1 -1
- 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 +22 -4
- package/src/agents/agents.ts +107 -125
- package/src/api/shared-types.ts +3 -0
- package/src/extension/config.ts +20 -0
- package/src/inspectors/herdr/inspector-runner.ts +19 -13
- package/src/runs/background/active-async-capacity.ts +0 -1
- package/src/runs/background/async-execution.ts +53 -7
- package/src/runs/background/async-resume.ts +3 -0
- package/src/runs/background/async-status.ts +18 -2
- package/src/runs/background/notify.ts +13 -1
- package/src/runs/background/run-status.ts +22 -2
- package/src/runs/background/subagent-runner.ts +30 -2
- package/src/runs/background/wait-completions.ts +13 -0
- package/src/runs/foreground/subagent-executor.ts +35 -3
- package/src/runs/shared/acceptance.ts +15 -9
- package/src/runs/shared/lane-metadata.ts +24 -3
- package/src/runs/shared/parallel-handoff.ts +4 -0
- package/src/runs/shared/pi-args.ts +8 -2
- package/src/runs/shared/task-intent.ts +5 -2
- package/src/runs/shared/worktree.ts +467 -63
- package/src/shared/types.ts +29 -0
- package/src/shared/utils.ts +18 -7
- package/src/slash/subagents-admin.ts +24 -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
|
@@ -3,7 +3,7 @@ import * as path from "node:path";
|
|
|
3
3
|
import { formatDuration, formatModelThinking, formatTokens, shortenPath } from "../../shared/formatters.ts";
|
|
4
4
|
import { previewDisplayText } from "../../shared/display-text.ts";
|
|
5
5
|
import { formatActivityLabel, formatParallelOutcome } from "../../shared/status-format.ts";
|
|
6
|
-
import { type ActivityState, type AsyncJobStep, type AsyncParallelGroupStatus, type AsyncStatus, type CostSummary, type Details, type HostStepNodeV1, type HostStepState, type LaunchResolvedChildExtensionsV1, type RuntimeAcknowledgedChildExtensionsV1, type NestedRunSummary, type SteeringStatus, type SubagentRunMode, type TimeoutRecoveryProjection, type TokenUsage, type TurnBudgetState, type UsageBudgetState, type WorkflowPreflightV1, type WorkflowGraphSnapshot } from "../../shared/types.ts";
|
|
6
|
+
import { type ActivityState, type AsyncJobStep, type AsyncParallelGroupStatus, type AsyncStatus, type CostSummary, type Details, type HostStepNodeV1, type HostStepState, type LaunchResolvedChildExtensionsV1, type RuntimeAcknowledgedChildExtensionsV1, type NestedRunSummary, type SteeringStatus, type SubagentRunMode, type TimeoutRecoveryProjection, type TokenUsage, type TurnBudgetState, type UsageBudgetState, type WorktreeNaming, type WorkflowPreflightV1, type WorkflowGraphSnapshot } from "../../shared/types.ts";
|
|
7
7
|
import type { ResolvedSubagentCapabilityCeiling, SubagentCapabilityAudit } from "../shared/capability-ceiling.ts";
|
|
8
8
|
import { readStatus } from "../../shared/utils.ts";
|
|
9
9
|
import { attachRootChildrenToSteps, buildNestedRouteIndex, findNestedRouteForRootId, type NestedRoute, projectNestedEvents } from "../shared/nested-events.ts";
|
|
@@ -24,6 +24,7 @@ import { validateAsyncStatusLaneMetadata } from "../shared/lane-metadata.ts";
|
|
|
24
24
|
import { formatWorkflowPreflightPlanSummary, formatWorkflowPreflightWarningSummary } from "../../workflows/workflow-preflight.ts";
|
|
25
25
|
import { workflowGraphStageNodes } from "../shared/workflow-graph.ts";
|
|
26
26
|
import { formatTimeoutRecoveryLines, projectTimeoutRecovery } from "../shared/mutation-evidence.ts";
|
|
27
|
+
import { formatWorkflowChecklistText, projectWorkflowChecklist } from "../../workflows/workflow-checklist.ts";
|
|
27
28
|
|
|
28
29
|
interface AsyncRunStepSummary {
|
|
29
30
|
index: number;
|
|
@@ -39,6 +40,8 @@ interface AsyncRunStepSummary {
|
|
|
39
40
|
lane?: AsyncJobStep["lane"];
|
|
40
41
|
worktreePath?: string;
|
|
41
42
|
branch?: string;
|
|
43
|
+
provider?: "native" | "worktrunk";
|
|
44
|
+
naming?: WorktreeNaming;
|
|
42
45
|
runId?: string;
|
|
43
46
|
outputName?: string;
|
|
44
47
|
structured?: boolean;
|
|
@@ -337,6 +340,8 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
|
|
|
337
340
|
...(step.lane ? { lane: step.lane } : {}),
|
|
338
341
|
...(step.worktreePath ? { worktreePath: step.worktreePath } : {}),
|
|
339
342
|
...(step.branch ? { branch: step.branch } : {}),
|
|
343
|
+
...(step.provider ? { provider: step.provider } : {}),
|
|
344
|
+
...(step.naming ? { naming: step.naming } : {}),
|
|
340
345
|
...(step.runId ? { runId: step.runId } : {}),
|
|
341
346
|
...(step.outputName ? { outputName: step.outputName } : {}),
|
|
342
347
|
...(step.structured ? { structured: step.structured } : {}),
|
|
@@ -614,7 +619,7 @@ function formatStepLine(step: AsyncRunStepSummary): string {
|
|
|
614
619
|
if (step.durationMs !== undefined) parts.push(formatDuration(step.durationMs));
|
|
615
620
|
if (step.tokens) parts.push(`${formatTokens(step.tokens.total)} tok`);
|
|
616
621
|
if (step.lane) parts.push(`lane ${step.lane.key}`);
|
|
617
|
-
if (step.worktreePath) parts.push(`worktree ${shortenPath(step.worktreePath)} · branch ${step.branch ?? "unknown"}`);
|
|
622
|
+
if (step.worktreePath) parts.push(`worktree ${shortenPath(step.worktreePath)} · branch ${step.branch ?? "unknown"}${step.provider ? ` · provider ${step.provider}` : ""}`);
|
|
618
623
|
return parts.join(" | ");
|
|
619
624
|
}
|
|
620
625
|
|
|
@@ -707,6 +712,17 @@ export function formatAsyncRunList(runs: AsyncRunSummary[], heading = "Active as
|
|
|
707
712
|
if (run.preflight) lines.push(formatWorkflowPreflightPlanSummary(run.preflight, { indent: " " }));
|
|
708
713
|
const preflightWarning = formatWorkflowPreflightWarningSummary(run.workflow?.preflightWarnings, { indent: " " });
|
|
709
714
|
if (preflightWarning) lines.push(preflightWarning);
|
|
715
|
+
if (run.mode === "workflow") {
|
|
716
|
+
const checklist = projectWorkflowChecklist({
|
|
717
|
+
graph: run.workflowGraph,
|
|
718
|
+
steps: run.steps,
|
|
719
|
+
hostSteps: run.hostSteps,
|
|
720
|
+
preflight: run.preflight,
|
|
721
|
+
trace: run.workflow?.trace,
|
|
722
|
+
now: run.lastUpdate ?? run.endedAt ?? Date.now(),
|
|
723
|
+
});
|
|
724
|
+
lines.push(...formatWorkflowChecklistText(checklist, " ", { includeItems: false }));
|
|
725
|
+
}
|
|
710
726
|
for (const step of run.steps) {
|
|
711
727
|
lines.push(` ${formatStepLine(step)}`);
|
|
712
728
|
lines.push(...formatTimeoutRecoveryLines(step.timeoutRecovery, " "));
|
|
@@ -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 } : {}),
|
|
@@ -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);
|
|
@@ -124,6 +124,7 @@ import {
|
|
|
124
124
|
findWorktreeTaskCwdConflict,
|
|
125
125
|
formatWorktreeDiffSummary,
|
|
126
126
|
formatWorktreeTaskCwdConflict,
|
|
127
|
+
WORKTREE_AGENT_CWD_PLACEHOLDER,
|
|
127
128
|
type WorktreeSetup,
|
|
128
129
|
} from "../shared/worktree.ts";
|
|
129
130
|
import { findModelInfo, resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
@@ -185,6 +186,8 @@ interface SubagentRunConfig {
|
|
|
185
186
|
worktreeSetupHook?: string;
|
|
186
187
|
worktreeSetupHookTimeoutMs?: number;
|
|
187
188
|
worktreeBaseDir?: string;
|
|
189
|
+
worktreeProvider?: import("../../shared/types.ts").WorktreeProvider;
|
|
190
|
+
worktreeBranchPrefix?: string;
|
|
188
191
|
controlConfig?: ResolvedControlConfig;
|
|
189
192
|
controlIntercomTarget?: string;
|
|
190
193
|
childIntercomTargets?: Array<string | undefined>;
|
|
@@ -2339,6 +2342,8 @@ function requiredStatusStep(statusPayload: RunnerStatusPayload, index: number):
|
|
|
2339
2342
|
function setStatusWorktreeReference(statusStep: RunnerStatusStep, worktree: WorktreeSetup["worktrees"][number]): void {
|
|
2340
2343
|
statusStep.worktreePath = worktree.path;
|
|
2341
2344
|
statusStep.branch = worktree.branch;
|
|
2345
|
+
if (worktree.provider) statusStep.provider = worktree.provider;
|
|
2346
|
+
if (worktree.naming) statusStep.naming = worktree.naming;
|
|
2342
2347
|
}
|
|
2343
2348
|
|
|
2344
2349
|
function markParallelGroupSetupFailure(input: {
|
|
@@ -2419,6 +2424,18 @@ function markParallelGroupRunning(input: {
|
|
|
2419
2424
|
}));
|
|
2420
2425
|
}
|
|
2421
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
|
+
|
|
2422
2439
|
function prepareParallelTaskRun(
|
|
2423
2440
|
task: SubagentStep,
|
|
2424
2441
|
cwd: string,
|
|
@@ -2427,8 +2444,9 @@ function prepareParallelTaskRun(
|
|
|
2427
2444
|
): { taskForRun: SubagentStep; taskCwd: string } {
|
|
2428
2445
|
if (!worktreeSetup) return { taskForRun: task, taskCwd: cwd };
|
|
2429
2446
|
const { cwd: _taskCwd, ...taskForRun } = task;
|
|
2447
|
+
const boundTask = bindWorktreeCwd(taskForRun, worktreeSetup.worktrees[taskIndex]!.agentCwd);
|
|
2430
2448
|
return {
|
|
2431
|
-
taskForRun,
|
|
2449
|
+
taskForRun: boundTask,
|
|
2432
2450
|
taskCwd: worktreeSetup.worktrees[taskIndex]!.agentCwd,
|
|
2433
2451
|
};
|
|
2434
2452
|
}
|
|
@@ -4475,6 +4493,10 @@ async function runSubagent(
|
|
|
4475
4493
|
try {
|
|
4476
4494
|
worktreeSetup = createWorktrees(cwd, `${id}-s${stepIndex}`, group.parallel.length, omitUndefinedProperties({
|
|
4477
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,
|
|
4478
4500
|
setupHook: config.worktreeSetupHook
|
|
4479
4501
|
? omitUndefinedProperties({ hookPath: config.worktreeSetupHook, timeoutMs: config.worktreeSetupHookTimeoutMs })
|
|
4480
4502
|
: undefined,
|
|
@@ -4907,6 +4929,10 @@ async function runSubagent(
|
|
|
4907
4929
|
try {
|
|
4908
4930
|
singleWorktreeSetup = createWorktrees(cwd, `${id}-s${stepIndex}`, 1, omitUndefinedProperties({
|
|
4909
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,
|
|
4910
4936
|
setupHook: config.worktreeSetupHook
|
|
4911
4937
|
? omitUndefinedProperties({ hookPath: config.worktreeSetupHook, timeoutMs: config.worktreeSetupHookTimeoutMs })
|
|
4912
4938
|
: undefined,
|
|
@@ -4961,7 +4987,9 @@ async function runSubagent(
|
|
|
4961
4987
|
}));
|
|
4962
4988
|
|
|
4963
4989
|
flushPendingStepSteers(flatIndex);
|
|
4964
|
-
const executionStep = singleWorktreeSetup
|
|
4990
|
+
const executionStep = singleWorktreeSetup
|
|
4991
|
+
? bindWorktreeCwd({ ...seqStep, cwd: singleCwd }, singleCwd)
|
|
4992
|
+
: seqStep;
|
|
4965
4993
|
let singleResult: Awaited<ReturnType<typeof runSingleStepWithTimeout>>;
|
|
4966
4994
|
try {
|
|
4967
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 } : {}),
|
|
@@ -1314,6 +1314,9 @@ function appendStepToAsyncChain(input: {
|
|
|
1314
1314
|
waitToolEnabled: input.deps.waitToolEnabled,
|
|
1315
1315
|
waitToolDefaultTimeoutMs: input.deps.waitToolDefaultTimeoutMs,
|
|
1316
1316
|
contextForAgent: contextPolicy.contextForAgent,
|
|
1317
|
+
worktreeBaseDir: input.deps.config.worktreeBaseDir,
|
|
1318
|
+
worktreeProvider: input.deps.config.worktreeProvider,
|
|
1319
|
+
worktreeBranchPrefix: input.deps.config.worktreeBranchPrefix,
|
|
1317
1320
|
asyncDir: resolved.location.asyncDir,
|
|
1318
1321
|
validateOutputBindings: false,
|
|
1319
1322
|
capabilityCeiling: intersectSubagentCapabilityCeilings(status.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(asyncCtx.currentSessionId)),
|
|
@@ -1687,6 +1690,8 @@ async function resumeExternalJobFollowUp(input: {
|
|
|
1687
1690
|
worktreeSetupHook: input.deps.config.worktreeSetupHook,
|
|
1688
1691
|
worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
|
|
1689
1692
|
worktreeBaseDir: input.deps.config.worktreeBaseDir,
|
|
1693
|
+
worktreeProvider: input.deps.config.worktreeProvider,
|
|
1694
|
+
worktreeBranchPrefix: input.deps.config.worktreeBranchPrefix,
|
|
1690
1695
|
controlConfig: resolveControlConfig(input.deps.config.control, undefined),
|
|
1691
1696
|
controlIntercomTarget: input.intercomBridge.active ? input.intercomBridge.orchestratorTarget : undefined,
|
|
1692
1697
|
childIntercomTarget: input.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
@@ -1943,6 +1948,8 @@ async function resumeAsyncRun(input: {
|
|
|
1943
1948
|
worktreeSetupHook: input.deps.config.worktreeSetupHook,
|
|
1944
1949
|
worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
|
|
1945
1950
|
worktreeBaseDir: input.deps.config.worktreeBaseDir,
|
|
1951
|
+
worktreeProvider: input.deps.config.worktreeProvider,
|
|
1952
|
+
worktreeBranchPrefix: input.deps.config.worktreeBranchPrefix,
|
|
1946
1953
|
controlConfig: resolveControlConfig(input.deps.config.control, input.params.control),
|
|
1947
1954
|
controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
|
|
1948
1955
|
childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
@@ -2056,7 +2063,11 @@ async function resumeAsyncRun(input: {
|
|
|
2056
2063
|
worktreeSetupHook: input.deps.config.worktreeSetupHook,
|
|
2057
2064
|
worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
|
|
2058
2065
|
worktreeBaseDir: input.deps.config.worktreeBaseDir,
|
|
2059
|
-
|
|
2066
|
+
worktreeProvider: input.deps.config.worktreeProvider,
|
|
2067
|
+
worktreeBranchPrefix: input.deps.config.worktreeBranchPrefix,
|
|
2068
|
+
// A retained async child already owns the recorded worktree. Resume it in
|
|
2069
|
+
// place rather than allocating a second provider worktree around it.
|
|
2070
|
+
worktree: input.params.worktree === true && !("managedWorktree" in target && target.managedWorktree === true),
|
|
2060
2071
|
lane: input.params.lane ?? recoveryDescriptor?.lane,
|
|
2061
2072
|
controlConfig: recoveryDescriptor?.controlConfig ?? resolveControlConfig(input.deps.config.control, input.params.control),
|
|
2062
2073
|
intercomBridge: input.params.intercomBridge ?? recoveryDescriptor?.intercomBridge,
|
|
@@ -3255,6 +3266,8 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
3255
3266
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
3256
3267
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
3257
3268
|
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
3269
|
+
worktreeProvider: deps.config.worktreeProvider,
|
|
3270
|
+
worktreeBranchPrefix: deps.config.worktreeBranchPrefix,
|
|
3258
3271
|
controlConfig,
|
|
3259
3272
|
intercomBridge: params.intercomBridge,
|
|
3260
3273
|
controlIntercomTarget,
|
|
@@ -3291,6 +3304,10 @@ function createSingleWorktreeSetup(
|
|
|
3291
3304
|
setupHook: ExtensionConfig["worktreeSetupHook"],
|
|
3292
3305
|
setupHookTimeoutMs: ExtensionConfig["worktreeSetupHookTimeoutMs"],
|
|
3293
3306
|
baseDir: ExtensionConfig["worktreeBaseDir"],
|
|
3307
|
+
provider: ExtensionConfig["worktreeProvider"],
|
|
3308
|
+
branchPrefix: ExtensionConfig["worktreeBranchPrefix"],
|
|
3309
|
+
label?: string,
|
|
3310
|
+
task?: string,
|
|
3294
3311
|
beforeCreate?: (setup: WorktreeSetup) => void,
|
|
3295
3312
|
): { setup?: WorktreeSetup; errorResult?: AgentToolResult<Details> } {
|
|
3296
3313
|
if (!enabled) return {};
|
|
@@ -3302,6 +3319,10 @@ function createSingleWorktreeSetup(
|
|
|
3302
3319
|
? { hookPath: setupHook, ...(setupHookTimeoutMs === undefined ? {} : { timeoutMs: setupHookTimeoutMs }) }
|
|
3303
3320
|
: undefined,
|
|
3304
3321
|
baseDir,
|
|
3322
|
+
provider,
|
|
3323
|
+
branchPrefix,
|
|
3324
|
+
labels: [label],
|
|
3325
|
+
tasks: [task],
|
|
3305
3326
|
beforeCreate,
|
|
3306
3327
|
})),
|
|
3307
3328
|
};
|
|
@@ -3662,6 +3683,10 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3662
3683
|
deps.config.worktreeSetupHook,
|
|
3663
3684
|
deps.config.worktreeSetupHookTimeoutMs,
|
|
3664
3685
|
deps.config.worktreeBaseDir,
|
|
3686
|
+
deps.config.worktreeProvider,
|
|
3687
|
+
deps.config.worktreeBranchPrefix,
|
|
3688
|
+
params.lane?.key ?? params.workflowKey,
|
|
3689
|
+
task,
|
|
3665
3690
|
(plannedSetup) => {
|
|
3666
3691
|
pendingHandoff = writePendingParallelHandoff({
|
|
3667
3692
|
manifestPath: parallelHandoffPath(artifactsDir, runId),
|
|
@@ -4164,6 +4189,10 @@ function workflowOutputPathMappingSummary(children: WorkflowScriptChildResult[])
|
|
|
4164
4189
|
return mappings.length > 0 ? ` Output path mappings: ${mappings.join("; ")}.` : "";
|
|
4165
4190
|
}
|
|
4166
4191
|
|
|
4192
|
+
function workflowDetailsResults(children: WorkflowScriptChildResult[]): SingleResult[] {
|
|
4193
|
+
return children.flatMap((child) => (child.results ?? []).map((result) => result.workflowKey ? result : { ...result, workflowKey: child.key }));
|
|
4194
|
+
}
|
|
4195
|
+
|
|
4167
4196
|
function workflowSteerReceipt(key: string, result: AgentToolResult<Details>): WorkflowSteerResult {
|
|
4168
4197
|
const steering = result.details.steering;
|
|
4169
4198
|
const error = result.content.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("\n") || undefined;
|
|
@@ -5519,7 +5548,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5519
5548
|
const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
|
|
5520
5549
|
return attachWorkflowMission(withRunFanoutBudget({
|
|
5521
5550
|
content: [{ type: "text", text: displayText }],
|
|
5522
|
-
details: compactOptional<Details>({ mode: "workflow", runId: _id, results: workflow.children
|
|
5551
|
+
details: compactOptional<Details>({ mode: "workflow", runId: _id, results: workflowDetailsResults(workflow.children), ...(workflowPreflight ? { preflight: workflowPreflight } : {}), workflowChildren, totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { value: workflow.value, trace: finalPreflightTrace, emits: workflow.emits, console: workflow.console, ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}), receipt }, chatProgress }),
|
|
5523
5552
|
}, workflowFanoutBudget));
|
|
5524
5553
|
} catch (error) {
|
|
5525
5554
|
const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
|
|
@@ -5546,7 +5575,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5546
5575
|
return attachWorkflowMission(withRunFanoutBudget({
|
|
5547
5576
|
content: [{ type: "text", text: displayText }],
|
|
5548
5577
|
isError: true,
|
|
5549
|
-
details: compactOptional<Details>({ mode: "workflow", runId: _id, results: partial.children
|
|
5578
|
+
details: compactOptional<Details>({ mode: "workflow", runId: _id, results: workflowDetailsResults(partial.children), ...(workflowPreflight ? { preflight: workflowPreflight } : {}), workflowChildren, totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { trace: finalPreflightTrace, emits: partial.emits, console: partial.console, ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}), receipt }, chatProgress }),
|
|
5550
5579
|
}, workflowFanoutBudget));
|
|
5551
5580
|
}
|
|
5552
5581
|
}
|
|
@@ -6245,6 +6274,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6245
6274
|
const canonicalParams = canonicalizeExecutionParams(effectiveParams, discoveredAgents, discovered.agentDiagnostics, unknownAgentDiagnosticContext);
|
|
6246
6275
|
if (canonicalParams.error) return buildRequestedModeError(effectiveParams, canonicalParams.error);
|
|
6247
6276
|
effectiveParams = canonicalParams.params!;
|
|
6277
|
+
if (effectiveParams.worktree === undefined && deps.config.worktree !== undefined) {
|
|
6278
|
+
effectiveParams = { ...effectiveParams, worktree: deps.config.worktree };
|
|
6279
|
+
}
|
|
6248
6280
|
const modelScope = discovered.modelScope;
|
|
6249
6281
|
effectiveParams = applySingleAgentLaunchDefaults(effectiveParams, discoveredAgents);
|
|
6250
6282
|
// An agent-level defaultContext is a preference, unlike an explicit request.
|
|
@@ -100,12 +100,13 @@ function inferLevel(input: {
|
|
|
100
100
|
const writeTask = taskMayWrite
|
|
101
101
|
|| (input.acceptanceRole === "writer" && !readOnlyTask)
|
|
102
102
|
|| (input.acceptanceRole === undefined && /\bworker\b/.test(agent) && !readOnlyTask);
|
|
103
|
-
const inferredReadOnly = readOnlyTask || (input.acceptanceRole === "read-only" && !taskMayWrite);
|
|
103
|
+
const inferredReadOnly = readOnlyTask || ((readOnlyAgent || input.acceptanceRole === "read-only") && !taskMayWrite);
|
|
104
104
|
const roleResolvesReadOnly = input.acceptanceRole !== undefined && inferredReadOnly;
|
|
105
|
+
const dynamicResolvesReadOnly = inferredReadOnly && !writeTask;
|
|
105
106
|
const keywordRiskReadOnly = input.acceptanceRole === undefined ? intent.kind === "read-only" : inferredReadOnly;
|
|
106
107
|
const risky = Boolean(input.async && writeTask)
|
|
107
|
-
|| (Boolean(input.dynamic) && !roleResolvesReadOnly)
|
|
108
|
-
|| (Boolean(input.dynamicGroup) && !roleResolvesReadOnly)
|
|
108
|
+
|| (Boolean(input.dynamic) && !roleResolvesReadOnly && !dynamicResolvesReadOnly)
|
|
109
|
+
|| (Boolean(input.dynamicGroup) && !roleResolvesReadOnly && !dynamicResolvesReadOnly)
|
|
109
110
|
|| (!keywordRiskReadOnly && /\b(?:release|migration|migrate|security|data[- ]loss|destructive|post-review|fix pass)\b/.test(task));
|
|
110
111
|
|
|
111
112
|
if (risky) {
|
|
@@ -131,7 +132,7 @@ function inferLevel(input: {
|
|
|
131
132
|
if (readOnlyAgent || readOnlyTask) {
|
|
132
133
|
reasons.push(input.acceptanceRole === "read-only" && !readOnlyTask ? "declared read-only acceptance role" : readOnlyAgent ? "read-only/reviewer-style agent" : "read-only task wording");
|
|
133
134
|
return {
|
|
134
|
-
level: "
|
|
135
|
+
level: "none",
|
|
135
136
|
reasons,
|
|
136
137
|
criteria: ["Return concrete findings with file paths and severity when applicable"],
|
|
137
138
|
evidence: ["review-findings", "residual-risks"],
|
|
@@ -207,6 +208,10 @@ function explicitAcceptanceCanDisable(explicit: AcceptanceConfig): boolean {
|
|
|
207
208
|
return explicit.level === "none" && typeof explicit.reason === "string" && explicit.reason.trim().length > 0;
|
|
208
209
|
}
|
|
209
210
|
|
|
211
|
+
function explicitAcceptanceRequestsPolicy(explicit: AcceptanceConfig): boolean {
|
|
212
|
+
return (explicit.level !== undefined && explicit.level !== "auto") || Object.keys(explicit).some((key) => key !== "level");
|
|
213
|
+
}
|
|
214
|
+
|
|
210
215
|
function unsupportedEvidenceKindMessage(pathLabel: string, item: unknown): string {
|
|
211
216
|
const value = typeof item === "string" ? ` "${item}"` : "";
|
|
212
217
|
return `${pathLabel}${value} is not a supported evidence kind. ${ACCEPTANCE_EVIDENCE_HELP}`;
|
|
@@ -437,12 +442,13 @@ export function resolveEffectiveAcceptance(input: {
|
|
|
437
442
|
};
|
|
438
443
|
}
|
|
439
444
|
const inferred = inferLevel(input);
|
|
445
|
+
const inferredLevel = inferred.level === "none" && explicitAcceptanceRequestsPolicy(explicit) ? "attested" : inferred.level;
|
|
440
446
|
const level = explicitAcceptanceCanDisable(explicit)
|
|
441
447
|
? "none"
|
|
442
448
|
: explicitLevel === "auto"
|
|
443
|
-
?
|
|
444
|
-
: (LEVEL_RANK[explicitLevel] >= LEVEL_RANK[
|
|
445
|
-
const evidence = unique([...(level ===
|
|
449
|
+
? inferredLevel
|
|
450
|
+
: (LEVEL_RANK[explicitLevel] >= LEVEL_RANK[inferredLevel] ? explicitLevel : inferredLevel);
|
|
451
|
+
const evidence = unique([...(level === inferredLevel ? inferred.evidence : requiredEvidenceForLevel(level)), ...(explicit.evidence ?? [])]);
|
|
446
452
|
const criteria = normalizeCriteria(
|
|
447
453
|
(explicit.criteria?.length ? explicit.criteria : inferred.criteria) as Array<string | { id?: string; must?: string; evidence?: AcceptanceEvidenceKind[]; severity?: "required" | "recommended" }>,
|
|
448
454
|
evidence,
|
|
@@ -452,8 +458,8 @@ export function resolveEffectiveAcceptance(input: {
|
|
|
452
458
|
level,
|
|
453
459
|
explicit: input.explicit !== undefined,
|
|
454
460
|
inferredReason: inferred.reasons,
|
|
455
|
-
criteria,
|
|
456
|
-
evidence,
|
|
461
|
+
criteria: level === "none" ? [] : criteria,
|
|
462
|
+
evidence: level === "none" ? [] : evidence,
|
|
457
463
|
verify: explicit.verify ?? [],
|
|
458
464
|
review,
|
|
459
465
|
stopRules: explicit.stopRules ?? [],
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AsyncStatus, WorkflowLaneMetadata, WorkflowLaneMode } from "../../shared/types.ts";
|
|
1
|
+
import type { AsyncStatus, ManagedWorktreeProvider, WorktreeNaming, WorkflowLaneMetadata, WorkflowLaneMode } from "../../shared/types.ts";
|
|
2
2
|
|
|
3
3
|
export const WORKFLOW_LANE_KEY_MAX_BYTES = 128;
|
|
4
4
|
export const WORKFLOW_LANE_SOURCE_REF_MAX_BYTES = 128;
|
|
@@ -8,6 +8,7 @@ export const WORKFLOW_LANE_OUTPUT_PATH_MAX_BYTES = 256;
|
|
|
8
8
|
export const WORKFLOW_LANE_OUTPUT_PATHS_MAX = 10;
|
|
9
9
|
export const WORKTREE_STATUS_PATH_MAX_BYTES = 4096;
|
|
10
10
|
export const WORKTREE_STATUS_BRANCH_MAX_BYTES = 256;
|
|
11
|
+
export const WORKTREE_STATUS_NAMING_LABEL_MAX_BYTES = 256;
|
|
11
12
|
|
|
12
13
|
const WORKFLOW_LANE_KEY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
13
14
|
const WORKFLOW_LANE_MODES = new Set<WorkflowLaneMode>(["mutation", "review", "scout", "gate"]);
|
|
@@ -75,16 +76,36 @@ export function assertWorkflowLaneKey(lane: WorkflowLaneMetadata | undefined, wo
|
|
|
75
76
|
export interface WorktreeStatusReference {
|
|
76
77
|
worktreePath: string;
|
|
77
78
|
branch: string;
|
|
79
|
+
provider?: ManagedWorktreeProvider;
|
|
80
|
+
naming?: WorktreeNaming;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function normalizeWorktreeNaming(value: unknown, label: string): WorktreeNaming {
|
|
84
|
+
assertPlainObject(value, label);
|
|
85
|
+
assertKnownFields(value, ["requestedBranch", "branchPrefix", "label", "sanitizedPathComponent", "collision", "collisionSuffix"], label);
|
|
86
|
+
const collision = value.collision;
|
|
87
|
+
if (collision !== undefined && collision !== "branch" && collision !== "path" && collision !== "both") throw new Error(`${label}.collision is invalid.`);
|
|
88
|
+
return {
|
|
89
|
+
requestedBranch: boundedNonEmptyString(value.requestedBranch, `${label}.requestedBranch`, WORKTREE_STATUS_BRANCH_MAX_BYTES),
|
|
90
|
+
branchPrefix: boundedNonEmptyString(value.branchPrefix, `${label}.branchPrefix`, WORKTREE_STATUS_BRANCH_MAX_BYTES),
|
|
91
|
+
label: boundedNonEmptyString(value.label, `${label}.label`, WORKTREE_STATUS_NAMING_LABEL_MAX_BYTES),
|
|
92
|
+
sanitizedPathComponent: boundedNonEmptyString(value.sanitizedPathComponent, `${label}.sanitizedPathComponent`, WORKTREE_STATUS_NAMING_LABEL_MAX_BYTES),
|
|
93
|
+
...(collision !== undefined ? { collision } : {}),
|
|
94
|
+
...(value.collisionSuffix !== undefined ? { collisionSuffix: boundedNonEmptyString(value.collisionSuffix, `${label}.collisionSuffix`, WORKTREE_STATUS_NAMING_LABEL_MAX_BYTES) } : {}),
|
|
95
|
+
};
|
|
78
96
|
}
|
|
79
97
|
|
|
80
98
|
/** Validate the display-only worktree fields copied into status.json. */
|
|
81
99
|
export function normalizeWorktreeStatusReference(value: unknown, label = "worktree status reference"): WorktreeStatusReference | undefined {
|
|
82
100
|
if (value === undefined) return undefined;
|
|
83
101
|
assertPlainObject(value, label);
|
|
84
|
-
assertKnownFields(value, ["worktreePath", "branch"], label);
|
|
102
|
+
assertKnownFields(value, ["worktreePath", "branch", "provider", "naming"], label);
|
|
103
|
+
if (value.provider !== undefined && value.provider !== "native" && value.provider !== "worktrunk") throw new Error(`${label}.provider is invalid.`);
|
|
85
104
|
return {
|
|
86
105
|
worktreePath: boundedNonEmptyString(value.worktreePath, `${label}.worktreePath`, WORKTREE_STATUS_PATH_MAX_BYTES),
|
|
87
106
|
branch: boundedNonEmptyString(value.branch, `${label}.branch`, WORKTREE_STATUS_BRANCH_MAX_BYTES),
|
|
107
|
+
...(value.provider !== undefined ? { provider: value.provider as ManagedWorktreeProvider } : {}),
|
|
108
|
+
...(value.naming !== undefined ? { naming: normalizeWorktreeNaming(value.naming, `${label}.naming`) } : {}),
|
|
88
109
|
};
|
|
89
110
|
}
|
|
90
111
|
|
|
@@ -100,6 +121,6 @@ export function validateAsyncStatusLaneMetadata(status: Pick<AsyncStatus, "runId
|
|
|
100
121
|
const hasPath = step.worktreePath !== undefined;
|
|
101
122
|
const hasBranch = step.branch !== undefined;
|
|
102
123
|
if (hasPath !== hasBranch) throw new Error(`${label}.steps[${index}] must include both worktreePath and branch.`);
|
|
103
|
-
if (hasPath) normalizeWorktreeStatusReference({ worktreePath: step.worktreePath, branch: step.branch }, `${label}.steps[${index}]`);
|
|
124
|
+
if (hasPath) normalizeWorktreeStatusReference({ worktreePath: step.worktreePath, branch: step.branch, ...(step.provider ? { provider: step.provider } : {}), ...(step.naming ? { naming: step.naming } : {}) }, `${label}.steps[${index}]`);
|
|
104
125
|
}
|
|
105
126
|
}
|
|
@@ -574,6 +574,8 @@ export function writeParallelHandoffGroup(input: {
|
|
|
574
574
|
index: worktree.index,
|
|
575
575
|
path: worktree.path,
|
|
576
576
|
branch: worktree.branch,
|
|
577
|
+
...(worktree.provider ? { provider: worktree.provider } : {}),
|
|
578
|
+
...(worktree.naming ? { naming: worktree.naming } : {}),
|
|
577
579
|
worktreeRemoved: false,
|
|
578
580
|
branchRemoved: false,
|
|
579
581
|
preserved: true,
|
|
@@ -653,6 +655,8 @@ export function discardPreservedWorktrees(
|
|
|
653
655
|
path: task.path,
|
|
654
656
|
agentCwd: task.path,
|
|
655
657
|
branch: task.branch,
|
|
658
|
+
...(task.provider ? { provider: task.provider } : {}),
|
|
659
|
+
...(task.naming ? { naming: task.naming } : {}),
|
|
656
660
|
index: task.index,
|
|
657
661
|
nodeModulesLinked: false,
|
|
658
662
|
syntheticPaths: [],
|
|
@@ -93,11 +93,12 @@ export function resolveSubagentTaskDelivery(
|
|
|
93
93
|
: "auto";
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
function shouldDeliverTaskViaFile(
|
|
96
|
+
export function shouldDeliverTaskViaFile(
|
|
97
97
|
task: string,
|
|
98
98
|
delivery: SubagentTaskDelivery,
|
|
99
|
+
platform: NodeJS.Platform = process.platform,
|
|
99
100
|
): boolean {
|
|
100
|
-
return delivery === "file" || task.length > TASK_ARG_LIMIT;
|
|
101
|
+
return delivery === "file" || platform === "darwin" || task.length > TASK_ARG_LIMIT;
|
|
101
102
|
}
|
|
102
103
|
const MAX_LAUNCH_RESOLVED_EXTENSION_IDS = 32;
|
|
103
104
|
const PROMPT_RUNTIME_EXTENSION_PATH = path.join(
|
|
@@ -845,6 +846,11 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
|
|
|
845
846
|
);
|
|
846
847
|
env[RUNTIME_EXTENSION_ACK_PATH_ENV] = runtimeAcknowledgedExtensionsPath;
|
|
847
848
|
let toolDiagnosticPath: string | undefined;
|
|
849
|
+
// Child launch environments are merged over process.env. Explicitly clear
|
|
850
|
+
// parent-scoped diagnostics so a nested zero-tool child cannot validate
|
|
851
|
+
// against, or overwrite, its parent's required-tool report.
|
|
852
|
+
env[REQUIRED_CHILD_TOOLS_ENV] = undefined;
|
|
853
|
+
env[CHILD_TOOL_DIAGNOSTIC_PATH_ENV] = undefined;
|
|
848
854
|
if (toolPlan.requiredChildTools.length > 0) {
|
|
849
855
|
if (!tempDir)
|
|
850
856
|
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
|
|
@@ -41,6 +41,7 @@ const REVIEWER_REQUIRED_EDIT_PATTERNS = [
|
|
|
41
41
|
// Accept serialized line separators too: workflow prompts can carry literal
|
|
42
42
|
// `\\n`/`\\r\\n` between clauses instead of decoded newlines.
|
|
43
43
|
const NO_EDIT_PROHIBITION_PATTERN = /(?:\b|\\(?:r\\n|n))(?:do not|don't|must not)\s+(?:edit|modify|write(?:\s+to)?|touch|change)\b((?:(?!\b(?:but|and|then)\b|\\(?:r\\n|n))[^.;,:!?\n–—-])*)/gi;
|
|
44
|
+
const COORDINATED_NO_EDIT_PROHIBITION_PATTERN = /(?:\b|\\(?:r\\n|n))(?:do not|don't|must not)\s+((?=(?:(?!\\(?:r\\n|n))[^.;:!?\n–—-])*\b(?:and|or)\s+(?:edit|modify|write(?:\s+to)?|touch|change)\b)(?:(?!\\(?:r\\n|n))[^.;:!?\n–—-])*?\b(?:and|or)\s+(?:edit|modify|write(?:\s+to)?|touch|change)\b(?:(?!\b(?:but|and|then)\b|\\(?:r\\n|n))[^.;,:!?\n–—-])*)/gi;
|
|
44
45
|
|
|
45
46
|
/** Objects of a no-edit prohibition that mean "the codebase in general" rather than a named scope. */
|
|
46
47
|
const GENERIC_PROHIBITION_OBJECT = /^\s*(?:(?:any|all|the|these|those|your|our|existing|project|product|source|sources|config|configs|repo|repository)[\s/,-]*)*(?:files?|code|codebase|sources?|anything|repo(?:sitory)?)?\s*$/i;
|
|
@@ -144,11 +145,13 @@ function analyzeNoEditProhibitions(taskText: string): NoEditProhibitionAnalysis
|
|
|
144
145
|
|| NO_TOOL_INTENT_PATTERNS.some((pattern) => pattern.test(taskText));
|
|
145
146
|
let blanket = present;
|
|
146
147
|
let strippedText = stripPatterns(taskText, [...REVIEW_ONLY_PATTERNS, ...NO_TOOL_INTENT_PATTERNS]);
|
|
147
|
-
|
|
148
|
+
const stripNoEditProhibition = (match: string, object: string, offset: number, source: string): string => {
|
|
148
149
|
present = true;
|
|
149
150
|
if (GENERIC_PROHIBITION_OBJECT.test(object) && !hasScopedProhibitionContinuation(source.slice(offset + match.length))) blanket = true;
|
|
150
151
|
return " ";
|
|
151
|
-
}
|
|
152
|
+
};
|
|
153
|
+
strippedText = strippedText.replace(new RegExp(COORDINATED_NO_EDIT_PROHIBITION_PATTERN.source, COORDINATED_NO_EDIT_PROHIBITION_PATTERN.flags), stripNoEditProhibition);
|
|
154
|
+
strippedText = strippedText.replace(new RegExp(NO_EDIT_PROHIBITION_PATTERN.source, NO_EDIT_PROHIBITION_PATTERN.flags), stripNoEditProhibition);
|
|
152
155
|
// Restore boundaries after stripping a prohibition from serialized prompts.
|
|
153
156
|
strippedText = strippedText.replace(/\\(?:r\\n|n)/g, "\n");
|
|
154
157
|
return { present, blanket, strippedText };
|