@wrongstack/core 0.73.1 → 0.82.6

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.
Files changed (79) hide show
  1. package/dist/{agent-bridge-C0Ze7Ldm.d.ts → agent-bridge-C9P_HPez.d.ts} +2 -2
  2. package/dist/{agent-subagent-runner-BmITbs1Q.d.ts → agent-subagent-runner-2Aq0jOSj.d.ts} +107 -102
  3. package/dist/{compactor-D_ExJajC.d.ts → compactor-CJq7LQev.d.ts} +3 -3
  4. package/dist/{config-Dy0CK_o6.d.ts → config-_DZ7dN-T.d.ts} +77 -75
  5. package/dist/{context-y87Jc5ei.d.ts → context-ToHAp4-U.d.ts} +119 -90
  6. package/dist/coordination/index.d.ts +16 -16
  7. package/dist/coordination/index.js +382 -43
  8. package/dist/coordination/index.js.map +1 -1
  9. package/dist/defaults/index.d.ts +31 -31
  10. package/dist/defaults/index.js +524 -110
  11. package/dist/defaults/index.js.map +1 -1
  12. package/dist/{director-state-BmYi3DGA.d.ts → director-state-CgIc30qi.d.ts} +19 -19
  13. package/dist/{events-BBAlxBuw.d.ts → events-DnRqXaZ3.d.ts} +77 -39
  14. package/dist/execution/index.d.ts +53 -53
  15. package/dist/execution/index.js +67 -23
  16. package/dist/execution/index.js.map +1 -1
  17. package/dist/extension/index.d.ts +9 -9
  18. package/dist/extension/index.js +8 -1
  19. package/dist/extension/index.js.map +1 -1
  20. package/dist/{goal-store-C7jcumEh.d.ts → goal-store-DvWLNu52.d.ts} +4 -4
  21. package/dist/{index-yQbZ2NQx.d.ts → index-BNOLadHw.d.ts} +28 -28
  22. package/dist/{index-BN6i2Nfg.d.ts → index-N0_c4bHQ.d.ts} +45 -45
  23. package/dist/index.d.ts +233 -160
  24. package/dist/index.js +825 -160
  25. package/dist/index.js.map +1 -1
  26. package/dist/infrastructure/index.d.ts +9 -9
  27. package/dist/infrastructure/index.js +29 -7
  28. package/dist/infrastructure/index.js.map +1 -1
  29. package/dist/kernel/index.d.ts +14 -14
  30. package/dist/kernel/index.js +7 -0
  31. package/dist/kernel/index.js.map +1 -1
  32. package/dist/logger-B72yyPc6.d.ts +12 -0
  33. package/dist/{logger-bOzkF5LL.d.ts → logger-C_27pj9i.d.ts} +12 -4
  34. package/dist/{mcp-servers-T0O6UN_w.d.ts → mcp-servers-Dck3T85_.d.ts} +20 -20
  35. package/dist/{mode-BO4SEUIv.d.ts → mode-CHo2XtHs.d.ts} +4 -4
  36. package/dist/models/index.d.ts +10 -10
  37. package/dist/models/index.js +8 -2
  38. package/dist/models/index.js.map +1 -1
  39. package/dist/{models-registry-BcYJDKLm.d.ts → models-registry-Be3osGt5.d.ts} +28 -28
  40. package/dist/{models-registry-Cuq1C8V9.d.ts → models-registry-Boz639EI.d.ts} +12 -12
  41. package/dist/{multi-agent-coordinator-BSBbZt0e.d.ts → multi-agent-coordinator-DllpCVkF.d.ts} +12 -12
  42. package/dist/{null-fleet-bus-BCIRT_nV.d.ts → null-fleet-bus-BY0AN-sr.d.ts} +129 -120
  43. package/dist/observability/index.d.ts +41 -41
  44. package/dist/observability/index.js.map +1 -1
  45. package/dist/{observability-BhnVLBLS.d.ts → observability-CoSNZdhX.d.ts} +4 -4
  46. package/dist/{parallel-eternal-engine-CjAYGaCw.d.ts → parallel-eternal-engine-D402RASp.d.ts} +49 -49
  47. package/dist/{path-resolver-BnqXa9Ze.d.ts → path-resolver-UPFTsDyD.d.ts} +6 -6
  48. package/dist/{permission-V5BLOrY6.d.ts → permission-14CChMmO.d.ts} +10 -8
  49. package/dist/{permission-policy-CBVx-d-8.d.ts → permission-policy-gW5htOo1.d.ts} +7 -7
  50. package/dist/{plan-templates-DBgrTGPu.d.ts → plan-templates-DRvPgkfZ.d.ts} +70 -32
  51. package/dist/{provider-runner-n3KkHT_w.d.ts → provider-runner-COAJM8tC.d.ts} +6 -6
  52. package/dist/{retry-policy-CG3qvH_e.d.ts → retry-policy-DSu6O6rD.d.ts} +4 -4
  53. package/dist/sdd/index.d.ts +47 -47
  54. package/dist/sdd/index.js +47 -22
  55. package/dist/sdd/index.js.map +1 -1
  56. package/dist/security/index.d.ts +6 -6
  57. package/dist/security/index.js +7 -1
  58. package/dist/security/index.js.map +1 -1
  59. package/dist/{selector-RvBR_YRW.d.ts → selector-11-fm95U.d.ts} +2 -2
  60. package/dist/{session-event-bridge-CDHxcmQU.d.ts → session-event-bridge-D0u-x576.d.ts} +7 -7
  61. package/dist/{session-reader-BIpwM60D.d.ts → session-reader-BQU-toaN.d.ts} +23 -23
  62. package/dist/{skill-CxuWrsKK.d.ts → skill-BJeF2DwY.d.ts} +1 -1
  63. package/dist/skills/index.d.ts +9 -9
  64. package/dist/skills/index.js +15 -3
  65. package/dist/skills/index.js.map +1 -1
  66. package/dist/storage/index.d.ts +15 -15
  67. package/dist/storage/index.js +398 -80
  68. package/dist/storage/index.js.map +1 -1
  69. package/dist/{system-prompt-CA11g6Jo.d.ts → system-prompt-C0rLCeyn.d.ts} +16 -11
  70. package/dist/{task-graph-D1YQbpxF.d.ts → task-graph-CikNdRTG.d.ts} +22 -22
  71. package/dist/types/index.d.ts +25 -25
  72. package/dist/types/index.js +61 -12
  73. package/dist/types/index.js.map +1 -1
  74. package/dist/utils/index.d.ts +46 -45
  75. package/dist/utils/index.js +64 -13
  76. package/dist/utils/index.js.map +1 -1
  77. package/dist/{wstack-paths-eMXnY1_X.d.ts → wstack-paths-BQMvEllz.d.ts} +10 -3
  78. package/package.json +1 -1
  79. package/dist/logger-DDd5C--Z.d.ts +0 -12
