@wrongstack/core 0.1.9 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/agent-bridge-6KPqsFx6.d.ts +33 -0
  2. package/dist/compactor-B4mQZXf2.d.ts +17 -0
  3. package/dist/config-BU9f_5yH.d.ts +193 -0
  4. package/dist/{provider-txgB0Oq9.d.ts → context-BmM2xGUZ.d.ts} +532 -472
  5. package/dist/coordination/index.d.ts +694 -0
  6. package/dist/coordination/index.js +1995 -0
  7. package/dist/coordination/index.js.map +1 -0
  8. package/dist/defaults/index.d.ts +34 -2309
  9. package/dist/defaults/index.js +3893 -3803
  10. package/dist/defaults/index.js.map +1 -1
  11. package/dist/events-BMNaEFZl.d.ts +218 -0
  12. package/dist/execution/index.d.ts +260 -0
  13. package/dist/execution/index.js +1625 -0
  14. package/dist/execution/index.js.map +1 -0
  15. package/dist/index.d.ts +47 -10
  16. package/dist/index.js +6617 -6093
  17. package/dist/index.js.map +1 -1
  18. package/dist/infrastructure/index.d.ts +10 -0
  19. package/dist/infrastructure/index.js +575 -0
  20. package/dist/infrastructure/index.js.map +1 -0
  21. package/dist/input-reader-E-ffP2ee.d.ts +12 -0
  22. package/dist/kernel/index.d.ts +15 -4
  23. package/dist/kernel/index.js.map +1 -1
  24. package/dist/logger-BH6AE0W9.d.ts +24 -0
  25. package/dist/logger-BMQgxvdy.d.ts +12 -0
  26. package/dist/mcp-servers-Dzgg4x1w.d.ts +100 -0
  27. package/dist/memory-CEXuo7sz.d.ts +16 -0
  28. package/dist/mode-CV077NjV.d.ts +27 -0
  29. package/dist/models/index.d.ts +60 -0
  30. package/dist/models/index.js +621 -0
  31. package/dist/models/index.js.map +1 -0
  32. package/dist/models-registry-DqzwpBQy.d.ts +46 -0
  33. package/dist/models-registry-Y2xbog0E.d.ts +95 -0
  34. package/dist/multi-agent-fmkRHtof.d.ts +283 -0
  35. package/dist/observability/index.d.ts +353 -0
  36. package/dist/observability/index.js +691 -0
  37. package/dist/observability/index.js.map +1 -0
  38. package/dist/observability-BhnVLBLS.d.ts +67 -0
  39. package/dist/path-resolver-CPRj4bFY.d.ts +10 -0
  40. package/dist/path-resolver-DBjaoXFq.d.ts +54 -0
  41. package/dist/plugin-DJk6LL8B.d.ts +434 -0
  42. package/dist/renderer-rk_1Swwc.d.ts +158 -0
  43. package/dist/sdd/index.d.ts +206 -0
  44. package/dist/sdd/index.js +864 -0
  45. package/dist/sdd/index.js.map +1 -0
  46. package/dist/secret-scrubber-CicHLN4G.d.ts +31 -0
  47. package/dist/secret-scrubber-DF88luOe.d.ts +54 -0
  48. package/dist/secret-vault-DoISxaKO.d.ts +19 -0
  49. package/dist/security/index.d.ts +30 -0
  50. package/dist/security/index.js +524 -0
  51. package/dist/security/index.js.map +1 -0
  52. package/dist/selector-BbJqiEP4.d.ts +51 -0
  53. package/dist/session-reader-Drq8RvJu.d.ts +150 -0
  54. package/dist/skill-DhfSizKv.d.ts +72 -0
  55. package/dist/storage/index.d.ts +382 -0
  56. package/dist/storage/index.js +1530 -0
  57. package/dist/storage/index.js.map +1 -0
  58. package/dist/{system-prompt-vAB0F54-.d.ts → system-prompt-BC_8ypCG.d.ts} +1 -1
  59. package/dist/task-graph-BITvWt4t.d.ts +160 -0
  60. package/dist/tool-executor-CpuJPYm9.d.ts +97 -0
  61. package/dist/types/index.d.ts +26 -4
  62. package/dist/types/index.js +1787 -4
  63. package/dist/types/index.js.map +1 -1
  64. package/dist/utils/index.d.ts +49 -2
  65. package/dist/utils/index.js +100 -2
  66. package/dist/utils/index.js.map +1 -1
  67. package/package.json +34 -2
  68. package/dist/mode-Pjt5vMS6.d.ts +0 -815
  69. package/dist/session-reader-9sOTgmeC.d.ts +0 -1087
