pi-cohort 7.3.1 → 7.4.1

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 CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [7.4.1] - 2026-10-19
4
+
5
+ ### Fixed
6
+ - Non-BLOCKED file-only failures and save errors return an artifact pointer or an explicit not-preserved reason instead of the child reply inline; BLOCKED handoffs and inline mode are unchanged. (#24)
7
+
8
+ ## [7.4.0] - 2026-10-19
9
+
10
+ ### Added
11
+ - Chains can use `{outputs.name.treePath}` in a later child's `cwd` to review a worktree task's base commit plus captured patch in a lazy detached checkout. Trees are shared per named output and cleaned up at chain end; only `treePath` output references are allowed in `cwd`, and not on `worktree: true` steps.
12
+
13
+ ### Fixed
14
+ - Children with both acceptance and `outputSchema` are instructed to write their acceptance report before calling `structured_output`, so both contracts can be satisfied.
15
+
3
16
  ## [7.3.1] - 2026-10-18
4
17
 
5
18
  ### Fixed
package/README.md CHANGED
@@ -126,8 +126,8 @@ subagent({ async: false, chain: [
126
126
  { agent: "implementer", as: "impl_2", task: "Implement task 2", outputSchema: statusSchema },
127
127
  ], worktree: true, stopOn: { path: "/status", in: ["NEEDS_CONTEXT", "BLOCKED"] } },
128
128
  { parallel: [
129
- { agent: "spec-reviewer", as: "sr_1", task: "Review {outputs.impl_1.patchPath}", outputSchema: verdictSchema },
130
- { agent: "spec-reviewer", as: "sr_2", task: "Review {outputs.impl_2.patchPath}", outputSchema: verdictSchema },
129
+ { agent: "spec-reviewer", as: "sr_1", cwd: "{outputs.impl_1.treePath}", acceptance: { level: "attested" }, task: "Review {outputs.impl_1.patchPath}", outputSchema: verdictSchema },
130
+ { agent: "spec-reviewer", as: "sr_2", cwd: "{outputs.impl_2.treePath}", acceptance: { level: "attested" }, task: "Review {outputs.impl_2.patchPath}", outputSchema: verdictSchema },
131
131
  ], stopOn: { path: "/verdict", in: ["FAIL"] } },
132
132
  ]})
133
133
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-cohort",
3
- "version": "7.3.1",
3
+ "version": "7.4.1",
4
4
  "description": "Delegate Pi work to focused child agents: code review, scouting, implementation, parallel audits, saved chains, and background jobs.",
5
5
  "author": "Jacek Juraszek",
6
6
  "license": "MIT",
@@ -446,13 +446,16 @@ export function executeAsyncChain(
446
446
  ...(s.model ? { model: s.model } : {}),
447
447
  };
448
448
  };
