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.
Files changed (36) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/docs/agents.md +5 -4
  3. package/docs/configuration.md +28 -2
  4. package/docs/models.md +5 -5
  5. package/docs/observability.md +4 -1
  6. package/docs/tool-reference.md +2 -2
  7. package/package.json +1 -1
  8. package/skills/pi-subagents/references/management-authoring-rpc.md +1 -1
  9. package/skills/pi-subagents/references/prompting-and-roles.md +3 -3
  10. package/src/agents/agent-management.ts +22 -4
  11. package/src/agents/agents.ts +107 -125
  12. package/src/api/shared-types.ts +3 -0
  13. package/src/extension/config.ts +20 -0
  14. package/src/inspectors/herdr/inspector-runner.ts +19 -13
  15. package/src/runs/background/active-async-capacity.ts +0 -1
  16. package/src/runs/background/async-execution.ts +53 -7
  17. package/src/runs/background/async-resume.ts +3 -0
  18. package/src/runs/background/async-status.ts +18 -2
  19. package/src/runs/background/notify.ts +13 -1
  20. package/src/runs/background/run-status.ts +22 -2
  21. package/src/runs/background/subagent-runner.ts +30 -2
  22. package/src/runs/background/wait-completions.ts +13 -0
  23. package/src/runs/foreground/subagent-executor.ts +35 -3
  24. package/src/runs/shared/acceptance.ts +15 -9
  25. package/src/runs/shared/lane-metadata.ts +24 -3
  26. package/src/runs/shared/parallel-handoff.ts +4 -0
  27. package/src/runs/shared/pi-args.ts +8 -2
  28. package/src/runs/shared/task-intent.ts +5 -2
  29. package/src/runs/shared/worktree.ts +467 -63
  30. package/src/shared/types.ts +29 -0
  31. package/src/shared/utils.ts +18 -7
  32. package/src/slash/subagents-admin.ts +24 -12
  33. package/src/tui/fleet-status.ts +61 -2
  34. package/src/tui/fleet.ts +12 -7
  35. package/src/tui/render.ts +222 -14
  36. 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: summary,
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) lines.push(...formatTimeoutRecoveryLines(child.timeoutRecovery, " "));
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 ? { ...seqStep, cwd: singleCwd } : seqStep;
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
- worktree: input.params.worktree === true,
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.flatMap((child) => (child.results ?? []) as SingleResult[]), ...(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 }),
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.flatMap((child) => (child.results ?? []) as SingleResult[]), ...(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 }),
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: "attested",
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
- ? inferred.level
444
- : (LEVEL_RANK[explicitLevel] >= LEVEL_RANK[inferred.level] ? explicitLevel : inferred.level);
445
- const evidence = unique([...(level === inferred.level ? inferred.evidence : requiredEvidenceForLevel(level)), ...(explicit.evidence ?? [])]);
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
- strippedText = strippedText.replace(new RegExp(NO_EDIT_PROHIBITION_PATTERN.source, NO_EDIT_PROHIBITION_PATTERN.flags), (match, object: string, offset: number, source: string) => {
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 };