@ferris1225/pi-subagents 4.2.2 → 4.2.5
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/README.md +4 -2
- package/agents/explorer.md +5 -8
- package/package.json +55 -55
- package/src/durable.ts +105 -33
- package/src/recovery.ts +20 -2
- package/src/rpc-run.ts +993 -993
- package/src/runtime.ts +3 -3
- package/src/thread-lifecycle.ts +7 -2
- package/src/tools.ts +1 -1
- package/src/worktree.ts +66 -30
package/README.md
CHANGED
|
@@ -127,7 +127,9 @@ main agent inspects the actual changes before calling anything done.
|
|
|
127
127
|
as a lane wait, not as slot queueing, and its process slot is already released.
|
|
128
128
|
- Setup and integration failures keep the useful patch and worktree, and record
|
|
129
129
|
where they are in `~/.pi/agent/pi-subagents-recovery.json`. Every later session
|
|
130
|
-
start repeats that notice until you remove the artifacts.
|
|
130
|
+
start repeats that notice until you remove the artifacts. When the changes had
|
|
131
|
+
already been applied and only the cleanup failed, the next session start
|
|
132
|
+
removes the retained copy itself and clears the notice.
|
|
131
133
|
|
|
132
134
|
## Threads: resume, stop
|
|
133
135
|
|
|
@@ -313,8 +315,8 @@ that removes it, so this directory does not grow without bound:
|
|
|
313
315
|
| Path | Holds | Removed |
|
|
314
316
|
| ------------------------------------------ | ---------------------------------------------------- | ---------------------------------------------------------------- |
|
|
315
317
|
| `pi-subagents.json` | Your configuration | Never — it is yours |
|
|
316
|
-
| `pi-subagents-threads.json` | One record per interrupted thread | When the thread settles, or after 30 days |
|
|
317
318
|
| `pi-subagents-recovery.json` | Worktree integration and cleanup failures | When the retained patch or worktree it points at is gone |
|
|
319
|
+
| `ferris-pi-subagents/<project>/pi-subagents-threads.json` | One record per interrupted thread | When the thread settles, or after 30 days |
|
|
318
320
|
| `ferris-pi-subagents/<project>/sessions/` | Retained child sessions that a resume continues from | When the pi session that produced it ends, or its owner is gone |
|
|
319
321
|
| `ferris-pi-subagents/<project>/worktrees/` | Isolated checkouts for parallel writers | On integration, or when its owning process is gone |
|
|
320
322
|
| `ferris-pi-subagents/<project>/results/` | Full text of truncated results | After 7 days, or beyond 50 per project |
|
package/agents/explorer.md
CHANGED
|
@@ -26,15 +26,12 @@ Thoroughness scales with the task (default medium): quick = targeted lookups in
|
|
|
26
26
|
|
|
27
27
|
## Final response
|
|
28
28
|
|
|
29
|
-
Return only
|
|
29
|
+
Return only retrieval results, one bare bullet per finding — a single line: path, the fact, nothing else:
|
|
30
30
|
|
|
31
31
|
```text
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
- `path/to/file.ts` — first symbol/section to verify and why
|
|
36
|
-
## Gaps
|
|
37
|
-
- unresolved uncertainty (omit this section when none)
|
|
32
|
+
- `path/to/file.ts:10-50` — the fact
|
|
33
|
+
Start here: `path/to/file.ts` — entry symbol and why (only when the caller could not guess it)
|
|
34
|
+
Gaps: unresolved uncertainty (only when real)
|
|
38
35
|
```
|
|
39
36
|
|
|
40
|
-
Do not repeat the task brief, inventory every file opened, paste nonessential code, or narrate search
|
|
37
|
+
No preamble or closing summary. Do not repeat the task brief, inventory every file opened, paste nonessential code, or narrate the search; every line must carry a path with a fact or name a gap — delete anything else. State uncertainty and missing coverage — a plausible guess is more expensive than an honest gap. Stay under 15 lines by default; go longer only when the brief genuinely demands a wide survey — the 40-line delivery cap truncates your tail (usually the Gaps) and the caller pays for every line.
|
package/package.json
CHANGED
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@ferris1225/pi-subagents",
|
|
3
|
-
"version": "4.2.
|
|
4
|
-
"description": "A managed sub-agent team for pi: specialized roles, pre-commit documentation sync, retained threads, auto-fix chains, model fallback, and Git worktree isolation.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"license": "MIT",
|
|
7
|
-
"publishConfig": {
|
|
8
|
-
"access": "public"
|
|
9
|
-
},
|
|
10
|
-
"keywords": [
|
|
11
|
-
"pi-package",
|
|
12
|
-
"pi-extension",
|
|
13
|
-
"subagent",
|
|
14
|
-
"sub-agent",
|
|
15
|
-
"delegation",
|
|
16
|
-
"code-cleanup",
|
|
17
|
-
"dead-code"
|
|
18
|
-
],
|
|
19
|
-
"files": [
|
|
20
|
-
"src",
|
|
21
|
-
"agents",
|
|
22
|
-
"README.md",
|
|
23
|
-
"LICENSE"
|
|
24
|
-
],
|
|
25
|
-
"pi": {
|
|
26
|
-
"extensions": [
|
|
27
|
-
"./src/index.ts"
|
|
28
|
-
]
|
|
29
|
-
},
|
|
30
|
-
"scripts": {
|
|
31
|
-
"check": "tsc --noEmit",
|
|
32
|
-
"test": "vitest run tests",
|
|
33
|
-
"prepack": "npm run check && npm test"
|
|
34
|
-
},
|
|
35
|
-
"peerDependencies": {
|
|
36
|
-
"@earendil-works/pi-agent-core": ">=0.84.4",
|
|
37
|
-
"@earendil-works/pi-ai": ">=0.84.4",
|
|
38
|
-
"@earendil-works/pi-coding-agent": ">=0.84.4",
|
|
39
|
-
"@earendil-works/pi-tui": ">=0.84.4",
|
|
40
|
-
"typebox": "*"
|
|
41
|
-
},
|
|
42
|
-
"devDependencies": {
|
|
43
|
-
"@earendil-works/pi-agent-core": "^0.84.4",
|
|
44
|
-
"@earendil-works/pi-ai": "^0.84.4",
|
|
45
|
-
"@earendil-works/pi-coding-agent": "^0.84.4",
|
|
46
|
-
"@earendil-works/pi-tui": "^0.84.4",
|
|
47
|
-
"@types/node": "^22.10.0",
|
|
48
|
-
"typebox": "^1.3.9",
|
|
49
|
-
"typescript": "^5.9.0",
|
|
50
|
-
"vitest": "^4.1.0"
|
|
51
|
-
},
|
|
52
|
-
"engines": {
|
|
53
|
-
"node": ">=22.19.0"
|
|
54
|
-
}
|
|
55
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@ferris1225/pi-subagents",
|
|
3
|
+
"version": "4.2.5",
|
|
4
|
+
"description": "A managed sub-agent team for pi: specialized roles, pre-commit documentation sync, retained threads, auto-fix chains, model fallback, and Git worktree isolation.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"pi-package",
|
|
12
|
+
"pi-extension",
|
|
13
|
+
"subagent",
|
|
14
|
+
"sub-agent",
|
|
15
|
+
"delegation",
|
|
16
|
+
"code-cleanup",
|
|
17
|
+
"dead-code"
|
|
18
|
+
],
|
|
19
|
+
"files": [
|
|
20
|
+
"src",
|
|
21
|
+
"agents",
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE"
|
|
24
|
+
],
|
|
25
|
+
"pi": {
|
|
26
|
+
"extensions": [
|
|
27
|
+
"./src/index.ts"
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"check": "tsc --noEmit",
|
|
32
|
+
"test": "vitest run tests",
|
|
33
|
+
"prepack": "npm run check && npm test"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@earendil-works/pi-agent-core": ">=0.84.4",
|
|
37
|
+
"@earendil-works/pi-ai": ">=0.84.4",
|
|
38
|
+
"@earendil-works/pi-coding-agent": ">=0.84.4",
|
|
39
|
+
"@earendil-works/pi-tui": ">=0.84.4",
|
|
40
|
+
"typebox": "*"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@earendil-works/pi-agent-core": "^0.84.4",
|
|
44
|
+
"@earendil-works/pi-ai": "^0.84.4",
|
|
45
|
+
"@earendil-works/pi-coding-agent": "^0.84.4",
|
|
46
|
+
"@earendil-works/pi-tui": "^0.84.4",
|
|
47
|
+
"@types/node": "^22.10.0",
|
|
48
|
+
"typebox": "^1.3.9",
|
|
49
|
+
"typescript": "^5.9.0",
|
|
50
|
+
"vitest": "^4.1.0"
|
|
51
|
+
},
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=22.19.0"
|
|
54
|
+
}
|
|
55
|
+
}
|
package/src/durable.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Durable thread state:
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* the OS temp
|
|
2
|
+
* Durable thread state: one manifest per project, inside that project's durable
|
|
3
|
+
* root beside its sessions and worktrees, letting interrupted (parked)
|
|
4
|
+
* sub-agent threads survive pi reloads and restarts. The durable state root
|
|
5
|
+
* also keeps their retained sessions and isolated worktrees out of the OS temp
|
|
6
|
+
* directory.
|
|
6
7
|
*
|
|
7
8
|
* Only parked threads are ever recorded: a thread that settles normally drops
|
|
8
|
-
* its record, so
|
|
9
|
-
*
|
|
9
|
+
* its record, so a manifest file exists exactly while unfinished work needs it
|
|
10
|
+
* and disappears on its own. Records are small path/state snapshots, never
|
|
10
11
|
* full transcripts; the retained Pi session files and worktrees they point at
|
|
11
12
|
* remain the actual context. Writes are atomic (tmp+rename) and serialized
|
|
12
13
|
* through the same withFileMutationQueue as the recovery manifest.
|
|
@@ -108,7 +109,15 @@ interface ThreadsManifest {
|
|
|
108
109
|
records: ThreadRecord[];
|
|
109
110
|
}
|
|
110
111
|
|
|
111
|
-
|
|
112
|
+
/** Each project's manifest lives inside its durable root, beside the sessions
|
|
113
|
+
* and worktrees its records point at. */
|
|
114
|
+
export function getThreadsManifestPath(configPath: string, cwd: string): string {
|
|
115
|
+
return join(getProjectRoot(configPath, cwd), THREADS_MANIFEST_FILE_NAME);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Location of the pre-per-project global manifest; only read by the
|
|
119
|
+
* one-time migration that folds it into the project roots. */
|
|
120
|
+
function getLegacyManifestPath(configPath: string): string {
|
|
112
121
|
return join(dirname(configPath), THREADS_MANIFEST_FILE_NAME);
|
|
113
122
|
}
|
|
114
123
|
|
|
@@ -181,9 +190,9 @@ function normalizeRecord(value: unknown): ThreadRecord | undefined {
|
|
|
181
190
|
};
|
|
182
191
|
}
|
|
183
192
|
|
|
184
|
-
|
|
193
|
+
async function readManifestRecords(path: string): Promise<ThreadRecord[]> {
|
|
185
194
|
try {
|
|
186
|
-
const parsed = JSON.parse(await readFile(
|
|
195
|
+
const parsed = JSON.parse(await readFile(path, "utf8")) as {
|
|
187
196
|
records?: unknown;
|
|
188
197
|
};
|
|
189
198
|
if (!Array.isArray(parsed.records)) return [];
|
|
@@ -196,8 +205,28 @@ export async function readThreadRecords(configPath: string): Promise<ThreadRecor
|
|
|
196
205
|
}
|
|
197
206
|
}
|
|
198
207
|
|
|
199
|
-
|
|
200
|
-
|
|
208
|
+
/** Manifest paths of every project that has a durable root. */
|
|
209
|
+
function projectManifestPaths(durableRoot: string): string[] {
|
|
210
|
+
try {
|
|
211
|
+
return readdirSync(durableRoot, { withFileTypes: true })
|
|
212
|
+
.filter((entry) => entry.isDirectory() && !entry.isSymbolicLink())
|
|
213
|
+
.map((entry) => join(durableRoot, entry.name, THREADS_MANIFEST_FILE_NAME));
|
|
214
|
+
} catch {
|
|
215
|
+
return [];
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Every parked record across all projects, for restore and the state-root
|
|
220
|
+
* sweeps that must see references from anywhere. */
|
|
221
|
+
export async function readThreadRecords(configPath: string): Promise<ThreadRecord[]> {
|
|
222
|
+
const manifests = await Promise.all(
|
|
223
|
+
projectManifestPaths(join(dirname(configPath), PROJECT_ROOTS_DIR_NAME))
|
|
224
|
+
.map((path) => readManifestRecords(path)),
|
|
225
|
+
);
|
|
226
|
+
return manifests.flat();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async function writeManifest(path: string, records: readonly ThreadRecord[]): Promise<void> {
|
|
201
230
|
if (records.length === 0) {
|
|
202
231
|
await rm(path, { force: true });
|
|
203
232
|
return;
|
|
@@ -217,26 +246,26 @@ async function writeManifest(configPath: string, records: readonly ThreadRecord[
|
|
|
217
246
|
}
|
|
218
247
|
|
|
219
248
|
export async function upsertThreadRecord(configPath: string, record: ThreadRecord): Promise<void> {
|
|
220
|
-
const path = getThreadsManifestPath(configPath);
|
|
249
|
+
const path = getThreadsManifestPath(configPath, record.cwd);
|
|
221
250
|
await withFileMutationQueue(path, async () => {
|
|
222
|
-
const records = await
|
|
251
|
+
const records = await readManifestRecords(path);
|
|
223
252
|
const index = records.findIndex((candidate) => candidate.runId === record.runId);
|
|
224
253
|
const merged: ThreadRecord = index === -1
|
|
225
254
|
? record
|
|
226
255
|
: { ...record, createdAt: records[index]!.createdAt };
|
|
227
256
|
if (index === -1) records.push(merged);
|
|
228
257
|
else records[index] = merged;
|
|
229
|
-
await writeManifest(
|
|
258
|
+
await writeManifest(path, records);
|
|
230
259
|
});
|
|
231
260
|
}
|
|
232
261
|
|
|
233
|
-
export async function removeThreadRecord(configPath: string, runId: number): Promise<void> {
|
|
234
|
-
const path = getThreadsManifestPath(configPath);
|
|
262
|
+
export async function removeThreadRecord(configPath: string, runId: number, cwd: string): Promise<void> {
|
|
263
|
+
const path = getThreadsManifestPath(configPath, cwd);
|
|
235
264
|
await withFileMutationQueue(path, async () => {
|
|
236
|
-
const records = await
|
|
265
|
+
const records = await readManifestRecords(path);
|
|
237
266
|
const next = records.filter((record) => record.runId !== runId);
|
|
238
267
|
if (next.length === records.length) return;
|
|
239
|
-
await writeManifest(
|
|
268
|
+
await writeManifest(path, next);
|
|
240
269
|
});
|
|
241
270
|
}
|
|
242
271
|
|
|
@@ -334,22 +363,65 @@ export async function pruneThreadRecords(
|
|
|
334
363
|
configPath: string,
|
|
335
364
|
now = Date.now(),
|
|
336
365
|
): Promise<void> {
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
366
|
+
const durableRoot = join(dirname(configPath), PROJECT_ROOTS_DIR_NAME);
|
|
367
|
+
for (const path of projectManifestPaths(durableRoot)) {
|
|
368
|
+
await withFileMutationQueue(path, async () => {
|
|
369
|
+
const records = await readManifestRecords(path);
|
|
370
|
+
if (records.length === 0) return;
|
|
371
|
+
let changed = false;
|
|
372
|
+
const kept: ThreadRecord[] = [];
|
|
373
|
+
for (const record of records) {
|
|
374
|
+
if (now - record.updatedAt <= PARKED_RECORD_MAX_AGE_MS) {
|
|
375
|
+
kept.push(record);
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
changed = true;
|
|
379
|
+
await discardRecordArtifacts(record);
|
|
347
380
|
}
|
|
348
|
-
changed
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
381
|
+
if (changed) await writeManifest(path, kept);
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/** One-time move of the pre-per-project global manifest beside the config into
|
|
387
|
+
* the project roots its records belong to, so an upgrade keeps parked work
|
|
388
|
+
* resumable and pi home is left without a manifest. Existing project records
|
|
389
|
+
* win over legacy ones; the legacy file is removed only after every group
|
|
390
|
+
* landed, and an unreadable file stays put for the next boot. */
|
|
391
|
+
export async function migrateLegacyThreadsManifest(configPath: string): Promise<void> {
|
|
392
|
+
const legacyPath = getLegacyManifestPath(configPath);
|
|
393
|
+
let records: ThreadRecord[];
|
|
394
|
+
try {
|
|
395
|
+
const parsed = JSON.parse(await readFile(legacyPath, "utf8")) as { records?: unknown };
|
|
396
|
+
if (!Array.isArray(parsed.records)) return;
|
|
397
|
+
records = parsed.records.flatMap((record) => {
|
|
398
|
+
const normalized = normalizeRecord(record);
|
|
399
|
+
return normalized ? [normalized] : [];
|
|
400
|
+
});
|
|
401
|
+
} catch {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
const groups = new Map<string, ThreadRecord[]>();
|
|
405
|
+
for (const record of records) {
|
|
406
|
+
const path = getThreadsManifestPath(configPath, record.cwd);
|
|
407
|
+
const group = groups.get(path);
|
|
408
|
+
if (group) group.push(record);
|
|
409
|
+
else groups.set(path, [record]);
|
|
410
|
+
}
|
|
411
|
+
let migrated = true;
|
|
412
|
+
for (const [path, group] of groups) {
|
|
413
|
+
await withFileMutationQueue(path, async () => {
|
|
414
|
+
const existing = await readManifestRecords(path);
|
|
415
|
+
const merged = [...existing];
|
|
416
|
+
for (const record of group) {
|
|
417
|
+
if (!merged.some((candidate) => candidate.runId === record.runId)) merged.push(record);
|
|
418
|
+
}
|
|
419
|
+
await writeManifest(path, merged);
|
|
420
|
+
}).catch(() => {
|
|
421
|
+
migrated = false;
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
if (migrated) await rm(legacyPath, { force: true }).catch(() => undefined);
|
|
353
425
|
}
|
|
354
426
|
|
|
355
427
|
/** Paths a manifest still references; used by the state-root sweep so
|
package/src/recovery.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { existsSync } from "node:fs";
|
|
|
5
5
|
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
7
7
|
import { stripVTControlCharacters } from "node:util";
|
|
8
|
-
import type
|
|
8
|
+
import { removeWorktreeGroup, worktreeGroupDir, type WorktreeFinalization } from "./worktree.ts";
|
|
9
9
|
|
|
10
10
|
export const RECOVERY_MANIFEST_FILE_NAME = "pi-subagents-recovery.json";
|
|
11
11
|
const RECOVERY_MANIFEST_VERSION = 1;
|
|
@@ -14,6 +14,8 @@ export interface RecoveryRecord {
|
|
|
14
14
|
runId: number;
|
|
15
15
|
createdAt: number;
|
|
16
16
|
integrated: boolean;
|
|
17
|
+
/** Repository a cleanup retry can prune stale worktree metadata against. */
|
|
18
|
+
originalRoot?: string;
|
|
17
19
|
worktreePath?: string;
|
|
18
20
|
patchPath?: string;
|
|
19
21
|
error?: string;
|
|
@@ -37,6 +39,7 @@ function normalizeRecord(value: unknown): RecoveryRecord | undefined {
|
|
|
37
39
|
runId: raw.runId,
|
|
38
40
|
createdAt: raw.createdAt,
|
|
39
41
|
integrated: raw.integrated === true,
|
|
42
|
+
...(typeof raw.originalRoot === "string" && raw.originalRoot ? { originalRoot: raw.originalRoot } : {}),
|
|
40
43
|
...(typeof raw.worktreePath === "string" && raw.worktreePath ? { worktreePath: raw.worktreePath } : {}),
|
|
41
44
|
...(typeof raw.patchPath === "string" && raw.patchPath ? { patchPath: raw.patchPath } : {}),
|
|
42
45
|
...(typeof raw.error === "string" && raw.error ? { error: raw.error } : {}),
|
|
@@ -105,6 +108,7 @@ export function recoveryRecordFromFinalization(
|
|
|
105
108
|
runId,
|
|
106
109
|
createdAt: now,
|
|
107
110
|
integrated: finalization.integrated,
|
|
111
|
+
...(finalization.originalRoot ? { originalRoot: finalization.originalRoot } : {}),
|
|
108
112
|
...(finalization.worktreePath ? { worktreePath: finalization.worktreePath } : {}),
|
|
109
113
|
...(finalization.patchPath ? { patchPath: finalization.patchPath } : {}),
|
|
110
114
|
...(finalization.error ? { error: finalization.error } : {}),
|
|
@@ -112,7 +116,10 @@ export function recoveryRecordFromFinalization(
|
|
|
112
116
|
}
|
|
113
117
|
|
|
114
118
|
/** Show retained recovery paths on every later session start until the user
|
|
115
|
-
* removes the artifacts.
|
|
119
|
+
* removes the artifacts. Records whose changes already landed only need the
|
|
120
|
+
* worktree group deleted — the step whose failure retained them — so each
|
|
121
|
+
* session start retries that removal first and forgets records it completes.
|
|
122
|
+
* Stale records are pruned automatically. */
|
|
116
123
|
export async function announceRecoveryRecords(
|
|
117
124
|
configPath: string,
|
|
118
125
|
ctx: {
|
|
@@ -123,6 +130,17 @@ export async function announceRecoveryRecords(
|
|
|
123
130
|
if (ctx.hasUI === false) return;
|
|
124
131
|
const records = await readRecoveryRecords(configPath);
|
|
125
132
|
if (records.length === 0) return;
|
|
133
|
+
for (const record of records) {
|
|
134
|
+
if (!record.integrated || !record.worktreePath) continue;
|
|
135
|
+
const groupDir = worktreeGroupDir(record.worktreePath);
|
|
136
|
+
if (!groupDir) continue;
|
|
137
|
+
if (!existsSync(record.worktreePath) && !(record.patchPath ? existsSync(record.patchPath) : false)) continue;
|
|
138
|
+
await removeWorktreeGroup({
|
|
139
|
+
originalRoot: record.originalRoot,
|
|
140
|
+
worktreePath: record.worktreePath,
|
|
141
|
+
tempDir: groupDir,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
126
144
|
const live = records.filter((record) =>
|
|
127
145
|
(record.worktreePath ? existsSync(record.worktreePath) : false) ||
|
|
128
146
|
(record.patchPath ? existsSync(record.patchPath) : false),
|