449
- const buildSeqStep = (s: SequentialStep, sessionFile?: string, behaviorCwd?: string, progressPrecreated = false, resolvedBehavior?: ResolvedStepBehavior, isolatedLeaf?: { runDir: string; index: number }) => {
449
+ const buildSeqStep = (s: SequentialStep, sessionFile?: string, behaviorCwd?: string, progressPrecreated = false, resolvedBehavior?: ResolvedStepBehavior, isolatedLeaf?: { runDir: string; index: number }, groupCwd?: string) => {
450
450
  const a = agents.find((x) => x.name === s.agent)!;
451
- const stepCwd = resolveChildCwd(runnerCwd, s.cwd);
452
- const instructionCwd = behaviorCwd ?? stepCwd;
451
+ const effectiveCwd = s.cwd ?? groupCwd;
452
+ const templatedCwd = effectiveCwd?.includes("{outputs.");
453
+ const stepCwd = templatedCwd ? effectiveCwd : resolveChildCwd(runnerCwd, effectiveCwd);
454
+ const preparationCwd = templatedCwd ? runnerCwd : stepCwd;
455
+ const instructionCwd = behaviorCwd ?? preparationCwd;
453
456
  const behavior = suppressProgressForReadOnlyTask(resolvedBehavior ?? resolveStepBehavior(a, buildStepOverrides(s), chainSkills), s.task, originalTask);
454
457
  const skillNames = behavior.skills === false ? [] : behavior.skills;
455
- const { resolved: resolvedSkills, missing: missingSkills } = resolveSkillsWithFallback(skillNames, stepCwd, ctx.cwd);
458
+ const { resolved: resolvedSkills, missing: missingSkills } = resolveSkillsWithFallback(skillNames, preparationCwd, ctx.cwd);
456
459
  if (missingSkills.includes("pi-cohort")) throw new UnavailableSubagentSkillError(UNAVAILABLE_SUBAGENT_SKILL_ERROR);
457
460
 
458
461
  let systemPrompt = a.systemPrompt?.trim() ?? "";
@@ -536,13 +539,14 @@ export function executeAsyncChain(
536
539
  try {
537
540
  steps = chain.map((s, stepIndex) => {
538
541
  if (isParallelStep(s)) {
542
+ const groupCwd = s.cwd?.includes("{outputs.") ? runnerCwd : resolveChildCwd(runnerCwd, s.cwd);
539
543
  const parallelBehaviors = s.parallel.map((task) => {
540
544
  const agent = agents.find((candidate) => candidate.name === task.agent)!;
541
545
  return suppressProgressForReadOnlyTask(resolveStepBehavior(agent, buildStepOverrides(task), chainSkills), task.task, originalTask);
542
546
  });
543
547
  const progressPrecreated = parallelBehaviors.some((behavior) => behavior.progress);
544
548
  if (progressPrecreated) {
545
- if (!s.worktree) writeInitialProgressFile(runnerCwd);
549
+ if (!s.worktree) writeInitialProgressFile(groupCwd);
546
550
  progressInstructionCreated = true;
547
551
  }
548
552
  return {
@@ -550,7 +554,7 @@ export function executeAsyncChain(
550
554
  let behaviorCwd: string | undefined;
551
555
  if (s.worktree) {
552
556
  try {
553
- behaviorCwd = resolveExpectedWorktreeAgentCwd(runnerCwd, `${id}-s${stepIndex}`, taskIndex);
557
+ behaviorCwd = resolveExpectedWorktreeAgentCwd(groupCwd, `${id}-s${stepIndex}`, taskIndex);
554
558
  } catch {
555
559
  behaviorCwd = undefined;
556
560
  }
@@ -562,8 +566,10 @@ export function executeAsyncChain(
562
566
  progressPrecreated,
563
567
  parallelBehaviors[taskIndex],
564
568
  s.worktree ? { runDir: path.join(asyncDir, `step-${stepIndex}`), index: taskIndex } : undefined,
569
+ s.cwd,
565
570
  );
566
571
  }),
572
+ cwd: s.cwd,
567
573
  concurrency: s.concurrency,
568
574
  failFast: s.failFast,
569
575
  worktree: s.worktree,
@@ -7,7 +7,7 @@ import type { Message } from "@earendil-works/pi-ai";
7
7
  import { writeAtomicJson } from "../../shared/atomic-json.ts";
8
8
  import { appendJsonl, getArtifactPaths } from "../../shared/artifacts.ts";
9
9
  import { getPiSpawnCommand, parsePiRuntime, PiRuntimeLaunchError, type PiRuntimeIdentity } from "../shared/pi-spawn.ts";
10
- import { captureSingleOutputSnapshot, finalizeSingleOutput, formatSavedOutputReference, resolveSingleOutput, type SingleOutputSnapshot } from "../shared/single-output.ts";
10
+ import { captureSingleOutputSnapshot, finalizeSingleOutput, formatFileOnlyFailure, formatSavedOutputReference, resolveSingleOutput, type SingleOutputSnapshot } from "../shared/single-output.ts";
11
11
  import {
12
12
  type ActivityState,
13
13
  type ArtifactConfig,
@@ -46,14 +46,14 @@ import {
46
46
  MAX_PARALLEL_CONCURRENCY,
47
47
  } from "../shared/parallel-utils.ts";
48
48
  import { buildPiArgs, cleanupTempDir, runDirEnv } from "../shared/pi-args.ts";
49
- import { ChainOutputValidationError, outputEntryFromAsyncResult, resolveOutputReferences } from "../shared/chain-outputs.ts";
49
+ import { ChainOutputValidationError, TreeRegistry, outputEntryFromAsyncResult, resolveOutputReferences } from "../shared/chain-outputs.ts";
50
50
  import { buildStopRecord, evaluateStopCondition } from "../shared/stop-conditions.ts";
51
51
  import { createStructuredOutputRuntime, readStructuredOutput } from "../shared/structured-output.ts";
52
52
  import { collectDynamicResults, DynamicFanoutError, materializeDynamicParallelStep, validateDynamicCollection } from "../shared/dynamic-fanout.ts";
53
53
  import { nestedSummaryFromAsyncStatus, writeNestedEvent } from "../shared/nested-events.ts";
54
54
  import { formatModelAttemptNote, isRetryableModelFailure } from "../shared/model-fallback.ts";
55
55
  import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
56
- import { detectSubagentError, extractTextFromContent, extractToolArgsPreview, getFinalOutput } from "../../shared/utils.ts";
56
+ import { detectSubagentError, extractTextFromContent, extractToolArgsPreview, getFinalOutput, resolveChildCwd } from "../../shared/utils.ts";
57
57
  import { blockedLine, evaluateCompletionMutationGuard } from "../shared/completion-guard.ts";
58
58
  import {
59
59
  createMutatingFailureState,
@@ -70,6 +70,7 @@ import { parseSessionTokens } from "../../shared/session-tokens.ts";
70
70
  import type { TokenUsage } from "../../shared/types.ts";
71
71
  import {
72
72
  cleanupWorktrees,
73
+ removeTrees,
73
74
  createWorktrees,
74
75
  diffWorktrees,
75
76
  findWorktreeTaskCwdConflict,
@@ -570,6 +571,7 @@ function writeRunLog(
570
571
  export interface SingleStepContext {
571
572
  previousOutput: string;
572
573
  outputs?: ChainOutputMap;
574
+ trees?: TreeRegistry;
573
575
  placeholder: string;
574
576
  cwd: string;
575
577
  sessionEnabled: boolean;
@@ -612,10 +614,20 @@ export async function runSingleStep(
612
614
  acceptance?: import("../../shared/types.ts").AcceptanceLedger;
613
615
  executionSurface?: ExecutionSurfaceResult;
614
616
  }> {
617
+ let childCwd: string;
618
+ const placeholderRegex = new RegExp(ctx.placeholder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g");
619
+ let task = step.task.replace(placeholderRegex, () => ctx.previousOutput);
620
+ try {
621
+ childCwd = resolveChildCwd(ctx.cwd, resolveOutputReferences(step.cwd ?? ctx.cwd, ctx.outputs ?? {}, ctx.trees));
622
+ task = resolveOutputReferences(task, ctx.outputs ?? {}, ctx.trees);
623
+ } catch (error) {
624
+ if (!(error instanceof ChainOutputValidationError)) throw error;
625
+ return { agent: step.agent, output: error.message, exitCode: 1, error: error.message, sessionFile: step.sessionFile };
626
+ }
615
627
  let backendSelection: import("../../execution-backend/selection.ts").ExecutionBackendSelectionResult;
616
628
  try {
617
629
  backendSelection = await selectConfiguredExecutionBackend({
618
- cwd: step.cwd ?? ctx.cwd,
630
+ cwd: childCwd,
619
631
  userConfig: { executionBackend: ctx.executionBackendConfig?.userPreference },
620
632
  preparation: ctx.executionBackendCoordinator,
621
633
  });
@@ -635,17 +647,9 @@ export async function runSingleStep(
635
647
  const effectiveStructuredOutput = step.structuredOutput ?? (step.structuredOutputSchema
636
648
  ? createStructuredOutputRuntime(step.structuredOutputSchema, path.join(path.dirname(ctx.outputFile), "structured-output"))
637
649
  : undefined);
638
- const placeholderRegex = new RegExp(ctx.placeholder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g");
639
- let task = step.task.replace(placeholderRegex, () => ctx.previousOutput);
640
- try {
641
- task = resolveOutputReferences(task, ctx.outputs ?? {});
642
- } catch (error) {
643
- if (!(error instanceof ChainOutputValidationError)) throw error;
644
- return { agent: step.agent, output: error.message, exitCode: 1, error: error.message, sessionFile: step.sessionFile };
645
- }
646
650
  const taskForCompletionGuard = task;
647
651
  if (step.effectiveAcceptance) {
648
- const acceptancePrompt = formatAcceptancePrompt(step.effectiveAcceptance);
652
+ const acceptancePrompt = formatAcceptancePrompt(step.effectiveAcceptance, { structuredOutput: Boolean(effectiveStructuredOutput) });
649
653
  if (acceptancePrompt) task = `${task}\n${acceptancePrompt}`;
650
654
  }
651
655
  const sessionEnabled = Boolean(step.sessionFile) || ctx.sessionEnabled;
@@ -689,7 +693,7 @@ export async function runSingleStep(
689
693
  runId: ctx.id,
690
694
  childId: `${step.agent}-${ctx.flatIndex}`,
691
695
  backend: selection.backend,
692
- cwd: step.cwd ?? ctx.cwd,
696
+ cwd: childCwd,
693
697
  title: step.agent,
694
698
  signal: ownerController.signal,
695
699
  });
@@ -735,7 +739,7 @@ export async function runSingleStep(
735
739
  extensions: step.extensions,
736
740
  systemPrompt: step.systemPrompt,
737
741
  systemPromptMode: step.systemPromptMode,
738
- cwd: step.cwd ?? ctx.cwd,
742
+ cwd: childCwd,
739
743
  promptFileStem: step.agent,
740
744
  runId: ctx.id,
741
745
  childAgentName: step.agent,
@@ -760,7 +764,7 @@ export async function runSingleStep(
760
764
  attemptId: `${ctx.id}-${ctx.flatIndex}-attempt-${index}`,
761
765
  args,
762
766
  environment: mergedEnv,
763
- cwd: step.cwd ?? ctx.cwd,
767
+ cwd: childCwd,
764
768
  sessionFile: attemptSessionFile,
765
769
  interruptSignal: interruptController.signal,
766
770
  onChildEvent: ctx.onChildEvent,
@@ -881,7 +885,7 @@ export async function runSingleStep(
881
885
  extensions: step.extensions,
882
886
  systemPrompt: step.systemPrompt,
883
887
  systemPromptMode: step.systemPromptMode,
884
- cwd: step.cwd ?? ctx.cwd,
888
+ cwd: childCwd,
885
889
  promptFileStem: step.agent,
886
890
  runId: ctx.id,
887
891
  childAgentName: step.agent,
@@ -897,7 +901,7 @@ export async function runSingleStep(
897
901
  try {
898
902
  run = await runPiStreaming(
899
903
  args,
900
- step.cwd ?? ctx.cwd,
904
+ childCwd,
901
905
  ctx.outputFile,
902
906
  env,
903
907
  ctx.runtime,
@@ -1003,6 +1007,8 @@ export async function runSingleStep(
1003
1007
  let acceptanceCanFailRun = false;
1004
1008
  let effectiveFinalExitCode = resolvedOutput.saveError ? 1 : finalResult?.exitCode ?? 1;
1005
1009
  let effectiveFinalError = finalResult?.error;
1010
+ let preservedAt: string | undefined;
1011
+ let notPreservedReason = "artifact output disabled";
1006
1012
  if (resolvedOutput.saveError) {
1007
1013
  effectiveFinalError = effectiveFinalError
1008
1014
  ? `${effectiveFinalError}\n${resolvedOutput.saveError}`
@@ -1014,7 +1020,7 @@ export async function runSingleStep(
1014
1020
  ? await evaluateAcceptance({
1015
1021
  acceptance: step.effectiveAcceptance,
1016
1022
  output: outputForAcceptance,
1017
- cwd: step.cwd ?? ctx.cwd,
1023
+ cwd: childCwd,
1018
1024
  })
1019
1025
  : undefined;
1020
1026
  const acceptanceFailure = acceptance ? acceptanceFailureMessage(acceptance) : undefined;
@@ -1033,7 +1039,14 @@ export async function runSingleStep(
1033
1039
 
1034
1040
  if (artifactPaths && ctx.artifactConfig?.enabled !== false) {
1035
1041
  if (ctx.artifactConfig?.includeOutput !== false) {
1036
- fs.writeFileSync(artifactPaths.outputPath, output, "utf-8");
1042
+ try {
1043
+ fs.writeFileSync(artifactPaths.outputPath, output, "utf-8");
1044
+ preservedAt = artifactPaths.outputPath;
1045
+ } catch (error) {
1046
+ const message = error instanceof Error ? error.message : String(error);
1047
+ notPreservedReason = `artifact write failed: ${message}`;
1048
+ throw error;
1049
+ }
1037
1050
  }
1038
1051
  if (ctx.artifactConfig?.includeMetadata !== false) {
1039
1052
  fs.writeFileSync(
@@ -1059,11 +1072,14 @@ export async function runSingleStep(
1059
1072
  effectiveFinalError = effectiveFinalError ? `${effectiveFinalError}\n${message}` : message;
1060
1073
  }
1061
1074
 
1075
+ const outputBeforeExternalFinalization = outputForSummary;
1076
+ let externalOutputPersisted = false;
1062
1077
  // Native runs have runPiStreaming write outputFile live; external runs persist it
1063
1078
  // before their surface can be destructively closed as delivered.
1064
1079
  if (externalOwner) {
1065
1080
  try {
1066
1081
  fs.writeFileSync(ctx.outputFile, outputForSummary, "utf-8");
1082
+ externalOutputPersisted = true;
1067
1083
  } catch (error) {
1068
1084
  const message = safeExternalFailure("output persistence", error);
1069
1085
  effectiveFinalExitCode = 1;
@@ -1086,6 +1102,30 @@ export async function runSingleStep(
1086
1102
  executionSurface = { handle: finishResult.handle, retained: finishResult.retained };
1087
1103
  }
1088
1104
 
1105
+ if (step.outputMode === "file-only"
1106
+ && (effectiveFinalExitCode !== 0 || resolvedOutput.saveError)
1107
+ && !blockedLine(effectiveFinalError ?? "")) {
1108
+ const pointer = formatFileOnlyFailure({
1109
+ outputPath: step.outputPath,
1110
+ saveError: resolvedOutput.saveError,
1111
+ ...(preservedAt !== undefined ? { preservedAt } : { notPreservedReason }),
1112
+ });
1113
+ outputForSummary = attemptNotes.length > 0
1114
+ ? `${attemptNotes.join("\n")}\n\n${pointer}`
1115
+ : pointer;
1116
+ }
1117
+
1118
+ // finish can turn a saved success into a failure after the pre-close log write.
1119
+ if (externalOutputPersisted && outputForSummary !== outputBeforeExternalFinalization) {
1120
+ try {
1121
+ fs.writeFileSync(ctx.outputFile, outputForSummary, "utf-8");
1122
+ } catch (error) {
1123
+ const message = safeExternalFailure("output persistence", error);
1124
+ effectiveFinalExitCode = 1;
1125
+ effectiveFinalError = effectiveFinalError ? `${effectiveFinalError}\n${message}` : message;
1126
+ }
1127
+ }
1128
+
1089
1129
  return {
1090
1130
  agent: step.agent,
1091
1131
  output: outputForSummary,
@@ -1204,8 +1244,9 @@ function prepareParallelTaskRun(
1204
1244
  cwd: string,
1205
1245
  worktreeSetup: WorktreeSetup | undefined,
1206
1246
  taskIndex: number,
1247
+ groupCwd?: string,
1207
1248
  ): { taskForRun: SubagentStep; taskCwd: string } {
1208
- if (!worktreeSetup) return { taskForRun: task, taskCwd: cwd };
1249
+ if (!worktreeSetup) return { taskForRun: { ...task, cwd: task.cwd ?? groupCwd }, taskCwd: cwd };
1209
1250
  return {
1210
1251
  taskForRun: { ...task, cwd: undefined },
1211
1252
  taskCwd: worktreeSetup.worktrees[taskIndex]!.agentCwd,
@@ -1251,6 +1292,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
1251
1292
  const overallStartTime = Date.now();
1252
1293
  const shareEnabled = config.share === true;
1253
1294
  const asyncDir = config.asyncDir;
1295
+ const trees = new TreeRegistry(asyncDir);
1254
1296
  const executionBackendConfig = config.executionBackends ?? {
1255
1297
  protocolVersion: 1 as const,
1256
1298
  registrations: [],
@@ -1669,7 +1711,6 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
1669
1711
  if (interrupted || statusPayload.state !== "running") return;
1670
1712
  interrupted = true;
1671
1713
  const now = Date.now();
1672
- statusPayload.state = "paused";
1673
1714
  currentActivityState = undefined;
1674
1715
  statusPayload.activityState = undefined;
1675
1716
  statusPayload.lastUpdate = now;
@@ -1683,11 +1724,6 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
1683
1724
  }
1684
1725
  }
1685
1726
  writeStatusPayload();
1686
- appendJsonl(eventsPath, JSON.stringify({
1687
- type: "subagent.run.paused",
1688
- ts: now,
1689
- runId: id,
1690
- }));
1691
1727
  activeChildInterrupt?.();
1692
1728
  };
1693
1729
  process.on(ASYNC_INTERRUPT_SIGNAL, interruptRunner);
@@ -1705,6 +1741,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
1705
1741
 
1706
1742
  let flatIndex = 0;
1707
1743
 
1744
+ try {
1708
1745
  for (let stepIndex = 0; stepIndex < steps.length; stepIndex++) {
1709
1746
  if (interrupted) break;
1710
1747
  const step = steps[stepIndex];
@@ -1884,6 +1921,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
1884
1921
  const singleResult = await runSingleStep(task, {
1885
1922
  previousOutput, placeholder, cwd, sessionEnabled,
1886
1923
  outputs,
1924
+ trees,
1887
1925
  sessionDir: config.sessionDir ? path.join(config.sessionDir, `dynamic-${stepIndex}-${taskIdx}`) : undefined,
1888
1926
  artifactsDir, artifactConfig, id,
1889
1927
  flatIndex: fi, flatStepCount: Math.max(statusPayload.steps.length, 1),
@@ -2022,13 +2060,14 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
2022
2060
 
2023
2061
  if (isParallelGroup(step)) {
2024
2062
  const group = step;
2063
+ const groupCwd = group.cwd?.includes("{outputs.") ? group.cwd : resolveChildCwd(cwd, group.cwd);
2025
2064
  const concurrency = group.concurrency ?? MAX_PARALLEL_CONCURRENCY;
2026
2065
  const failFast = group.failFast ?? false;
2027
2066
  const groupStartFlatIndex = flatIndex;
2028
2067
  let aborted = false;
2029
2068
  let worktreeSetup: WorktreeSetup | undefined;
2030
2069
  if (group.worktree) {
2031
- const worktreeTaskCwdConflict = findWorktreeTaskCwdConflict(group.parallel, cwd);
2070
+ const worktreeTaskCwdConflict = findWorktreeTaskCwdConflict(group.parallel, groupCwd);
2032
2071
  if (worktreeTaskCwdConflict) {
2033
2072
  const failedAt = Date.now();
2034
2073
  markParallelGroupSetupFailure({
@@ -2036,7 +2075,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
2036
2075
  results,
2037
2076
  group,
2038
2077
  groupStartFlatIndex,
2039
- setupError: formatWorktreeTaskCwdConflict(worktreeTaskCwdConflict, cwd),
2078
+ setupError: formatWorktreeTaskCwdConflict(worktreeTaskCwdConflict, groupCwd),
2040
2079
  failedAt,
2041
2080
  statusPath,
2042
2081
  eventsPath,
@@ -2048,7 +2087,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
2048
2087
  break;
2049
2088
  }
2050
2089
  try {
2051
- worktreeSetup = createWorktrees(cwd, `${id}-s${stepIndex}`, group.parallel.length, {
2090
+ worktreeSetup = createWorktrees(groupCwd, `${id}-s${stepIndex}`, group.parallel.length, {
2052
2091
  agents: group.parallel.map((task) => task.agent),
2053
2092
  setupHook: config.worktreeSetupHook
2054
2093
  ? { hookPath: config.worktreeSetupHook, timeoutMs: config.worktreeSetupHookTimeoutMs }
@@ -2076,7 +2115,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
2076
2115
  }
2077
2116
 
2078
2117
  try {
2079
- if (group.worktree) ensureParallelProgressFile(cwd, group);
2118
+ if (group.worktree) ensureParallelProgressFile(groupCwd, group);
2080
2119
  const groupStartTime = Date.now();
2081
2120
  markParallelGroupRunning({
2082
2121
  statusPayload,
@@ -2133,11 +2172,12 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
2133
2172
  const taskSessionDir = config.sessionDir
2134
2173
  ? path.join(config.sessionDir, `parallel-${fi}`)
2135
2174
  : undefined;
2136
- const { taskForRun, taskCwd } = prepareParallelTaskRun(task, cwd, worktreeSetup, taskIdx);
2175
+ const { taskForRun, taskCwd } = prepareParallelTaskRun(task, cwd, worktreeSetup, taskIdx, group.cwd);
2137
2176
 
2138
2177
  const singleResult = await runSingleStep(taskForRun, {
2139
2178
  previousOutput, placeholder, cwd: taskCwd, sessionEnabled,
2140
2179
  outputs,
2180
+ trees,
2141
2181
  sessionDir: taskSessionDir,
2142
2182
  artifactsDir, artifactConfig, id,
2143
2183
  flatIndex: fi, flatStepCount: flatSteps.length,
@@ -2247,7 +2287,12 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
2247
2287
  agent: parallelResults[t]!.agent,
2248
2288
  output: parallelResults[t]!.output,
2249
2289
  structuredOutput: parallelResults[t]!.structuredOutput,
2250
- }, stepIndex, patchInfoForDiff(diffs[t]));
2290
+ }, stepIndex, worktreeSetup ? {
2291
+ ...patchInfoForDiff(diffs[t]),
2292
+ taskIndex: t,
2293
+ baseCommit: worktreeSetup.baseCommit,
2294
+ repoCwd: worktreeSetup.cwd,
2295
+ } : undefined);
2251
2296
  }
2252
2297
  statusPayload.outputs = outputs;
2253
2298
  if (!interrupted && group.stopOn) {
@@ -2310,6 +2355,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
2310
2355
  const singleResult = await runSingleStep(seqStep, {
2311
2356
  previousOutput, placeholder, cwd, sessionEnabled,
2312
2357
  outputs,
2358
+ trees,
2313
2359
  sessionDir: config.sessionDir ? path.join(config.sessionDir, `parallel-${flatIndex}`) : undefined,
2314
2360
  artifactsDir, artifactConfig, id,
2315
2361
  flatIndex, flatStepCount: flatSteps.length,
@@ -2423,6 +2469,18 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
2423
2469
  }
2424
2470
  }
2425
2471
 
2472
+ } finally {
2473
+ removeTrees(trees.created);
2474
+ }
2475
+
2476
+ if (interrupted) {
2477
+ const pausedAt = Date.now();
2478
+ statusPayload.state = "paused";
2479
+ statusPayload.lastUpdate = pausedAt;
2480
+ writeStatusPayload();
2481
+ appendJsonl(eventsPath, JSON.stringify({ type: "subagent.run.paused", ts: pausedAt, runId: id }));
2482
+ }
2483
+
2426
2484
  let summary = results.map((r) => `${r.agent}:\n${r.output}`).join("\n\n");
2427
2485
  let truncated = false;
2428
2486