@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.
- package/dist/{agent-bridge-C0Ze7Ldm.d.ts → agent-bridge-C9P_HPez.d.ts} +2 -2
- package/dist/{agent-subagent-runner-BmITbs1Q.d.ts → agent-subagent-runner-2Aq0jOSj.d.ts} +107 -102
- package/dist/{compactor-D_ExJajC.d.ts → compactor-CJq7LQev.d.ts} +3 -3
- package/dist/{config-Dy0CK_o6.d.ts → config-_DZ7dN-T.d.ts} +77 -75
- package/dist/{context-y87Jc5ei.d.ts → context-ToHAp4-U.d.ts} +119 -90
- package/dist/coordination/index.d.ts +16 -16
- package/dist/coordination/index.js +382 -43
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +31 -31
- package/dist/defaults/index.js +524 -110
- package/dist/defaults/index.js.map +1 -1
- package/dist/{director-state-BmYi3DGA.d.ts → director-state-CgIc30qi.d.ts} +19 -19
- package/dist/{events-BBAlxBuw.d.ts → events-DnRqXaZ3.d.ts} +77 -39
- package/dist/execution/index.d.ts +53 -53
- package/dist/execution/index.js +67 -23
- package/dist/execution/index.js.map +1 -1
- package/dist/extension/index.d.ts +9 -9
- package/dist/extension/index.js +8 -1
- package/dist/extension/index.js.map +1 -1
- package/dist/{goal-store-C7jcumEh.d.ts → goal-store-DvWLNu52.d.ts} +4 -4
- package/dist/{index-yQbZ2NQx.d.ts → index-BNOLadHw.d.ts} +28 -28
- package/dist/{index-BN6i2Nfg.d.ts → index-N0_c4bHQ.d.ts} +45 -45
- package/dist/index.d.ts +233 -160
- package/dist/index.js +825 -160
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +9 -9
- package/dist/infrastructure/index.js +29 -7
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +14 -14
- package/dist/kernel/index.js +7 -0
- package/dist/kernel/index.js.map +1 -1
- package/dist/logger-B72yyPc6.d.ts +12 -0
- package/dist/{logger-bOzkF5LL.d.ts → logger-C_27pj9i.d.ts} +12 -4
- package/dist/{mcp-servers-T0O6UN_w.d.ts → mcp-servers-Dck3T85_.d.ts} +20 -20
- package/dist/{mode-BO4SEUIv.d.ts → mode-CHo2XtHs.d.ts} +4 -4
- package/dist/models/index.d.ts +10 -10
- package/dist/models/index.js +8 -2
- package/dist/models/index.js.map +1 -1
- package/dist/{models-registry-BcYJDKLm.d.ts → models-registry-Be3osGt5.d.ts} +28 -28
- package/dist/{models-registry-Cuq1C8V9.d.ts → models-registry-Boz639EI.d.ts} +12 -12
- package/dist/{multi-agent-coordinator-BSBbZt0e.d.ts → multi-agent-coordinator-DllpCVkF.d.ts} +12 -12
- package/dist/{null-fleet-bus-BCIRT_nV.d.ts → null-fleet-bus-BY0AN-sr.d.ts} +129 -120
- package/dist/observability/index.d.ts +41 -41
- package/dist/observability/index.js.map +1 -1
- package/dist/{observability-BhnVLBLS.d.ts → observability-CoSNZdhX.d.ts} +4 -4
- package/dist/{parallel-eternal-engine-CjAYGaCw.d.ts → parallel-eternal-engine-D402RASp.d.ts} +49 -49
- package/dist/{path-resolver-BnqXa9Ze.d.ts → path-resolver-UPFTsDyD.d.ts} +6 -6
- package/dist/{permission-V5BLOrY6.d.ts → permission-14CChMmO.d.ts} +10 -8
- package/dist/{permission-policy-CBVx-d-8.d.ts → permission-policy-gW5htOo1.d.ts} +7 -7
- package/dist/{plan-templates-DBgrTGPu.d.ts → plan-templates-DRvPgkfZ.d.ts} +70 -32
- package/dist/{provider-runner-n3KkHT_w.d.ts → provider-runner-COAJM8tC.d.ts} +6 -6
- package/dist/{retry-policy-CG3qvH_e.d.ts → retry-policy-DSu6O6rD.d.ts} +4 -4
- package/dist/sdd/index.d.ts +47 -47
- package/dist/sdd/index.js +47 -22
- package/dist/sdd/index.js.map +1 -1
- package/dist/security/index.d.ts +6 -6
- package/dist/security/index.js +7 -1
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-RvBR_YRW.d.ts → selector-11-fm95U.d.ts} +2 -2
- package/dist/{session-event-bridge-CDHxcmQU.d.ts → session-event-bridge-D0u-x576.d.ts} +7 -7
- package/dist/{session-reader-BIpwM60D.d.ts → session-reader-BQU-toaN.d.ts} +23 -23
- package/dist/{skill-CxuWrsKK.d.ts → skill-BJeF2DwY.d.ts} +1 -1
- package/dist/skills/index.d.ts +9 -9
- package/dist/skills/index.js +15 -3
- package/dist/skills/index.js.map +1 -1
- package/dist/storage/index.d.ts +15 -15
- package/dist/storage/index.js +398 -80
- package/dist/storage/index.js.map +1 -1
- package/dist/{system-prompt-CA11g6Jo.d.ts → system-prompt-C0rLCeyn.d.ts} +16 -11
- package/dist/{task-graph-D1YQbpxF.d.ts → task-graph-CikNdRTG.d.ts} +22 -22
- package/dist/types/index.d.ts +25 -25
- package/dist/types/index.js +61 -12
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +46 -45
- package/dist/utils/index.js +64 -13
- package/dist/utils/index.js.map +1 -1
- package/dist/{wstack-paths-eMXnY1_X.d.ts → wstack-paths-BQMvEllz.d.ts} +10 -3
- package/package.json +1 -1
- package/dist/logger-DDd5C--Z.d.ts +0 -12
package/dist/storage/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomBytes, randomUUID, createHash } from 'crypto';
|
|
2
2
|
import * as fsp from 'fs/promises';
|
|
3
|
-
import * as
|
|
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 =
|
|
13
|
+
const dir = path2.dirname(targetPath);
|
|
14
14
|
await fsp.mkdir(dir, { recursive: true });
|
|
15
|
-
const 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
|
-
|
|
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
|
|
207
|
+
return path2.join(this.dir, `${id}${ext}`);
|
|
182
208
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
280
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
312
|
-
|
|
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 ?
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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(
|
|
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
|
|
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:
|
|
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
|
|
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]
|
|
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
|
|
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]
|
|
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
|
|
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
|
|
2795
|
-
const
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
2933
|
-
const root =
|
|
2934
|
-
const rel =
|
|
2935
|
-
if (rel.startsWith("..") ||
|
|
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(
|
|
3495
|
-
return
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
3960
|
-
const traversesUp = normalizedRel === ".." || normalizedRel.startsWith(`..${
|
|
3961
|
-
if (
|
|
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 =
|
|
3965
|
-
const root =
|
|
3966
|
-
const relative3 =
|
|
3967
|
-
if (relative3.startsWith("..") ||
|
|
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;
|