pi-cohort 7.4.4 → 7.5.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 +13 -0
- package/package.json +1 -1
- package/src/extension/index.ts +1 -3
- package/src/runs/background/async-execution.ts +12 -0
- package/src/runs/background/subagent-runner.ts +5 -0
- package/src/runs/foreground/chain-execution.ts +5 -0
- package/src/runs/foreground/subagent-executor.ts +23 -0
- package/src/runs/shared/worktree.ts +104 -20
- package/src/shared/types.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [7.5.0] - 2026-10-26
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `worktreeTeardownHook` and `worktreeTeardownHookTimeoutMs` let consumers clean up external resources before task checkout removal, including setup rollback; failures produce best-effort warnings without changing task results. (#27)
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
- The Tests workflow passes on Windows again: the fanout extension-inheritance integration test hands its preload to `--import` as a `file://` URL instead of a bare drive-letter path.
|
|
10
|
+
|
|
11
|
+
## [7.4.5] - 2026-10-23
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- Child personas that allow `subagent` and inherit extensions now start without a duplicate-tool error; the explicit child extension is the only registrant. (#26)
|
|
15
|
+
|
|
3
16
|
## [7.4.4] - 2026-10-22
|
|
4
17
|
|
|
5
18
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-cohort",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.5.0",
|
|
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",
|
package/src/extension/index.ts
CHANGED
|
@@ -34,8 +34,7 @@ import { registerSlashSubagentBridge } from "../slash/slash-bridge.ts";
|
|
|
34
34
|
import { clearSlashSnapshots, getSlashRenderableSnapshot, resolveSlashMessageDetails, restoreSlashFinalSnapshots, type SlashMessageDetails } from "../slash/slash-live-state.ts";
|
|
35
35
|
import { inspectSubagentStatus } from "../runs/background/run-status.ts";
|
|
36
36
|
import registerSubagentNotify, { type SubagentNotifyDetails } from "../runs/background/notify.ts";
|
|
37
|
-
import { SUBAGENT_CHILD_ENV
|
|
38
|
-
import registerFanoutChildSubagentExtension from "./fanout-child.ts";
|
|
37
|
+
import { SUBAGENT_CHILD_ENV } from "../runs/shared/pi-args.ts";
|
|
39
38
|
import { formatDuration, shortenPath } from "../shared/formatters.ts";
|
|
40
39
|
import { loadConfig } from "./config.ts";
|
|
41
40
|
import { deriveForwardedFlags } from "../runs/shared/forward-flags.ts";
|
|
@@ -242,7 +241,6 @@ function formatSubagentRoster(cwd: string): string | undefined {
|
|
|
242
241
|
|
|
243
242
|
export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
244
243
|
if (process.env[SUBAGENT_CHILD_ENV] === "1") {
|
|
245
|
-
if (process.env[SUBAGENT_FANOUT_CHILD_ENV] === "1") registerFanoutChildSubagentExtension(pi);
|
|
246
244
|
return;
|
|
247
245
|
}
|
|
248
246
|
const globalStore = globalThis as Record<string, unknown>;
|
|
@@ -80,6 +80,8 @@ interface AsyncChainParams {
|
|
|
80
80
|
maxSubagentDepth: number;
|
|
81
81
|
worktreeSetupHook?: string;
|
|
82
82
|
worktreeSetupHookTimeoutMs?: number;
|
|
83
|
+
worktreeTeardownHook?: string;
|
|
84
|
+
worktreeTeardownHookTimeoutMs?: number;
|
|
83
85
|
controlConfig?: ResolvedControlConfig;
|
|
84
86
|
nestedRoute?: NestedRouteInfo;
|
|
85
87
|
forwardedFlags?: string[];
|
|
@@ -115,6 +117,8 @@ interface AsyncSingleParams {
|
|
|
115
117
|
maxSubagentDepth: number;
|
|
116
118
|
worktreeSetupHook?: string;
|
|
117
119
|
worktreeSetupHookTimeoutMs?: number;
|
|
120
|
+
worktreeTeardownHook?: string;
|
|
121
|
+
worktreeTeardownHookTimeoutMs?: number;
|
|
118
122
|
controlConfig?: ResolvedControlConfig;
|
|
119
123
|
nestedRoute?: NestedRouteInfo;
|
|
120
124
|
forwardedFlags?: string[];
|
|
@@ -376,6 +380,8 @@ export function executeAsyncChain(
|
|
|
376
380
|
maxSubagentDepth,
|
|
377
381
|
worktreeSetupHook,
|
|
378
382
|
worktreeSetupHookTimeoutMs,
|
|
383
|
+
worktreeTeardownHook,
|
|
384
|
+
worktreeTeardownHookTimeoutMs,
|
|
379
385
|
controlConfig,
|
|
380
386
|
nestedRoute,
|
|
381
387
|
forwardedFlags,
|
|
@@ -630,6 +636,8 @@ export function executeAsyncChain(
|
|
|
630
636
|
forwardedFlags,
|
|
631
637
|
worktreeSetupHook,
|
|
632
638
|
worktreeSetupHookTimeoutMs,
|
|
639
|
+
worktreeTeardownHook,
|
|
640
|
+
worktreeTeardownHookTimeoutMs,
|
|
633
641
|
controlConfig,
|
|
634
642
|
resultMode,
|
|
635
643
|
executionBackends: detachedExecutionBackends(ctx.executionBackend),
|
|
@@ -748,6 +756,8 @@ export function executeAsyncSingle(
|
|
|
748
756
|
maxSubagentDepth,
|
|
749
757
|
worktreeSetupHook,
|
|
750
758
|
worktreeSetupHookTimeoutMs,
|
|
759
|
+
worktreeTeardownHook,
|
|
760
|
+
worktreeTeardownHookTimeoutMs,
|
|
751
761
|
controlConfig,
|
|
752
762
|
nestedRoute,
|
|
753
763
|
forwardedFlags,
|
|
@@ -840,6 +850,8 @@ export function executeAsyncSingle(
|
|
|
840
850
|
forwardedFlags,
|
|
841
851
|
worktreeSetupHook,
|
|
842
852
|
worktreeSetupHookTimeoutMs,
|
|
853
|
+
worktreeTeardownHook,
|
|
854
|
+
worktreeTeardownHookTimeoutMs,
|
|
843
855
|
controlConfig,
|
|
844
856
|
resultMode: "single",
|
|
845
857
|
executionBackends: detachedExecutionBackends(ctx.executionBackend),
|
|
@@ -120,6 +120,8 @@ interface SubagentRunConfig {
|
|
|
120
120
|
forwardedFlags?: string[];
|
|
121
121
|
worktreeSetupHook?: string;
|
|
122
122
|
worktreeSetupHookTimeoutMs?: number;
|
|
123
|
+
worktreeTeardownHook?: string;
|
|
124
|
+
worktreeTeardownHookTimeoutMs?: number;
|
|
123
125
|
controlConfig?: ResolvedControlConfig;
|
|
124
126
|
resultMode?: SubagentRunMode;
|
|
125
127
|
dynamicFanoutMaxItems?: number;
|
|
@@ -2133,6 +2135,9 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2133
2135
|
setupHook: config.worktreeSetupHook
|
|
2134
2136
|
? { hookPath: config.worktreeSetupHook, timeoutMs: config.worktreeSetupHookTimeoutMs }
|
|
2135
2137
|
: undefined,
|
|
2138
|
+
teardownHook: config.worktreeTeardownHook
|
|
2139
|
+
? { hookPath: config.worktreeTeardownHook, timeoutMs: config.worktreeTeardownHookTimeoutMs }
|
|
2140
|
+
: undefined,
|
|
2136
2141
|
});
|
|
2137
2142
|
} catch (error) {
|
|
2138
2143
|
const setupError = error instanceof Error ? error.message : String(error);
|
|
@@ -420,6 +420,8 @@ interface ChainExecutionParams {
|
|
|
420
420
|
nestedRoute?: NestedRouteInfo;
|
|
421
421
|
worktreeSetupHook?: string;
|
|
422
422
|
worktreeSetupHookTimeoutMs?: number;
|
|
423
|
+
worktreeTeardownHook?: string;
|
|
424
|
+
worktreeTeardownHookTimeoutMs?: number;
|
|
423
425
|
forwardedFlags?: string[];
|
|
424
426
|
}
|
|
425
427
|
|
|
@@ -660,6 +662,9 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
660
662
|
setupHook: params.worktreeSetupHook
|
|
661
663
|
? { hookPath: params.worktreeSetupHook, timeoutMs: params.worktreeSetupHookTimeoutMs }
|
|
662
664
|
: undefined,
|
|
665
|
+
teardownHook: params.worktreeTeardownHook
|
|
666
|
+
? { hookPath: params.worktreeTeardownHook, timeoutMs: params.worktreeTeardownHookTimeoutMs }
|
|
667
|
+
: undefined,
|
|
663
668
|
});
|
|
664
669
|
} catch (error) {
|
|
665
670
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -665,6 +665,8 @@ async function resumeAsyncRun(input: {
|
|
|
665
665
|
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
666
666
|
worktreeSetupHook: input.deps.config.worktreeSetupHook,
|
|
667
667
|
worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
|
|
668
|
+
worktreeTeardownHook: input.deps.config.worktreeTeardownHook,
|
|
669
|
+
worktreeTeardownHookTimeoutMs: input.deps.config.worktreeTeardownHookTimeoutMs,
|
|
668
670
|
controlConfig: resolveControlConfig(input.deps.config.control, input.params.control),
|
|
669
671
|
availableModels,
|
|
670
672
|
forwardedFlags: input.deps.forwardedFlags,
|
|
@@ -1073,6 +1075,8 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1073
1075
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1074
1076
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1075
1077
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1078
|
+
worktreeTeardownHook: deps.config.worktreeTeardownHook,
|
|
1079
|
+
worktreeTeardownHookTimeoutMs: deps.config.worktreeTeardownHookTimeoutMs,
|
|
1076
1080
|
controlConfig,
|
|
1077
1081
|
nestedRoute,
|
|
1078
1082
|
forwardedFlags: deps.forwardedFlags,
|
|
@@ -1102,6 +1106,8 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1102
1106
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1103
1107
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1104
1108
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1109
|
+
worktreeTeardownHook: deps.config.worktreeTeardownHook,
|
|
1110
|
+
worktreeTeardownHookTimeoutMs: deps.config.worktreeTeardownHookTimeoutMs,
|
|
1105
1111
|
controlConfig,
|
|
1106
1112
|
nestedRoute,
|
|
1107
1113
|
forwardedFlags: deps.forwardedFlags,
|
|
@@ -1144,6 +1150,8 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1144
1150
|
maxSubagentDepth,
|
|
1145
1151
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1146
1152
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1153
|
+
worktreeTeardownHook: deps.config.worktreeTeardownHook,
|
|
1154
|
+
worktreeTeardownHookTimeoutMs: deps.config.worktreeTeardownHookTimeoutMs,
|
|
1147
1155
|
controlConfig,
|
|
1148
1156
|
nestedRoute,
|
|
1149
1157
|
acceptance: params.acceptance,
|
|
@@ -1204,6 +1212,8 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
1204
1212
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1205
1213
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1206
1214
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1215
|
+
worktreeTeardownHook: deps.config.worktreeTeardownHook,
|
|
1216
|
+
worktreeTeardownHookTimeoutMs: deps.config.worktreeTeardownHookTimeoutMs,
|
|
1207
1217
|
forwardedFlags: deps.forwardedFlags,
|
|
1208
1218
|
});
|
|
1209
1219
|
|
|
@@ -1237,6 +1247,8 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
1237
1247
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1238
1248
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1239
1249
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1250
|
+
worktreeTeardownHook: deps.config.worktreeTeardownHook,
|
|
1251
|
+
worktreeTeardownHookTimeoutMs: deps.config.worktreeTeardownHookTimeoutMs,
|
|
1240
1252
|
controlConfig,
|
|
1241
1253
|
nestedRoute: data.nestedRoute,
|
|
1242
1254
|
forwardedFlags: deps.forwardedFlags,
|
|
@@ -1299,6 +1311,8 @@ function createParallelWorktreeSetup(
|
|
|
1299
1311
|
tasks: TaskParam[],
|
|
1300
1312
|
setupHook: ExtensionConfig["worktreeSetupHook"],
|
|
1301
1313
|
setupHookTimeoutMs: ExtensionConfig["worktreeSetupHookTimeoutMs"],
|
|
1314
|
+
teardownHook: ExtensionConfig["worktreeTeardownHook"],
|
|
1315
|
+
teardownHookTimeoutMs: ExtensionConfig["worktreeTeardownHookTimeoutMs"],
|
|
1302
1316
|
): { setup?: WorktreeSetup; errorResult?: AgentToolResult<Details> } {
|
|
1303
1317
|
if (!enabled) return {};
|
|
1304
1318
|
try {
|
|
@@ -1308,6 +1322,9 @@ function createParallelWorktreeSetup(
|
|
|
1308
1322
|
setupHook: setupHook
|
|
1309
1323
|
? { hookPath: setupHook, timeoutMs: setupHookTimeoutMs }
|
|
1310
1324
|
: undefined,
|
|
1325
|
+
teardownHook: teardownHook
|
|
1326
|
+
? { hookPath: teardownHook, timeoutMs: teardownHookTimeoutMs }
|
|
1327
|
+
: undefined,
|
|
1311
1328
|
}),
|
|
1312
1329
|
};
|
|
1313
1330
|
} catch (error) {
|
|
@@ -1691,6 +1708,8 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
1691
1708
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1692
1709
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1693
1710
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1711
|
+
worktreeTeardownHook: deps.config.worktreeTeardownHook,
|
|
1712
|
+
worktreeTeardownHookTimeoutMs: deps.config.worktreeTeardownHookTimeoutMs,
|
|
1694
1713
|
controlConfig,
|
|
1695
1714
|
forwardedFlags: deps.forwardedFlags,
|
|
1696
1715
|
});
|
|
@@ -1709,6 +1728,8 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
1709
1728
|
tasks,
|
|
1710
1729
|
deps.config.worktreeSetupHook,
|
|
1711
1730
|
deps.config.worktreeSetupHookTimeoutMs,
|
|
1731
|
+
deps.config.worktreeTeardownHook,
|
|
1732
|
+
deps.config.worktreeTeardownHookTimeoutMs,
|
|
1712
1733
|
);
|
|
1713
1734
|
if (errorResult) return errorResult;
|
|
1714
1735
|
|
|
@@ -1952,6 +1973,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
1952
1973
|
maxSubagentDepth,
|
|
1953
1974
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1954
1975
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1976
|
+
worktreeTeardownHook: deps.config.worktreeTeardownHook,
|
|
1977
|
+
worktreeTeardownHookTimeoutMs: deps.config.worktreeTeardownHookTimeoutMs,
|
|
1955
1978
|
controlConfig,
|
|
1956
1979
|
forwardedFlags: deps.forwardedFlags,
|
|
1957
1980
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
-
import
|
|
2
|
+
import fs from "node:fs";
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
|
|
@@ -10,6 +10,8 @@ export interface WorktreeSetup {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
interface WorktreeInfo {
|
|
13
|
+
hookInput: WorktreeSetupHookInput;
|
|
14
|
+
teardownHook?: ResolvedWorktreeTeardownHook;
|
|
13
15
|
path: string;
|
|
14
16
|
agentCwd: string;
|
|
15
17
|
branch: string;
|
|
@@ -41,7 +43,18 @@ interface WorktreeSetupHookConfig {
|
|
|
41
43
|
timeoutMs?: number;
|
|
42
44
|
}
|
|
43
45
|
|
|
46
|
+
interface WorktreeTeardownHookConfig {
|
|
47
|
+
hookPath: string;
|
|
48
|
+
timeoutMs?: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface ResolvedWorktreeTeardownHook {
|
|
52
|
+
hookPath: string;
|
|
53
|
+
timeoutMs: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
44
56
|
interface CreateWorktreesOptions {
|
|
57
|
+
teardownHook?: WorktreeTeardownHookConfig;
|
|
45
58
|
agents?: string[];
|
|
46
59
|
setupHook?: WorktreeSetupHookConfig;
|
|
47
60
|
}
|
|
@@ -229,6 +242,43 @@ function resolveWorktreeSetupHook(
|
|
|
229
242
|
};
|
|
230
243
|
}
|
|
231
244
|
|
|
245
|
+
function resolveWorktreeTeardownHook(
|
|
246
|
+
repoRoot: string,
|
|
247
|
+
config: WorktreeTeardownHookConfig | undefined,
|
|
248
|
+
): ResolvedWorktreeTeardownHook | undefined {
|
|
249
|
+
if (!config) return undefined;
|
|
250
|
+
const hookPath = config.hookPath.trim();
|
|
251
|
+
if (!hookPath) {
|
|
252
|
+
throw new Error("worktree teardown hook path cannot be empty");
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const expandedHookPath = hookPath.startsWith("~/") ? path.join(os.homedir(), hookPath.slice(2)) : hookPath;
|
|
256
|
+
let resolvedPath: string;
|
|
257
|
+
if (path.isAbsolute(expandedHookPath)) {
|
|
258
|
+
resolvedPath = expandedHookPath;
|
|
259
|
+
} else if (expandedHookPath.includes("/") || expandedHookPath.includes("\\")) {
|
|
260
|
+
resolvedPath = path.resolve(repoRoot, expandedHookPath);
|
|
261
|
+
} else {
|
|
262
|
+
throw new Error("worktree teardown hook must be an absolute path or a repo-relative path");
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
266
|
+
throw new Error(`worktree teardown hook not found: ${resolvedPath}`);
|
|
267
|
+
}
|
|
268
|
+
if (fs.statSync(resolvedPath).isDirectory()) {
|
|
269
|
+
throw new Error(`worktree teardown hook must be a file, got directory: ${resolvedPath}`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const timeoutMs = config.timeoutMs === undefined ? 30000 : config.timeoutMs;
|
|
273
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
|
|
274
|
+
throw new Error("worktree teardown hook timeout must be an integer greater than 0");
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
hookPath: resolvedPath,
|
|
278
|
+
timeoutMs: timeoutMs,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
232
282
|
function normalizeSyntheticPath(worktreePath: string, rawPath: string): string {
|
|
233
283
|
const trimmed = rawPath.trim();
|
|
234
284
|
if (!trimmed) throw new Error("synthetic path cannot be empty");
|
|
@@ -313,6 +363,40 @@ function runWorktreeSetupHook(
|
|
|
313
363
|
return [...uniquePaths];
|
|
314
364
|
}
|
|
315
365
|
|
|
366
|
+
function warnWorktreeCleanup(message: string): void {
|
|
367
|
+
try { fs.write(2, message + "\n", () => {}); } catch {}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function runWorktreeTeardownHook(
|
|
371
|
+
hook: ResolvedWorktreeTeardownHook | undefined,
|
|
372
|
+
input: WorktreeSetupHookInput,
|
|
373
|
+
): void {
|
|
374
|
+
if (!hook) return;
|
|
375
|
+
try {
|
|
376
|
+
const result = spawnSync(hook.hookPath, [], {
|
|
377
|
+
cwd: input.worktreePath,
|
|
378
|
+
encoding: "utf-8",
|
|
379
|
+
input: JSON.stringify(input),
|
|
380
|
+
timeout: hook.timeoutMs,
|
|
381
|
+
killSignal: "SIGKILL",
|
|
382
|
+
shell: false,
|
|
383
|
+
stdio: ["pipe", "ignore", "pipe"],
|
|
384
|
+
});
|
|
385
|
+
let cause: string;
|
|
386
|
+
if (result.error) {
|
|
387
|
+
cause = "code" in result.error && result.error.code === "ETIMEDOUT"
|
|
388
|
+
? `timed out after ${hook.timeoutMs}ms`
|
|
389
|
+
: result.error.message;
|
|
390
|
+
} else if (result.status !== 0) {
|
|
391
|
+
cause = result.signal ? `signal ${result.signal}` : `exit code ${result.status}`;
|
|
392
|
+
} else return;
|
|
393
|
+
const stderr = result.stderr?.trim();
|
|
394
|
+
warnWorktreeCleanup(`worktree teardown hook failed for '${input.worktreePath}': ${cause}${stderr ? `: ${stderr}` : ""}`);
|
|
395
|
+
} catch (error) {
|
|
396
|
+
warnWorktreeCleanup(`worktree teardown hook failed for '${input.worktreePath}': ${error instanceof Error ? error.message : String(error)}`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
316
400
|
function createSingleWorktree(
|
|
317
401
|
toplevel: string,
|
|
318
402
|
cwdRelative: string,
|
|
@@ -320,6 +404,7 @@ function createSingleWorktree(
|
|
|
320
404
|
index: number,
|
|
321
405
|
baseCommit: string,
|
|
322
406
|
setupHook: ResolvedWorktreeSetupHook | undefined,
|
|
407
|
+
teardownHook: ResolvedWorktreeTeardownHook | undefined,
|
|
323
408
|
agent: string | undefined,
|
|
324
409
|
): WorktreeInfo {
|
|
325
410
|
const branch = buildWorktreeBranch(runId, index);
|
|
@@ -331,27 +416,22 @@ function createSingleWorktree(
|
|
|
331
416
|
}
|
|
332
417
|
|
|
333
418
|
const agentCwd = cwdRelative ? path.join(worktreePath, cwdRelative) : worktreePath;
|
|
419
|
+
const hookInput: WorktreeSetupHookInput = {
|
|
420
|
+
version: 1, repoRoot: toplevel, worktreePath, agentCwd, branch, index, runId, baseCommit, agent,
|
|
421
|
+
};
|
|
334
422
|
try {
|
|
335
423
|
const nodeModulesLinked = linkNodeModulesIfPresent(toplevel, worktreePath);
|
|
336
424
|
const syntheticPaths = nodeModulesLinked ? ["node_modules"] : [];
|
|
337
425
|
|
|
338
426
|
if (setupHook) {
|
|
339
|
-
const hookSyntheticPaths = runWorktreeSetupHook(setupHook,
|
|
340
|
-
version: 1,
|
|
341
|
-
repoRoot: toplevel,
|
|
342
|
-
worktreePath,
|
|
343
|
-
agentCwd,
|
|
344
|
-
branch,
|
|
345
|
-
index,
|
|
346
|
-
runId,
|
|
347
|
-
baseCommit,
|
|
348
|
-
agent,
|
|
349
|
-
});
|
|
427
|
+
const hookSyntheticPaths = runWorktreeSetupHook(setupHook, hookInput);
|
|
350
428
|
syntheticPaths.push(...hookSyntheticPaths);
|
|
351
429
|
}
|
|
352
430
|
|
|
353
431
|
return {
|
|
354
432
|
path: worktreePath,
|
|
433
|
+
hookInput,
|
|
434
|
+
teardownHook,
|
|
355
435
|
agentCwd,
|
|
356
436
|
branch,
|
|
357
437
|
index,
|
|
@@ -359,11 +439,12 @@ function createSingleWorktree(
|
|
|
359
439
|
syntheticPaths,
|
|
360
440
|
};
|
|
361
441
|
} catch (error) {
|
|
362
|
-
|
|
363
|
-
|
|
442
|
+
runWorktreeTeardownHook(teardownHook, hookInput);
|
|
443
|
+
try { runGitChecked(toplevel, ["worktree", "remove", "--force", worktreePath]); } catch (cleanupError) {
|
|
444
|
+
warnWorktreeCleanup(`worktree remove failed for '${worktreePath}': ${cleanupError}`);
|
|
364
445
|
}
|
|
365
|
-
try { runGitChecked(toplevel, ["branch", "-D", branch]); } catch {
|
|
366
|
-
|
|
446
|
+
try { runGitChecked(toplevel, ["branch", "-D", branch]); } catch (cleanupError) {
|
|
447
|
+
warnWorktreeCleanup(`branch -D '${branch}' failed for '${worktreePath}': ${cleanupError}`);
|
|
367
448
|
}
|
|
368
449
|
throw error;
|
|
369
450
|
}
|
|
@@ -477,11 +558,12 @@ function writeEmptyPatch(patchPath: string): void {
|
|
|
477
558
|
}
|
|
478
559
|
|
|
479
560
|
function cleanupSingleWorktree(repoCwd: string, worktree: WorktreeInfo): void {
|
|
480
|
-
|
|
481
|
-
|
|
561
|
+
runWorktreeTeardownHook(worktree.teardownHook, worktree.hookInput);
|
|
562
|
+
try { runGitChecked(repoCwd, ["worktree", "remove", "--force", worktree.path]); } catch (error) {
|
|
563
|
+
warnWorktreeCleanup(`worktree remove failed for '${worktree.path}': ${error}`);
|
|
482
564
|
}
|
|
483
|
-
try { runGitChecked(repoCwd, ["branch", "-D", worktree.branch]); } catch {
|
|
484
|
-
|
|
565
|
+
try { runGitChecked(repoCwd, ["branch", "-D", worktree.branch]); } catch (error) {
|
|
566
|
+
warnWorktreeCleanup(`branch -D '${worktree.branch}' failed for '${worktree.path}': ${error}`);
|
|
485
567
|
}
|
|
486
568
|
}
|
|
487
569
|
|
|
@@ -498,6 +580,7 @@ export function patchPathForDiff(diff: WorktreeDiff): string | undefined {
|
|
|
498
580
|
export function createWorktrees(cwd: string, runId: string, count: number, options?: CreateWorktreesOptions): WorktreeSetup {
|
|
499
581
|
const repo = resolveRepoState(cwd);
|
|
500
582
|
const setupHook = resolveWorktreeSetupHook(repo.toplevel, options?.setupHook);
|
|
583
|
+
const teardownHook = resolveWorktreeTeardownHook(repo.toplevel, options?.teardownHook);
|
|
501
584
|
const worktrees: WorktreeInfo[] = [];
|
|
502
585
|
|
|
503
586
|
try {
|
|
@@ -509,6 +592,7 @@ export function createWorktrees(cwd: string, runId: string, count: number, optio
|
|
|
509
592
|
index,
|
|
510
593
|
repo.baseCommit,
|
|
511
594
|
setupHook,
|
|
595
|
+
teardownHook,
|
|
512
596
|
options?.agents?.[index],
|
|
513
597
|
));
|
|
514
598
|
}
|
package/src/shared/types.ts
CHANGED
|
@@ -869,6 +869,8 @@ export interface ExtensionConfig {
|
|
|
869
869
|
chain?: ExtensionChainConfig;
|
|
870
870
|
worktreeSetupHook?: string;
|
|
871
871
|
worktreeSetupHookTimeoutMs?: number;
|
|
872
|
+
worktreeTeardownHook?: string;
|
|
873
|
+
worktreeTeardownHookTimeoutMs?: number;
|
|
872
874
|
/** Print a roster of discovered subagents/chains into chat at session start. Default: true. */
|
|
873
875
|
showRosterOnStart?: boolean;
|
|
874
876
|
/** Forward the parent pi's extension CLI flags (e.g. --no-autofix) into spawned children. Default: true. */
|