@wrongstack/core 0.77.0 → 0.84.1
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-EWdqs8v6.d.ts → agent-bridge-C9P_HPez.d.ts} +2 -2
- package/dist/{agent-subagent-runner-D8qW8OSC.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 +318 -37
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +32 -32
- package/dist/defaults/index.js +433 -81
- 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-CYaoLN5_.d.ts → events-DnRqXaZ3.d.ts} +43 -42
- package/dist/execution/index.d.ts +53 -53
- package/dist/execution/index.js +72 -29
- 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-DIxjTOga.d.ts → index-BNOLadHw.d.ts} +28 -28
- package/dist/{index-Dsda0uCn.d.ts → index-N0_c4bHQ.d.ts} +45 -45
- package/dist/index.d.ts +167 -167
- package/dist/index.js +617 -155
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +9 -9
- package/dist/infrastructure/index.js +13 -5
- 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-BppKxDqZ.d.ts → logger-C_27pj9i.d.ts} +6 -7
- 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-DpbG3wiy.d.ts → multi-agent-coordinator-DllpCVkF.d.ts} +12 -12
- package/dist/{null-fleet-bus-u5ys3lW_.d.ts → null-fleet-bus-BY0AN-sr.d.ts} +121 -121
- 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-Dn0P8Pbj.d.ts → parallel-eternal-engine-D402RASp.d.ts} +49 -49
- package/dist/{path-resolver-B32v2JIq.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-BcUwLlMQ.d.ts → plan-templates-DRvPgkfZ.d.ts} +65 -32
- package/dist/{provider-runner-CSi_7l0h.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/{secret-scrubber-7rSC_emZ.d.ts → secret-scrubber-yGBFQYju.d.ts} +10 -2
- package/dist/security/index.d.ts +7 -7
- package/dist/security/index.js +15 -8
- 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 +378 -76
- 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 +26 -26
- package/dist/types/index.js +53 -17
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +57 -45
- package/dist/utils/index.js +66 -12
- package/dist/utils/index.js.map +1 -1
- package/dist/{wstack-paths-D7evAFWM.d.ts → wstack-paths-BQMvEllz.d.ts} +2 -2
- 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,6 +173,12 @@ function isEmptyMessage(msg) {
|
|
|
167
173
|
}
|
|
168
174
|
|
|
169
175
|
// src/storage/session-store.ts
|
|
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
|
+
}
|
|
170
182
|
function sanitizeModel(model) {
|
|
171
183
|
return model.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
172
184
|
}
|
|
@@ -177,7 +189,7 @@ function generateSessionId(startedAt, model) {
|
|
|
177
189
|
const modelPart = model ? `_${sanitizeModel(model)}` : "";
|
|
178
190
|
return `${date}/${time}Z${modelPart}_${suffix}`;
|
|
179
191
|
}
|
|
180
|
-
var DefaultSessionStore = class {
|
|
192
|
+
var DefaultSessionStore = class _DefaultSessionStore {
|
|
181
193
|
dir;
|
|
182
194
|
events;
|
|
183
195
|
secretScrubber;
|
|
@@ -186,9 +198,13 @@ var DefaultSessionStore = class {
|
|
|
186
198
|
this.events = opts.events;
|
|
187
199
|
this.secretScrubber = opts.secretScrubber;
|
|
188
200
|
}
|
|
201
|
+
/** Absolute path to the session index file. */
|
|
202
|
+
get indexFile() {
|
|
203
|
+
return path2.join(this.dir, "_index.jsonl");
|
|
204
|
+
}
|
|
189
205
|
/** Join session ID to its absolute path within the store directory. */
|
|
190
206
|
sessionPath(id, ext) {
|
|
191
|
-
return
|
|
207
|
+
return path2.join(this.dir, `${id}${ext}`);
|
|
192
208
|
}
|
|
193
209
|
/**
|
|
194
210
|
* Ensure the directory implied by the session ID exists. When the ID
|
|
@@ -196,7 +212,7 @@ var DefaultSessionStore = class {
|
|
|
196
212
|
* subdirectory so sessions group naturally by day.
|
|
197
213
|
*/
|
|
198
214
|
async ensureShardDir(id) {
|
|
199
|
-
const dirPath =
|
|
215
|
+
const dirPath = path2.dirname(path2.join(this.dir, id));
|
|
200
216
|
await ensureDir(dirPath);
|
|
201
217
|
return dirPath;
|
|
202
218
|
}
|
|
@@ -204,7 +220,7 @@ var DefaultSessionStore = class {
|
|
|
204
220
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
205
221
|
const id = meta.id && meta.id.length > 0 ? meta.id : generateSessionId(startedAt, meta.model ?? meta.provider);
|
|
206
222
|
const shardDir = await this.ensureShardDir(id);
|
|
207
|
-
const file =
|
|
223
|
+
const file = path2.join(shardDir, `${path2.basename(id)}.jsonl`);
|
|
208
224
|
let handle;
|
|
209
225
|
try {
|
|
210
226
|
handle = await fsp.open(file, "a", 384);
|
|
@@ -218,7 +234,8 @@ var DefaultSessionStore = class {
|
|
|
218
234
|
return new FileSessionWriter(id, handle, startedAt, meta, this.events, {
|
|
219
235
|
dir: shardDir,
|
|
220
236
|
filePath: file,
|
|
221
|
-
secretScrubber: this.secretScrubber
|
|
237
|
+
secretScrubber: this.secretScrubber,
|
|
238
|
+
onClose: (s) => this.appendToIndex(s)
|
|
222
239
|
});
|
|
223
240
|
} catch (err) {
|
|
224
241
|
await handle.close().catch(() => {
|
|
@@ -249,7 +266,7 @@ var DefaultSessionStore = class {
|
|
|
249
266
|
provider: data.metadata.provider
|
|
250
267
|
},
|
|
251
268
|
this.events,
|
|
252
|
-
{ 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) }
|
|
253
270
|
);
|
|
254
271
|
return { writer, data };
|
|
255
272
|
} catch (err) {
|
|
@@ -279,6 +296,15 @@ var DefaultSessionStore = class {
|
|
|
279
296
|
async list(limit = 20) {
|
|
280
297
|
try {
|
|
281
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
|
+
}
|
|
282
308
|
const ids = await this.collectSessionIds(this.dir);
|
|
283
309
|
const sessions = await Promise.all(ids.map((id) => this.summaryFor(id).catch(() => null)));
|
|
284
310
|
const out = sessions.filter((s) => s !== null);
|
|
@@ -292,16 +318,121 @@ var DefaultSessionStore = class {
|
|
|
292
318
|
return [];
|
|
293
319
|
}
|
|
294
320
|
}
|
|
295
|
-
|
|
296
|
-
|
|
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) {
|
|
297
418
|
const ids = [];
|
|
298
|
-
|
|
419
|
+
let entries;
|
|
420
|
+
try {
|
|
421
|
+
entries = await fsp.readdir(dir, { withFileTypes: true });
|
|
422
|
+
} catch {
|
|
423
|
+
return ids;
|
|
424
|
+
}
|
|
299
425
|
for (const entry of entries) {
|
|
300
|
-
|
|
426
|
+
if (entry.name.startsWith(".") && entry.name !== ".wrongstack") continue;
|
|
427
|
+
if (entry.name === "shared" || entry.name === "subagents" || entry.name === "attachments")
|
|
428
|
+
continue;
|
|
301
429
|
if (entry.isDirectory()) {
|
|
302
|
-
|
|
430
|
+
const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
|
|
431
|
+
ids.push(...await this.collectSessionIds(path2.join(dir, entry.name), childPrefix, depth + 1));
|
|
303
432
|
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
304
|
-
|
|
433
|
+
if (entry.name === "_index.jsonl") continue;
|
|
434
|
+
const base = entry.name.replace(/\.jsonl$/, "");
|
|
435
|
+
ids.push(prefix ? `${prefix}/${base}` : base);
|
|
305
436
|
}
|
|
306
437
|
}
|
|
307
438
|
return ids;
|
|
@@ -324,9 +455,70 @@ var DefaultSessionStore = class {
|
|
|
324
455
|
return summary;
|
|
325
456
|
}
|
|
326
457
|
}
|
|
327
|
-
|
|
328
|
-
|
|
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);
|
|
329
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;
|
|
330
522
|
}
|
|
331
523
|
async clearHistory(id) {
|
|
332
524
|
await this.ensureShardDir(id);
|
|
@@ -348,13 +540,42 @@ var DefaultSessionStore = class {
|
|
|
348
540
|
const data = await this.load(id);
|
|
349
541
|
const firstUser = data.events.find((e) => e.type === "user_input");
|
|
350
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
|
+
}
|
|
351
565
|
return {
|
|
352
566
|
id,
|
|
353
567
|
title,
|
|
354
568
|
startedAt: data.metadata.startedAt,
|
|
569
|
+
endedAt: data.metadata.endedAt,
|
|
355
570
|
model: data.metadata.model ?? "unknown",
|
|
356
571
|
provider: data.metadata.provider ?? "unknown",
|
|
357
|
-
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
|
|
358
579
|
};
|
|
359
580
|
} catch {
|
|
360
581
|
return {
|
|
@@ -453,9 +674,10 @@ var FileSessionWriter = class {
|
|
|
453
674
|
this.meta = meta;
|
|
454
675
|
this.events = events;
|
|
455
676
|
this.resumed = opts.resumed ?? false;
|
|
456
|
-
this.manifestFile = opts.dir ?
|
|
677
|
+
this.manifestFile = opts.dir ? path2.join(opts.dir, `${path2.basename(id)}.summary.json`) : "";
|
|
457
678
|
this.filePath = opts.filePath ?? "";
|
|
458
679
|
this.secretScrubber = opts.secretScrubber;
|
|
680
|
+
this.onCloseCb = opts.onClose;
|
|
459
681
|
this.summary = {
|
|
460
682
|
id,
|
|
461
683
|
title: "(empty session)",
|
|
@@ -485,6 +707,15 @@ var FileSessionWriter = class {
|
|
|
485
707
|
appendFailCount = 0;
|
|
486
708
|
lastAppendWarnAt = 0;
|
|
487
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;
|
|
488
719
|
/**
|
|
489
720
|
* Scrub secrets out of conversation-turn events before they are observed
|
|
490
721
|
* for the summary, written to the JSONL log, or surfaced on resume. Only
|
|
@@ -562,8 +793,22 @@ var FileSessionWriter = class {
|
|
|
562
793
|
observeForSummary(event) {
|
|
563
794
|
if (event.type === "tool_use") {
|
|
564
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;
|
|
565
799
|
} else if (event.type === "tool_result") {
|
|
566
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";
|
|
567
812
|
}
|
|
568
813
|
if (event.type === "user_input" && this.summary.title === "(empty session)") {
|
|
569
814
|
this.summary = { ...this.summary, title: userInputTitle(event.content) };
|
|
@@ -574,18 +819,35 @@ var FileSessionWriter = class {
|
|
|
574
819
|
} else if (event.type === "session_end") {
|
|
575
820
|
const total = event.usage.input + event.usage.output;
|
|
576
821
|
if (total > 0) this.summary = { ...this.summary, tokenTotal: total };
|
|
822
|
+
} else if (event.type === "in_flight_start") {
|
|
823
|
+
this.iterationCount++;
|
|
577
824
|
}
|
|
578
825
|
}
|
|
579
826
|
async close() {
|
|
580
827
|
if (this.closing) return;
|
|
581
828
|
this.closing = true;
|
|
582
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
|
+
};
|
|
583
841
|
if (this.manifestFile) {
|
|
584
842
|
try {
|
|
585
843
|
await atomicWrite(this.manifestFile, JSON.stringify(this.summary), { mode: 384 });
|
|
586
844
|
} catch {
|
|
587
845
|
}
|
|
588
846
|
}
|
|
847
|
+
try {
|
|
848
|
+
await this.onCloseCb?.(this.summary);
|
|
849
|
+
} catch {
|
|
850
|
+
}
|
|
589
851
|
try {
|
|
590
852
|
await this.handle.close();
|
|
591
853
|
} catch {
|
|
@@ -627,7 +889,7 @@ var FileSessionWriter = class {
|
|
|
627
889
|
let targetCheckpointLine = -1;
|
|
628
890
|
let afterTarget = false;
|
|
629
891
|
for (let i = 0; i < lines.length; i++) {
|
|
630
|
-
const line = lines[i];
|
|
892
|
+
const line = expectDefined2(lines[i]);
|
|
631
893
|
if (!line.trim()) continue;
|
|
632
894
|
let event;
|
|
633
895
|
try {
|
|
@@ -732,7 +994,7 @@ function userInputTitle(content) {
|
|
|
732
994
|
var QueueStore = class {
|
|
733
995
|
file;
|
|
734
996
|
constructor(opts) {
|
|
735
|
-
this.file =
|
|
997
|
+
this.file = path2.join(opts.dir, "queue.json");
|
|
736
998
|
}
|
|
737
999
|
async write(items) {
|
|
738
1000
|
if (items.length === 0) {
|
|
@@ -799,7 +1061,7 @@ var DefaultAttachmentStore = class {
|
|
|
799
1061
|
let data = input.data;
|
|
800
1062
|
if (this.spoolDir && bytes >= this.spoolThreshold) {
|
|
801
1063
|
await fsp.mkdir(this.spoolDir, { recursive: true });
|
|
802
|
-
spooledPath =
|
|
1064
|
+
spooledPath = path2.join(this.spoolDir, `${id}.bin`);
|
|
803
1065
|
await atomicWrite(spooledPath, input.data, {
|
|
804
1066
|
encoding: input.kind === "image" ? "base64" : "utf8"
|
|
805
1067
|
});
|
|
@@ -984,7 +1246,7 @@ ${body.trim()}`);
|
|
|
984
1246
|
async remember(text, scope = "project-memory") {
|
|
985
1247
|
return this.runSerialized(scope, async () => {
|
|
986
1248
|
const file = this.files[scope];
|
|
987
|
-
await ensureDir(
|
|
1249
|
+
await ensureDir(path2.dirname(file));
|
|
988
1250
|
let existing = "";
|
|
989
1251
|
try {
|
|
990
1252
|
existing = await fsp.readFile(file, "utf8");
|
|
@@ -1623,7 +1885,7 @@ var RecoveryLock = class {
|
|
|
1623
1885
|
sessionStore;
|
|
1624
1886
|
probe;
|
|
1625
1887
|
constructor(opts) {
|
|
1626
|
-
this.file =
|
|
1888
|
+
this.file = path2.join(opts.dir, LOCK_FILE);
|
|
1627
1889
|
this.pid = opts.pid ?? process.pid;
|
|
1628
1890
|
this.hostname = opts.hostname ?? os.hostname();
|
|
1629
1891
|
this.maxAgeMs = opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
@@ -1681,7 +1943,7 @@ var RecoveryLock = class {
|
|
|
1681
1943
|
* null return before calling this.
|
|
1682
1944
|
*/
|
|
1683
1945
|
async write(sessionId) {
|
|
1684
|
-
await ensureDir(
|
|
1946
|
+
await ensureDir(path2.dirname(this.file));
|
|
1685
1947
|
const lock = {
|
|
1686
1948
|
v: 1,
|
|
1687
1949
|
sessionId,
|
|
@@ -1785,6 +2047,12 @@ function compileUserRegex(pattern, flags) {
|
|
|
1785
2047
|
}
|
|
1786
2048
|
|
|
1787
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
|
+
}
|
|
1788
2056
|
var DefaultSessionReader = class {
|
|
1789
2057
|
store;
|
|
1790
2058
|
constructor(opts) {
|
|
@@ -1846,7 +2114,7 @@ var DefaultSessionReader = class {
|
|
|
1846
2114
|
continue;
|
|
1847
2115
|
}
|
|
1848
2116
|
for (let i = 0; i < data.events.length; i++) {
|
|
1849
|
-
const ev = data.events[i];
|
|
2117
|
+
const ev = expectDefined3(data.events[i]);
|
|
1850
2118
|
if (allowedTypes && !allowedTypes.has(ev.type)) continue;
|
|
1851
2119
|
const text = eventText(ev);
|
|
1852
2120
|
if (text === null) continue;
|
|
@@ -2059,6 +2327,12 @@ function renderPlainText(meta, events) {
|
|
|
2059
2327
|
}
|
|
2060
2328
|
return lines.join("\n");
|
|
2061
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
|
+
}
|
|
2062
2336
|
var FILE_VERSION = 1;
|
|
2063
2337
|
var MAX_TEXT_LENGTH = 2e3;
|
|
2064
2338
|
var MAX_ANNOTATIONS = 1e3;
|
|
@@ -2150,7 +2424,7 @@ var AnnotationsStore = class {
|
|
|
2150
2424
|
return;
|
|
2151
2425
|
}
|
|
2152
2426
|
const next = {
|
|
2153
|
-
...all[idx],
|
|
2427
|
+
...expectDefined4(all[idx]),
|
|
2154
2428
|
resolved: true,
|
|
2155
2429
|
resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2156
2430
|
resolvedBy: input.resolvedBy
|
|
@@ -2166,7 +2440,7 @@ var AnnotationsStore = class {
|
|
|
2166
2440
|
if (!sessionId || sessionId.includes("/") || sessionId.includes("\\") || sessionId.includes("..")) {
|
|
2167
2441
|
throw new Error(`Invalid sessionId: ${sessionId}`);
|
|
2168
2442
|
}
|
|
2169
|
-
return
|
|
2443
|
+
return path2.join(this.dir, `${sessionId}.annotations.json`);
|
|
2170
2444
|
}
|
|
2171
2445
|
async readFile(sessionId) {
|
|
2172
2446
|
const fp = this.filePath(sessionId);
|
|
@@ -2323,7 +2597,7 @@ var ReplayLogStore = class {
|
|
|
2323
2597
|
out.push({
|
|
2324
2598
|
sessionId,
|
|
2325
2599
|
entryCount: all.length,
|
|
2326
|
-
path:
|
|
2600
|
+
path: path2.join(this.dir, name)
|
|
2327
2601
|
});
|
|
2328
2602
|
}
|
|
2329
2603
|
return out.sort((a, b) => a.sessionId.localeCompare(b.sessionId));
|
|
@@ -2333,7 +2607,7 @@ var ReplayLogStore = class {
|
|
|
2333
2607
|
if (!sessionId || sessionId.includes("/") || sessionId.includes("\\") || sessionId.includes("..")) {
|
|
2334
2608
|
throw new Error(`Invalid sessionId: ${sessionId}`);
|
|
2335
2609
|
}
|
|
2336
|
-
return
|
|
2610
|
+
return path2.join(this.dir, `${sessionId}.replay.jsonl`);
|
|
2337
2611
|
}
|
|
2338
2612
|
async readAll(sessionId) {
|
|
2339
2613
|
const fp = this.filePath(sessionId);
|
|
@@ -2383,6 +2657,12 @@ var ReplayLogStore = class {
|
|
|
2383
2657
|
return next;
|
|
2384
2658
|
}
|
|
2385
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
|
+
}
|
|
2386
2666
|
var SessionRecovery = class {
|
|
2387
2667
|
constructor(dir) {
|
|
2388
2668
|
this.dir = dir;
|
|
@@ -2423,7 +2703,7 @@ var SessionRecovery = class {
|
|
|
2423
2703
|
const lines = raw.split("\n").filter((l) => l.trim());
|
|
2424
2704
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
2425
2705
|
try {
|
|
2426
|
-
const ev = JSON.parse(lines[i]);
|
|
2706
|
+
const ev = JSON.parse(expectDefined5(lines[i]));
|
|
2427
2707
|
if (ev.type === "in_flight_start") {
|
|
2428
2708
|
return {
|
|
2429
2709
|
sessionId,
|
|
@@ -2475,13 +2755,13 @@ var SessionRecovery = class {
|
|
|
2475
2755
|
let lastCheckpoint = null;
|
|
2476
2756
|
let lastCheckpointIdx = -1;
|
|
2477
2757
|
for (let i = 0; i < events.length; i++) {
|
|
2478
|
-
if (events[i]
|
|
2479
|
-
lastCheckpoint = events[i];
|
|
2758
|
+
if (events[i]?.type === "checkpoint") {
|
|
2759
|
+
lastCheckpoint = expectDefined5(events[i]);
|
|
2480
2760
|
lastCheckpointIdx = i;
|
|
2481
2761
|
}
|
|
2482
2762
|
}
|
|
2483
2763
|
const pendingEvents = lastCheckpointIdx >= 0 ? events.slice(lastCheckpointIdx + 1) : events;
|
|
2484
|
-
const lastEv = events[events.length - 1];
|
|
2764
|
+
const lastEv = expectDefined5(events[events.length - 1]);
|
|
2485
2765
|
const inFlightStart = lastEv.type === "in_flight_start" ? lastEv : null;
|
|
2486
2766
|
const context = inFlightStart && inFlightStart.type === "in_flight_start" ? inFlightStart.context : null;
|
|
2487
2767
|
return {
|
|
@@ -2521,9 +2801,15 @@ var SessionRecovery = class {
|
|
|
2521
2801
|
if (!sessionId || sessionId.includes("/") || sessionId.includes("\\") || sessionId.includes("..")) {
|
|
2522
2802
|
throw new Error(`Invalid sessionId: ${sessionId}`);
|
|
2523
2803
|
}
|
|
2524
|
-
return
|
|
2804
|
+
return path2.join(this.dir, `${sessionId}.jsonl`);
|
|
2525
2805
|
}
|
|
2526
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
|
+
}
|
|
2527
2813
|
var GENESIS_PREV = "0".repeat(64);
|
|
2528
2814
|
var DEFAULT_FSYNC_EVERY = 100;
|
|
2529
2815
|
var ToolAuditLog = class {
|
|
@@ -2590,7 +2876,7 @@ var ToolAuditLog = class {
|
|
|
2590
2876
|
async verify(sessionId) {
|
|
2591
2877
|
const entries = await this.readAll(sessionId);
|
|
2592
2878
|
if (entries.length === 0) return { ok: true, entries: 0 };
|
|
2593
|
-
if (entries[0]
|
|
2879
|
+
if (entries[0]?.prevHash !== GENESIS_PREV) {
|
|
2594
2880
|
return {
|
|
2595
2881
|
ok: false,
|
|
2596
2882
|
brokenAt: 0,
|
|
@@ -2599,7 +2885,7 @@ var ToolAuditLog = class {
|
|
|
2599
2885
|
}
|
|
2600
2886
|
let prevHash = GENESIS_PREV;
|
|
2601
2887
|
for (let i = 0; i < entries.length; i++) {
|
|
2602
|
-
const e = entries[i];
|
|
2888
|
+
const e = expectDefined6(entries[i]);
|
|
2603
2889
|
if (e.prevHash !== prevHash) {
|
|
2604
2890
|
return {
|
|
2605
2891
|
ok: false,
|
|
@@ -2639,7 +2925,7 @@ var ToolAuditLog = class {
|
|
|
2639
2925
|
if (!sessionId || sessionId.includes("/") || sessionId.includes("\\") || sessionId.includes("..")) {
|
|
2640
2926
|
throw new Error(`Invalid sessionId: ${sessionId}`);
|
|
2641
2927
|
}
|
|
2642
|
-
return
|
|
2928
|
+
return path2.join(this.dir, `${sessionId}.audit.jsonl`);
|
|
2643
2929
|
}
|
|
2644
2930
|
async readAll(sessionId) {
|
|
2645
2931
|
const fp = this.filePath(sessionId);
|
|
@@ -2807,11 +3093,20 @@ var SessionAnalyzer = class {
|
|
|
2807
3093
|
}
|
|
2808
3094
|
calcDuration(events) {
|
|
2809
3095
|
if (events.length < 2) return 0;
|
|
2810
|
-
const
|
|
2811
|
-
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();
|
|
2812
3101
|
return last - first;
|
|
2813
3102
|
}
|
|
2814
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
|
+
}
|
|
2815
3110
|
var DefaultSessionRewinder = class {
|
|
2816
3111
|
constructor(sessionsDir, projectRoot) {
|
|
2817
3112
|
this.sessionsDir = sessionsDir;
|
|
@@ -2820,7 +3115,7 @@ var DefaultSessionRewinder = class {
|
|
|
2820
3115
|
sessionsDir;
|
|
2821
3116
|
projectRoot;
|
|
2822
3117
|
async listCheckpoints(sessionId) {
|
|
2823
|
-
const file =
|
|
3118
|
+
const file = path2.join(this.sessionsDir, `${sessionId}.jsonl`);
|
|
2824
3119
|
const raw = await fsp.readFile(file, "utf8");
|
|
2825
3120
|
const events = parseEvents(raw);
|
|
2826
3121
|
const fileCountMap = /* @__PURE__ */ new Map();
|
|
@@ -2845,12 +3140,12 @@ var DefaultSessionRewinder = class {
|
|
|
2845
3140
|
return checkpoints;
|
|
2846
3141
|
}
|
|
2847
3142
|
async rewindToCheckpoint(sessionId, checkpointIndex) {
|
|
2848
|
-
const file =
|
|
3143
|
+
const file = path2.join(this.sessionsDir, `${sessionId}.jsonl`);
|
|
2849
3144
|
const raw = await fsp.readFile(file, "utf8");
|
|
2850
3145
|
const events = parseEvents(raw);
|
|
2851
3146
|
let targetIdx = -1;
|
|
2852
3147
|
for (let i = 0; i < events.length; i++) {
|
|
2853
|
-
const event = events[i];
|
|
3148
|
+
const event = expectDefined7(events[i]);
|
|
2854
3149
|
if (event.type === "checkpoint") {
|
|
2855
3150
|
const checkpointEvent = event;
|
|
2856
3151
|
if (checkpointEvent.promptIndex === checkpointIndex) {
|
|
@@ -2864,7 +3159,7 @@ var DefaultSessionRewinder = class {
|
|
|
2864
3159
|
}
|
|
2865
3160
|
const snapshotsToRevert = [];
|
|
2866
3161
|
for (let i = targetIdx + 1; i < events.length; i++) {
|
|
2867
|
-
const event = events[i];
|
|
3162
|
+
const event = expectDefined7(events[i]);
|
|
2868
3163
|
if (event.type === "checkpoint") {
|
|
2869
3164
|
break;
|
|
2870
3165
|
}
|
|
@@ -2880,7 +3175,7 @@ var DefaultSessionRewinder = class {
|
|
|
2880
3175
|
return { ...result, toPromptIndex: checkpointIndex, removedEvents };
|
|
2881
3176
|
}
|
|
2882
3177
|
async rewindLastN(sessionId, n) {
|
|
2883
|
-
const file =
|
|
3178
|
+
const file = path2.join(this.sessionsDir, `${sessionId}.jsonl`);
|
|
2884
3179
|
const raw = await fsp.readFile(file, "utf8");
|
|
2885
3180
|
const events = parseEvents(raw);
|
|
2886
3181
|
const checkpoints = [];
|
|
@@ -2909,7 +3204,7 @@ var DefaultSessionRewinder = class {
|
|
|
2909
3204
|
return { ...result, toPromptIndex: targetIndex, removedEvents: snapshotsToRevert.length };
|
|
2910
3205
|
}
|
|
2911
3206
|
async rewindToStart(sessionId) {
|
|
2912
|
-
const file =
|
|
3207
|
+
const file = path2.join(this.sessionsDir, `${sessionId}.jsonl`);
|
|
2913
3208
|
const raw = await fsp.readFile(file, "utf8");
|
|
2914
3209
|
const events = parseEvents(raw);
|
|
2915
3210
|
const allSnapshots = [];
|
|
@@ -2945,10 +3240,10 @@ async function revertSnapshots(snapshots, projectRoot) {
|
|
|
2945
3240
|
for (const snapshot of snapshots) {
|
|
2946
3241
|
for (const file of snapshot.files) {
|
|
2947
3242
|
try {
|
|
2948
|
-
const absPath =
|
|
2949
|
-
const root =
|
|
2950
|
-
const rel =
|
|
2951
|
-
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)) {
|
|
2952
3247
|
errors.push(`${file.path}: path resolves outside project root \u2014 skipping`);
|
|
2953
3248
|
continue;
|
|
2954
3249
|
}
|
|
@@ -3507,8 +3802,8 @@ var FsError = class extends WrongStackError {
|
|
|
3507
3802
|
// src/storage/goal-store.ts
|
|
3508
3803
|
var MAX_JOURNAL_ENTRIES = 500;
|
|
3509
3804
|
function goalFilePath(projectRoot) {
|
|
3510
|
-
const hash = createHash("sha256").update(
|
|
3511
|
-
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");
|
|
3512
3807
|
}
|
|
3513
3808
|
async function loadGoal(filePath) {
|
|
3514
3809
|
let raw;
|
|
@@ -3623,7 +3918,7 @@ var DefaultPromptStore = class {
|
|
|
3623
3918
|
if (!file.endsWith(".json")) continue;
|
|
3624
3919
|
try {
|
|
3625
3920
|
const raw = JSON.parse(
|
|
3626
|
-
await fsp.readFile(
|
|
3921
|
+
await fsp.readFile(path2.join(this.dir, file), "utf8")
|
|
3627
3922
|
);
|
|
3628
3923
|
entries.push(raw.entry);
|
|
3629
3924
|
} catch {
|
|
@@ -3636,7 +3931,7 @@ var DefaultPromptStore = class {
|
|
|
3636
3931
|
);
|
|
3637
3932
|
}
|
|
3638
3933
|
async get(id) {
|
|
3639
|
-
const file =
|
|
3934
|
+
const file = path2.join(this.dir, `${id}.json`);
|
|
3640
3935
|
try {
|
|
3641
3936
|
const raw = JSON.parse(await fsp.readFile(file, "utf8"));
|
|
3642
3937
|
return raw.entry;
|
|
@@ -3646,12 +3941,12 @@ var DefaultPromptStore = class {
|
|
|
3646
3941
|
}
|
|
3647
3942
|
async save(entry) {
|
|
3648
3943
|
await ensureDir(this.dir);
|
|
3649
|
-
const file =
|
|
3944
|
+
const file = path2.join(this.dir, `${entry.id}.json`);
|
|
3650
3945
|
const raw = { version: 1, entry };
|
|
3651
3946
|
await atomicWrite(file, JSON.stringify(raw, null, 2));
|
|
3652
3947
|
}
|
|
3653
3948
|
async delete(id) {
|
|
3654
|
-
const file =
|
|
3949
|
+
const file = path2.join(this.dir, `${id}.json`);
|
|
3655
3950
|
try {
|
|
3656
3951
|
await fsp.unlink(file);
|
|
3657
3952
|
return true;
|
|
@@ -3679,13 +3974,19 @@ var DefaultPromptStore = class {
|
|
|
3679
3974
|
};
|
|
3680
3975
|
}
|
|
3681
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
|
+
}
|
|
3682
3983
|
var ALL_SYNC_CATEGORIES = ["settings", "skills", "prompts", "memory", "history"];
|
|
3683
3984
|
var CloudSync = class {
|
|
3684
3985
|
constructor(paths, getConfig, setConfig) {
|
|
3685
3986
|
this.paths = paths;
|
|
3686
3987
|
this.getConfig = getConfig;
|
|
3687
3988
|
this.setConfig = setConfig;
|
|
3688
|
-
this.statePath =
|
|
3989
|
+
this.statePath = path2.join(paths.globalRoot, "sync-state.json");
|
|
3689
3990
|
}
|
|
3690
3991
|
paths;
|
|
3691
3992
|
getConfig;
|
|
@@ -3721,8 +4022,8 @@ var CloudSync = class {
|
|
|
3721
4022
|
const cfg = this.getConfig();
|
|
3722
4023
|
if (!cfg?.enabled) return { ok: false, action: "push", categories: [], message: "Not enabled." };
|
|
3723
4024
|
const parts = cfg.repo.split("/");
|
|
3724
|
-
const owner = parts[0];
|
|
3725
|
-
const repoName = parts[1];
|
|
4025
|
+
const owner = expectDefined8(parts[0]);
|
|
4026
|
+
const repoName = expectDefined8(parts[1]);
|
|
3726
4027
|
const branch = "main";
|
|
3727
4028
|
const baseTreeSha = this.state?.sha;
|
|
3728
4029
|
const { treeEntries, rev } = await this.buildLocalTree(cfg.categories);
|
|
@@ -3774,8 +4075,8 @@ var CloudSync = class {
|
|
|
3774
4075
|
const cfg = this.getConfig();
|
|
3775
4076
|
if (!cfg?.enabled) return { ok: false, action: "pull", categories: [], message: "Not enabled." };
|
|
3776
4077
|
const pullParts = cfg.repo.split("/");
|
|
3777
|
-
const owner = pullParts[0];
|
|
3778
|
-
const repoName = pullParts[1];
|
|
4078
|
+
const owner = expectDefined8(pullParts[0]);
|
|
4079
|
+
const repoName = expectDefined8(pullParts[1]);
|
|
3779
4080
|
const branchData = await this.getRef(token, owner, repoName, "main");
|
|
3780
4081
|
const currentSha = branchData.object.sha;
|
|
3781
4082
|
const commitData = await this.getCommit(token, owner, repoName, currentSha);
|
|
@@ -3792,7 +4093,7 @@ var CloudSync = class {
|
|
|
3792
4093
|
const rel = segments.slice(2).join("/");
|
|
3793
4094
|
const destPath = resolvePulledCategoryPath(cat, localPath, rel, entry.path);
|
|
3794
4095
|
const blobData = await this.getBlob(token, owner, repoName, entry.sha);
|
|
3795
|
-
await fsp.mkdir(
|
|
4096
|
+
await fsp.mkdir(path2.dirname(destPath), { recursive: true });
|
|
3796
4097
|
await fsp.writeFile(destPath, Buffer.from(blobData, "base64"));
|
|
3797
4098
|
}
|
|
3798
4099
|
const localRev = await this.hashLocalCategories(cfg.categories);
|
|
@@ -3830,7 +4131,7 @@ var CloudSync = class {
|
|
|
3830
4131
|
// ── GitHub API helpers ──────────────────────────────────────────────
|
|
3831
4132
|
async githubFetch(token, owner, repo, method, pathSegment, body) {
|
|
3832
4133
|
const url = `https://api.github.com/repos/${owner}/${repo}${pathSegment}`;
|
|
3833
|
-
const
|
|
4134
|
+
const init = {
|
|
3834
4135
|
signal: AbortSignal.timeout(15e3),
|
|
3835
4136
|
method,
|
|
3836
4137
|
headers: {
|
|
@@ -3838,9 +4139,10 @@ var CloudSync = class {
|
|
|
3838
4139
|
Accept: "application/vnd.github+json",
|
|
3839
4140
|
"X-GitHub-Api-Version": "2022-11-28",
|
|
3840
4141
|
"Content-Type": "application/json"
|
|
3841
|
-
}
|
|
3842
|
-
|
|
3843
|
-
|
|
4142
|
+
}
|
|
4143
|
+
};
|
|
4144
|
+
if (body !== void 0) init.body = JSON.stringify(body);
|
|
4145
|
+
const res = await fetch(url, init);
|
|
3844
4146
|
if (!res.ok) {
|
|
3845
4147
|
const errText = await res.text();
|
|
3846
4148
|
throw new Error(`GitHub API ${method} ${pathSegment} failed (${res.status}): ${errText}`);
|
|
@@ -3898,7 +4200,7 @@ var CloudSync = class {
|
|
|
3898
4200
|
const files = await this.walkDir(localPath, localPath);
|
|
3899
4201
|
for (const file of files) {
|
|
3900
4202
|
const content = await fsp.readFile(file, "utf8");
|
|
3901
|
-
const rel =
|
|
4203
|
+
const rel = path2.relative(localPath, file).replace(/\\/g, "/");
|
|
3902
4204
|
entries.push({ path: `data/${cat}/${rel}`, content, mode: "100644" });
|
|
3903
4205
|
hashes.push(content);
|
|
3904
4206
|
}
|
|
@@ -3955,7 +4257,7 @@ var CloudSync = class {
|
|
|
3955
4257
|
const results = [];
|
|
3956
4258
|
const entries = await fsp.readdir(dir, { withFileTypes: true });
|
|
3957
4259
|
for (const entry of entries) {
|
|
3958
|
-
const full =
|
|
4260
|
+
const full = path2.join(dir, entry.name);
|
|
3959
4261
|
if (entry.isDirectory()) {
|
|
3960
4262
|
results.push(...await this.walkDir(full, base));
|
|
3961
4263
|
} else {
|
|
@@ -3972,15 +4274,15 @@ function resolvePulledCategoryPath(cat, localPath, rel, remotePath) {
|
|
|
3972
4274
|
return localPath;
|
|
3973
4275
|
}
|
|
3974
4276
|
if (!rel) return localPath;
|
|
3975
|
-
const normalizedRel =
|
|
3976
|
-
const traversesUp = normalizedRel === ".." || normalizedRel.startsWith(`..${
|
|
3977
|
-
if (
|
|
4277
|
+
const normalizedRel = path2.normalize(rel);
|
|
4278
|
+
const traversesUp = normalizedRel === ".." || normalizedRel.startsWith(`..${path2.sep}`);
|
|
4279
|
+
if (path2.isAbsolute(normalizedRel) || traversesUp) {
|
|
3978
4280
|
throw new Error(`Refusing CloudSync path traversal: ${remotePath}`);
|
|
3979
4281
|
}
|
|
3980
|
-
const dest =
|
|
3981
|
-
const root =
|
|
3982
|
-
const relative3 =
|
|
3983
|
-
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)) {
|
|
3984
4286
|
throw new Error(`Refusing CloudSync path outside category root: ${remotePath}`);
|
|
3985
4287
|
}
|
|
3986
4288
|
return dest;
|