@cydm/pie 2.6.5 → 2.6.7

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-QKSK5K5N.js";
5
- import "../../../chunks/chunk-AETQBDEG.js";
6
- import "../../../chunks/chunk-5OXH6NUH.js";
4
+ } from "../../../chunks/chunk-T6GMEDIE.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,10 +24461,50 @@ 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) {
24467
- if (secret.length < 4) continue;
24468
24508
  try {
24469
24509
  const base642 = Buffer.from(secret, "utf-8").toString("base64");
24470
24510
  if (base642.length >= 4) {
@@ -24472,13 +24512,39 @@ function encodedSecretVariants(secrets) {
24472
24512
  const base64url2 = Buffer.from(secret, "utf-8").toString("base64url");
24473
24513
  if (base64url2 !== base642 && base64url2.length >= 4) variants.push(base64url2);
24474
24514
  }
24515
+ } catch {
24516
+ }
24517
+ try {
24475
24518
  const urlEncoded = encodeURIComponent(secret);
24476
- if (urlEncoded !== secret && urlEncoded.length >= 4) variants.push(urlEncoded);
24519
+ if (urlEncoded !== secret && urlEncoded.length >= 4 && urlEncoded !== percentEncodeAll(secret)) {
24520
+ variants.push(urlEncoded);
24521
+ const urlLowerHex = urlEncoded.replace(/%[0-9A-F]{2}/g, (m) => `%${m.slice(1).toLowerCase()}`);
24522
+ if (urlLowerHex !== urlEncoded) variants.push(urlLowerHex);
24523
+ }
24477
24524
  } catch {
24478
24525
  }
24479
24526
  }
24480
24527
  return variants;
24481
24528
  }