@@ -1,6 +1,6 @@
1
1
  import { randomBytes, randomUUID, createHash } from 'crypto';
2
2
  import * as fsp from 'fs/promises';
3
- import * as path14 from 'path';
3
+ import * as path2 from 'path';
4
4
  import * as os from 'os';
5
5
 
6
6
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
@@ -10,9 +10,9 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
10
10
  throw Error('Dynamic require of "' + x + '" is not supported');
11
11
  });
12
12
  async function atomicWrite(targetPath, content, opts = {}) {
13
- const dir = path14.dirname(targetPath);
13
+ const dir = path2.dirname(targetPath);
14
14
  await fsp.mkdir(dir, { recursive: true });
15
- const tmp = path14.join(dir, `.${path14.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
15
+ const tmp = path2.join(dir, `.${path2.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
16
16
  try {
17
17
  if (typeof content === "string") {
18
18
  await fsp.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
@@ -75,6 +75,12 @@ async function renameWithRetry(from, to) {
75
75
  }
76
76
 
77
77
  // src/utils/message-invariants.ts
78
+ function expectDefined(value) {
79
+ if (value === null || value === void 0) {
80
+ throw new Error("Expected value to be defined");
81
+ }
82
+ return value;
83
+ }
78
84
  function repairToolUseAdjacency(messages) {
79
85
  const removedToolUses = [];
80
86
  const removedToolResults = [];
@@ -82,7 +88,7 @@ function repairToolUseAdjacency(messages) {
82
88
  let changed = false;
83
89
  const out = [];
84
90
  for (let i = 0; i < messages.length; i++) {
85
- const original = messages[i];
91
+ const original = expectDefined(messages[i]);
86
92
  let msg = original;
87
93
  if (hasToolUse(msg)) {
88
94
  const nextIds = toolResultIds(messages[i + 1]);
@@ -167,7 +173,23 @@ function isEmptyMessage(msg) {
167
173
  }
168
174
 
169
175
  // src/storage/session-store.ts
170
- var DefaultSessionStore = class {
176
+ function expectDefined2(value) {
177
+ if (value === null || value === void 0) {
178
+ throw new Error("Expected value to be defined");
179
+ }
180
+ return value;
181
+ }
182
+ function sanitizeModel(model) {
183
+ return model.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
184
+ }
185
+ function generateSessionId(startedAt, model) {
186
+ const date = startedAt.slice(0, 10);
187
+ const time = startedAt.slice(11, 19).replace(/:/g, "-");
188
+ const suffix = randomBytes(2).toString("hex");
189
+ const modelPart = model ? `_${sanitizeModel(model)}` : "";
190
+ return `${date}/${time}Z${modelPart}_${suffix}`;
191
+ }
192
+ var DefaultSessionStore = class _DefaultSessionStore {
171
193
  dir;
172
194
  events;
173
195
  secretScrubber;
@@ -176,19 +198,29 @@ var DefaultSessionStore = class {
176
198
  this.events = opts.events;
177
199
  this.secretScrubber = opts.secretScrubber;
178
200
  }
201
+ /** Absolute path to the session index file. */
202
+ get indexFile() {
203
+ return path2.join(this.dir, "_index.jsonl");
204
+ }
179
205
  /** Join session ID to its absolute path within the store directory. */
180
206
  sessionPath(id, ext) {
181
- return path14.join(this.dir, `${id}${ext}`);
207
+ return path2.join(this.dir, `${id}${ext}`);
182
208
  }
183
- async ensureShardDir(_id) {
184
- await ensureDir(this.dir);
185
- return this.dir;
209
+ /**
210
+ * Ensure the directory implied by the session ID exists. When the ID
211
+ * contains a date prefix like `2026-06-06/...`, this creates the date
212
+ * subdirectory so sessions group naturally by day.
213
+ */
214
+ async ensureShardDir(id) {
215
+ const dirPath = path2.dirname(path2.join(this.dir, id));
216
+ await ensureDir(dirPath);
217
+ return dirPath;
186
218
  }
187
219
  async create(meta) {
188
220
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
189
- const id = meta.id ?? `${startedAt.replace(/[:.]/g, "-")}-${randomBytes(2).toString("hex")}`;
221
+ const id = meta.id && meta.id.length > 0 ? meta.id : generateSessionId(startedAt, meta.model ?? meta.provider);
190
222
  const shardDir = await this.ensureShardDir(id);
191
- const file = path14.join(shardDir, `${id}.jsonl`);
223
+ const file = path2.join(shardDir, `${path2.basename(id)}.jsonl`);
192
224
  let handle;
193
225
  try {
194
226
  handle = await fsp.open(file, "a", 384);
@@ -202,7 +234,8 @@ var DefaultSessionStore = class {
202
234
  return new FileSessionWriter(id, handle, startedAt, meta, this.events, {
203
235
  dir: shardDir,
204
236
  filePath: file,
205
- secretScrubber: this.secretScrubber
237
+ secretScrubber: this.secretScrubber,
238
+ onClose: (s) => this.appendToIndex(s)
206
239
  });
207
240
  } catch (err) {
208
241
  await handle.close().catch(() => {
@@ -233,7 +266,7 @@ var DefaultSessionStore = class {
233
266
  provider: data.metadata.provider
234
267
  },
235
268
  this.events,
236
- { resumed: true, dir: this.dir, filePath: file, secretScrubber: this.secretScrubber }
269
+ { resumed: true, dir: this.dir, filePath: file, secretScrubber: this.secretScrubber, onClose: (s) => this.appendToIndex(s) }
237
270
  );
238
271
  return { writer, data };
239
272
  } catch (err) {
@@ -263,6 +296,15 @@ var DefaultSessionStore = class {
263
296
  async list(limit = 20) {
264
297
  try {
265
298
  await ensureDir(this.dir);
299
+ const indexed = await this.readIndex();
300
+ if (indexed.length > 0) {
301
+ indexed.sort((a, b) => {
302
+ if (a.startedAt < b.startedAt) return 1;
303
+ if (a.startedAt > b.startedAt) return -1;
304
+ return a.id.localeCompare(b.id);
305
+ });
306
+ return indexed.slice(0, limit);
307
+ }
266
308
  const ids = await this.collectSessionIds(this.dir);
267
309
  const sessions = await Promise.all(ids.map((id) => this.summaryFor(id).catch(() => null)));
268
310
  const out = sessions.filter((s) => s !== null);
@@ -276,16 +318,121 @@ var DefaultSessionStore = class {
276
318
  return [];
277
319
  }
278
320
  }
279
- /** Recursively collect all session IDs from shard subdirectories. */
280
- async collectSessionIds(dir) {
321
+ // ── Session index (_index.jsonl) ─────────────────────────────────────────
322
+ //
323
+ // One JSON line per closed session, appended atomically on close().
324
+ // When a session is deleted, a tombstone {action:"delete",id:"..."} is
325
+ // appended. On read, tombstones filter out matching session entries.
326
+ // This keeps listing O(lines-in-index) instead of O(files-on-disk).
327
+ //
328
+ // The index auto-compacts every N appends to prevent unbounded growth
329
+ // from tombstones and duplicate entries (resume cycles).
330
+ indexAppendCount = 0;
331
+ static COMPACT_EVERY = 30;
332
+ /** Append a session summary to the index. */
333
+ async appendToIndex(summary) {
334
+ try {
335
+ await ensureDir(this.dir);
336
+ const line = JSON.stringify(summary) + "\n";
337
+ await fsp.appendFile(this.indexFile, line, "utf8");
338
+ this.indexAppendCount++;
339
+ if (this.indexAppendCount >= _DefaultSessionStore.COMPACT_EVERY) {
340
+ await this.compactIndex();
341
+ this.indexAppendCount = 0;
342
+ }
343
+ } catch {
344
+ }
345
+ }
346
+ /** Append a tombstone entry for a deleted session. */
347
+ async writeTombstone(id) {
348
+ try {
349
+ await ensureDir(this.dir);
350
+ const line = JSON.stringify({ action: "delete", id }) + "\n";
351
+ await fsp.appendFile(this.indexFile, line, "utf8");
352
+ this.indexAppendCount++;
353
+ } catch {
354
+ }
355
+ }
356
+ /**
357
+ * Compact the index: read all entries, drop tombstones, deduplicate
358
+ * (keep latest per session), and rewrite. Atomic via temp+rename.
359
+ */
360
+ async compactIndex() {
361
+ const entries = await this.readIndex();
362
+ if (entries.length === 0) return;
363
+ const tmp = `${this.indexFile}.compact.tmp`;
364
+ const lines = entries.map((s) => JSON.stringify(s)).join("\n") + "\n";
365
+ await fsp.writeFile(tmp, lines, "utf8");
366
+ await fsp.rename(tmp, this.indexFile);
367
+ }
368
+ /**
369
+ * Read the index file and return deduplicated session summaries.
370
+ * Entries with a matching tombstone are filtered out.
371
+ * Returns empty array when the index doesn't exist or is corrupt.
372
+ */
373
+ async readIndex() {
374
+ let raw;
375
+ try {
376
+ raw = await fsp.readFile(this.indexFile, "utf8");
377
+ } catch {
378
+ return [];
379
+ }
380
+ const deleted = /* @__PURE__ */ new Set();
381
+ const seen = /* @__PURE__ */ new Map();
382
+ for (const line of raw.split("\n")) {
383
+ if (!line.trim()) continue;
384
+ try {
385
+ const entry = JSON.parse(line);
386
+ if (entry.action === "delete" && entry.id) {
387
+ deleted.add(entry.id);
388
+ seen.delete(entry.id);
389
+ continue;
390
+ }
391
+ if (entry.id && !deleted.has(entry.id)) {
392
+ seen.set(entry.id, entry);
393
+ }
394
+ } catch {
395
+ }
396
+ }
397
+ return Array.from(seen.values());
398
+ }
399
+ /**
400
+ * Rebuild the index from disk by scanning all sessions and writing a
401
+ * fresh _index.jsonl. Useful after manual cleanup or index corruption.
402
+ */
403
+ async rebuildIndex() {
404
+ const ids = await this.collectSessionIds(this.dir);
405
+ const summaries = await Promise.all(ids.map((id) => this.summaryFor(id).catch(() => null)));
406
+ const valid = summaries.filter((s) => s !== null);
407
+ const tmp = `${this.indexFile}.tmp`;
408
+ const lines = valid.map((s) => JSON.stringify(s)).join("\n") + "\n";
409
+ await fsp.writeFile(tmp, lines, "utf8");
410
+ await fsp.rename(tmp, this.indexFile);
411
+ return valid.length;
412
+ }
413
+ /** Recursively collect session IDs from date-shard subdirectories.
414
+ * IDs include the date-prefix path (e.g. "2026-06-06/17-46-57Z_…").
415
+ * Skips `.jsonl`/`.summary.json` root files, dot-files, and
416
+ * sub-directories that belong to fleet/subagent sessions. */
417
+ async collectSessionIds(dir, prefix = "", depth = 0) {
281
418
  const ids = [];
282
- const entries = await fsp.readdir(dir, { withFileTypes: true });
419
+ let entries;
420
+ try {
421
+ entries = await fsp.readdir(dir, { withFileTypes: true });
422
+ } catch {
423
+ return ids;
424
+ }
283
425
  for (const entry of entries) {
284
- const full = path14.join(dir, entry.name);
426
+ if (entry.name.startsWith(".") && entry.name !== ".wrongstack") continue;
427
+ if (entry.name === "shared" || entry.name === "subagents" || entry.name === "attachments")
428
+ continue;
285
429
  if (entry.isDirectory()) {
286
- ids.push(...await this.collectSessionIds(full));
430
+ const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
431
+ ids.push(...await this.collectSessionIds(path2.join(dir, entry.name), childPrefix, depth + 1));
287
432
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
288
- ids.push(entry.name.replace(/\.jsonl$/, ""));
433
+ if (entry.name === "_index.jsonl") continue;
434
+ const base = entry.name.replace(/\.jsonl$/, "");
435
+ ids.push(prefix ? `${prefix}/${base}` : base);
289
436
  }
290
437
  }
291
438
  return ids;
@@ -308,9 +455,70 @@ var DefaultSessionStore = class {
308
455
  return summary;
309
456
  }
310
457
  }
311
- async delete(id) {
312
- await fsp.unlink(this.sessionPath(id, ".jsonl"));
458
+ /**
459
+ * Delete a session and all associated files: JSONL, summary, plan/todos
460
+ * sidecars, and the session directory (fleet.json, shared/, subagents/).
461
+ */
462
+ async deleteSession(id) {
463
+ await fsp.unlink(this.sessionPath(id, ".jsonl")).catch(() => void 0);
313
464
  await fsp.unlink(this.sessionPath(id, ".summary.json")).catch(() => void 0);
465
+ const shardDir = path2.dirname(path2.join(this.dir, id));
466
+ const base = path2.basename(id);
467
+ for (const ext of [".plan.json", ".todos.json"]) {
468
+ await fsp.unlink(path2.join(shardDir, `${base}${ext}`)).catch(() => void 0);
469
+ }
470
+ const sessDir = path2.join(shardDir, base);
471
+ await fsp.rm(sessDir, { recursive: true, force: true }).catch(() => void 0);
472
+ await this.writeTombstone(id);
473
+ }
474
+ async delete(id) {
475
+ await this.deleteSession(id);
476
+ }
477
+ async prune(maxAgeDays = 30) {
478
+ const cutoff = Date.now() - maxAgeDays * 864e5;
479
+ let deleted = 0;
480
+ let activeSessionId = null;
481
+ try {
482
+ const raw = await fsp.readFile(path2.join(this.dir, "active.json"), "utf8");
483
+ const active = JSON.parse(raw);
484
+ activeSessionId = active.sessionId ?? null;
485
+ } catch {
486
+ }
487
+ const entries = await fsp.readdir(this.dir, { withFileTypes: true }).catch(() => []);
488
+ for (const entry of entries) {
489
+ if (!entry.isDirectory()) continue;
490
+ const dateDir = path2.join(this.dir, entry.name);
491
+ const files = await fsp.readdir(dateDir, { withFileTypes: true }).catch(() => []);
492
+ for (const file of files) {
493
+ if (!file.isFile() || !file.name.endsWith(".jsonl")) continue;
494
+ const jsonlPath = path2.join(dateDir, file.name);
495
+ try {
496
+ const stat5 = await fsp.stat(jsonlPath);
497
+ if (stat5.mtimeMs >= cutoff) continue;
498
+ } catch {
499
+ continue;
500
+ }
501
+ const id = `${entry.name}/${file.name.replace(/\.jsonl$/, "")}`;
502
+ if (activeSessionId && id === activeSessionId) continue;
503
+ await this.deleteSession(id);
504
+ deleted++;
505
+ }
506
+ }
507
+ if (deleted > 0) {
508
+ await this.compactIndex().catch(() => void 0);
509
+ }
510
+ for (const entry of entries) {
511
+ if (!entry.isDirectory()) continue;
512
+ const dateDir = path2.join(this.dir, entry.name);
513
+ try {
514
+ const remaining = await fsp.readdir(dateDir);
515
+ if (remaining.length === 0) {
516
+ await fsp.rmdir(dateDir).catch(() => void 0);
517
+ }
518
+ } catch {
519
+ }
520
+ }
521
+ return deleted;
314
522
  }
315
523
  async clearHistory(id) {
316
524
  await this.ensureShardDir(id);
@@ -332,13 +540,42 @@ var DefaultSessionStore = class {
332
540
  const data = await this.load(id);
333
541
  const firstUser = data.events.find((e) => e.type === "user_input");
334
542
  const title = firstUser && firstUser.type === "user_input" ? userInputTitle(firstUser.content) : "(empty session)";
543
+ let iterationCount = 0;
544
+ let toolCallCount = 0;
545
+ let toolErrorCount = 0;
546
+ let fileChangeCount = 0;
547
+ const toolBreakdown = {};
548
+ let outcome = void 0;
549
+ const lastEvent = data.events[data.events.length - 1];
550
+ for (const e of data.events) {
551
+ if (e.type === "in_flight_start") iterationCount++;
552
+ else if (e.type === "tool_call_start") {
553
+ toolCallCount++;
554
+ toolBreakdown[e.name] = (toolBreakdown[e.name] ?? 0) + 1;
555
+ } else if (e.type === "tool_result" && e.isError) toolErrorCount++;
556
+ else if (e.type === "file_snapshot") fileChangeCount += e.files.length;
557
+ }
558
+ if (lastEvent?.type === "session_end") {
559
+ outcome = "completed";
560
+ } else if (lastEvent?.type === "in_flight_start") {
561
+ outcome = "aborted";
562
+ } else if (data.events.some((e) => e.type === "error")) {
563
+ outcome = "error";
564
+ }
335
565
  return {
336
566
  id,
337
567
  title,
338
568
  startedAt: data.metadata.startedAt,
569
+ endedAt: data.metadata.endedAt,
339
570
  model: data.metadata.model ?? "unknown",
340
571
  provider: data.metadata.provider ?? "unknown",
341
- tokenTotal: data.usage.input + data.usage.output
572
+ tokenTotal: data.usage.input + data.usage.output,
573
+ iterationCount: iterationCount > 0 ? iterationCount : void 0,
574
+ toolCallCount: toolCallCount > 0 ? toolCallCount : void 0,
575
+ toolErrorCount: toolErrorCount > 0 ? toolErrorCount : void 0,
576
+ fileChangeCount: fileChangeCount > 0 ? fileChangeCount : void 0,
577
+ toolBreakdown: Object.keys(toolBreakdown).length > 0 ? toolBreakdown : {},
578
+ outcome
342
579
  };
343
580
  } catch {
344
581
  return {
@@ -437,9 +674,10 @@ var FileSessionWriter = class {
437
674
  this.meta = meta;
438
675
  this.events = events;
439
676
  this.resumed = opts.resumed ?? false;
440
- this.manifestFile = opts.dir ? path14.join(opts.dir, `${id}.summary.json`) : "";
677
+ this.manifestFile = opts.dir ? path2.join(opts.dir, `${path2.basename(id)}.summary.json`) : "";
441
678
  this.filePath = opts.filePath ?? "";
442
679
  this.secretScrubber = opts.secretScrubber;
680
+ this.onCloseCb = opts.onClose;
443
681
  this.summary = {
444
682
  id,
445
683
  title: "(empty session)",
@@ -469,6 +707,15 @@ var FileSessionWriter = class {
469
707
  appendFailCount = 0;
470
708
  lastAppendWarnAt = 0;
471
709
  secretScrubber;
710
+ onCloseCb;
711
+ // ── Enriched summary tracking ──────────────────────────────────────────
712
+ iterationCount = 0;
713
+ toolCallCount = 0;
714
+ toolErrorCount = 0;
715
+ toolBreakdown = {};
716
+ fileChangeCount = 0;
717
+ compactionCount = 0;
718
+ outcome = void 0;
472
719
  /**
473
720
  * Scrub secrets out of conversation-turn events before they are observed
474
721
  * for the summary, written to the JSONL log, or surfaced on resume. Only
@@ -546,8 +793,22 @@ var FileSessionWriter = class {
546
793
  observeForSummary(event) {
547
794
  if (event.type === "tool_use") {
548
795
  this.openToolUses.add(event.id);
796
+ } else if (event.type === "tool_call_start") {
797
+ this.toolCallCount++;
798
+ this.toolBreakdown[event.name] = (this.toolBreakdown[event.name] ?? 0) + 1;
549
799
  } else if (event.type === "tool_result") {
550
800
  this.openToolUses.delete(event.id);
801
+ if (event.isError) {
802
+ this.toolErrorCount++;
803
+ this.outcome = "error";
804
+ }
805
+ } else if (event.type === "file_snapshot") {
806
+ this.fileChangeCount += event.files.length;
807
+ } else if (event.type === "compaction") {
808
+ this.compactionCount++;
809
+ }
810
+ if (event.type === "error" || event.type === "provider_error") {
811
+ this.outcome = "error";
551
812
  }
552
813
  if (event.type === "user_input" && this.summary.title === "(empty session)") {
553
814
  this.summary = { ...this.summary, title: userInputTitle(event.content) };
@@ -558,18 +819,35 @@ var FileSessionWriter = class {
558
819
  } else if (event.type === "session_end") {
559
820
  const total = event.usage.input + event.usage.output;
560
821
  if (total > 0) this.summary = { ...this.summary, tokenTotal: total };
822
+ } else if (event.type === "in_flight_start") {
823
+ this.iterationCount++;
561
824
  }
562
825
  }
563
826
  async close() {
564
827
  if (this.closing) return;
565
828
  this.closing = true;
566
829
  this.closed = true;
830
+ this.summary = {
831
+ ...this.summary,
832
+ endedAt: (/* @__PURE__ */ new Date()).toISOString(),
833
+ iterationCount: this.iterationCount,
834
+ toolCallCount: this.toolCallCount,
835
+ toolErrorCount: this.toolErrorCount,
836
+ fileChangeCount: this.fileChangeCount,
837
+ compactionCount: this.compactionCount > 0 ? this.compactionCount : void 0,
838
+ toolBreakdown: { ...this.toolBreakdown },
839
+ outcome: this.outcome ?? "completed"
840
+ };
567
841
  if (this.manifestFile) {
568
842
  try {
569
843
  await atomicWrite(this.manifestFile, JSON.stringify(this.summary), { mode: 384 });
570
844
  } catch {
571
845
  }
572
846
  }
847
+ try {
848
+ await this.onCloseCb?.(this.summary);
849
+ } catch {
850
+ }
573
851
  try {
574
852
  await this.handle.close();
575
853
  } catch {
@@ -611,7 +889,7 @@ var FileSessionWriter = class {
611
889
  let targetCheckpointLine = -1;
612
890
  let afterTarget = false;
613
891
  for (let i = 0; i < lines.length; i++) {
614
- const line = lines[i];
892
+ const line = expectDefined2(lines[i]);
615
893
  if (!line.trim()) continue;
616
894
  let event;
617
895
  try {
@@ -716,7 +994,7 @@ function userInputTitle(content) {
716
994
  var QueueStore = class {
717
995
  file;
718
996
  constructor(opts) {
719
- this.file = path14.join(opts.dir, "queue.json");
997
+ this.file = path2.join(opts.dir, "queue.json");
720
998
  }
721
999
  async write(items) {
722
1000
  if (items.length === 0) {
@@ -783,7 +1061,7 @@ var DefaultAttachmentStore = class {
783
1061
  let data = input.data;
784
1062
  if (this.spoolDir && bytes >= this.spoolThreshold) {
785
1063
  await fsp.mkdir(this.spoolDir, { recursive: true });
786
- spooledPath = path14.join(this.spoolDir, `${id}.bin`);
1064
+ spooledPath = path2.join(this.spoolDir, `${id}.bin`);
787
1065
  await atomicWrite(spooledPath, input.data, {
788
1066
  encoding: input.kind === "image" ? "base64" : "utf8"
789
1067
  });
@@ -968,7 +1246,7 @@ ${body.trim()}`);
968
1246
  async remember(text, scope = "project-memory") {
969
1247
  return this.runSerialized(scope, async () => {
970
1248
  const file = this.files[scope];
971
- await ensureDir(path14.dirname(file));
1249
+ await ensureDir(path2.dirname(file));
972
1250
  let existing = "";
973
1251
  try {
974
1252
  existing = await fsp.readFile(file, "utf8");
@@ -1607,7 +1885,7 @@ var RecoveryLock = class {
1607
1885
  sessionStore;
1608
1886
  probe;
1609
1887
  constructor(opts) {
1610
- this.file = path14.join(opts.dir, LOCK_FILE);
1888
+ this.file = path2.join(opts.dir, LOCK_FILE);
1611
1889
  this.pid = opts.pid ?? process.pid;
1612
1890
  this.hostname = opts.hostname ?? os.hostname();
1613
1891
  this.maxAgeMs = opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
@@ -1665,7 +1943,7 @@ var RecoveryLock = class {
1665
1943
  * null return before calling this.
1666
1944
  */
1667
1945
  async write(sessionId) {
1668
- await ensureDir(path14.dirname(this.file));
1946
+ await ensureDir(path2.dirname(this.file));
1669
1947
  const lock = {
1670
1948
  v: 1,
1671
1949
  sessionId,
@@ -1769,6 +2047,12 @@ function compileUserRegex(pattern, flags) {
1769
2047
  }
1770
2048
 
1771
2049
  // src/storage/session-reader.ts
2050
+ function expectDefined3(value) {
2051
+ if (value === null || value === void 0) {
2052
+ throw new Error("Expected value to be defined");
2053
+ }
2054
+ return value;
2055
+ }
1772
2056
  var DefaultSessionReader = class {
1773
2057
  store;
1774
2058
  constructor(opts) {
@@ -1830,7 +2114,7 @@ var DefaultSessionReader = class {
1830
2114
  continue;
1831
2115
  }
1832
2116
  for (let i = 0; i < data.events.length; i++) {
1833
- const ev = data.events[i];
2117
+ const ev = expectDefined3(data.events[i]);
1834
2118
  if (allowedTypes && !allowedTypes.has(ev.type)) continue;
1835
2119
  const text = eventText(ev);
1836
2120
  if (text === null) continue;
@@ -2043,6 +2327,12 @@ function renderPlainText(meta, events) {
2043
2327
  }
2044
2328
  return lines.join("\n");
2045
2329
  }
2330
+ function expectDefined4(value) {
2331
+ if (value === null || value === void 0) {
2332
+ throw new Error("Expected value to be defined");
2333
+ }
2334
+ return value;
2335
+ }
2046
2336
  var FILE_VERSION = 1;
2047
2337
  var MAX_TEXT_LENGTH = 2e3;
2048
2338
  var MAX_ANNOTATIONS = 1e3;
@@ -2134,7 +2424,7 @@ var AnnotationsStore = class {
2134
2424
  return;
2135
2425
  }
2136
2426
  const next = {
2137
- ...all[idx],
2427
+ ...expectDefined4(all[idx]),
2138
2428
  resolved: true,
2139
2429
  resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
2140
2430
  resolvedBy: input.resolvedBy
@@ -2150,7 +2440,7 @@ var AnnotationsStore = class {
2150
2440
  if (!sessionId || sessionId.includes("/") || sessionId.includes("\\") || sessionId.includes("..")) {
2151
2441
  throw new Error(`Invalid sessionId: ${sessionId}`);
2152
2442
  }
2153
- return path14.join(this.dir, `${sessionId}.annotations.json`);
2443
+ return path2.join(this.dir, `${sessionId}.annotations.json`);
2154
2444
  }
2155
2445
  async readFile(sessionId) {
2156
2446
  const fp = this.filePath(sessionId);
@@ -2307,7 +2597,7 @@ var ReplayLogStore = class {
2307
2597
  out.push({
2308
2598
  sessionId,
2309
2599
  entryCount: all.length,
2310
- path: path14.join(this.dir, name)
2600
+ path: path2.join(this.dir, name)
2311
2601
  });
2312
2602
  }
2313
2603
  return out.sort((a, b) => a.sessionId.localeCompare(b.sessionId));
@@ -2317,7 +2607,7 @@ var ReplayLogStore = class {
2317
2607
  if (!sessionId || sessionId.includes("/") || sessionId.includes("\\") || sessionId.includes("..")) {
2318
2608
  throw new Error(`Invalid sessionId: ${sessionId}`);
2319
2609
  }
2320
- return path14.join(this.dir, `${sessionId}.replay.jsonl`);
2610
+ return path2.join(this.dir, `${sessionId}.replay.jsonl`);
2321
2611
  }
2322
2612
  async readAll(sessionId) {
2323
2613
  const fp = this.filePath(sessionId);
@@ -2367,6 +2657,12 @@ var ReplayLogStore = class {
2367
2657
  return next;
2368
2658
  }
2369
2659
  };
2660
+ function expectDefined5(value) {
2661
+ if (value === null || value === void 0) {
2662
+ throw new Error("Expected value to be defined");
2663
+ }
2664
+ return value;
2665
+ }
2370
2666
  var SessionRecovery = class {
2371
2667
  constructor(dir) {
2372
2668
  this.dir = dir;
@@ -2407,7 +2703,7 @@ var SessionRecovery = class {
2407
2703
  const lines = raw.split("\n").filter((l) => l.trim());
2408
2704
  for (let i = lines.length - 1; i >= 0; i--) {
2409
2705
  try {
2410
- const ev = JSON.parse(lines[i]);
2706
+ const ev = JSON.parse(expectDefined5(lines[i]));
2411
2707
  if (ev.type === "in_flight_start") {
2412
2708
  return {
2413
2709
  sessionId,
@@ -2459,13 +2755,13 @@ var SessionRecovery = class {
2459
2755
  let lastCheckpoint = null;
2460
2756
  let lastCheckpointIdx = -1;
2461
2757
  for (let i = 0; i < events.length; i++) {
2462
- if (events[i].type === "checkpoint") {
2463
- lastCheckpoint = events[i];
2758
+ if (events[i]?.type === "checkpoint") {
2759
+ lastCheckpoint = expectDefined5(events[i]);
2464
2760
  lastCheckpointIdx = i;
2465
2761
  }
2466
2762
  }
2467
2763
  const pendingEvents = lastCheckpointIdx >= 0 ? events.slice(lastCheckpointIdx + 1) : events;
2468
- const lastEv = events[events.length - 1];
2764
+ const lastEv = expectDefined5(events[events.length - 1]);
2469
2765
  const inFlightStart = lastEv.type === "in_flight_start" ? lastEv : null;
2470
2766
  const context = inFlightStart && inFlightStart.type === "in_flight_start" ? inFlightStart.context : null;
2471
2767
  return {
@@ -2505,9 +2801,15 @@ var SessionRecovery = class {
2505
2801
  if (!sessionId || sessionId.includes("/") || sessionId.includes("\\") || sessionId.includes("..")) {
2506
2802
  throw new Error(`Invalid sessionId: ${sessionId}`);
2507
2803
  }
2508
- return path14.join(this.dir, `${sessionId}.jsonl`);
2804
+ return path2.join(this.dir, `${sessionId}.jsonl`);
2509
2805
  }
2510
2806
  };
2807
+ function expectDefined6(value) {
2808
+ if (value === null || value === void 0) {
2809
+ throw new Error("Expected value to be defined");
2810
+ }
2811
+ return value;
2812
+ }
2511
2813
  var GENESIS_PREV = "0".repeat(64);
2512
2814
  var DEFAULT_FSYNC_EVERY = 100;
2513
2815
  var ToolAuditLog = class {
@@ -2574,7 +2876,7 @@ var ToolAuditLog = class {
2574
2876
  async verify(sessionId) {
2575
2877
  const entries = await this.readAll(sessionId);
2576
2878
  if (entries.length === 0) return { ok: true, entries: 0 };
2577
- if (entries[0].prevHash !== GENESIS_PREV) {
2879
+ if (entries[0]?.prevHash !== GENESIS_PREV) {
2578
2880
  return {
2579
2881
  ok: false,
2580
2882
  brokenAt: 0,
@@ -2583,7 +2885,7 @@ var ToolAuditLog = class {
2583
2885
  }
2584
2886
  let prevHash = GENESIS_PREV;
2585
2887
  for (let i = 0; i < entries.length; i++) {
2586
- const e = entries[i];
2888
+ const e = expectDefined6(entries[i]);
2587
2889
  if (e.prevHash !== prevHash) {
2588
2890
  return {
2589
2891
  ok: false,
@@ -2623,7 +2925,7 @@ var ToolAuditLog = class {
2623
2925
  if (!sessionId || sessionId.includes("/") || sessionId.includes("\\") || sessionId.includes("..")) {
2624
2926
  throw new Error(`Invalid sessionId: ${sessionId}`);
2625
2927
  }
2626
- return path14.join(this.dir, `${sessionId}.audit.jsonl`);
2928
+ return path2.join(this.dir, `${sessionId}.audit.jsonl`);
2627
2929
  }
2628
2930
  async readAll(sessionId) {
2629
2931
  const fp = this.filePath(sessionId);
@@ -2791,11 +3093,20 @@ var SessionAnalyzer = class {
2791
3093
  }
2792
3094
  calcDuration(events) {
2793
3095
  if (events.length < 2) return 0;
2794
- const first = new Date(events[0].ts).getTime();
2795
- const last = new Date(events[events.length - 1].ts).getTime();
3096
+ const firstEvent = events[0];
3097
+ const lastEvent = events[events.length - 1];
3098
+ if (!firstEvent || !lastEvent) return 0;
3099
+ const first = new Date(firstEvent.ts).getTime();
3100
+ const last = new Date(lastEvent.ts).getTime();
2796
3101
  return last - first;
2797
3102
  }
2798
3103
  };
3104
+ function expectDefined7(value) {
3105
+ if (value === null || value === void 0) {
3106
+ throw new Error("Expected value to be defined");
3107
+ }
3108
+ return value;
3109
+ }
2799
3110
  var DefaultSessionRewinder = class {
2800
3111
  constructor(sessionsDir, projectRoot) {
2801
3112
  this.sessionsDir = sessionsDir;
@@ -2804,7 +3115,7 @@ var DefaultSessionRewinder = class {
2804
3115
  sessionsDir;
2805
3116
  projectRoot;
2806
3117
  async listCheckpoints(sessionId) {
2807
- const file = path14.join(this.sessionsDir, `${sessionId}.jsonl`);
3118
+ const file = path2.join(this.sessionsDir, `${sessionId}.jsonl`);
2808
3119
  const raw = await fsp.readFile(file, "utf8");
2809
3120
  const events = parseEvents(raw);
2810
3121
  const fileCountMap = /* @__PURE__ */ new Map();
@@ -2829,12 +3140,12 @@ var DefaultSessionRewinder = class {
2829
3140
  return checkpoints;
2830
3141
  }
2831
3142
  async rewindToCheckpoint(sessionId, checkpointIndex) {
2832
- const file = path14.join(this.sessionsDir, `${sessionId}.jsonl`);
3143
+ const file = path2.join(this.sessionsDir, `${sessionId}.jsonl`);
2833
3144
  const raw = await fsp.readFile(file, "utf8");
2834
3145
  const events = parseEvents(raw);
2835
3146
  let targetIdx = -1;
2836
3147
  for (let i = 0; i < events.length; i++) {
2837
- const event = events[i];
3148
+ const event = expectDefined7(events[i]);
2838
3149
  if (event.type === "checkpoint") {
2839
3150
  const checkpointEvent = event;
2840
3151
  if (checkpointEvent.promptIndex === checkpointIndex) {
@@ -2848,7 +3159,7 @@ var DefaultSessionRewinder = class {
2848
3159
  }
2849
3160
  const snapshotsToRevert = [];
2850
3161
  for (let i = targetIdx + 1; i < events.length; i++) {
2851
- const event = events[i];
3162
+ const event = expectDefined7(events[i]);
2852
3163
  if (event.type === "checkpoint") {
2853
3164
  break;
2854
3165
  }
@@ -2864,7 +3175,7 @@ var DefaultSessionRewinder = class {
2864
3175
  return { ...result, toPromptIndex: checkpointIndex, removedEvents };
2865
3176
  }
2866
3177
  async rewindLastN(sessionId, n) {
2867
- const file = path14.join(this.sessionsDir, `${sessionId}.jsonl`);
3178
+ const file = path2.join(this.sessionsDir, `${sessionId}.jsonl`);
2868
3179
  const raw = await fsp.readFile(file, "utf8");
2869
3180
  const events = parseEvents(raw);
2870
3181
  const checkpoints = [];
@@ -2893,7 +3204,7 @@ var DefaultSessionRewinder = class {
2893
3204
  return { ...result, toPromptIndex: targetIndex, removedEvents: snapshotsToRevert.length };
2894
3205
  }
2895
3206
  async rewindToStart(sessionId) {
2896
- const file = path14.join(this.sessionsDir, `${sessionId}.jsonl`);
3207
+ const file = path2.join(this.sessionsDir, `${sessionId}.jsonl`);
2897
3208
  const raw = await fsp.readFile(file, "utf8");
2898
3209
  const events = parseEvents(raw);
2899
3210
  const allSnapshots = [];
@@ -2929,10 +3240,10 @@ async function revertSnapshots(snapshots, projectRoot) {
2929
3240
  for (const snapshot of snapshots) {
2930
3241
  for (const file of snapshot.files) {
2931
3242
  try {
2932
- const absPath = path14.resolve(file.path);
2933
- const root = path14.resolve(projectRoot);
2934
- const rel = path14.relative(root, absPath);
2935
- if (rel.startsWith("..") || path14.isAbsolute(rel)) {
3243
+ const absPath = path2.resolve(file.path);
3244
+ const root = path2.resolve(projectRoot);
3245
+ const rel = path2.relative(root, absPath);
3246
+ if (rel.startsWith("..") || path2.isAbsolute(rel)) {
2936
3247
  errors.push(`${file.path}: path resolves outside project root \u2014 skipping`);
2937
3248
  continue;
2938
3249
  }
@@ -2968,7 +3279,7 @@ async function loadTodosCheckpoint(filePath) {
2968
3279
  const parsed = JSON.parse(raw);
2969
3280
  if (parsed?.version !== 1 || !Array.isArray(parsed.todos)) return null;
2970
3281
  return parsed.todos.filter(
2971
- (t) => !!t && typeof t.id === "string" && typeof t.content === "string" && typeof t.status === "string"
3282
+ (t) => !!t && typeof t.id === "string" && typeof t.content === "string" && typeof t.status === "string" && (t.activeForm === void 0 || typeof t.activeForm === "string")
2972
3283
  );
2973
3284
  } catch {
2974
3285
  return null;
@@ -3491,8 +3802,8 @@ var FsError = class extends WrongStackError {
3491
3802
  // src/storage/goal-store.ts
3492
3803
  var MAX_JOURNAL_ENTRIES = 500;
3493
3804
  function goalFilePath(projectRoot) {
3494
- const hash = createHash("sha256").update(path14.resolve(projectRoot)).digest("hex").slice(0, 12);
3495
- return path14.join(os.homedir(), ".wrongstack", "projects", hash, "goal.json");
3805
+ const hash = createHash("sha256").update(path2.resolve(projectRoot)).digest("hex").slice(0, 12);
3806
+ return path2.join(os.homedir(), ".wrongstack", "projects", hash, "goal.json");
3496
3807
  }
3497
3808
  async function loadGoal(filePath) {
3498
3809
  let raw;
@@ -3607,7 +3918,7 @@ var DefaultPromptStore = class {
3607
3918
  if (!file.endsWith(".json")) continue;
3608
3919
  try {
3609
3920
  const raw = JSON.parse(
3610
- await fsp.readFile(path14.join(this.dir, file), "utf8")
3921
+ await fsp.readFile(path2.join(this.dir, file), "utf8")
3611
3922
  );
3612
3923
  entries.push(raw.entry);
3613
3924
  } catch {
@@ -3620,7 +3931,7 @@ var DefaultPromptStore = class {
3620
3931
  );
3621
3932
  }
3622
3933
  async get(id) {
3623
- const file = path14.join(this.dir, `${id}.json`);
3934
+ const file = path2.join(this.dir, `${id}.json`);
3624
3935
  try {
3625
3936
  const raw = JSON.parse(await fsp.readFile(file, "utf8"));
3626
3937
  return raw.entry;
@@ -3630,12 +3941,12 @@ var DefaultPromptStore = class {
3630
3941
  }
3631
3942
  async save(entry) {
3632
3943
  await ensureDir(this.dir);
3633
- const file = path14.join(this.dir, `${entry.id}.json`);
3944
+ const file = path2.join(this.dir, `${entry.id}.json`);
3634
3945
  const raw = { version: 1, entry };
3635
3946
  await atomicWrite(file, JSON.stringify(raw, null, 2));
3636
3947
  }
3637
3948
  async delete(id) {
3638
- const file = path14.join(this.dir, `${id}.json`);
3949
+ const file = path2.join(this.dir, `${id}.json`);
3639
3950
  try {
3640
3951
  await fsp.unlink(file);
3641
3952
  return true;
@@ -3663,13 +3974,19 @@ var DefaultPromptStore = class {
3663
3974
  };
3664
3975
  }
3665
3976
  };
3977
+ function expectDefined8(value) {
3978
+ if (value === null || value === void 0) {
3979
+ throw new Error("Expected value to be defined");
3980
+ }
3981
+ return value;
3982
+ }
3666
3983
  var ALL_SYNC_CATEGORIES = ["settings", "skills", "prompts", "memory", "history"];
3667
3984
  var CloudSync = class {
3668
3985
  constructor(paths, getConfig, setConfig) {
3669
3986
  this.paths = paths;
3670
3987
  this.getConfig = getConfig;
3671
3988
  this.setConfig = setConfig;
3672
- this.statePath = path14.join(paths.globalRoot, "sync-state.json");
3989
+ this.statePath = path2.join(paths.globalRoot, "sync-state.json");
3673
3990
  }
3674
3991
  paths;
3675
3992
  getConfig;
@@ -3705,8 +4022,8 @@ var CloudSync = class {
3705
4022
  const cfg = this.getConfig();
3706
4023
  if (!cfg?.enabled) return { ok: false, action: "push", categories: [], message: "Not enabled." };
3707
4024
  const parts = cfg.repo.split("/");
3708
- const owner = parts[0];
3709
- const repoName = parts[1];
4025
+ const owner = expectDefined8(parts[0]);
4026
+ const repoName = expectDefined8(parts[1]);
3710
4027
  const branch = "main";
3711
4028
  const baseTreeSha = this.state?.sha;
3712
4029
  const { treeEntries, rev } = await this.buildLocalTree(cfg.categories);
@@ -3758,8 +4075,8 @@ var CloudSync = class {
3758
4075
  const cfg = this.getConfig();
3759
4076
  if (!cfg?.enabled) return { ok: false, action: "pull", categories: [], message: "Not enabled." };
3760
4077
  const pullParts = cfg.repo.split("/");
3761
- const owner = pullParts[0];
3762
- const repoName = pullParts[1];
4078
+ const owner = expectDefined8(pullParts[0]);
4079
+ const repoName = expectDefined8(pullParts[1]);
3763
4080
  const branchData = await this.getRef(token, owner, repoName, "main");
3764
4081
  const currentSha = branchData.object.sha;
3765
4082
  const commitData = await this.getCommit(token, owner, repoName, currentSha);
@@ -3776,7 +4093,7 @@ var CloudSync = class {
3776
4093
  const rel = segments.slice(2).join("/");
3777
4094
  const destPath = resolvePulledCategoryPath(cat, localPath, rel, entry.path);
3778
4095
  const blobData = await this.getBlob(token, owner, repoName, entry.sha);
3779
- await fsp.mkdir(path14.dirname(destPath), { recursive: true });
4096
+ await fsp.mkdir(path2.dirname(destPath), { recursive: true });
3780
4097
  await fsp.writeFile(destPath, Buffer.from(blobData, "base64"));
3781
4098
  }
3782
4099
  const localRev = await this.hashLocalCategories(cfg.categories);
@@ -3814,7 +4131,7 @@ var CloudSync = class {
3814
4131
  // ── GitHub API helpers ──────────────────────────────────────────────
3815
4132
  async githubFetch(token, owner, repo, method, pathSegment, body) {
3816
4133
  const url = `https://api.github.com/repos/${owner}/${repo}${pathSegment}`;
3817
- const res = await fetch(url, {
4134
+ const init = {
3818
4135
  signal: AbortSignal.timeout(15e3),
3819
4136
  method,
3820
4137
  headers: {
@@ -3822,9 +4139,10 @@ var CloudSync = class {
3822
4139
  Accept: "application/vnd.github+json",
3823
4140
  "X-GitHub-Api-Version": "2022-11-28",
3824
4141
  "Content-Type": "application/json"
3825
- },
3826
- body: body !== void 0 ? JSON.stringify(body) : void 0
3827
- });
4142
+ }
4143
+ };
4144
+ if (body !== void 0) init.body = JSON.stringify(body);
4145
+ const res = await fetch(url, init);
3828
4146
  if (!res.ok) {
3829
4147
  const errText = await res.text();
3830
4148
  throw new Error(`GitHub API ${method} ${pathSegment} failed (${res.status}): ${errText}`);
@@ -3882,7 +4200,7 @@ var CloudSync = class {
3882
4200
  const files = await this.walkDir(localPath, localPath);
3883
4201
  for (const file of files) {
3884
4202
  const content = await fsp.readFile(file, "utf8");
3885
- const rel = path14.relative(localPath, file).replace(/\\/g, "/");
4203
+ const rel = path2.relative(localPath, file).replace(/\\/g, "/");
3886
4204
  entries.push({ path: `data/${cat}/${rel}`, content, mode: "100644" });
3887
4205
  hashes.push(content);
3888
4206
  }
@@ -3939,7 +4257,7 @@ var CloudSync = class {
3939
4257
  const results = [];
3940
4258
  const entries = await fsp.readdir(dir, { withFileTypes: true });
3941
4259
  for (const entry of entries) {
3942
- const full = path14.join(dir, entry.name);
4260
+ const full = path2.join(dir, entry.name);
3943
4261
  if (entry.isDirectory()) {
3944
4262
  results.push(...await this.walkDir(full, base));
3945
4263
  } else {
@@ -3956,15 +4274,15 @@ function resolvePulledCategoryPath(cat, localPath, rel, remotePath) {
3956
4274
  return localPath;
3957
4275
  }
3958
4276
  if (!rel) return localPath;
3959
- const normalizedRel = path14.normalize(rel);
3960
- const traversesUp = normalizedRel === ".." || normalizedRel.startsWith(`..${path14.sep}`);
3961
- if (path14.isAbsolute(normalizedRel) || traversesUp) {
4277
+ const normalizedRel = path2.normalize(rel);
4278
+ const traversesUp = normalizedRel === ".." || normalizedRel.startsWith(`..${path2.sep}`);
4279
+ if (path2.isAbsolute(normalizedRel) || traversesUp) {
3962
4280
  throw new Error(`Refusing CloudSync path traversal: ${remotePath}`);
3963
4281
  }
3964
- const dest = path14.resolve(localPath, normalizedRel);
3965
- const root = path14.resolve(localPath);
3966
- const relative3 = path14.relative(root, dest);
3967
- if (relative3.startsWith("..") || path14.isAbsolute(relative3)) {
4282
+ const dest = path2.resolve(localPath, normalizedRel);
4283
+ const root = path2.resolve(localPath);
4284
+ const relative3 = path2.relative(root, dest);
4285
+ if (relative3.startsWith("..") || path2.isAbsolute(relative3)) {
3968
4286
  throw new Error(`Refusing CloudSync path outside category root: ${remotePath}`);
3969
4287
  }
3970
4288
  return dest;