@jun133/kitty 0.0.8 → 0.0.9

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.
@@ -0,0 +1,654 @@
1
+ import {
2
+ createEmptyCheckpoint,
3
+ createEmptySessionDiff,
4
+ createEmptyTaskState,
5
+ deriveTaskState,
6
+ deriveTodoItems,
7
+ normalizeSessionCheckpoint,
8
+ normalizeSessionDiffState,
9
+ normalizeSessionMemory,
10
+ normalizeSessionRecord,
11
+ normalizeSessionTodos,
12
+ normalizeSessionWorkset,
13
+ normalizeTodoItems
14
+ } from "./chunk-3KMC6H5K.mjs";
15
+
16
+ // src/session/store.ts
17
+ import crypto from "crypto";
18
+ import fs2 from "fs/promises";
19
+ import path2 from "path";
20
+
21
+ // src/session/errors.ts
22
+ var SessionStoreError = class extends Error {
23
+ code;
24
+ sessionPath;
25
+ constructor(message, options) {
26
+ super(message, options.cause ? { cause: options.cause } : void 0);
27
+ this.name = this.constructor.name;
28
+ this.code = options.code;
29
+ this.sessionPath = options.sessionPath;
30
+ }
31
+ };
32
+ var SessionNotFoundError = class extends SessionStoreError {
33
+ constructor(message, options = {}) {
34
+ super(message, {
35
+ code: "SESSION_NOT_FOUND",
36
+ sessionPath: options.sessionPath,
37
+ cause: options.cause
38
+ });
39
+ }
40
+ };
41
+ var SessionCorruptError = class extends SessionStoreError {
42
+ constructor(message, options = {}) {
43
+ super(message, {
44
+ code: "SESSION_CORRUPT",
45
+ sessionPath: options.sessionPath,
46
+ cause: options.cause
47
+ });
48
+ }
49
+ };
50
+ var UnsupportedSessionSchemaError = class extends SessionStoreError {
51
+ constructor(message, options = {}) {
52
+ super(message, {
53
+ code: "SESSION_UNSUPPORTED_SCHEMA",
54
+ sessionPath: options.sessionPath,
55
+ cause: options.cause
56
+ });
57
+ }
58
+ };
59
+ function createSessionNotFoundError(sessionId, sessionPath, cause) {
60
+ return new SessionNotFoundError(
61
+ `Session '${sessionId}' was not found at ${sessionPath}. Rebind the host or create a new formal session explicitly.`,
62
+ {
63
+ sessionPath,
64
+ cause
65
+ }
66
+ );
67
+ }
68
+ function createInvalidSessionJsonError(sessionPath, cause) {
69
+ return new SessionCorruptError(
70
+ `Session snapshot '${sessionPath}' is corrupt: invalid JSON. Fix or delete the broken snapshot before retrying.`,
71
+ {
72
+ sessionPath,
73
+ cause
74
+ }
75
+ );
76
+ }
77
+ function createSessionCorruptError(sessionPath, detail) {
78
+ return new SessionCorruptError(
79
+ `Session snapshot '${sessionPath}' is corrupt: ${detail}. Fix or delete the broken snapshot before retrying.`,
80
+ {
81
+ sessionPath
82
+ }
83
+ );
84
+ }
85
+ function createUnsupportedSessionSchemaError(sessionPath, receivedVersion, expectedVersion) {
86
+ return new UnsupportedSessionSchemaError(
87
+ `Session snapshot '${sessionPath}' has unsupported schema version '${String(receivedVersion)}'. Expected ${expectedVersion}. Rebuild the session snapshot instead of guessing.`,
88
+ {
89
+ sessionPath
90
+ }
91
+ );
92
+ }
93
+
94
+ // src/session/memoryAsset.ts
95
+ import fs from "fs/promises";
96
+ import path from "path";
97
+
98
+ // src/runtime/memory/metadata.ts
99
+ var EVIDENCE_PREFIX = "evidence:";
100
+ var KIND_PREFIX = "kind:";
101
+ var SCOPE_PREFIX = "scope:";
102
+ var TAGS_PREFIX = "tags:";
103
+ var UPDATED_PREFIX = "updated:";
104
+ function parseRuntimeMemoryAssetMetadata(body, fallback) {
105
+ const title = readTitle(body);
106
+ const metadataLines = readMetadataLines(body);
107
+ const kind = readKind(metadataLines, fallback.kind);
108
+ const evidenceRefs = readEvidenceRefs(metadataLines, kind, fallback.basename);
109
+ return {
110
+ title,
111
+ kind,
112
+ updatedAt: readFirstMetadataValue(metadataLines, UPDATED_PREFIX),
113
+ evidenceRefs,
114
+ scope: readFirstMetadataValue(metadataLines, SCOPE_PREFIX),
115
+ tags: readCsvMetadataValues(metadataLines, TAGS_PREFIX)
116
+ };
117
+ }
118
+ function renderRuntimeMemoryAssetDocument(input) {
119
+ return [
120
+ `# ${input.title.trim()}`,
121
+ "",
122
+ `Kind: ${input.kind}`,
123
+ `Updated: ${input.timestamp}`,
124
+ renderOptionalLine("Evidence", normalizeList(input.evidenceRefs).join(", ")),
125
+ renderOptionalLine("Scope", input.scope?.trim()),
126
+ renderOptionalLine("Tags", normalizeList(input.tags).join(", ")),
127
+ "",
128
+ input.content.trim(),
129
+ ""
130
+ ].filter((line) => line !== void 0).join("\n");
131
+ }
132
+ function readTitle(body) {
133
+ const line = body.split(/\r?\n/).find((item) => item.trim().startsWith("# "));
134
+ return line?.replace(/^#\s+/, "").trim() || void 0;
135
+ }
136
+ function readMetadataLines(body) {
137
+ return body.split(/\r?\n/).map((line) => line.trim()).filter((line) => /^[A-Za-z][A-Za-z -]*:/.test(line));
138
+ }
139
+ function readKind(lines, fallback) {
140
+ const value = readFirstMetadataValue(lines, KIND_PREFIX);
141
+ return isRuntimeMemoryAssetKind(value) ? value : fallback;
142
+ }
143
+ function readEvidenceRefs(lines, kind, basename) {
144
+ const refs = lines.filter((line) => line.toLowerCase().startsWith(EVIDENCE_PREFIX)).flatMap((line) => line.slice(EVIDENCE_PREFIX.length).split(",")).map((value) => value.trim()).filter(Boolean);
145
+ if (refs.length > 0) {
146
+ return [...new Set(refs)];
147
+ }
148
+ return kind === "session" ? [`session:${basename}`] : [];
149
+ }
150
+ function readFirstMetadataValue(lines, prefix) {
151
+ const line = lines.find((item) => item.toLowerCase().startsWith(prefix));
152
+ const value = line?.slice(prefix.length).trim();
153
+ return value || void 0;
154
+ }
155
+ function readCsvMetadataValues(lines, prefix) {
156
+ return normalizeList(
157
+ lines.filter((line) => line.toLowerCase().startsWith(prefix)).flatMap((line) => line.slice(prefix.length).split(","))
158
+ );
159
+ }
160
+ function normalizeList(values) {
161
+ return [...new Set((values ?? []).map((value) => value?.trim()).filter((value) => Boolean(value)))];
162
+ }
163
+ function renderOptionalLine(label, value) {
164
+ return value ? `${label}: ${value}` : void 0;
165
+ }
166
+ function isRuntimeMemoryAssetKind(value) {
167
+ return value === "evidence" || value === "project" || value === "session" || value === "user";
168
+ }
169
+
170
+ // src/session/memoryAsset.ts
171
+ function getSessionMemoryAssetPath(memorySessionsDir, sessionId) {
172
+ return path.join(memorySessionsDir, `${sanitizeSessionId(sessionId)}.md`);
173
+ }
174
+ async function writeSessionMemoryAsset(input) {
175
+ const memory = input.session.sessionMemory;
176
+ if (!memory?.summary.trim()) {
177
+ await fs.rm(getSessionMemoryAssetPath(input.memorySessionsDir, input.session.id), { force: true }).catch(() => void 0);
178
+ return void 0;
179
+ }
180
+ await fs.mkdir(input.memorySessionsDir, { recursive: true });
181
+ const file = getSessionMemoryAssetPath(input.memorySessionsDir, input.session.id);
182
+ await fs.writeFile(file, renderSessionMemoryAsset(input.session), "utf8");
183
+ return file;
184
+ }
185
+ function renderSessionMemoryAsset(session) {
186
+ const memory = session.sessionMemory;
187
+ if (!memory) {
188
+ return "";
189
+ }
190
+ return renderRuntimeMemoryAssetDocument({
191
+ kind: "session",
192
+ title: "Session Memory",
193
+ timestamp: memory.updatedAt,
194
+ evidenceRefs: [`session:${session.id}`],
195
+ scope: session.id,
196
+ tags: ["same-session", "continuity"],
197
+ content: memory.summary
198
+ });
199
+ }
200
+ function sanitizeSessionId(value) {
201
+ return value.replace(/[^a-zA-Z0-9._-]/g, "_");
202
+ }
203
+
204
+ // src/session/snapshot.ts
205
+ var CURRENT_SESSION_SCHEMA_VERSION = 1;
206
+ var SESSION_SNAPSHOT_KEYS = /* @__PURE__ */ new Set([
207
+ "schemaVersion",
208
+ "id",
209
+ "createdAt",
210
+ "updatedAt",
211
+ "cwd",
212
+ "title",
213
+ "messageCount",
214
+ "messages",
215
+ "sessionMemory",
216
+ "todoItems",
217
+ "taskState",
218
+ "checkpoint",
219
+ "sessionDiff",
220
+ "contextBudget",
221
+ "workset"
222
+ ]);
223
+ function serializeSessionSnapshot(session) {
224
+ return `${JSON.stringify({
225
+ schemaVersion: CURRENT_SESSION_SCHEMA_VERSION,
226
+ ...session
227
+ }, null, 2)}
228
+ `;
229
+ }
230
+ function parseSessionSnapshot(raw, sessionPath) {
231
+ let parsed;
232
+ try {
233
+ parsed = JSON.parse(raw);
234
+ } catch (error) {
235
+ if (error instanceof SyntaxError) {
236
+ throw createInvalidSessionJsonError(sessionPath, error);
237
+ }
238
+ throw error;
239
+ }
240
+ const record = expectRecord(parsed, sessionPath, "Session snapshot");
241
+ rejectUnknownSessionKeys(record, sessionPath);
242
+ const schemaVersion = record.schemaVersion;
243
+ if (schemaVersion !== CURRENT_SESSION_SCHEMA_VERSION) {
244
+ throw createUnsupportedSessionSchemaError(sessionPath, schemaVersion, CURRENT_SESSION_SCHEMA_VERSION);
245
+ }
246
+ const candidate = {
247
+ id: readRequiredString(record, "id", sessionPath),
248
+ createdAt: readRequiredString(record, "createdAt", sessionPath),
249
+ updatedAt: readRequiredString(record, "updatedAt", sessionPath),
250
+ cwd: readRequiredString(record, "cwd", sessionPath),
251
+ title: readOptionalString(record.title, "title", sessionPath),
252
+ messageCount: typeof record.messageCount === "number" ? Math.trunc(record.messageCount) : 0,
253
+ messages: readMessages(record.messages, sessionPath),
254
+ sessionMemory: normalizeSessionMemory(record.sessionMemory),
255
+ todoItems: readTodoItems(record.todoItems, sessionPath),
256
+ taskState: readOptionalObject(record.taskState, "taskState", sessionPath),
257
+ checkpoint: readOptionalObject(record.checkpoint, "checkpoint", sessionPath),
258
+ sessionDiff: readOptionalObject(record.sessionDiff, "sessionDiff", sessionPath),
259
+ contextBudget: readContextBudget(record.contextBudget, sessionPath),
260
+ workset: normalizeSessionWorkset(record.workset)
261
+ };
262
+ return normalizeLoadedSessionRecord(candidate);
263
+ }
264
+ function prepareSessionRecordForSave(session) {
265
+ const normalizedMessages = Array.isArray(session.messages) ? session.messages : [];
266
+ const prepared = {
267
+ ...session,
268
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
269
+ title: session.title,
270
+ messageCount: normalizedMessages.length,
271
+ messages: normalizedMessages,
272
+ sessionMemory: normalizeSessionMemory(session.sessionMemory),
273
+ todoItems: deriveTodoItems(normalizedMessages, session.todoItems ?? []),
274
+ taskState: deriveTaskState(normalizedMessages, session.taskState)
275
+ };
276
+ return normalizeSessionDiffState(normalizeSessionCheckpoint({
277
+ ...prepared,
278
+ workset: normalizeSessionWorkset(prepared.workset)
279
+ }));
280
+ }
281
+ function normalizeLoadedSessionRecord(session) {
282
+ return normalizeSessionDiffState(normalizeSessionCheckpoint(
283
+ normalizeSessionTodos(normalizeSessionRecord(session))
284
+ ));
285
+ }
286
+ function rejectUnknownSessionKeys(record, sessionPath) {
287
+ const unknownKeys = Object.keys(record).filter((key) => !SESSION_SNAPSHOT_KEYS.has(key));
288
+ if (unknownKeys.length > 0) {
289
+ throw createSessionCorruptError(sessionPath, `unrecognized field(s): ${unknownKeys.join(", ")}`);
290
+ }
291
+ }
292
+ function readMessages(value, sessionPath) {
293
+ if (!Array.isArray(value)) {
294
+ throw createSessionCorruptError(sessionPath, "messages must be an array");
295
+ }
296
+ return value.map((entry, index) => readMessage(entry, index, sessionPath));
297
+ }
298
+ function readTodoItems(value, sessionPath) {
299
+ if (value === void 0) {
300
+ return void 0;
301
+ }
302
+ try {
303
+ return normalizeTodoItems(value);
304
+ } catch (error) {
305
+ throw createSessionCorruptError(sessionPath, error instanceof Error ? error.message : String(error));
306
+ }
307
+ }
308
+ function readContextBudget(value, sessionPath) {
309
+ if (value === void 0) {
310
+ return void 0;
311
+ }
312
+ const record = expectRecord(value, sessionPath, "contextBudget");
313
+ const version = record.version;
314
+ if (version !== 1) {
315
+ throw createSessionCorruptError(sessionPath, "contextBudget.version must be 1");
316
+ }
317
+ const compressionMode = readRequiredString(record, "compressionMode", sessionPath, "contextBudget");
318
+ if (compressionMode !== "none" && compressionMode !== "normal" && compressionMode !== "aggressive" && compressionMode !== "hard") {
319
+ throw createSessionCorruptError(sessionPath, "contextBudget.compressionMode must be one of none|normal|aggressive|hard");
320
+ }
321
+ return {
322
+ version,
323
+ limitChars: readRequiredNumber(record, "limitChars", sessionPath, "contextBudget"),
324
+ estimatedChars: readRequiredNumber(record, "estimatedChars", sessionPath, "contextBudget"),
325
+ remainingChars: readRequiredNumber(record, "remainingChars", sessionPath, "contextBudget"),
326
+ usageRatio: readRequiredNumber(record, "usageRatio", sessionPath, "contextBudget"),
327
+ compressed: readRequiredBoolean(record, "compressed", sessionPath, "contextBudget"),
328
+ compressionMode,
329
+ compressionReason: readRequiredString(record, "compressionReason", sessionPath, "contextBudget"),
330
+ sources: readContextBudgetSources(record.sources, sessionPath),
331
+ promptHotspots: readContextBudgetHotspots(record.promptHotspots, sessionPath),
332
+ cacheLayout: readContextCacheLayout(record.cacheLayout, sessionPath)
333
+ };
334
+ }
335
+ function readContextCacheLayout(value, sessionPath) {
336
+ if (value === void 0) {
337
+ return void 0;
338
+ }
339
+ const record = expectRecord(value, sessionPath, "contextBudget.cacheLayout");
340
+ return {
341
+ stablePrefixFingerprint: readRequiredString(record, "stablePrefixFingerprint", sessionPath, "contextBudget.cacheLayout"),
342
+ volatileTailFingerprint: readRequiredString(record, "volatileTailFingerprint", sessionPath, "contextBudget.cacheLayout"),
343
+ stablePrefixChars: readRequiredNumber(record, "stablePrefixChars", sessionPath, "contextBudget.cacheLayout"),
344
+ volatileTailChars: readRequiredNumber(record, "volatileTailChars", sessionPath, "contextBudget.cacheLayout"),
345
+ stableSources: readStringArray(record.stableSources, sessionPath, "contextBudget.cacheLayout.stableSources"),
346
+ volatileSources: readStringArray(record.volatileSources, sessionPath, "contextBudget.cacheLayout.volatileSources")
347
+ };
348
+ }
349
+ function readContextBudgetSources(value, sessionPath) {
350
+ if (value === void 0) {
351
+ return [];
352
+ }
353
+ if (!Array.isArray(value)) {
354
+ throw createSessionCorruptError(sessionPath, "contextBudget.sources must be an array");
355
+ }
356
+ return value.map((entry, index) => {
357
+ const record = expectRecord(entry, sessionPath, `contextBudget.sources[${index}]`);
358
+ const name = readRequiredString(record, "name", sessionPath, `contextBudget.sources[${index}]`);
359
+ if (name !== "systemPrompt" && name !== "nearFieldConversation" && name !== "conversationSummary" && name !== "compactedConversation") {
360
+ throw createSessionCorruptError(sessionPath, `contextBudget.sources[${index}].name is invalid`);
361
+ }
362
+ const messages = record.messages === void 0 ? void 0 : readRequiredNumber(record, "messages", sessionPath, `contextBudget.sources[${index}]`);
363
+ return {
364
+ name,
365
+ chars: readRequiredNumber(record, "chars", sessionPath, `contextBudget.sources[${index}]`),
366
+ messages
367
+ };
368
+ });
369
+ }
370
+ function readContextBudgetHotspots(value, sessionPath) {
371
+ if (!Array.isArray(value)) {
372
+ throw createSessionCorruptError(sessionPath, "contextBudget.promptHotspots must be an array");
373
+ }
374
+ return value.map((entry, index) => {
375
+ const record = expectRecord(entry, sessionPath, `contextBudget.promptHotspots[${index}]`);
376
+ const layer = readRequiredString(record, "layer", sessionPath, `contextBudget.promptHotspots[${index}]`);
377
+ if (layer !== "static" && layer !== "profile" && layer !== "runtimeFacts") {
378
+ throw createSessionCorruptError(sessionPath, `contextBudget.promptHotspots[${index}].layer must be one of static|profile|runtimeFacts`);
379
+ }
380
+ return {
381
+ layer,
382
+ title: readRequiredString(record, "title", sessionPath, `contextBudget.promptHotspots[${index}]`),
383
+ chars: readRequiredNumber(record, "chars", sessionPath, `contextBudget.promptHotspots[${index}]`),
384
+ lines: readRequiredNumber(record, "lines", sessionPath, `contextBudget.promptHotspots[${index}]`)
385
+ };
386
+ });
387
+ }
388
+ function readStringArray(value, sessionPath, scope) {
389
+ if (!Array.isArray(value)) {
390
+ throw createSessionCorruptError(sessionPath, `${scope} must be an array`);
391
+ }
392
+ return value.map((entry, index) => {
393
+ if (typeof entry !== "string") {
394
+ throw createSessionCorruptError(sessionPath, `${scope}[${index}] must be a string`);
395
+ }
396
+ return entry;
397
+ });
398
+ }
399
+ function readMessage(value, index, sessionPath) {
400
+ const record = expectRecord(value, sessionPath, `messages[${index}]`);
401
+ const role = readRequiredString(record, "role", sessionPath, `messages[${index}]`);
402
+ if (role !== "system" && role !== "user" && role !== "assistant" && role !== "tool") {
403
+ throw createSessionCorruptError(sessionPath, `messages[${index}].role must be one of system|user|assistant|tool`);
404
+ }
405
+ return {
406
+ role,
407
+ content: readMessageContent(record.content, sessionPath, `messages[${index}]`),
408
+ source: readMessageSource(record.source, sessionPath, `messages[${index}]`),
409
+ name: readOptionalString(record.name, "name", sessionPath, `messages[${index}]`),
410
+ tool_call_id: readOptionalString(record.tool_call_id, "tool_call_id", sessionPath, `messages[${index}]`),
411
+ tool_calls: readToolCalls(record.tool_calls, sessionPath, index),
412
+ reasoningContent: readOptionalString(record.reasoningContent, "reasoningContent", sessionPath, `messages[${index}]`),
413
+ createdAt: readRequiredString(record, "createdAt", sessionPath, `messages[${index}]`)
414
+ };
415
+ }
416
+ function readMessageContent(value, sessionPath, scope) {
417
+ if (value === null) {
418
+ return null;
419
+ }
420
+ if (typeof value !== "string") {
421
+ throw createSessionCorruptError(sessionPath, `${scope}.content must be a string or null`);
422
+ }
423
+ return value;
424
+ }
425
+ function readToolCalls(value, sessionPath, index) {
426
+ if (value === void 0) {
427
+ return void 0;
428
+ }
429
+ if (!Array.isArray(value)) {
430
+ throw createSessionCorruptError(sessionPath, `messages[${index}].tool_calls must be an array`);
431
+ }
432
+ return value.map((entry, toolIndex) => {
433
+ const record = expectRecord(entry, sessionPath, `messages[${index}].tool_calls[${toolIndex}]`);
434
+ const type = readRequiredString(record, "type", sessionPath, `messages[${index}].tool_calls[${toolIndex}]`);
435
+ if (type !== "function") {
436
+ throw createSessionCorruptError(sessionPath, `messages[${index}].tool_calls[${toolIndex}].type must be 'function'`);
437
+ }
438
+ const fn = readOptionalObject(record.function, "function", sessionPath, `messages[${index}].tool_calls[${toolIndex}]`);
439
+ if (!fn) {
440
+ throw createSessionCorruptError(sessionPath, `messages[${index}].tool_calls[${toolIndex}].function is required`);
441
+ }
442
+ return {
443
+ id: readRequiredString(record, "id", sessionPath, `messages[${index}].tool_calls[${toolIndex}]`),
444
+ type,
445
+ function: {
446
+ name: readRequiredString(fn, "name", sessionPath, `messages[${index}].tool_calls[${toolIndex}].function`),
447
+ arguments: readRequiredString(fn, "arguments", sessionPath, `messages[${index}].tool_calls[${toolIndex}].function`)
448
+ }
449
+ };
450
+ });
451
+ }
452
+ function readOptionalObject(value, fieldName, sessionPath, scope) {
453
+ if (value === void 0) {
454
+ return void 0;
455
+ }
456
+ return expectRecord(value, sessionPath, scope ? `${scope}.${fieldName}` : fieldName);
457
+ }
458
+ function expectRecord(value, sessionPath, label) {
459
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
460
+ throw createSessionCorruptError(sessionPath, `${label} must be an object`);
461
+ }
462
+ return value;
463
+ }
464
+ function readRequiredString(record, key, sessionPath, scope) {
465
+ const value = readOptionalString(record[key], key, sessionPath, scope);
466
+ if (!value) {
467
+ throw createSessionCorruptError(sessionPath, `${scope ? `${scope}.` : ""}${key} is required`);
468
+ }
469
+ return value;
470
+ }
471
+ function readMessageSource(value, sessionPath, scope) {
472
+ if (value === void 0) {
473
+ return void 0;
474
+ }
475
+ if (value !== "external" && value !== "internal") {
476
+ throw createSessionCorruptError(sessionPath, `${scope}.source must be external or internal`);
477
+ }
478
+ return value;
479
+ }
480
+ function readRequiredNumber(record, key, sessionPath, scope) {
481
+ const value = record[key];
482
+ if (typeof value !== "number" || !Number.isFinite(value)) {
483
+ throw createSessionCorruptError(sessionPath, `${scope ? `${scope}.` : ""}${key} must be a finite number`);
484
+ }
485
+ return value;
486
+ }
487
+ function readRequiredBoolean(record, key, sessionPath, scope) {
488
+ const value = record[key];
489
+ if (typeof value !== "boolean") {
490
+ throw createSessionCorruptError(sessionPath, `${scope ? `${scope}.` : ""}${key} must be a boolean`);
491
+ }
492
+ return value;
493
+ }
494
+ function readOptionalString(value, key, sessionPath, scope) {
495
+ if (value === void 0) {
496
+ return void 0;
497
+ }
498
+ if (typeof value !== "string") {
499
+ throw createSessionCorruptError(sessionPath, `${scope ? `${scope}.` : ""}${key} must be a string`);
500
+ }
501
+ return value;
502
+ }
503
+
504
+ // src/session/store.ts
505
+ var SessionStore = class {
506
+ constructor(sessionsDir, options = {}) {
507
+ this.sessionsDir = sessionsDir;
508
+ this.options = options;
509
+ }
510
+ async create(cwd) {
511
+ return createSessionRecord(cwd);
512
+ }
513
+ async save(session) {
514
+ const updated = prepareSessionRecordForSave(session);
515
+ await fs2.mkdir(this.sessionsDir, { recursive: true });
516
+ await fs2.writeFile(this.getPath(updated.id), serializeSessionSnapshot(updated), "utf8");
517
+ await writeSessionMemoryAsset({
518
+ memorySessionsDir: this.options.memorySessionsDir ?? this.defaultMemorySessionsDir(),
519
+ session: updated
520
+ });
521
+ return updated;
522
+ }
523
+ async load(id) {
524
+ const sessionPath = this.getPath(id);
525
+ const raw = await this.readSnapshotFile(id, sessionPath);
526
+ return parseSessionSnapshot(raw, sessionPath);
527
+ }
528
+ async loadLatest() {
529
+ const sessions = await this.list(1);
530
+ return sessions[0] ?? null;
531
+ }
532
+ async list(limit = 20) {
533
+ return (await this.listReadable(limit)).sessions;
534
+ }
535
+ async listReadable(limit = 20) {
536
+ await fs2.mkdir(this.sessionsDir, { recursive: true });
537
+ const entries = await fs2.readdir(this.sessionsDir, { withFileTypes: true });
538
+ const sessions = [];
539
+ const skipped = [];
540
+ for (const entry of entries.filter((item) => item.isFile() && item.name.endsWith(".json"))) {
541
+ const sessionPath = path2.join(this.sessionsDir, entry.name);
542
+ try {
543
+ const raw = await fs2.readFile(sessionPath, "utf8");
544
+ sessions.push(parseSessionSnapshot(raw, sessionPath));
545
+ } catch (error) {
546
+ skipped.push(toSkippedSessionSnapshot(error, sessionPath));
547
+ }
548
+ }
549
+ return {
550
+ sessions: sessions.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)).slice(0, limit),
551
+ skipped
552
+ };
553
+ }
554
+ async appendMessages(session, messages) {
555
+ const next = {
556
+ ...session,
557
+ messages: [...session.messages, ...messages]
558
+ };
559
+ return this.save(next);
560
+ }
561
+ getPath(id) {
562
+ return path2.join(this.sessionsDir, `${id}.json`);
563
+ }
564
+ defaultMemorySessionsDir() {
565
+ return path2.join(path2.dirname(this.sessionsDir), "memory", "sessions");
566
+ }
567
+ async readSnapshotFile(id, sessionPath) {
568
+ try {
569
+ return await fs2.readFile(sessionPath, "utf8");
570
+ } catch (error) {
571
+ if (error.code === "ENOENT") {
572
+ throw createSessionNotFoundError(id, sessionPath, error);
573
+ }
574
+ throw error;
575
+ }
576
+ }
577
+ };
578
+ function toSkippedSessionSnapshot(error, fallbackPath) {
579
+ if (error instanceof SessionStoreError) {
580
+ return {
581
+ path: error.sessionPath ?? fallbackPath,
582
+ code: error.code,
583
+ error: error.message
584
+ };
585
+ }
586
+ return {
587
+ path: fallbackPath,
588
+ code: "SESSION_READ_FAILED",
589
+ error: error instanceof Error ? error.message : String(error)
590
+ };
591
+ }
592
+ var InProcessSessionStore = class {
593
+ sessions = /* @__PURE__ */ new Map();
594
+ async create(cwd) {
595
+ return createSessionRecord(cwd);
596
+ }
597
+ async save(session) {
598
+ const prepared = prepareSessionRecordForSave(session);
599
+ this.sessions.set(prepared.id, prepared);
600
+ return prepared;
601
+ }
602
+ async load(id) {
603
+ const session = this.sessions.get(id);
604
+ if (!session) {
605
+ throw createSessionNotFoundError(id, `in-process:${id}`);
606
+ }
607
+ return session;
608
+ }
609
+ async loadLatest() {
610
+ const sessions = await this.list(1);
611
+ return sessions[0] ?? null;
612
+ }
613
+ async list(limit = 20) {
614
+ return [...this.sessions.values()].sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)).slice(0, limit);
615
+ }
616
+ async listReadable(limit = 20) {
617
+ return {
618
+ sessions: await this.list(limit),
619
+ skipped: []
620
+ };
621
+ }
622
+ async appendMessages(session, messages) {
623
+ return this.save({
624
+ ...session,
625
+ messages: [...session.messages, ...messages]
626
+ });
627
+ }
628
+ };
629
+ async function createSessionRecord(cwd) {
630
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
631
+ return prepareSessionRecordForSave({
632
+ id: createSessionId(),
633
+ createdAt: timestamp,
634
+ updatedAt: timestamp,
635
+ cwd,
636
+ messageCount: 0,
637
+ messages: [],
638
+ taskState: createEmptyTaskState(timestamp),
639
+ checkpoint: createEmptyCheckpoint(timestamp),
640
+ sessionDiff: createEmptySessionDiff(timestamp)
641
+ });
642
+ }
643
+ function createSessionId() {
644
+ const date = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:.TZ]/g, "").slice(0, 14);
645
+ const random = crypto.randomUUID().slice(0, 8);
646
+ return `${date}-${random}`;
647
+ }
648
+
649
+ export {
650
+ parseRuntimeMemoryAssetMetadata,
651
+ SessionStore,
652
+ InProcessSessionStore,
653
+ createSessionRecord
654
+ };