24529
+ function unicodeEscapeAll(text) {
24530
+ let out = "";
24531
+ for (let index = 0; index < text.length; index++) {
24532
+ out += `\\u${text.charCodeAt(index).toString(16).padStart(4, "0")}`;
24533
+ }
24534
+ return out;
24535
+ }
24536
+ function unicodeEscapeVariants(secrets) {
24537
+ const variants = [];
24538
+ for (const secret of secrets) {
24539
+ const escaped = unicodeEscapeAll(secret);
24540
+ if (escaped !== secret && escaped.length >= 4) {
24541
+ variants.push(escaped);
24542
+ const escapedUpper = escaped.replace(/[0-9a-f]{4}/g, (m) => m.toUpperCase());
24543
+ if (escapedUpper !== escaped) variants.push(escapedUpper);
24544
+ }
24545
+ }
24546
+ return variants;
24547
+ }
24482
24548
  function hexSecretVariants(secrets) {
24483
24549
  const variants = [];
24484
24550
  for (const secret of secrets) {
@@ -24492,9 +24558,9 @@ function hexSecretVariants(secrets) {
24492
24558
  return variants;
24493
24559
  }
24494
24560
  var MIN_SECRET_FRAGMENT = 8;
24495
- function redactSecretFragments(text, secrets) {
24561
+ function collectSecretFragments(text, secrets, spans) {
24496
24562
  const candidates = secrets.filter((secret) => secret.length >= MIN_SECRET_FRAGMENT);
24497
- if (candidates.length === 0 || text.length < MIN_SECRET_FRAGMENT) return text;
24563
+ if (candidates.length === 0 || text.length < MIN_SECRET_FRAGMENT) return;
24498
24564
  const grams = /* @__PURE__ */ new Map();
24499
24565
  for (const secret of candidates) {
24500
24566
  for (let offset = 0; offset + MIN_SECRET_FRAGMENT <= secret.length; offset++) {
@@ -24504,9 +24570,7 @@ function redactSecretFragments(text, secrets) {
24504
24570
  else grams.set(gram, [{ secret, offset }]);
24505
24571
  }
24506
24572
  }
24507
- let out = "";
24508
- let index = 0;
24509
- while (index < text.length) {
24573
+ for (let index = 0; index < text.length; index++) {
24510
24574
  const bucket = grams.get(text.slice(index, index + MIN_SECRET_FRAGMENT));
24511
24575
  let best = 0;
24512
24576
  if (bucket) {
@@ -24517,48 +24581,94 @@ function redactSecretFragments(text, secrets) {
24517
24581
  if (length > best) best = length;
24518
24582
  }
24519
24583
  }
24520
- if (best > 0) {
24521
- out += "[redacted]";
24522
- index += best;
24523
- } else {
24524
- out += text[index];
24525
- index += 1;
24526
- }
24584
+ if (best > 0) spans.push({ start: index, end: index + best });
24527
24585
  }
24528
- return out;
24529
24586
  }
24530
- function redactLeadingSecretSuffix(text, secrets) {
24531
- if (secrets.length === 0 || text.length === 0) return text;
24587
+ function collectLeadingSecretSuffix(text, secrets, spans) {
24588
+ if (secrets.length === 0 || text.length === 0) return;
24532
24589
  for (const secret of secrets) {
24533
24590
  for (let keep = Math.min(secret.length - 1, text.length); keep >= 4; keep--) {
24534
24591
  if (text.startsWith(secret.slice(secret.length - keep))) {
24535
- return `[redacted]${text.slice(keep)}`;
24592
+ spans.push({ start: 0, end: keep });
24593
+ break;
24536
24594
  }
24537
24595
  }
24538
24596
  }
24539
- return text;
24540
24597
  }
24541
- function redactTrailingSecretPrefix(text, secrets) {
24542
- if (secrets.length === 0 || text.length === 0) return text;
24598
+ function collectTrailingSecretPrefix(text, secrets, spans) {
24599
+ if (secrets.length === 0 || text.length === 0) return;
24543
24600
  for (const secret of secrets) {
24544
24601
  for (let keep = Math.min(secret.length - 1, text.length); keep >= 4; keep--) {
24545
24602
  if (text.endsWith(secret.slice(0, keep))) {
24546
- return `${text.slice(0, text.length - keep)}[redacted]`;
24603
+ spans.push({ start: text.length - keep, end: text.length });
24604
+ break;
24547
24605
  }
24548
24606
  }
24549
24607
  }
24550
- return text;
24551
24608
  }
24552
24609
  function redactAllSecrets(text, secrets) {
24553
24610
  const encoded = encodedSecretVariants(secrets);
24554
- const wholeVariants = [...encoded, ...hexSecretVariants(secrets)];
24555
- return redactTrailingSecretPrefix(
24556
- redactLeadingSecretSuffix(
24557
- redactSecretFragments(redactSecrets(text, wholeVariants), encoded),
24558
- secrets
24559
- ),
24560
- secrets
24561
- );
24611
+ const wholeVariants = [...encoded, ...hexSecretVariants(secrets), ...unicodeEscapeVariants(secrets)];
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);
24638
+ }
24639
+ function collectCaseInsensitiveEncodedSecrets(text, secrets, spans) {
24640
+ for (const secret of secrets) {
24641
+ const patterns = [];
24642
+ try {
24643
+ const urlEncoded = encodeURIComponent(secret);
24644
+ if (urlEncoded !== secret && urlEncoded.length >= 4 && /%[0-9A-F]{2}/i.test(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);
24656
+ }
24657
+ } catch {
24658
+ }
24659
+ try {
24660
+ if (secret.length >= 6) {
24661
+ const hex = Buffer.from(secret, "utf-8").toString("hex");
24662
+ if (hex.length >= 12) patterns.push(hex);
24663
+ }
24664
+ } catch {
24665
+ }
24666
+ const escaped = unicodeEscapeAll(secret);
24667
+ if (escaped !== secret && escaped.length >= 4) patterns.push(escaped);
24668
+ for (const pattern of patterns) {
24669
+ collectPatternSpans(text, new RegExp(escapeRegExpLiteral(pattern), "gi"), spans);
24670
+ }
24671
+ }
24562
24672
  }
24563
24673
  function redactStderrTail(tail, secrets) {
24564
24674
  return redactAllSecrets(tail, secrets);
@@ -7,9 +7,9 @@ import {
7
7
  isPlanModeSafeCommand,
8
8
  markCompletedPlanSteps,
9
9
  restoreExecutionState
10
- } from "../../../chunks/chunk-QKSK5K5N.js";
11
- import "../../../chunks/chunk-AETQBDEG.js";
12
- import "../../../chunks/chunk-5OXH6NUH.js";
10
+ } from "../../../chunks/chunk-T6GMEDIE.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-TFRQE7UQ.js";
4
+ } from "../../../chunks/chunk-VUKF7U7Q.js";
5
5
  import {
6
+ SUBAGENT_SESSION_RECORD_VERSION,
6
7
  createSharedFileSystemTools,
7
8
  createSubagentCapability
8
- } from "../../../chunks/chunk-QKSK5K5N.js";
9
- import "../../../chunks/chunk-AETQBDEG.js";
10
- import "../../../chunks/chunk-5OXH6NUH.js";
9
+ } from "../../../chunks/chunk-T6GMEDIE.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-QKSK5K5N.js";
12
- import "../../../chunks/chunk-AETQBDEG.js";
13
- import "../../../chunks/chunk-5OXH6NUH.js";
11
+ } from "../../../chunks/chunk-T6GMEDIE.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