@@ -0,0 +1,1530 @@
1
+ import { randomBytes, randomUUID } from 'crypto';
2
+ import * as fsp from 'fs/promises';
3
+ import * as path2 from 'path';
4
+ import 'fs';
5
+ import * as os from 'os';
6
+
7
+ // src/storage/session-store.ts
8
+ async function atomicWrite(targetPath, content, opts = {}) {
9
+ const dir = path2.dirname(targetPath);
10
+ await fsp.mkdir(dir, { recursive: true });
11
+ const tmp = path2.join(dir, `.${path2.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
12
+ try {
13
+ if (typeof content === "string") {
14
+ await fsp.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
15
+ } else {
16
+ await fsp.writeFile(tmp, content, { flag: "wx" });
17
+ }
18
+ try {
19
+ const fh = await fsp.open(tmp, "r+");
20
+ try {
21
+ await fh.sync();
22
+ } finally {
23
+ await fh.close();
24
+ }
25
+ } catch {
26
+ }
27
+ let mode;
28
+ try {
29
+ const stat3 = await fsp.stat(targetPath);
30
+ mode = stat3.mode & 511;
31
+ } catch {
32
+ mode = opts.mode;
33
+ }
34
+ if (mode !== void 0) {
35
+ await fsp.chmod(tmp, mode);
36
+ }
37
+ await fsp.rename(tmp, targetPath);
38
+ } catch (err) {
39
+ try {
40
+ await fsp.unlink(tmp);
41
+ } catch {
42
+ }
43
+ throw err;
44
+ }
45
+ }
46
+ async function ensureDir(dir) {
47
+ await fsp.mkdir(dir, { recursive: true });
48
+ }
49
+
50
+ // src/storage/session-store.ts
51
+ var DefaultSessionStore = class {
52
+ dir;
53
+ events;
54
+ constructor(opts) {
55
+ this.dir = opts.dir;
56
+ this.events = opts.events;
57
+ }
58
+ async create(meta) {
59
+ await ensureDir(this.dir);
60
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
61
+ const id = meta.id ?? `${startedAt.replace(/[:.]/g, "-")}-${randomBytes(2).toString("hex")}`;
62
+ const file = path2.join(this.dir, `${id}.jsonl`);
63
+ let handle;
64
+ try {
65
+ handle = await fsp.open(file, "a", 384);
66
+ } catch (err) {
67
+ throw new Error(
68
+ `Failed to open session file: ${err instanceof Error ? err.message : String(err)}`,
69
+ {
70
+ cause: err
71
+ }
72
+ );
73
+ }
74
+ try {
75
+ return new FileSessionWriter(id, handle, startedAt, meta, { dir: this.dir, filePath: file });
76
+ } catch (err) {
77
+ await handle.close().catch(() => {
78
+ });
79
+ throw err;
80
+ }
81
+ }
82
+ async resume(id) {
83
+ const data = await this.load(id);
84
+ const file = path2.join(this.dir, `${id}.jsonl`);
85
+ let handle;
86
+ try {
87
+ handle = await fsp.open(file, "a", 384);
88
+ } catch (err) {
89
+ throw new Error(
90
+ `Failed to open session "${id}" for append: ${err instanceof Error ? err.message : String(err)}`,
91
+ { cause: err }
92
+ );
93
+ }
94
+ const writer = new FileSessionWriter(
95
+ id,
96
+ handle,
97
+ (/* @__PURE__ */ new Date()).toISOString(),
98
+ {
99
+ id,
100
+ model: data.metadata.model,
101
+ provider: data.metadata.provider
102
+ },
103
+ { resumed: true, dir: this.dir, filePath: file }
104
+ );
105
+ return { writer, data };
106
+ }
107
+ async load(id) {
108
+ const file = path2.join(this.dir, `${id}.jsonl`);
109
+ const raw = await fsp.readFile(file, "utf8");
110
+ const lines = raw.split("\n").filter((l) => l.trim());
111
+ const events = [];
112
+ for (const line of lines) {
113
+ try {
114
+ const parsed = JSON.parse(line);
115
+ if (parsed !== null && typeof parsed === "object" && typeof parsed.type === "string" && typeof parsed.ts === "string") {
116
+ events.push(parsed);
117
+ }
118
+ } catch {
119
+ }
120
+ }
121
+ const meta = this.metaFromEvents(id, events);
122
+ const { messages, usage } = this.replay(events, id);
123
+ return { metadata: meta, events, messages, usage };
124
+ }
125
+ async list(limit = 20) {
126
+ try {
127
+ await ensureDir(this.dir);
128
+ const files = await fsp.readdir(this.dir);
129
+ const ids = files.filter((f) => f.endsWith(".jsonl")).map((f) => f.replace(/\.jsonl$/, ""));
130
+ const sessions = await Promise.all(ids.map((id) => this.summaryFor(id).catch(() => null)));
131
+ const out = sessions.filter((s) => s !== null);
132
+ out.sort((a, b) => {
133
+ if (a.startedAt < b.startedAt) return 1;
134
+ if (a.startedAt > b.startedAt) return -1;
135
+ return a.id.localeCompare(b.id);
136
+ });
137
+ return out.slice(0, limit);
138
+ } catch {
139
+ return [];
140
+ }
141
+ }
142
+ async summaryFor(id) {
143
+ const manifest = path2.join(this.dir, `${id}.summary.json`);
144
+ try {
145
+ const raw = await fsp.readFile(manifest, "utf8");
146
+ return JSON.parse(raw);
147
+ } catch {
148
+ const full = path2.join(this.dir, `${id}.jsonl`);
149
+ const stat3 = await fsp.stat(full);
150
+ const summary = await this.summarize(id, stat3.mtime.toISOString());
151
+ await fsp.writeFile(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
152
+ console.warn(
153
+ `[session-store] Failed to write manifest for "${id}":`,
154
+ err instanceof Error ? err.message : String(err)
155
+ );
156
+ });
157
+ return summary;
158
+ }
159
+ }
160
+ async delete(id) {
161
+ await fsp.unlink(path2.join(this.dir, `${id}.jsonl`));
162
+ await fsp.unlink(path2.join(this.dir, `${id}.summary.json`)).catch(() => void 0);
163
+ }
164
+ async summarize(id, mtime) {
165
+ try {
166
+ const data = await this.load(id);
167
+ const firstUser = data.events.find((e) => e.type === "user_input");
168
+ const title = firstUser && firstUser.type === "user_input" ? userInputTitle(firstUser.content) : "(empty session)";
169
+ return {
170
+ id,
171
+ title,
172
+ startedAt: data.metadata.startedAt,
173
+ model: data.metadata.model ?? "unknown",
174
+ provider: data.metadata.provider ?? "unknown",
175
+ tokenTotal: data.usage.input + data.usage.output
176
+ };
177
+ } catch {
178
+ return {
179
+ id,
180
+ title: "(damaged)",
181
+ startedAt: mtime,
182
+ model: "unknown",
183
+ provider: "unknown",
184
+ tokenTotal: 0
185
+ };
186
+ }
187
+ }
188
+ metaFromEvents(id, events) {
189
+ const start = events.find((e) => e.type === "session_start");
190
+ const end = events.find((e) => e.type === "session_end");
191
+ return {
192
+ id,
193
+ startedAt: start?.ts ?? (/* @__PURE__ */ new Date(0)).toISOString(),
194
+ endedAt: end?.ts,
195
+ model: start?.model,
196
+ provider: start?.provider
197
+ };
198
+ }
199
+ replay(events, sessionId = "unknown") {
200
+ const messages = [];
201
+ let usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
202
+ const openToolUses = /* @__PURE__ */ new Set();
203
+ for (const e of events) {
204
+ if (e.type === "user_input") {
205
+ openToolUses.clear();
206
+ messages.push({ role: "user", content: e.content });
207
+ } else if (e.type === "llm_response") {
208
+ messages.push({ role: "assistant", content: e.content });
209
+ for (const b of e.content) {
210
+ if (b.type === "tool_use") openToolUses.add(b.id);
211
+ }
212
+ usage = {
213
+ input: usage.input + (e.usage.input ?? 0),
214
+ output: usage.output + (e.usage.output ?? 0),
215
+ cacheRead: (usage.cacheRead ?? 0) + (e.usage.cacheRead ?? 0),
216
+ cacheWrite: (usage.cacheWrite ?? 0) + (e.usage.cacheWrite ?? 0)
217
+ };
218
+ } else if (e.type === "tool_result") {
219
+ if (!openToolUses.has(e.id)) {
220
+ this.events?.emit("session.damaged", {
221
+ sessionId,
222
+ detail: `Orphan tool_result "${e.id}" has no matching tool_use`
223
+ });
224
+ continue;
225
+ }
226
+ openToolUses.delete(e.id);
227
+ const content = [
228
+ {
229
+ type: "tool_result",
230
+ tool_use_id: e.id,
231
+ content: typeof e.content === "string" ? e.content : JSON.stringify(e.content),
232
+ is_error: e.isError
233
+ }
234
+ ];
235
+ const last = messages[messages.length - 1];
236
+ if (last && last.role === "user") {
237
+ if (Array.isArray(last.content)) {
238
+ last.content.push(...content);
239
+ } else if (typeof last.content === "string") {
240
+ last.content = [{ type: "text", text: last.content }, ...content];
241
+ } else {
242
+ messages.push({ role: "user", content });
243
+ }
244
+ } else {
245
+ messages.push({ role: "user", content });
246
+ }
247
+ }
248
+ }
249
+ if (openToolUses.size > 0) {
250
+ this.events?.emit("session.damaged", {
251
+ sessionId,
252
+ detail: `${openToolUses.size} tool_use blocks without matching results \u2014 replay truncated`
253
+ });
254
+ return { messages, usage };
255
+ }
256
+ return { messages, usage };
257
+ }
258
+ };
259
+ var FileSessionWriter = class {
260
+ constructor(id, handle, startedAt, meta, opts = {}) {
261
+ this.id = id;
262
+ this.handle = handle;
263
+ this.startedAt = startedAt;
264
+ this.meta = meta;
265
+ this.resumed = opts.resumed ?? false;
266
+ this.manifestFile = opts.dir ? path2.join(opts.dir, `${id}.summary.json`) : "";
267
+ this.filePath = opts.filePath ?? "";
268
+ this.summary = {
269
+ id,
270
+ title: "(empty session)",
271
+ startedAt,
272
+ model: meta.model ?? "unknown",
273
+ provider: meta.provider ?? "unknown",
274
+ tokenTotal: 0
275
+ };
276
+ }
277
+ id;
278
+ handle;
279
+ startedAt;
280
+ meta;
281
+ closed = false;
282
+ manifestFile;
283
+ summary;
284
+ tokenIn = 0;
285
+ tokenOut = 0;
286
+ filePath;
287
+ initDone = false;
288
+ resumed;
289
+ appendFailCount = 0;
290
+ lastAppendWarnAt = 0;
291
+ async writeSessionStart() {
292
+ if (this.initDone || this.closed) return;
293
+ this.initDone = true;
294
+ const record = `${JSON.stringify({
295
+ type: this.resumed ? "session_resumed" : "session_start",
296
+ ts: this.startedAt,
297
+ id: this.id,
298
+ model: this.meta.model ?? "unknown",
299
+ provider: this.meta.provider ?? "unknown"
300
+ })}
301
+ `;
302
+ try {
303
+ if (this.filePath) {
304
+ await fsp.writeFile(this.filePath, record, { flag: "a", mode: 384 });
305
+ }
306
+ } catch {
307
+ }
308
+ }
309
+ async append(event) {
310
+ if (this.closed) return;
311
+ if (!this.initDone) {
312
+ await this.writeSessionStart();
313
+ }
314
+ this.observeForSummary(event);
315
+ try {
316
+ await this.handle.appendFile(`${JSON.stringify(event)}
317
+ `, "utf8");
318
+ } catch (err) {
319
+ this.appendFailCount++;
320
+ const now = Date.now();
321
+ if (now - this.lastAppendWarnAt > 5e3) {
322
+ const suppressed = this.appendFailCount - 1;
323
+ const tail = suppressed > 0 ? ` (+${suppressed} suppressed)` : "";
324
+ console.warn(
325
+ "[session] append failed:",
326
+ err instanceof Error ? err.message : String(err),
327
+ tail
328
+ );
329
+ this.lastAppendWarnAt = now;
330
+ this.appendFailCount = 0;
331
+ }
332
+ }
333
+ }
334
+ /**
335
+ * Watch events as they're appended and keep the summary state hot, so
336
+ * `close()` can flush a `<id>.summary.json` manifest without re-reading
337
+ * the JSONL. `list()` reads only manifests, turning a per-session full
338
+ * parse into a single stat+read.
339
+ */
340
+ observeForSummary(event) {
341
+ if (event.type === "user_input" && this.summary.title === "(empty session)") {
342
+ this.summary = { ...this.summary, title: userInputTitle(event.content) };
343
+ } else if (event.type === "llm_response") {
344
+ this.tokenIn += event.usage.input;
345
+ this.tokenOut += event.usage.output;
346
+ this.summary = { ...this.summary, tokenTotal: this.tokenIn + this.tokenOut };
347
+ } else if (event.type === "session_end") {
348
+ const total = event.usage.input + event.usage.output;
349
+ if (total > 0) this.summary = { ...this.summary, tokenTotal: total };
350
+ }
351
+ }
352
+ async close() {
353
+ if (this.closed) return;
354
+ this.closed = true;
355
+ if (this.manifestFile) {
356
+ try {
357
+ await fsp.writeFile(this.manifestFile, JSON.stringify(this.summary), { mode: 384 });
358
+ } catch {
359
+ }
360
+ }
361
+ try {
362
+ await this.handle.close();
363
+ } catch {
364
+ }
365
+ }
366
+ };
367
+ function userInputTitle(content) {
368
+ if (typeof content === "string") return content.slice(0, 60);
369
+ const text = content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
370
+ return (text || "(non-text input)").slice(0, 60);
371
+ }
372
+ var QueueStore = class {
373
+ file;
374
+ constructor(opts) {
375
+ this.file = path2.join(opts.dir, "queue.json");
376
+ }
377
+ async write(items) {
378
+ if (items.length === 0) {
379
+ await this.clear();
380
+ return;
381
+ }
382
+ await atomicWrite(this.file, JSON.stringify(items), { mode: 384 });
383
+ }
384
+ async read() {
385
+ let raw;
386
+ try {
387
+ raw = await fsp.readFile(this.file, "utf8");
388
+ } catch (err) {
389
+ const code = err.code;
390
+ if (code === "ENOENT") return [];
391
+ return [];
392
+ }
393
+ let parsed;
394
+ try {
395
+ parsed = JSON.parse(raw);
396
+ } catch {
397
+ return [];
398
+ }
399
+ if (!Array.isArray(parsed)) return [];
400
+ const out = [];
401
+ for (const v of parsed) {
402
+ if (isPersistedQueueItem(v)) out.push(v);
403
+ }
404
+ return out;
405
+ }
406
+ async clear() {
407
+ try {
408
+ await fsp.unlink(this.file);
409
+ } catch (err) {
410
+ const code = err.code;
411
+ if (code === "ENOENT") return;
412
+ console.warn(`QueueStore.clear() failed for ${this.file}: ${err.message}`);
413
+ }
414
+ }
415
+ };
416
+ function isPersistedQueueItem(v) {
417
+ if (typeof v !== "object" || v === null) return false;
418
+ const o = v;
419
+ return typeof o["displayText"] === "string" && Array.isArray(o["blocks"]);
420
+ }
421
+ var DEFAULT_SPOOL_THRESHOLD = 256 * 1024;
422
+ var PLACEHOLDER_RE = /\[(pasted|image|file) #(\d+)\]/g;
423
+ var DefaultAttachmentStore = class {
424
+ items = /* @__PURE__ */ new Map();
425
+ refs = [];
426
+ nextSeq = { text: 0, image: 0, file: 0 };
427
+ spoolDir;
428
+ spoolThreshold;
429
+ constructor(opts = {}) {
430
+ this.spoolDir = opts.spoolDir;
431
+ this.spoolThreshold = opts.spoolThresholdBytes ?? DEFAULT_SPOOL_THRESHOLD;
432
+ }
433
+ async add(input) {
434
+ const seq = ++this.nextSeq[input.kind];
435
+ const id = `${kindPrefix(input.kind)}-${seq}-${randomBytes(3).toString("hex")}`;
436
+ const bytes = Buffer.byteLength(input.data, input.kind === "image" ? "base64" : "utf8");
437
+ let spooledPath;
438
+ let data = input.data;
439
+ if (this.spoolDir && bytes >= this.spoolThreshold) {
440
+ await fsp.mkdir(this.spoolDir, { recursive: true });
441
+ spooledPath = path2.join(this.spoolDir, `${id}.bin`);
442
+ await fsp.writeFile(spooledPath, input.data, input.kind === "image" ? "base64" : "utf8");
443
+ data = void 0;
444
+ }
445
+ const att = {
446
+ id,
447
+ kind: input.kind,
448
+ meta: input.meta ?? {},
449
+ data,
450
+ path: spooledPath,
451
+ bytes,
452
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
453
+ };
454
+ this.items.set(id, att);
455
+ const ref = { id, kind: input.kind, seq, meta: att.meta };
456
+ this.refs.push(ref);
457
+ return ref;
458
+ }
459
+ async get(id) {
460
+ return this.items.get(id);
461
+ }
462
+ list() {
463
+ return [...this.refs];
464
+ }
465
+ async expand(text) {
466
+ const matches = [...text.matchAll(PLACEHOLDER_RE)];
467
+ if (matches.length === 0) return text ? [{ type: "text", text }] : [];
468
+ const blocks = [];
469
+ let lastIndex = 0;
470
+ for (const m of matches) {
471
+ const idx = m.index ?? 0;
472
+ const before = text.slice(lastIndex, idx);
473
+ if (before) blocks.push({ type: "text", text: before });
474
+ const kind = prefixToKind(m[1]);
475
+ const seq = Number(m[2]);
476
+ const ref = this.refs.find((r) => r.kind === kind && r.seq === seq);
477
+ const att = ref ? this.items.get(ref.id) : void 0;
478
+ if (!att) {
479
+ blocks.push({ type: "text", text: m[0] });
480
+ } else {
481
+ blocks.push(await this.toBlock(att));
482
+ }
483
+ lastIndex = idx + m[0].length;
484
+ }
485
+ const tail = text.slice(lastIndex);
486
+ if (tail) blocks.push({ type: "text", text: tail });
487
+ return mergeAdjacentText(blocks);
488
+ }
489
+ async clear() {
490
+ this.items.clear();
491
+ this.refs.length = 0;
492
+ this.nextSeq = { text: 0, image: 0, file: 0 };
493
+ }
494
+ async toBlock(att) {
495
+ if (att.kind === "image") {
496
+ const data = att.data ?? (att.path ? await fsp.readFile(att.path, { encoding: "base64" }) : "");
497
+ return {
498
+ type: "image",
499
+ source: {
500
+ type: "base64",
501
+ media_type: att.meta.mediaType ?? "image/png",
502
+ data
503
+ }
504
+ };
505
+ }
506
+ const raw = att.data ?? (att.path ? await fsp.readFile(att.path, "utf8") : "");
507
+ const label = att.meta.filename ? `<file path="${att.meta.filename}">` : "<pasted>";
508
+ const close = att.meta.filename ? "</file>" : "</pasted>";
509
+ return { type: "text", text: `${label}
510
+ ${raw}
511
+ ${close}` };
512
+ }
513
+ };
514
+ function kindPrefix(kind) {
515
+ return kind === "text" ? "pasted" : kind;
516
+ }
517
+ function prefixToKind(prefix) {
518
+ if (prefix === "pasted") return "text";
519
+ if (prefix === "image") return "image";
520
+ return "file";
521
+ }
522
+ function mergeAdjacentText(blocks) {
523
+ const out = [];
524
+ for (const b of blocks) {
525
+ const prev = out[out.length - 1];
526
+ if (b.type === "text" && prev && prev.type === "text") {
527
+ prev.text += b.text;
528
+ } else {
529
+ out.push(b);
530
+ }
531
+ }
532
+ return out;
533
+ }
534
+ var MAX_BYTES_TOTAL = 32e3;
535
+ var DefaultMemoryStore = class {
536
+ files;
537
+ /**
538
+ * Per-scope serialization queue. `remember` / `forget` / `consolidate` /
539
+ * `clear` are read-modify-write against a single file; without a lock,
540
+ * two concurrent calls on the same scope can read the same baseline and
541
+ * the later write silently drops the earlier entry. We chain each
542
+ * mutation onto the prior promise for the same scope so they run in
543
+ * issue order. Different scopes still proceed in parallel.
544
+ *
545
+ * The chain tracks only the last pending write. If a write fails, its
546
+ * error is caught and swallowed (line 43) so the chain stays alive for
547
+ * subsequent calls. A crash between atomicWrite() and backup copy leaves
548
+ * the file at its new content with no backup — acceptable for an optional
549
+ * backup whose worst case is losing a memory consolidation pass.
550
+ */
551
+ writeChain = /* @__PURE__ */ new Map();
552
+ constructor(opts) {
553
+ this.files = {
554
+ "project-agents": opts.paths.inProjectAgentsFile,
555
+ "project-memory": opts.paths.projectMemory,
556
+ "user-memory": opts.paths.globalMemory
557
+ };
558
+ }
559
+ async runSerialized(scope, work) {
560
+ const prior = this.writeChain.get(scope) ?? Promise.resolve();
561
+ const next = prior.catch(() => void 0).then(work);
562
+ this.writeChain.set(scope, next);
563
+ try {
564
+ return await next;
565
+ } finally {
566
+ if (this.writeChain.get(scope) === next) {
567
+ this.writeChain.delete(scope);
568
+ }
569
+ }
570
+ }
571
+ async readAll() {
572
+ const parts = [];
573
+ for (const scope of ["project-agents", "project-memory", "user-memory"]) {
574
+ const body = await this.read(scope);
575
+ if (body.trim()) parts.push(`## ${labelOf(scope)}
576
+
577
+ ${body.trim()}`);
578
+ }
579
+ return parts.join("\n\n");
580
+ }
581
+ async read(scope) {
582
+ try {
583
+ return await fsp.readFile(this.files[scope], "utf8");
584
+ } catch {
585
+ return "";
586
+ }
587
+ }
588
+ async remember(text, scope = "project-memory") {
589
+ return this.runSerialized(scope, async () => {
590
+ const file = this.files[scope];
591
+ await ensureDir(path2.dirname(file));
592
+ let existing = "";
593
+ try {
594
+ existing = await fsp.readFile(file, "utf8");
595
+ } catch {
596
+ }
597
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
598
+ const id = `mem_${Date.now()}_${randomUUID().slice(0, 8)}`;
599
+ const entry = `
600
+ - [${ts}] ${id} ${text.replace(/\n/g, " ")}
601
+ `;
602
+ const next = existing.trim() ? existing.replace(/\n+$/, "") + entry : `# WrongStack Memory
603
+ ${entry}`;
604
+ await atomicWrite(file, next);
605
+ const buf = Buffer.byteLength(next, "utf8");
606
+ if (buf > MAX_BYTES_TOTAL) {
607
+ await this.consolidateUnsafe(scope);
608
+ }
609
+ });
610
+ }
611
+ async forget(query, scope = "project-memory") {
612
+ return this.runSerialized(scope, async () => this.forgetUnsafe(query, scope));
613
+ }
614
+ async forgetUnsafe(query, scope) {
615
+ const file = this.files[scope];
616
+ let existing;
617
+ try {
618
+ existing = await fsp.readFile(file, "utf8");
619
+ } catch {
620
+ return 0;
621
+ }
622
+ const needle = query.toLowerCase();
623
+ const idMatcher = /mem_\d+_\w+/;
624
+ let removed = 0;
625
+ const lines = existing.split("\n").filter((line) => {
626
+ const trimmed = line.trim();
627
+ if (!trimmed.startsWith("- ")) return true;
628
+ if (idMatcher.test(query)) {
629
+ const afterBracket = trimmed.indexOf("] ");
630
+ if (afterBracket !== -1) {
631
+ const afterTs = trimmed.slice(afterBracket + 2);
632
+ const entryIdMatch = /^mem_\d+_\w+/.exec(afterTs);
633
+ if (entryIdMatch && entryIdMatch[0] === query) {
634
+ removed++;
635
+ return false;
636
+ }
637
+ }
638
+ }
639
+ if (trimmed.toLowerCase().includes(needle)) {
640
+ removed++;
641
+ return false;
642
+ }
643
+ return true;
644
+ });
645
+ if (removed > 0) {
646
+ await atomicWrite(file, lines.join("\n"));
647
+ }
648
+ return removed;
649
+ }
650
+ async consolidate(scope) {
651
+ return this.runSerialized(scope, async () => this.consolidateUnsafe(scope));
652
+ }
653
+ async consolidateUnsafe(scope) {
654
+ const file = this.files[scope];
655
+ let existing;
656
+ try {
657
+ existing = await fsp.readFile(file, "utf8");
658
+ } catch {
659
+ return;
660
+ }
661
+ const seen = /* @__PURE__ */ new Set();
662
+ const lines = existing.split("\n").filter((line) => {
663
+ const trimmed = line.trim();
664
+ if (!trimmed.startsWith("- ")) return true;
665
+ const norm = trimmed.replace(/\[[^\]]+\]/, "").replace(/\bmem_\d+_\w+\s*/, "").trim().toLowerCase();
666
+ if (seen.has(norm)) return false;
667
+ seen.add(norm);
668
+ return true;
669
+ });
670
+ const next = lines.join("\n");
671
+ const backup = `${file}.bak.${Date.now()}`;
672
+ try {
673
+ await fsp.copyFile(file, backup);
674
+ } catch {
675
+ }
676
+ try {
677
+ await atomicWrite(file, next);
678
+ } catch {
679
+ return;
680
+ }
681
+ }
682
+ async clear(scope) {
683
+ if (scope) {
684
+ await this.runSerialized(scope, async () => atomicWrite(this.files[scope], ""));
685
+ return;
686
+ }
687
+ await Promise.all(
688
+ ["project-agents", "project-memory", "user-memory"].map(
689
+ (s) => this.runSerialized(s, async () => atomicWrite(this.files[s], ""))
690
+ )
691
+ );
692
+ }
693
+ };
694
+ function labelOf(scope) {
695
+ switch (scope) {
696
+ case "project-agents":
697
+ return "Project AGENTS.md";
698
+ case "project-memory":
699
+ return "Project memory";
700
+ case "user-memory":
701
+ return "User memory";
702
+ }
703
+ }
704
+
705
+ // src/storage/config-store.ts
706
+ var DefaultConfigStore = class {
707
+ current;
708
+ watchers = /* @__PURE__ */ new Set();
709
+ constructor(initial) {
710
+ this.current = deepFreeze(structuredClone(initial));
711
+ }
712
+ get() {
713
+ return this.current;
714
+ }
715
+ getSection(key) {
716
+ return this.current[key];
717
+ }
718
+ getExtension(pluginName) {
719
+ const ext = this.current.extensions?.[pluginName];
720
+ return ext ? ext : FROZEN_EMPTY;
721
+ }
722
+ update(partial) {
723
+ const next = deepFreeze(structuredClone({ ...this.current, ...partial }));
724
+ if (next.version !== 1) {
725
+ throw new Error(`ConfigStore.update: version must remain 1, got ${String(next.version)}`);
726
+ }
727
+ const prev = this.current;
728
+ this.current = next;
729
+ for (const w of this.watchers) {
730
+ try {
731
+ w(next, prev);
732
+ } catch (err) {
733
+ console.error("[config-store] watcher threw:", err);
734
+ }
735
+ }
736
+ return next;
737
+ }
738
+ watch(cb) {
739
+ this.watchers.add(cb);
740
+ return () => this.watchers.delete(cb);
741
+ }
742
+ };
743
+ var FROZEN_EMPTY = Object.freeze({});
744
+ function deepFreeze(obj) {
745
+ if (obj === null || typeof obj !== "object") return obj;
746
+ if (Object.isFrozen(obj)) return obj;
747
+ for (const key of Object.keys(obj)) {
748
+ const v = obj[key];
749
+ if (v !== null && typeof v === "object" && !Object.isFrozen(v)) {
750
+ deepFreeze(v);
751
+ }
752
+ }
753
+ return Object.freeze(obj);
754
+ }
755
+ function decryptConfigSecrets(cfg, vault) {
756
+ return walk(cfg, vault, (v, key) => {
757
+ try {
758
+ return vault.decrypt(v);
759
+ } catch (err) {
760
+ console.warn(
761
+ `[secret-vault] Failed to decrypt "${key}":`,
762
+ err instanceof Error ? err.message : err
763
+ );
764
+ return "";
765
+ }
766
+ });
767
+ }
768
+ function walk(node, vault, transform) {
769
+ if (node === null || node === void 0) return node;
770
+ if (typeof node !== "object") return node;
771
+ if (Array.isArray(node)) {
772
+ return node.map((item) => walk(item, vault, transform));
773
+ }
774
+ const out = {};
775
+ for (const [k, v] of Object.entries(node)) {
776
+ if (typeof v === "string" && isSecretField(k)) {
777
+ out[k] = transform(v, k);
778
+ } else if (typeof v === "object" && v !== null) {
779
+ out[k] = walk(v, vault, transform);
780
+ } else {
781
+ out[k] = v;
782
+ }
783
+ }
784
+ return out;
785
+ }
786
+ var SECRET_KEY_PATTERN = /(?:apikey|api_key|authtoken|auth_token|bearer|secret|password|passwd|pwd|refreshtoken|refresh_token|sessionkey|session_key|access[_-]?token|private[_-]?key)/i;
787
+ var NON_SECRET_OVERRIDES = /* @__PURE__ */ new Set(["publickey", "public_key"]);
788
+ function isSecretField(name) {
789
+ const lc = name.toLowerCase();
790
+ if (NON_SECRET_OVERRIDES.has(lc)) return false;
791
+ return SECRET_KEY_PATTERN.test(lc);
792
+ }
793
+
794
+ // src/utils/safe-json.ts
795
+ function safeParse(input, maxBytes = 5e6) {
796
+ if (input.length > maxBytes) {
797
+ return { ok: false, error: `Input exceeds limit (${maxBytes} bytes)` };
798
+ }
799
+ try {
800
+ return { ok: true, value: JSON.parse(input) };
801
+ } catch (err) {
802
+ return {
803
+ ok: false,
804
+ error: err instanceof Error ? err.message : String(err)
805
+ };
806
+ }
807
+ }
808
+
809
+ // src/storage/config-loader.ts
810
+ var BEHAVIOR_DEFAULTS = {
811
+ version: 1,
812
+ context: {
813
+ warnThreshold: 0.6,
814
+ softThreshold: 0.75,
815
+ hardThreshold: 0.9,
816
+ autoCompact: true,
817
+ preserveK: 10,
818
+ eliseThreshold: 2e3
819
+ },
820
+ tools: {
821
+ defaultExecutionStrategy: "smart",
822
+ maxIterations: 100,
823
+ iterationTimeoutMs: 3e5,
824
+ sessionTimeoutMs: 18e5,
825
+ perIterationOutputCapBytes: 1e5,
826
+ autoExtendLimit: true
827
+ },
828
+ log: { level: "info" },
829
+ features: {
830
+ mcp: true,
831
+ plugins: true,
832
+ memory: true,
833
+ modelsRegistry: true,
834
+ skills: true
835
+ }
836
+ };
837
+ var ENV_MAP = {
838
+ WRONGSTACK_PROVIDER: (c, v) => {
839
+ c.provider = v;
840
+ },
841
+ WRONGSTACK_MODEL: (c, v) => {
842
+ c.model = v;
843
+ },
844
+ WRONGSTACK_API_KEY: (c, v) => {
845
+ c.apiKey = v;
846
+ },
847
+ WRONGSTACK_BASE_URL: (c, v) => {
848
+ c.baseUrl = v;
849
+ },
850
+ WRONGSTACK_LOG_LEVEL: (c, v) => {
851
+ if (!c.log) c.log = { level: "info" };
852
+ c.log.level = v;
853
+ }
854
+ };
855
+ function isPrimitiveArray(a) {
856
+ return a.every((v) => v === null || typeof v !== "object");
857
+ }
858
+ var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
859
+ function deepMerge(base, patch) {
860
+ if (typeof base !== "object" || base === null) return patch ?? base;
861
+ if (typeof patch !== "object" || patch === null) return base;
862
+ const out = { ...base };
863
+ for (const [k, v] of Object.entries(patch)) {
864
+ if (FORBIDDEN_PROTO_KEYS.has(k)) continue;
865
+ const existing = out[k];
866
+ if (Array.isArray(v)) {
867
+ if (Array.isArray(existing) && isPrimitiveArray(v) && isPrimitiveArray(existing)) {
868
+ out[k] = [.../* @__PURE__ */ new Set([...existing, ...v])];
869
+ } else {
870
+ out[k] = v;
871
+ if (process.env.WRONGSTACK_DEBUG_CONFIG) {
872
+ console.warn(
873
+ `[config] Non-primitive array for "${k}" replaced (global + local config merge). Global entries: ${existing?.length ?? 0}, local entries: ${v.length}.`
874
+ );
875
+ }
876
+ }
877
+ } else if (typeof v === "object" && v !== null && typeof existing === "object" && existing !== null) {
878
+ out[k] = deepMerge(existing, v);
879
+ } else if (v !== void 0) {
880
+ out[k] = v;
881
+ }
882
+ }
883
+ return out;
884
+ }
885
+ var DefaultConfigLoader = class {
886
+ paths;
887
+ strict;
888
+ vault;
889
+ extraSources;
890
+ constructor(opts) {
891
+ this.paths = opts.paths;
892
+ this.strict = opts.strict ?? false;
893
+ this.vault = opts.vault;
894
+ this.extraSources = opts.sources ?? [];
895
+ }
896
+ async load(opts = {}) {
897
+ let cfg = { ...BEHAVIOR_DEFAULTS };
898
+ const [global, local] = await Promise.all([
899
+ this.readJson(this.paths.globalConfig),
900
+ this.readJson(this.paths.projectLocalConfig)
901
+ ]);
902
+ cfg = deepMerge(cfg, global);
903
+ cfg = deepMerge(cfg, local);
904
+ for (const [key, fn] of Object.entries(ENV_MAP)) {
905
+ const v = process.env[key];
906
+ if (v) fn(cfg, v);
907
+ }
908
+ const sorted = [...this.extraSources].sort((a, b) => {
909
+ const pd = (a.priority ?? 50) - (b.priority ?? 50);
910
+ if (pd !== 0) return pd;
911
+ return a.name.localeCompare(b.name);
912
+ });
913
+ for (const src of sorted) {
914
+ try {
915
+ const patch = await src.read();
916
+ if (patch && Object.keys(patch).length > 0) {
917
+ cfg = deepMerge(cfg, patch);
918
+ }
919
+ } catch (err) {
920
+ console.warn(`Config source "${src.name}" failed`, err);
921
+ }
922
+ }
923
+ if (opts.cliFlags) {
924
+ cfg = deepMerge(cfg, opts.cliFlags);
925
+ }
926
+ if (this.vault) {
927
+ cfg = decryptConfigSecrets(cfg, this.vault);
928
+ }
929
+ if (cfg.providers) {
930
+ for (const pcfg of Object.values(cfg.providers)) {
931
+ if (!pcfg || typeof pcfg !== "object") continue;
932
+ const rawKeys = pcfg.apiKeys;
933
+ if (!Array.isArray(rawKeys) || rawKeys.length === 0) continue;
934
+ const keys = rawKeys.filter(
935
+ (k) => !!k && typeof k === "object" && typeof k.label === "string" && typeof k.apiKey === "string"
936
+ );
937
+ if (keys.length === 0) continue;
938
+ const existing = pcfg.apiKey;
939
+ if (existing && existing.length > 0) continue;
940
+ const activeLabel = pcfg.activeKey;
941
+ const chosen = activeLabel ? keys.find((k) => k.label === activeLabel) ?? keys[0] : keys[0];
942
+ if (chosen?.apiKey) {
943
+ pcfg.apiKey = chosen.apiKey;
944
+ }
945
+ }
946
+ }
947
+ this.validateBehavior(cfg);
948
+ if (this.strict) {
949
+ this.validateIdentity(cfg);
950
+ }
951
+ return Object.freeze(cfg);
952
+ }
953
+ async readJson(file) {
954
+ let raw;
955
+ try {
956
+ raw = await fsp.readFile(file, "utf8");
957
+ } catch (err) {
958
+ if (err.code !== "ENOENT") {
959
+ console.warn(`[config] Failed to read "${file}":`, err);
960
+ }
961
+ return {};
962
+ }
963
+ const parsed = safeParse(raw);
964
+ if (!parsed.ok || !parsed.value) {
965
+ console.warn(
966
+ `[config] Failed to parse "${file}": invalid JSON. Falling back to defaults for this layer.`
967
+ );
968
+ return {};
969
+ }
970
+ return parsed.value;
971
+ }
972
+ validateBehavior(cfg) {
973
+ if (cfg.version === void 0) throw new Error("Config: missing version field");
974
+ if (cfg.version !== 1) throw new Error(`Config: unsupported version ${cfg.version}`);
975
+ const c = cfg.context;
976
+ if (!c) throw new Error("Config: missing context section");
977
+ const fields = ["warnThreshold", "softThreshold", "hardThreshold"];
978
+ for (const f of fields) {
979
+ const v = c[f];
980
+ if (typeof v !== "number" || !Number.isFinite(v)) {
981
+ throw new Error(`Config: context.${String(f)} must be a finite number (got ${typeof v})`);
982
+ }
983
+ }
984
+ if (c.warnThreshold >= c.softThreshold || c.softThreshold >= c.hardThreshold) {
985
+ throw new Error("Config: context thresholds must satisfy warn < soft < hard");
986
+ }
987
+ }
988
+ validateIdentity(cfg) {
989
+ if (!cfg.provider) {
990
+ throw new Error(
991
+ "Config: no provider configured. Run `wstack init` or set WRONGSTACK_PROVIDER."
992
+ );
993
+ }
994
+ if (!cfg.model) {
995
+ throw new Error("Config: no model configured. Run `wstack init` or set WRONGSTACK_MODEL.");
996
+ }
997
+ }
998
+ };
999
+
1000
+ // src/storage/config-migration.ts
1001
+ var ConfigMigrationError = class extends Error {
1002
+ fromVersion;
1003
+ targetVersion;
1004
+ missingStep;
1005
+ constructor(opts) {
1006
+ super(opts.message);
1007
+ this.name = "ConfigMigrationError";
1008
+ this.fromVersion = opts.fromVersion;
1009
+ this.targetVersion = opts.targetVersion;
1010
+ this.missingStep = opts.missingStep;
1011
+ }
1012
+ };
1013
+ function runConfigMigrations(input, targetVersion, migrations) {
1014
+ const initial = typeof input["version"] === "number" ? input["version"] : 1;
1015
+ let current = { ...input };
1016
+ let currentVersion = initial;
1017
+ const applied = [];
1018
+ let shouldPersist = false;
1019
+ let guard = 0;
1020
+ while (currentVersion !== targetVersion) {
1021
+ if (++guard > 100) {
1022
+ throw new ConfigMigrationError({
1023
+ message: `Config migration looped past 100 steps (from v${initial} toward v${targetVersion})`,
1024
+ fromVersion: initial,
1025
+ targetVersion,
1026
+ missingStep: currentVersion
1027
+ });
1028
+ }
1029
+ const step = migrations.find((m) => m.from === currentVersion);
1030
+ if (!step) {
1031
+ throw new ConfigMigrationError({
1032
+ message: `No migration registered from config v${currentVersion} (target v${targetVersion}). Update the framework or revert the config file.`,
1033
+ fromVersion: initial,
1034
+ targetVersion,
1035
+ missingStep: currentVersion
1036
+ });
1037
+ }
1038
+ const ctx = { fromVersion: currentVersion, shouldPersist: false };
1039
+ const next = step.migrate(current, ctx);
1040
+ if (typeof next["version"] !== "number" || next["version"] !== step.to) {
1041
+ next["version"] = step.to;
1042
+ }
1043
+ current = next;
1044
+ currentVersion = step.to;
1045
+ applied.push(`v${step.from}\u2192v${step.to}`);
1046
+ shouldPersist = shouldPersist || ctx.shouldPersist || step.from < step.to;
1047
+ }
1048
+ return { config: current, applied, shouldPersist };
1049
+ }
1050
+ var DEFAULT_CONFIG_MIGRATIONS = [];
1051
+ var LOCK_FILE = "active.json";
1052
+ var DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
1053
+ var RecoveryLock = class {
1054
+ file;
1055
+ pid;
1056
+ hostname;
1057
+ maxAgeMs;
1058
+ sessionStore;
1059
+ probe;
1060
+ constructor(opts) {
1061
+ this.file = path2.join(opts.dir, LOCK_FILE);
1062
+ this.pid = opts.pid ?? process.pid;
1063
+ this.hostname = opts.hostname ?? os.hostname();
1064
+ this.maxAgeMs = opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
1065
+ this.sessionStore = opts.sessionStore;
1066
+ this.probe = opts.isPidAlive ?? defaultIsPidAlive;
1067
+ }
1068
+ /**
1069
+ * Examine the lockfile and decide whether it represents an abandoned
1070
+ * session. Returns `null` if the file is missing, points to a live
1071
+ * instance, references a clean-closed session, is too old, or is
1072
+ * malformed. Otherwise returns enough detail to prompt the user.
1073
+ *
1074
+ * Important: this is a read-only check. We never delete an active
1075
+ * lock from here — if another wstack instance is alive, the caller
1076
+ * should bail or run with a fresh session instead.
1077
+ */
1078
+ async checkAbandoned() {
1079
+ const lock = await this.readLock();
1080
+ if (!lock) return null;
1081
+ const ageMs = Date.now() - new Date(lock.startedAt).getTime();
1082
+ if (Number.isNaN(ageMs) || ageMs < 0) {
1083
+ return null;
1084
+ }
1085
+ if (ageMs > this.maxAgeMs) return null;
1086
+ if (lock.hostname === this.hostname && this.probe(lock.pid)) {
1087
+ return null;
1088
+ }
1089
+ let messageCount = 0;
1090
+ if (this.sessionStore) {
1091
+ try {
1092
+ const data = await this.sessionStore.load(lock.sessionId);
1093
+ const closed = data.events.some((e) => e.type === "session_end");
1094
+ if (closed) return null;
1095
+ messageCount = data.messages.length;
1096
+ } catch {
1097
+ return null;
1098
+ }
1099
+ }
1100
+ return {
1101
+ sessionId: lock.sessionId,
1102
+ pid: lock.pid,
1103
+ startedAt: lock.startedAt,
1104
+ ageMs,
1105
+ messageCount
1106
+ };
1107
+ }
1108
+ /**
1109
+ * Claim the lock for the given session. Overwrites any existing lock
1110
+ * — the caller should have already handled abandonment (via
1111
+ * `checkAbandoned`) before calling this.
1112
+ */
1113
+ async write(sessionId) {
1114
+ await ensureDir(path2.dirname(this.file));
1115
+ const lock = {
1116
+ v: 1,
1117
+ sessionId,
1118
+ pid: this.pid,
1119
+ hostname: this.hostname,
1120
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
1121
+ };
1122
+ const tmp = `${this.file}.tmp`;
1123
+ await fsp.writeFile(tmp, JSON.stringify(lock), { mode: 384 });
1124
+ await fsp.rename(tmp, this.file);
1125
+ }
1126
+ /**
1127
+ * Release the lock. Idempotent — silently succeeds if the file is
1128
+ * already gone (e.g. someone else cleared it, or the directory was
1129
+ * wiped).
1130
+ */
1131
+ async clear() {
1132
+ try {
1133
+ await fsp.unlink(this.file);
1134
+ } catch (err) {
1135
+ const code = err.code;
1136
+ if (code === "ENOENT") return;
1137
+ throw err;
1138
+ }
1139
+ }
1140
+ async readLock() {
1141
+ let raw;
1142
+ try {
1143
+ raw = await fsp.readFile(this.file, "utf8");
1144
+ } catch (err) {
1145
+ const code = err.code;
1146
+ if (code === "ENOENT") return null;
1147
+ return null;
1148
+ }
1149
+ try {
1150
+ const parsed = JSON.parse(raw);
1151
+ if (!isLockFile(parsed)) return null;
1152
+ return parsed;
1153
+ } catch {
1154
+ return null;
1155
+ }
1156
+ }
1157
+ };
1158
+ function isLockFile(v) {
1159
+ if (typeof v !== "object" || v === null) return false;
1160
+ const o = v;
1161
+ return o["v"] === 1 && typeof o["sessionId"] === "string" && typeof o["pid"] === "number" && typeof o["hostname"] === "string" && typeof o["startedAt"] === "string";
1162
+ }
1163
+ function defaultIsPidAlive(pid) {
1164
+ if (!Number.isInteger(pid) || pid <= 0) return false;
1165
+ try {
1166
+ process.kill(pid, 0);
1167
+ return true;
1168
+ } catch (err) {
1169
+ const code = err.code;
1170
+ if (code === "EPERM") return true;
1171
+ return false;
1172
+ }
1173
+ }
1174
+
1175
+ // src/storage/session-reader.ts
1176
+ var DefaultSessionReader = class {
1177
+ store;
1178
+ constructor(opts) {
1179
+ this.store = opts.store;
1180
+ }
1181
+ async query(q = {}) {
1182
+ const raw = await this.store.list(q.limit ? Math.max(q.limit * 4, 100) : 1e3);
1183
+ const titleNeedle = q.titleContains?.toLowerCase();
1184
+ const filtered = raw.filter((s) => {
1185
+ if (q.since && s.startedAt < q.since) return false;
1186
+ if (q.until && s.startedAt > q.until) return false;
1187
+ if (q.provider && s.provider !== q.provider) return false;
1188
+ if (q.model && s.model !== q.model) return false;
1189
+ if (q.minTokens !== void 0 && s.tokenTotal < q.minTokens) return false;
1190
+ if (titleNeedle && !s.title.toLowerCase().includes(titleNeedle)) return false;
1191
+ return true;
1192
+ });
1193
+ const out = filtered.map((s) => ({
1194
+ id: s.id,
1195
+ title: s.title,
1196
+ startedAt: s.startedAt,
1197
+ provider: s.provider,
1198
+ model: s.model,
1199
+ tokenTotal: s.tokenTotal
1200
+ }));
1201
+ return q.limit ? out.slice(0, q.limit) : out;
1202
+ }
1203
+ async *replay(sessionId) {
1204
+ const data = await this.store.load(sessionId);
1205
+ for (const e of data.events) yield e;
1206
+ }
1207
+ async search(q, sessionId) {
1208
+ const limit = q.limit ?? 100;
1209
+ const matcher = buildMatcher(q);
1210
+ const allowedTypes = q.types ? new Set(q.types) : null;
1211
+ const ids = sessionId ? [sessionId] : (await this.store.list(1e3)).map((s) => s.id);
1212
+ const hits = [];
1213
+ for (const id of ids) {
1214
+ let data;
1215
+ try {
1216
+ data = await this.store.load(id);
1217
+ } catch {
1218
+ continue;
1219
+ }
1220
+ for (let i = 0; i < data.events.length; i++) {
1221
+ const ev = data.events[i];
1222
+ if (allowedTypes && !allowedTypes.has(ev.type)) continue;
1223
+ const text = eventText(ev);
1224
+ if (text === null) continue;
1225
+ const hit = matcher(text);
1226
+ if (!hit) continue;
1227
+ hits.push({
1228
+ sessionId: id,
1229
+ eventIndex: i,
1230
+ ts: ev.ts,
1231
+ type: ev.type,
1232
+ snippet: snippetOf(text, hit.start, hit.end)
1233
+ });
1234
+ if (hits.length >= limit) return hits;
1235
+ }
1236
+ }
1237
+ return hits;
1238
+ }
1239
+ async export(sessionId, opts) {
1240
+ const data = await this.store.load(sessionId);
1241
+ const includeTools = opts.includeTools ?? true;
1242
+ const includeDiagnostics = opts.includeDiagnostics ?? true;
1243
+ const filtered = data.events.filter((e) => {
1244
+ if (!includeTools && (e.type === "tool_use" || e.type === "tool_result" || e.type === "tool_call_start" || e.type === "tool_call_end")) {
1245
+ return false;
1246
+ }
1247
+ if (!includeDiagnostics && (e.type === "error" || e.type === "compaction" || e.type === "message_truncated")) {
1248
+ return false;
1249
+ }
1250
+ return true;
1251
+ });
1252
+ if (opts.format === "json") {
1253
+ return JSON.stringify({ metadata: data.metadata, events: filtered }, null, 2);
1254
+ }
1255
+ if (opts.format === "text") {
1256
+ return renderPlainText(data.metadata, filtered);
1257
+ }
1258
+ return renderMarkdown(data.metadata, filtered);
1259
+ }
1260
+ async metadata(sessionId) {
1261
+ const data = await this.store.load(sessionId);
1262
+ return data.metadata;
1263
+ }
1264
+ };
1265
+ function buildMatcher(q) {
1266
+ const ci = q.caseInsensitive ?? true;
1267
+ if (q.regex) {
1268
+ const flags = ci ? "i" : "";
1269
+ const re = new RegExp(q.query, flags);
1270
+ return (text) => {
1271
+ const m = re.exec(text);
1272
+ return m ? { start: m.index, end: m.index + m[0].length } : null;
1273
+ };
1274
+ }
1275
+ const needle = ci ? q.query.toLowerCase() : q.query;
1276
+ return (text) => {
1277
+ const hay = ci ? text.toLowerCase() : text;
1278
+ const idx = hay.indexOf(needle);
1279
+ return idx === -1 ? null : { start: idx, end: idx + needle.length };
1280
+ };
1281
+ }
1282
+ function eventText(e) {
1283
+ switch (e.type) {
1284
+ case "user_input":
1285
+ return contentToString(e.content);
1286
+ case "llm_response":
1287
+ return contentToString(e.content);
1288
+ case "tool_use":
1289
+ return `${e.name} ${JSON.stringify(e.input)}`;
1290
+ case "tool_result":
1291
+ return typeof e.content === "string" ? e.content : JSON.stringify(e.content);
1292
+ case "error":
1293
+ return `${e.phase}: ${e.message}`;
1294
+ case "session_start":
1295
+ case "session_resumed":
1296
+ return `${e.model}/${e.provider}`;
1297
+ case "task_created":
1298
+ case "task_completed":
1299
+ return e.title;
1300
+ case "task_failed":
1301
+ return `${e.title}: ${e.error}`;
1302
+ case "skill_activated":
1303
+ case "skill_deactivated":
1304
+ return e.skillName;
1305
+ default:
1306
+ return null;
1307
+ }
1308
+ }
1309
+ function contentToString(content) {
1310
+ if (typeof content === "string") return content;
1311
+ return content.map((b) => {
1312
+ switch (b.type) {
1313
+ case "text":
1314
+ return b.text;
1315
+ case "tool_use":
1316
+ return `[tool_use:${b.name} ${JSON.stringify(b.input)}]`;
1317
+ case "tool_result":
1318
+ return typeof b.content === "string" ? b.content : JSON.stringify(b.content);
1319
+ default:
1320
+ return "";
1321
+ }
1322
+ }).join("\n");
1323
+ }
1324
+ var SNIPPET_RADIUS = 60;
1325
+ function snippetOf(text, start, end) {
1326
+ const from = Math.max(0, start - SNIPPET_RADIUS);
1327
+ const to = Math.min(text.length, end + SNIPPET_RADIUS);
1328
+ const prefix = from > 0 ? "\u2026" : "";
1329
+ const suffix = to < text.length ? "\u2026" : "";
1330
+ return prefix + text.slice(from, to).replace(/\s+/g, " ").trim() + suffix;
1331
+ }
1332
+ function renderMarkdown(meta, events) {
1333
+ const lines = [];
1334
+ lines.push(`# Session ${meta.id}`);
1335
+ lines.push("");
1336
+ if (meta.model || meta.provider) {
1337
+ lines.push(`- **Model:** ${meta.provider ?? "?"}/${meta.model ?? "?"}`);
1338
+ }
1339
+ lines.push(`- **Started:** ${meta.startedAt}`);
1340
+ if (meta.endedAt) lines.push(`- **Ended:** ${meta.endedAt}`);
1341
+ lines.push("");
1342
+ lines.push("---");
1343
+ lines.push("");
1344
+ for (const e of events) {
1345
+ switch (e.type) {
1346
+ case "user_input": {
1347
+ lines.push(`## User \u2014 ${e.ts}`);
1348
+ lines.push("");
1349
+ lines.push(contentToString(e.content));
1350
+ lines.push("");
1351
+ break;
1352
+ }
1353
+ case "llm_response": {
1354
+ lines.push(`## Assistant \u2014 ${e.ts}`);
1355
+ lines.push("");
1356
+ lines.push(contentToString(e.content));
1357
+ if (e.stopReason && e.stopReason !== "end_turn") {
1358
+ lines.push("");
1359
+ lines.push(`*stop: ${e.stopReason}*`);
1360
+ }
1361
+ lines.push("");
1362
+ break;
1363
+ }
1364
+ case "tool_use": {
1365
+ lines.push(`### Tool call: \`${e.name}\``);
1366
+ lines.push("");
1367
+ lines.push("```json");
1368
+ lines.push(JSON.stringify(e.input, null, 2));
1369
+ lines.push("```");
1370
+ lines.push("");
1371
+ break;
1372
+ }
1373
+ case "tool_result": {
1374
+ const body = typeof e.content === "string" ? e.content : JSON.stringify(e.content, null, 2);
1375
+ lines.push(`### Tool result${e.isError ? " (error)" : ""}`);
1376
+ lines.push("");
1377
+ lines.push("```");
1378
+ lines.push(body);
1379
+ lines.push("```");
1380
+ lines.push("");
1381
+ break;
1382
+ }
1383
+ case "error": {
1384
+ lines.push(`> **Error** (${e.phase}): ${e.message}`);
1385
+ lines.push("");
1386
+ break;
1387
+ }
1388
+ case "compaction": {
1389
+ lines.push(`> **Compaction**: ${e.before} \u2192 ${e.after} tokens`);
1390
+ lines.push("");
1391
+ break;
1392
+ }
1393
+ }
1394
+ }
1395
+ return lines.join("\n");
1396
+ }
1397
+ function renderPlainText(meta, events) {
1398
+ const lines = [];
1399
+ lines.push(
1400
+ `Session ${meta.id} \u2014 ${meta.provider ?? "?"}/${meta.model ?? "?"} \u2014 started ${meta.startedAt}`
1401
+ );
1402
+ lines.push("".padEnd(72, "-"));
1403
+ for (const e of events) {
1404
+ switch (e.type) {
1405
+ case "user_input":
1406
+ lines.push(`[${e.ts}] USER`);
1407
+ lines.push(contentToString(e.content));
1408
+ lines.push("");
1409
+ break;
1410
+ case "llm_response":
1411
+ lines.push(`[${e.ts}] ASSISTANT`);
1412
+ lines.push(contentToString(e.content));
1413
+ lines.push("");
1414
+ break;
1415
+ case "tool_use":
1416
+ lines.push(`[${e.ts}] TOOL_USE ${e.name} ${JSON.stringify(e.input)}`);
1417
+ break;
1418
+ case "tool_result":
1419
+ lines.push(
1420
+ `[${e.ts}] TOOL_RESULT${e.isError ? " (error)" : ""} ${typeof e.content === "string" ? e.content : JSON.stringify(e.content)}`
1421
+ );
1422
+ break;
1423
+ case "error":
1424
+ lines.push(`[${e.ts}] ERROR (${e.phase}): ${e.message}`);
1425
+ break;
1426
+ }
1427
+ }
1428
+ return lines.join("\n");
1429
+ }
1430
+
1431
+ // src/storage/session-analyzer.ts
1432
+ var SessionAnalyzer = class {
1433
+ analyze(events) {
1434
+ const toolUsageCount = {};
1435
+ const errors = [];
1436
+ const modeChanges = [];
1437
+ const tasksById = /* @__PURE__ */ new Map();
1438
+ let sessionId = "";
1439
+ for (const event of events) {
1440
+ if (event.type === "session_start" || event.type === "session_resumed") {
1441
+ if (!sessionId) sessionId = event.id;
1442
+ }
1443
+ if (event.type === "tool_use") {
1444
+ toolUsageCount[event.name] = (toolUsageCount[event.name] ?? 0) + 1;
1445
+ }
1446
+ if (event.type === "error") {
1447
+ errors.push({ ts: event.ts, phase: event.phase, message: event.message });
1448
+ }
1449
+ if (event.type === "mode_changed") {
1450
+ modeChanges.push({ ts: event.ts, from: event.from, to: event.to });
1451
+ }
1452
+ if (event.type === "task_created") {
1453
+ tasksById.set(event.taskId, {
1454
+ taskId: event.taskId,
1455
+ title: event.title,
1456
+ status: "created",
1457
+ createdAt: event.ts
1458
+ });
1459
+ }
1460
+ if (event.type === "task_updated") {
1461
+ const t = tasksById.get(event.taskId);
1462
+ if (t) t.status = event.status;
1463
+ }
1464
+ if (event.type === "task_completed") {
1465
+ const t = tasksById.get(event.taskId);
1466
+ if (t) {
1467
+ t.status = "completed";
1468
+ t.completedAt = event.ts;
1469
+ } else {
1470
+ tasksById.set(event.taskId, {
1471
+ taskId: event.taskId,
1472
+ title: event.title,
1473
+ status: "completed",
1474
+ createdAt: event.ts,
1475
+ completedAt: event.ts
1476
+ });
1477
+ }
1478
+ }
1479
+ if (event.type === "task_failed") {
1480
+ const t = tasksById.get(event.taskId);
1481
+ if (t) {
1482
+ t.status = "failed";
1483
+ t.completedAt = event.ts;
1484
+ } else {
1485
+ tasksById.set(event.taskId, {
1486
+ taskId: event.taskId,
1487
+ title: event.title,
1488
+ status: "failed",
1489
+ createdAt: event.ts,
1490
+ completedAt: event.ts
1491
+ });
1492
+ }
1493
+ }
1494
+ }
1495
+ return {
1496
+ sessionId,
1497
+ totalDuration: this.calcDuration(events),
1498
+ toolUsageCount,
1499
+ errorCount: errors.length,
1500
+ modeChanges,
1501
+ tasks: Array.from(tasksById.values())
1502
+ };
1503
+ }
1504
+ query(events, filter) {
1505
+ return events.filter((e) => {
1506
+ if (filter.eventTypes?.length && !filter.eventTypes.includes(e.type)) return false;
1507
+ if (filter.toolNames?.length && e.type === "tool_use") {
1508
+ const toolEvent = e;
1509
+ if (!filter.toolNames.includes(toolEvent.name)) return false;
1510
+ }
1511
+ if (filter.timeRange) {
1512
+ const ts = new Date(e.ts).getTime();
1513
+ const start = new Date(filter.timeRange.start).getTime();
1514
+ const end = new Date(filter.timeRange.end).getTime();
1515
+ if (ts < start || ts > end) return false;
1516
+ }
1517
+ return true;
1518
+ });
1519
+ }
1520
+ calcDuration(events) {
1521
+ if (events.length < 2) return 0;
1522
+ const first = new Date(events[0].ts).getTime();
1523
+ const last = new Date(events[events.length - 1].ts).getTime();
1524
+ return last - first;
1525
+ }
1526
+ };
1527
+
1528
+ export { ConfigMigrationError, DEFAULT_CONFIG_MIGRATIONS, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultMemoryStore, DefaultSessionReader, DefaultSessionStore, QueueStore, RecoveryLock, SessionAnalyzer, runConfigMigrations };
1529
+ //# sourceMappingURL=index.js.map
1530
+ //# sourceMappingURL=index.js.map