@cydm/pie 2.6.6 → 2.6.8

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.
@@ -1,9 +1,9 @@
1
1
  import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
2
  import {
3
3
  createAskUserCapability
4
- } from "../../../chunks/chunk-H6BAM2N4.js";
5
- import "../../../chunks/chunk-KEARZX6F.js";
6
- import "../../../chunks/chunk-G5B6D35K.js";
4
+ } from "../../../chunks/chunk-DFWWHVJL.js";
5
+ import "../../../chunks/chunk-PAC62XU4.js";
6
+ import "../../../chunks/chunk-7B4IIBVS.js";
7
7
  import "../../../chunks/chunk-XXGYUYXI.js";
8
8
  import "../../../chunks/chunk-LZQXQOPJ.js";
9
9
 
@@ -24461,6 +24461,47 @@ function redactSecrets(text, secrets) {
24461
24461
  }
24462
24462
  return sanitized;
24463
24463
  }
24464
+ function escapeRegExpLiteral(literal2) {
24465
+ return literal2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
24466
+ }
24467
+ function percentEncodeAll(secret) {
24468
+ return [...Buffer.from(secret, "utf-8")].map((byte) => `%${byte.toString(16).toUpperCase().padStart(2, "0")}`).join("");
24469
+ }
24470
+ function shortEncodedSecretTokens(secrets) {
24471
+ const tokens = { base64: [], percent: [] };
24472
+ for (const secret of secrets) {
24473
+ try {
24474
+ const base64url2 = Buffer.from(secret, "utf-8").toString("base64url");
24475
+ if (base64url2 !== secret && base64url2.length > 0 && base64url2.length < 4) tokens.base64.push(base64url2);
24476
+ } catch {
24477
+ }
24478
+ try {
24479
+ const urlEncoded = encodeURIComponent(secret);
24480
+ if (urlEncoded !== secret && urlEncoded.length > 0 && urlEncoded.length < 4 && /%[0-9A-F]{2}/i.test(urlEncoded)) {
24481
+ tokens.percent.push(urlEncoded);
24482
+ }
24483
+ const percentAll = percentEncodeAll(secret);
24484
+ if (percentAll !== secret && percentAll.length > 0 && percentAll.length < 4) tokens.percent.push(percentAll);
24485
+ } catch {
24486
+ }
24487
+ }
24488
+ return tokens;
24489
+ }
24490
+ function collectPatternSpans(text, pattern, spans) {
24491
+ for (const match of text.matchAll(new RegExp(`(?=(${pattern.source}))`, pattern.flags))) {
24492
+ spans.push({ start: match.index, end: match.index + match[1].length });
24493
+ }
24494
+ }
24495
+ function collectShortEncodedTokens(text, secrets, spans) {
24496
+ const { base64: base642, percent } = shortEncodedSecretTokens(secrets);
24497
+ for (const token of base642) {
24498
+ const padding = "=".repeat((4 - token.length % 4) % 4);
24499
+ collectPatternSpans(text, new RegExp(`(?<![A-Za-z0-9+/_-])${escapeRegExpLiteral(token)}(?:${padding})?(?![A-Za-z0-9+/=_-])`, "g"), spans);
24500
+ }
24501
+ for (const token of percent) {
24502
+ collectPatternSpans(text, new RegExp(`(?<![%A-Za-z0-9])${escapeRegExpLiteral(token)}(?![%A-Za-z0-9])`, "gi"), spans);
24503
+ }
24504
+ }
24464
24505
  function encodedSecretVariants(secrets) {
24465
24506
  const variants = [...secrets];
24466
24507
  for (const secret of secrets) {
@@ -24475,7 +24516,7 @@ function encodedSecretVariants(secrets) {
24475
24516
  }
24476
24517
  try {
24477
24518
  const urlEncoded = encodeURIComponent(secret);
24478
- if (urlEncoded !== secret && urlEncoded.length >= 4) {
24519
+ if (urlEncoded !== secret && urlEncoded.length >= 4 && urlEncoded !== percentEncodeAll(secret)) {
24479
24520
  variants.push(urlEncoded);
24480
24521
  const urlLowerHex = urlEncoded.replace(/%[0-9A-F]{2}/g, (m) => `%${m.slice(1).toLowerCase()}`);
24481
24522
  if (urlLowerHex !== urlEncoded) variants.push(urlLowerHex);
@@ -24517,9 +24558,9 @@ function hexSecretVariants(secrets) {
24517
24558
  return variants;
24518
24559
  }
24519
24560
  var MIN_SECRET_FRAGMENT = 8;
24520
- function redactSecretFragments(text, secrets) {
24561
+ function collectSecretFragments(text, secrets, spans) {
24521
24562
  const candidates = secrets.filter((secret) => secret.length >= MIN_SECRET_FRAGMENT);
24522
- if (candidates.length === 0 || text.length < MIN_SECRET_FRAGMENT) return text;
24563
+ if (candidates.length === 0 || text.length < MIN_SECRET_FRAGMENT) return;
24523
24564
  const grams = /* @__PURE__ */ new Map();
24524
24565
  for (const secret of candidates) {
24525
24566
  for (let offset = 0; offset + MIN_SECRET_FRAGMENT <= secret.length; offset++) {
@@ -24529,9 +24570,7 @@ function redactSecretFragments(text, secrets) {
24529
24570
  else grams.set(gram, [{ secret, offset }]);
24530
24571
  }
24531
24572
  }
24532
- let out = "";
24533
- let index = 0;
24534
- while (index < text.length) {
24573
+ for (let index = 0; index < text.length; index++) {
24535
24574
  const bucket = grams.get(text.slice(index, index + MIN_SECRET_FRAGMENT));
24536
24575
  let best = 0;
24537
24576
  if (bucket) {
@@ -24542,61 +24581,78 @@ function redactSecretFragments(text, secrets) {
24542
24581
  if (length > best) best = length;
24543
24582
  }
24544
24583
  }
24545
- if (best > 0) {
24546
- out += "[redacted]";
24547
- index += best;
24548
- } else {
24549
- out += text[index];
24550
- index += 1;
24551
- }
24584
+ if (best > 0) spans.push({ start: index, end: index + best });
24552
24585
  }
24553
- return out;
24554
24586
  }
24555
- function redactLeadingSecretSuffix(text, secrets) {
24556
- if (secrets.length === 0 || text.length === 0) return text;
24587
+ function collectLeadingSecretSuffix(text, secrets, spans) {
24588
+ if (secrets.length === 0 || text.length === 0) return;
24557
24589
  for (const secret of secrets) {
24558
24590
  for (let keep = Math.min(secret.length - 1, text.length); keep >= 4; keep--) {
24559
24591
  if (text.startsWith(secret.slice(secret.length - keep))) {
24560
- return `[redacted]${text.slice(keep)}`;
24592
+ spans.push({ start: 0, end: keep });
24593
+ break;
24561
24594
  }
24562
24595
  }
24563
24596
  }
24564
- return text;
24565
24597
  }
24566
- function redactTrailingSecretPrefix(text, secrets) {
24567
- if (secrets.length === 0 || text.length === 0) return text;
24598
+ function collectTrailingSecretPrefix(text, secrets, spans) {
24599
+ if (secrets.length === 0 || text.length === 0) return;
24568
24600
  for (const secret of secrets) {
24569
24601
  for (let keep = Math.min(secret.length - 1, text.length); keep >= 4; keep--) {
24570
24602
  if (text.endsWith(secret.slice(0, keep))) {
24571
- return `${text.slice(0, text.length - keep)}[redacted]`;
24603
+ spans.push({ start: text.length - keep, end: text.length });
24604
+ break;
24572
24605
  }
24573
24606
  }
24574
24607
  }
24575
- return text;
24576
24608
  }
24577
24609
  function redactAllSecrets(text, secrets) {
24578
24610
  const encoded = encodedSecretVariants(secrets);
24579
24611
  const wholeVariants = [...encoded, ...hexSecretVariants(secrets), ...unicodeEscapeVariants(secrets)];
24580
- return redactTrailingSecretPrefix(
24581
- redactLeadingSecretSuffix(
24582
- redactCaseInsensitiveEncodedSecrets(
24583
- redactSecretFragments(redactSecrets(text, wholeVariants), encoded),
24584
- secrets
24585
- ),
24586
- encoded
24587
- ),
24588
- encoded
24589
- );
24612
+ const spans = [];
24613
+ for (const variant of new Set(wholeVariants)) {
24614
+ if (!variant) continue;
24615
+ for (let start = text.indexOf(variant); start !== -1; start = text.indexOf(variant, start + 1)) {
24616
+ spans.push({ start, end: start + variant.length });
24617
+ }
24618
+ }
24619
+ collectCaseInsensitiveEncodedSecrets(text, secrets, spans);
24620
+ collectShortEncodedTokens(text, secrets, spans);
24621
+ collectSecretFragments(text, encoded, spans);
24622
+ collectLeadingSecretSuffix(text, encoded, spans);
24623
+ collectTrailingSecretPrefix(text, encoded, spans);
24624
+ spans.sort((a, b) => a.start - b.start || b.end - a.end);
24625
+ const merged = [];
24626
+ for (const span of spans) {
24627
+ const previous = merged[merged.length - 1];
24628
+ if (previous && span.start < previous.end) previous.end = Math.max(previous.end, span.end);
24629
+ else merged.push({ ...span });
24630
+ }
24631
+ let out = "";
24632
+ let cursor = 0;
24633
+ for (const { start, end } of merged) {
24634
+ out += text.slice(cursor, start) + "[redacted]";
24635
+ cursor = end;
24636
+ }
24637
+ return out + text.slice(cursor);
24590
24638
  }
24591
- function redactCaseInsensitiveEncodedSecrets(text, secrets) {
24592
- const escapeRegExpLiteral = (literal2) => literal2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
24593
- let sanitized = text;
24639
+ function collectCaseInsensitiveEncodedSecrets(text, secrets, spans) {
24594
24640
  for (const secret of secrets) {
24595
24641
  const patterns = [];
24596
24642
  try {
24597
24643
  const urlEncoded = encodeURIComponent(secret);
24598
24644
  if (urlEncoded !== secret && urlEncoded.length >= 4 && /%[0-9A-F]{2}/i.test(urlEncoded)) {
24599
- patterns.push(urlEncoded);
24645
+ const urlPattern = urlEncoded.split(/(%[0-9A-F]{2})/g).map(
24646
+ (part) => /^%[0-9A-F]{2}$/.test(part) ? part.replace(/[A-F]/g, (digit) => `[${digit}${digit.toLowerCase()}]`) : escapeRegExpLiteral(part)
24647
+ ).join("");
24648
+ collectPatternSpans(text, new RegExp(urlPattern, "g"), spans);
24649
+ }
24650
+ } catch {
24651
+ }
24652
+ try {
24653
+ const percentAll = percentEncodeAll(secret);
24654
+ if (percentAll !== secret && percentAll.length >= 4 && /%[0-9A-F]{2}/i.test(percentAll)) {
24655
+ patterns.push(percentAll);
24600
24656
  }
24601
24657
  } catch {
24602
24658
  }
@@ -24610,10 +24666,9 @@ function redactCaseInsensitiveEncodedSecrets(text, secrets) {
24610
24666
  const escaped = unicodeEscapeAll(secret);
24611
24667
  if (escaped !== secret && escaped.length >= 4) patterns.push(escaped);
24612
24668
  for (const pattern of patterns) {
24613
- sanitized = sanitized.replace(new RegExp(escapeRegExpLiteral(pattern), "gi"), "[redacted]");
24669
+ collectPatternSpans(text, new RegExp(escapeRegExpLiteral(pattern), "gi"), spans);
24614
24670
  }
24615
24671
  }
24616
- return sanitized;
24617
24672
  }
24618
24673
  function redactStderrTail(tail, secrets) {
24619
24674
  return redactAllSecrets(tail, secrets);
@@ -7,9 +7,9 @@ import {
7
7
  isPlanModeSafeCommand,
8
8
  markCompletedPlanSteps,
9
9
  restoreExecutionState
10
- } from "../../../chunks/chunk-H6BAM2N4.js";
11
- import "../../../chunks/chunk-KEARZX6F.js";
12
- import "../../../chunks/chunk-G5B6D35K.js";
10
+ } from "../../../chunks/chunk-DFWWHVJL.js";
11
+ import "../../../chunks/chunk-PAC62XU4.js";
12
+ import "../../../chunks/chunk-7B4IIBVS.js";
13
13
  import {
14
14
  Type
15
15
  } from "../../../chunks/chunk-XXGYUYXI.js";
@@ -1,20 +1,372 @@
1
1
  import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
2
  import {
3
3
  createCliHostCapabilities
4
- } from "../../../chunks/chunk-OWSXALCG.js";
4
+ } from "../../../chunks/chunk-YUVGEY6X.js";
5
5
  import {
6
+ SUBAGENT_SESSION_RECORD_VERSION,
6
7
  createSharedFileSystemTools,
7
8
  createSubagentCapability
8
- } from "../../../chunks/chunk-H6BAM2N4.js";
9
- import "../../../chunks/chunk-KEARZX6F.js";
10
- import "../../../chunks/chunk-G5B6D35K.js";
9
+ } from "../../../chunks/chunk-DFWWHVJL.js";
10
+ import "../../../chunks/chunk-PAC62XU4.js";
11
+ import "../../../chunks/chunk-7B4IIBVS.js";
11
12
  import "../../../chunks/chunk-5353DEEC.js";
12
13
  import "../../../chunks/chunk-XXGYUYXI.js";
13
14
  import "../../../chunks/chunk-LZQXQOPJ.js";
14
15
 
16
+ // src/files/subagent-session-store.ts
17
+ import { execFileSync } from "node:child_process";
18
+ import fs from "node:fs";
19
+ import * as path from "node:path";
20
+ var MAX_RECORD_BYTES = 4 * 1024 * 1024;
21
+ function isSafeSessionId(id) {
22
+ return /^[A-Za-z0-9._-]+$/.test(id);
23
+ }
24
+ var RECLAIM_ORPHAN_MS = 6e4;
25
+ var MALFORMED_LOCK_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
26
+ function defaultReadProcessStartTime(pid) {
27
+ if (!Number.isInteger(pid) || pid <= 0) return void 0;
28
+ if (process.platform === "win32") {
29
+ try {
30
+ const output = execFileSync(
31
+ "powershell",
32
+ [
33
+ "-NoProfile",
34
+ "-NonInteractive",
35
+ "-Command",
36
+ `$p = Get-CimInstance Win32_Process -Filter "ProcessId=${pid}"; if ($p) { $p.CreationDate.ToString('o') }`
37
+ ],
38
+ { encoding: "utf-8", timeout: 4e3 }
39
+ );
40
+ const line = output.split(/\r?\n/).find((entry) => entry.trim().length > 0);
41
+ return line?.trim();
42
+ } catch {
43
+ return void 0;
44
+ }
45
+ }
46
+ try {
47
+ const output = execFileSync("ps", ["-p", String(pid), "-o", "lstart="], {
48
+ encoding: "utf-8",
49
+ timeout: 1e3,
50
+ env: { ...process.env, LC_ALL: "C", LANG: "C", TZ: "UTC" }
51
+ });
52
+ const trimmed = output.trim();
53
+ return trimmed.length > 0 ? trimmed : void 0;
54
+ } catch {
55
+ return void 0;
56
+ }
57
+ }
58
+ function leaseOwnerAlive(owner, readStartTime) {
59
+ if (!Number.isInteger(owner.pid) || owner.pid <= 0) return void 0;
60
+ try {
61
+ process.kill(owner.pid, 0);
62
+ } catch (error) {
63
+ if (error.code === "ESRCH") return false;
64
+ }
65
+ if (typeof owner.startedAt !== "string" || owner.startedAt.length === 0) {
66
+ return true;
67
+ }
68
+ const current = readStartTime(owner.pid);
69
+ return current === void 0 || current === owner.startedAt;
70
+ }
71
+ function publishLinkedExclusive(targetPath, content, stagingToken) {
72
+ const staging = `${targetPath}.stage-${stagingToken}`;
73
+ let staged = false;
74
+ try {
75
+ fs.writeFileSync(staging, content, { mode: 384, flag: "wx" });
76
+ staged = true;
77
+ fs.linkSync(staging, targetPath);
78
+ } finally {
79
+ if (staged) {
80
+ try {
81
+ fs.unlinkSync(staging);
82
+ } catch {
83
+ }
84
+ }
85
+ }
86
+ }
87
+ function publishReclaimMarker(reclaimPath, marker) {
88
+ publishLinkedExclusive(reclaimPath, JSON.stringify(marker), marker.token);
89
+ }
90
+ function reclaimMarkerOrphaned(content, mtimeMs, now, readStartTime) {
91
+ try {
92
+ const marker = JSON.parse(content);
93
+ const alive = leaseOwnerAlive(marker, readStartTime);
94
+ if (alive !== void 0) return !alive;
95
+ } catch {
96
+ }
97
+ return now() - mtimeMs > RECLAIM_ORPHAN_MS;
98
+ }
99
+ function removeOrphanedReclaimMarker(reclaimPath, now, readStartTime) {
100
+ let content;
101
+ let mtimeMs;
102
+ try {
103
+ content = fs.readFileSync(reclaimPath, "utf8");
104
+ mtimeMs = fs.statSync(reclaimPath).mtimeMs;
105
+ } catch {
106
+ return true;
107
+ }
108
+ if (!reclaimMarkerOrphaned(content, mtimeMs, now, readStartTime)) return false;
109
+ let current;
110
+ try {
111
+ current = fs.readFileSync(reclaimPath, "utf8");
112
+ } catch {
113
+ current = void 0;
114
+ }
115
+ if (current !== content) return false;
116
+ try {
117
+ fs.unlinkSync(reclaimPath);
118
+ } catch {
119
+ }
120
+ return true;
121
+ }
122
+ function createSubagentSessionStore(sessionsDir, log, deps) {
123
+ const now = deps?.now ?? Date.now;
124
+ const readStartTime = deps?.readProcessStartTime ?? defaultReadProcessStartTime;
125
+ let selfStartTime;
126
+ let selfStartTimeRead = false;
127
+ const ownStartTime = () => {
128
+ if (!selfStartTimeRead) {
129
+ selfStartTime = readStartTime(process.pid);
130
+ selfStartTimeRead = true;
131
+ }
132
+ return selfStartTime;
133
+ };
134
+ const recordPath = (id) => {
135
+ if (!isSafeSessionId(id)) return void 0;
136
+ const filePath = path.join(sessionsDir, `${id}.json`);
137
+ return path.dirname(filePath) === sessionsDir ? filePath : void 0;
138
+ };
139
+ const recordLeaseHeld = (filePath) => {
140
+ let markerStat;
141
+ let markerContent;
142
+ try {
143
+ markerStat = fs.statSync(`${filePath}.lock.reclaim`);
144
+ markerContent = fs.readFileSync(`${filePath}.lock.reclaim`, "utf8");
145
+ } catch (error) {
146
+ const code = error.code;
147
+ if (code === "ENOENT") {
148
+ } else return true;
149
+ }
150
+ if (markerContent !== void 0 && !reclaimMarkerOrphaned(markerContent, markerStat.mtimeMs, now, readStartTime)) {
151
+ return true;
152
+ }
153
+ let lockStat;
154
+ let content;
155
+ try {
156
+ lockStat = fs.statSync(`${filePath}.lock`);
157
+ content = fs.readFileSync(`${filePath}.lock`, "utf8");
158
+ } catch (error) {
159
+ const code = error.code;
160
+ if (code === "ENOENT") return false;
161
+ return true;
162
+ }
163
+ try {
164
+ const lock = JSON.parse(content);
165
+ const alive = leaseOwnerAlive(lock, readStartTime);
166
+ if (alive !== void 0) return alive;
167
+ } catch {
168
+ }
169
+ return now() - lockStat.mtimeMs <= MALFORMED_LOCK_MAX_AGE_MS;
170
+ };
171
+ const acquireLease = (id) => {
172
+ const filePath = recordPath(id);
173
+ if (!filePath) throw new Error(`Invalid subagent session id "${id}"`);
174
+ const lockPath = `${filePath}.lock`;
175
+ fs.mkdirSync(sessionsDir, { recursive: true });
176
+ const token = `${process.pid}-${now()}-${Math.random()}`;
177
+ for (let attempt = 0; attempt < 2; attempt++) {
178
+ try {
179
+ const owner = { pid: process.pid, startedAt: ownStartTime(), timestamp: now(), token };
180
+ publishLinkedExclusive(lockPath, JSON.stringify(owner), token);
181
+ return () => {
182
+ try {
183
+ if (JSON.parse(fs.readFileSync(lockPath, "utf8")).token === token) fs.unlinkSync(lockPath);
184
+ } catch {
185
+ }
186
+ };
187
+ } catch (error) {
188
+ if (error.code !== "EEXIST") throw error;
189
+ const reclaimPath = `${lockPath}.reclaim`;
190
+ const publishMarker = () => {
191
+ publishReclaimMarker(reclaimPath, { pid: process.pid, startedAt: ownStartTime(), token });
192
+ };
193
+ try {
194
+ publishMarker();
195
+ } catch (reclaimError) {
196
+ if (reclaimError.code !== "EEXIST") throw reclaimError;
197
+ if (!removeOrphanedReclaimMarker(reclaimPath, now, readStartTime)) {
198
+ throw new Error(`Subagent session "${id}" is busy (session lease held by another process).`);
199
+ }
200
+ try {
201
+ publishMarker();
202
+ } catch {
203
+ throw new Error(`Subagent session "${id}" is busy (session lease held by another process).`);
204
+ }
205
+ }
206
+ try {
207
+ const stat = fs.statSync(lockPath);
208
+ let lockContent;
209
+ try {
210
+ lockContent = fs.readFileSync(lockPath, "utf8");
211
+ } catch (readError) {
212
+ if (readError.code === "ENOENT") continue;
213
+ throw new Error(`Subagent session "${id}" is busy (session lease is unreadable).`);
214
+ }
215
+ let stale = false;
216
+ try {
217
+ const lock = JSON.parse(lockContent);
218
+ const alive = leaseOwnerAlive(lock, readStartTime);
219
+ stale = alive === void 0 ? now() - stat.mtimeMs > MALFORMED_LOCK_MAX_AGE_MS : !alive;
220
+ } catch {
221
+ stale = now() - stat.mtimeMs > MALFORMED_LOCK_MAX_AGE_MS;
222
+ }
223
+ if (!stale) throw new Error(`Subagent session "${id}" is busy (session lease held by another process).`);
224
+ let current;
225
+ try {
226
+ current = fs.readFileSync(lockPath, "utf8");
227
+ } catch {
228
+ current = void 0;
229
+ }
230
+ if (current !== lockContent) {
231
+ throw new Error(`Subagent session "${id}" is busy (session lease changed during reclamation).`);
232
+ }
233
+ try {
234
+ fs.unlinkSync(lockPath);
235
+ } catch {
236
+ }
237
+ } finally {
238
+ try {
239
+ if (JSON.parse(fs.readFileSync(reclaimPath, "utf8")).token === token) fs.unlinkSync(reclaimPath);
240
+ } catch {
241
+ }
242
+ }
243
+ }
244
+ }
245
+ throw new Error(`Subagent session "${id}" is busy (could not acquire session lease).`);
246
+ };
247
+ return {
248
+ acquire(id) {
249
+ return acquireLease(id);
250
+ },
251
+ load(id) {
252
+ const filePath = recordPath(id);
253
+ if (!filePath) return void 0;
254
+ let content;
255
+ try {
256
+ content = fs.readFileSync(filePath, "utf8");
257
+ } catch (error) {
258
+ const code = error.code;
259
+ if (code === "ENOENT" || code === "ENOTDIR") return void 0;
260
+ throw error;
261
+ }
262
+ try {
263
+ const parsed = JSON.parse(content);
264
+ if (parsed && typeof parsed === "object" && typeof parsed.version === "number" && parsed.version !== SUBAGENT_SESSION_RECORD_VERSION) {
265
+ throw new Error(`session record version ${parsed.version} is not readable by this build (supported version ${SUBAGENT_SESSION_RECORD_VERSION}); it may be history written by a newer Pie`);
266
+ }
267
+ if (!parsed || typeof parsed !== "object" || parsed.version !== SUBAGENT_SESSION_RECORD_VERSION || parsed.id !== id || !Array.isArray(parsed.messages)) {
268
+ return void 0;
269
+ }
270
+ return parsed;
271
+ } catch (error) {
272
+ if (error instanceof SyntaxError) {
273
+ return void 0;
274
+ }
275
+ throw error;
276
+ }
277
+ },
278
+ save(record) {
279
+ const filePath = recordPath(record.id);
280
+ if (!filePath) return false;
281
+ try {
282
+ const payload = JSON.stringify(record);
283
+ if (Buffer.byteLength(payload, "utf8") > MAX_RECORD_BYTES) {
284
+ log?.(`[subagent-sessions] skipping persist of ${record.id}: over the ${MAX_RECORD_BYTES}-byte cap`);
285
+ return false;
286
+ }
287
+ fs.mkdirSync(sessionsDir, { recursive: true });
288
+ const tmpPath = `${filePath}.tmp-${process.pid}`;
289
+ try {
290
+ const fd = fs.openSync(tmpPath, "w", 384);
291
+ try {
292
+ fs.writeFileSync(fd, payload);
293
+ fs.fsyncSync(fd);
294
+ } finally {
295
+ fs.closeSync(fd);
296
+ }
297
+ fs.renameSync(tmpPath, filePath);
298
+ } catch (error) {
299
+ try {
300
+ fs.unlinkSync(tmpPath);
301
+ } catch {
302
+ }
303
+ throw error;
304
+ }
305
+ try {
306
+ const dirFd = fs.openSync(sessionsDir, "r");
307
+ try {
308
+ fs.fsyncSync(dirFd);
309
+ } finally {
310
+ fs.closeSync(dirFd);
311
+ }
312
+ } catch (error) {
313
+ const code = error.code;
314
+ if (!(code === "EISDIR" || code === "EPERM" || code === "EINVAL" || code === "ENOTSUP" || code === "EOPNOTSUPP" || code === "EBUSY")) {
315
+ throw error;
316
+ }
317
+ }
318
+ return true;
319
+ } catch (error) {
320
+ log?.(`[subagent-sessions] persist of ${record.id} failed: ${error instanceof Error ? error.message : String(error)}`);
321
+ return false;
322
+ }
323
+ },
324
+ sweep({ maxAgeMs, maxRecords }) {
325
+ try {
326
+ if (!fs.existsSync(sessionsDir)) return;
327
+ const nowMs = now();
328
+ const entries = fs.readdirSync(sessionsDir).filter((file) => file.endsWith(".json")).map((file) => {
329
+ try {
330
+ const stat = fs.statSync(path.join(sessionsDir, file));
331
+ return { file, mtimeMs: stat.mtimeMs, dev: stat.dev, ino: stat.ino };
332
+ } catch {
333
+ return { file, mtimeMs: 0, dev: -1, ino: -1 };
334
+ }
335
+ }).sort((a, b) => b.mtimeMs - a.mtimeMs);
336
+ entries.forEach((entry, index) => {
337
+ if (index < maxRecords && nowMs - entry.mtimeMs <= maxAgeMs) return;
338
+ const filePath = path.join(sessionsDir, entry.file);
339
+ if (recordLeaseHeld(filePath)) return;
340
+ let release;
341
+ try {
342
+ release = acquireLease(entry.file.slice(0, -".json".length));
343
+ } catch {
344
+ return;
345
+ }
346
+ try {
347
+ try {
348
+ const stat = fs.statSync(filePath);
349
+ if (stat.dev !== entry.dev || stat.ino !== entry.ino || stat.mtimeMs !== entry.mtimeMs) return;
350
+ } catch {
351
+ return;
352
+ }
353
+ try {
354
+ fs.unlinkSync(filePath);
355
+ } catch {
356
+ }
357
+ } finally {
358
+ void Promise.resolve(release()).catch(() => void 0);
359
+ }
360
+ });
361
+ } catch {
362
+ }
363
+ }
364
+ };
365
+ }
366
+
15
367
  // builtin/extensions/subagent/index.ts
16
368
  import * as os from "os";
17
- import * as path from "path";
369
+ import * as path2 from "path";
18
370
  function subagentExtension(ctx) {
19
371
  ctx.log("Subagent extension loaded (transparent parallel mode)");
20
372
  const resolveRuntimePair = () => {
@@ -25,6 +377,10 @@ function subagentExtension(ctx) {
25
377
  };
26
378
  const skills = ctx.skills || [];
27
379
  const capability = createSubagentCapability({
380
+ // Persisted alongside the CLI's own session state: a sessionId from an
381
+ // earlier result stays recallable across restarts and /resume. Tools and
382
+ // credentials are re-resolved at recall time, never persisted.
383
+ sessionStore: createSubagentSessionStore(path2.join(os.homedir(), ".pie", "subagent-sessions"), (message) => ctx.log(message)),
28
384
  get apiKey() {
29
385
  return resolveRuntimePair().apiKey;
30
386
  },
@@ -47,7 +403,7 @@ function subagentExtension(ctx) {
47
403
  if (mode === "edit") {
48
404
  throw new Error("Edit delegation requires the parent's executable runtime tools; fallback tools are read-mode only.");
49
405
  }
50
- const configDir = path.join(os.homedir(), ".pie");
406
+ const configDir = path2.join(os.homedir(), ".pie");
51
407
  const fsOptions = ctx.yoloMode ? { allowlistedDirs: ["/"] } : {
52
408
  accessRoots: [
53
409
  ctx.cwd,
@@ -8,9 +8,9 @@ import {
8
8
  executeManageTodoList,
9
9
  executionStateToTodos,
10
10
  restoreExecutionState
11
- } from "../../../chunks/chunk-H6BAM2N4.js";
12
- import "../../../chunks/chunk-KEARZX6F.js";
13
- import "../../../chunks/chunk-G5B6D35K.js";
11
+ } from "../../../chunks/chunk-DFWWHVJL.js";
12
+ import "../../../chunks/chunk-PAC62XU4.js";
13
+ import "../../../chunks/chunk-7B4IIBVS.js";
14
14
  import "../../../chunks/chunk-XXGYUYXI.js";
15
15
  import "../../../chunks/chunk-LZQXQOPJ.js";
16
16
 
@@ -21,6 +21,6 @@ function tryFastPath(argv, options) {
21
21
 
22
22
  // vendor/chrome-devtools-axi/bin/chrome-devtools-axi.ts
23
23
  if (!tryFastPath(process.argv.slice(2), { version: VERSION })) {
24
- const { main } = await import("./cli-QAILYGLM.js");
24
+ const { main } = await import("./cli-4ITIR57L.js");
25
25
  await main(process.argv.slice(2));
26
26
  }