@cuylabs/agent-core 0.3.0 → 0.5.0
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/README.md +216 -41
- package/dist/builder-RcTZuYnO.d.ts +34 -0
- package/dist/capabilities/index.d.ts +97 -0
- package/dist/capabilities/index.js +46 -0
- package/dist/chunk-6TDTQJ4P.js +116 -0
- package/dist/chunk-7MUFEN4K.js +559 -0
- package/dist/chunk-BDBZ3SLK.js +745 -0
- package/dist/chunk-DWYX7ASF.js +26 -0
- package/dist/chunk-FG4MD5MU.js +54 -0
- package/dist/chunk-IMGQOTU2.js +2019 -0
- package/dist/chunk-IVUJDISU.js +556 -0
- package/dist/chunk-LRHOS4ZN.js +584 -0
- package/dist/chunk-OTUGSCED.js +691 -0
- package/dist/chunk-P6YF7USR.js +182 -0
- package/dist/chunk-QAQADS4X.js +258 -0
- package/dist/chunk-QWFMX226.js +879 -0
- package/dist/{chunk-6VKLWNRE.js → chunk-SDSBEQXG.js} +1 -132
- package/dist/chunk-VBWWUHWI.js +724 -0
- package/dist/chunk-VEKUXUVF.js +41 -0
- package/dist/chunk-X635CM2F.js +305 -0
- package/dist/chunk-YUUJK53A.js +91 -0
- package/dist/chunk-ZXAKHMWH.js +283 -0
- package/dist/config-D2xeGEHK.d.ts +52 -0
- package/dist/context/index.d.ts +259 -0
- package/dist/context/index.js +26 -0
- package/dist/identifiers-BLUxFqV_.d.ts +12 -0
- package/dist/index-p0kOsVsE.d.ts +1067 -0
- package/dist/index-tmhaADz5.d.ts +198 -0
- package/dist/index.d.ts +185 -4316
- package/dist/index.js +1238 -5368
- package/dist/mcp/index.d.ts +26 -0
- package/dist/mcp/index.js +14 -0
- package/dist/messages-BYWGn8TY.d.ts +110 -0
- package/dist/middleware/index.d.ts +7 -0
- package/dist/middleware/index.js +12 -0
- package/dist/models/index.d.ts +33 -0
- package/dist/models/index.js +12 -0
- package/dist/network-D76DS5ot.d.ts +5 -0
- package/dist/prompt/index.d.ts +224 -0
- package/dist/prompt/index.js +45 -0
- package/dist/reasoning/index.d.ts +71 -0
- package/dist/reasoning/index.js +47 -0
- package/dist/registry-CuRWWtcT.d.ts +164 -0
- package/dist/resolver-DOfZ-xuk.d.ts +254 -0
- package/dist/runner-C7aMP_x3.d.ts +596 -0
- package/dist/runtime/index.d.ts +357 -0
- package/dist/runtime/index.js +64 -0
- package/dist/session-manager-Uawm2Le7.d.ts +274 -0
- package/dist/skill/index.d.ts +103 -0
- package/dist/skill/index.js +39 -0
- package/dist/storage/index.d.ts +167 -0
- package/dist/storage/index.js +50 -0
- package/dist/sub-agent/index.d.ts +14 -0
- package/dist/sub-agent/index.js +15 -0
- package/dist/tool/index.d.ts +173 -1
- package/dist/tool/index.js +12 -3
- package/dist/tool-DYp6-cC3.d.ts +239 -0
- package/dist/tool-pFAnJc5Y.d.ts +419 -0
- package/dist/tracker-DClqYqTj.d.ts +96 -0
- package/dist/tracking/index.d.ts +109 -0
- package/dist/tracking/index.js +20 -0
- package/dist/types-CQaXbRsS.d.ts +47 -0
- package/dist/types-MM1JoX5T.d.ts +810 -0
- package/dist/types-VQgymC1N.d.ts +156 -0
- package/package.json +89 -5
- package/dist/index-QR704uRr.d.ts +0 -472
|
@@ -0,0 +1,745 @@
|
|
|
1
|
+
// src/storage/types.ts
|
|
2
|
+
var STORAGE_VERSION = 1;
|
|
3
|
+
|
|
4
|
+
// src/storage/utils.ts
|
|
5
|
+
function generateEntryId(existingIds) {
|
|
6
|
+
for (let i = 0; i < 100; i++) {
|
|
7
|
+
const id = crypto.randomUUID().slice(0, 8);
|
|
8
|
+
if (!existingIds?.has(id)) return id;
|
|
9
|
+
}
|
|
10
|
+
return crypto.randomUUID();
|
|
11
|
+
}
|
|
12
|
+
function parseJSONL(content) {
|
|
13
|
+
const entries = [];
|
|
14
|
+
const lines = content.split("\n");
|
|
15
|
+
for (const line of lines) {
|
|
16
|
+
const trimmed = line.trim();
|
|
17
|
+
if (!trimmed) continue;
|
|
18
|
+
try {
|
|
19
|
+
entries.push(JSON.parse(trimmed));
|
|
20
|
+
} catch {
|
|
21
|
+
console.warn("[Storage] Skipping malformed JSONL line");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return entries;
|
|
25
|
+
}
|
|
26
|
+
function toJSONL(entry) {
|
|
27
|
+
return JSON.stringify(entry) + "\n";
|
|
28
|
+
}
|
|
29
|
+
function toJSONLBatch(entries) {
|
|
30
|
+
return entries.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
31
|
+
}
|
|
32
|
+
function serializeMessage(message) {
|
|
33
|
+
const serialized = {
|
|
34
|
+
id: message.id,
|
|
35
|
+
role: message.role,
|
|
36
|
+
content: typeof message.content === "string" ? message.content : JSON.stringify(message.content),
|
|
37
|
+
createdAt: message.createdAt.toISOString()
|
|
38
|
+
};
|
|
39
|
+
if (message.role === "user" && "system" in message) {
|
|
40
|
+
serialized.system = message.system;
|
|
41
|
+
}
|
|
42
|
+
if (message.role === "assistant") {
|
|
43
|
+
if ("finish" in message) serialized.finish = message.finish;
|
|
44
|
+
if ("tokens" in message) serialized.tokens = message.tokens;
|
|
45
|
+
if ("cost" in message) serialized.cost = message.cost;
|
|
46
|
+
if ("error" in message) serialized.error = message.error;
|
|
47
|
+
if ("toolCalls" in message && message.toolCalls) serialized.toolCalls = message.toolCalls;
|
|
48
|
+
}
|
|
49
|
+
if (message.role === "tool") {
|
|
50
|
+
if ("toolCallId" in message) serialized.toolCallId = message.toolCallId;
|
|
51
|
+
if ("toolName" in message) serialized.toolName = message.toolName;
|
|
52
|
+
if ("result" in message) serialized.result = message.result;
|
|
53
|
+
}
|
|
54
|
+
return serialized;
|
|
55
|
+
}
|
|
56
|
+
function deserializeMessage(serialized) {
|
|
57
|
+
const base = {
|
|
58
|
+
id: serialized.id,
|
|
59
|
+
createdAt: new Date(serialized.createdAt)
|
|
60
|
+
};
|
|
61
|
+
switch (serialized.role) {
|
|
62
|
+
case "user":
|
|
63
|
+
return {
|
|
64
|
+
...base,
|
|
65
|
+
role: "user",
|
|
66
|
+
content: serialized.content,
|
|
67
|
+
system: serialized.system
|
|
68
|
+
};
|
|
69
|
+
case "assistant":
|
|
70
|
+
return {
|
|
71
|
+
...base,
|
|
72
|
+
role: "assistant",
|
|
73
|
+
content: serialized.content,
|
|
74
|
+
finish: serialized.finish,
|
|
75
|
+
tokens: serialized.tokens,
|
|
76
|
+
cost: serialized.cost,
|
|
77
|
+
error: serialized.error,
|
|
78
|
+
toolCalls: serialized.toolCalls
|
|
79
|
+
};
|
|
80
|
+
case "tool":
|
|
81
|
+
return {
|
|
82
|
+
...base,
|
|
83
|
+
role: "tool",
|
|
84
|
+
content: serialized.content,
|
|
85
|
+
toolCallId: serialized.toolCallId,
|
|
86
|
+
toolName: serialized.toolName,
|
|
87
|
+
result: serialized.result
|
|
88
|
+
};
|
|
89
|
+
case "system":
|
|
90
|
+
return {
|
|
91
|
+
...base,
|
|
92
|
+
role: "system",
|
|
93
|
+
content: serialized.content
|
|
94
|
+
};
|
|
95
|
+
default:
|
|
96
|
+
throw new Error(`Unknown message role: ${serialized.role}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function createMessageEntry(message, parentId, existingIds) {
|
|
100
|
+
return {
|
|
101
|
+
type: "message",
|
|
102
|
+
id: generateEntryId(existingIds),
|
|
103
|
+
parentId,
|
|
104
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
105
|
+
message: serializeMessage(message)
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function createMetadataEntry(updates, parentId, existingIds) {
|
|
109
|
+
return {
|
|
110
|
+
type: "metadata",
|
|
111
|
+
id: generateEntryId(existingIds),
|
|
112
|
+
parentId,
|
|
113
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
114
|
+
...updates
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function extractSessionInfo(entries, path) {
|
|
118
|
+
if (entries.length === 0) return null;
|
|
119
|
+
const header = entries[0];
|
|
120
|
+
if (header.type !== "header") return null;
|
|
121
|
+
let title = header.title;
|
|
122
|
+
let name;
|
|
123
|
+
let messageCount = 0;
|
|
124
|
+
let firstMessage;
|
|
125
|
+
let lastTimestamp = header.timestamp;
|
|
126
|
+
for (let i = 1; i < entries.length; i++) {
|
|
127
|
+
const entry = entries[i];
|
|
128
|
+
lastTimestamp = entry.timestamp;
|
|
129
|
+
if (entry.type === "message") {
|
|
130
|
+
messageCount++;
|
|
131
|
+
if (!firstMessage && entry.message.role === "user") {
|
|
132
|
+
firstMessage = entry.message.content.slice(0, 100);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (entry.type === "metadata") {
|
|
136
|
+
if (entry.title !== void 0) title = entry.title;
|
|
137
|
+
if (entry.name !== void 0) name = entry.name;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
id: header.id,
|
|
142
|
+
path,
|
|
143
|
+
cwd: header.cwd,
|
|
144
|
+
title,
|
|
145
|
+
name,
|
|
146
|
+
parentSessionId: header.parentSessionId,
|
|
147
|
+
createdAt: new Date(header.timestamp),
|
|
148
|
+
updatedAt: new Date(lastTimestamp),
|
|
149
|
+
messageCount,
|
|
150
|
+
firstMessage
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function getLeafId(entries) {
|
|
154
|
+
if (entries.length <= 1) return null;
|
|
155
|
+
const childMap = /* @__PURE__ */ new Map();
|
|
156
|
+
for (const entry of entries) {
|
|
157
|
+
if (entry.type === "header") continue;
|
|
158
|
+
const e = entry;
|
|
159
|
+
const children = childMap.get(e.parentId) ?? [];
|
|
160
|
+
children.push(e.id);
|
|
161
|
+
childMap.set(e.parentId, children);
|
|
162
|
+
}
|
|
163
|
+
const _allIds = new Set(entries.slice(1).map((e) => e.id));
|
|
164
|
+
const hasChildren = /* @__PURE__ */ new Set();
|
|
165
|
+
for (const children of childMap.values()) {
|
|
166
|
+
for (const child of children) {
|
|
167
|
+
const entry = entries.find((e) => e.type !== "header" && e.id === child);
|
|
168
|
+
if (entry && entry.type !== "header") {
|
|
169
|
+
const parentId = entry.parentId;
|
|
170
|
+
if (parentId) hasChildren.add(parentId);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
for (let i = entries.length - 1; i >= 1; i--) {
|
|
175
|
+
const entry = entries[i];
|
|
176
|
+
if (!hasChildren.has(entry.id)) {
|
|
177
|
+
return entry.id;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
function buildEntryPath(entries, targetId) {
|
|
183
|
+
const byId = /* @__PURE__ */ new Map();
|
|
184
|
+
for (const entry of entries) {
|
|
185
|
+
if (entry.type !== "header") {
|
|
186
|
+
byId.set(entry.id, entry);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const path = [];
|
|
190
|
+
let currentId = targetId;
|
|
191
|
+
while (currentId) {
|
|
192
|
+
const entry = byId.get(currentId);
|
|
193
|
+
if (!entry) break;
|
|
194
|
+
path.unshift(entry);
|
|
195
|
+
currentId = entry.parentId;
|
|
196
|
+
}
|
|
197
|
+
return path;
|
|
198
|
+
}
|
|
199
|
+
function buildMessagesFromEntries(entries, leafId) {
|
|
200
|
+
const targetLeaf = leafId ?? getLeafId(entries);
|
|
201
|
+
if (!targetLeaf) return [];
|
|
202
|
+
const path = buildEntryPath(entries, targetLeaf);
|
|
203
|
+
const messages = [];
|
|
204
|
+
let skipUntilId;
|
|
205
|
+
for (const entry of path) {
|
|
206
|
+
if (entry.type === "compaction") {
|
|
207
|
+
skipUntilId = entry.firstKeptEntryId;
|
|
208
|
+
messages.push({
|
|
209
|
+
id: entry.id,
|
|
210
|
+
role: "system",
|
|
211
|
+
content: `## Previous Conversation Summary
|
|
212
|
+
|
|
213
|
+
${entry.summary}`,
|
|
214
|
+
createdAt: new Date(entry.timestamp)
|
|
215
|
+
});
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (skipUntilId && entry.id !== skipUntilId) {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (entry.id === skipUntilId) {
|
|
222
|
+
skipUntilId = void 0;
|
|
223
|
+
}
|
|
224
|
+
if (entry.type === "message") {
|
|
225
|
+
messages.push(deserializeMessage(entry.message));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return messages;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// src/storage/memory.ts
|
|
232
|
+
var MemoryStorage = class {
|
|
233
|
+
sessions = /* @__PURE__ */ new Map();
|
|
234
|
+
async create(header) {
|
|
235
|
+
if (this.sessions.has(header.id)) {
|
|
236
|
+
throw new Error(`Session already exists: ${header.id}`);
|
|
237
|
+
}
|
|
238
|
+
this.sessions.set(header.id, [header]);
|
|
239
|
+
}
|
|
240
|
+
async append(sessionId, entry) {
|
|
241
|
+
const entries = this.sessions.get(sessionId);
|
|
242
|
+
if (!entries) {
|
|
243
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
244
|
+
}
|
|
245
|
+
entries.push(entry);
|
|
246
|
+
}
|
|
247
|
+
async appendBatch(sessionId, entries) {
|
|
248
|
+
const existing = this.sessions.get(sessionId);
|
|
249
|
+
if (!existing) {
|
|
250
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
251
|
+
}
|
|
252
|
+
existing.push(...entries);
|
|
253
|
+
}
|
|
254
|
+
async read(sessionId) {
|
|
255
|
+
const entries = this.sessions.get(sessionId);
|
|
256
|
+
if (!entries) {
|
|
257
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
258
|
+
}
|
|
259
|
+
return [...entries];
|
|
260
|
+
}
|
|
261
|
+
async delete(sessionId) {
|
|
262
|
+
return this.sessions.delete(sessionId);
|
|
263
|
+
}
|
|
264
|
+
async exists(sessionId) {
|
|
265
|
+
return this.sessions.has(sessionId);
|
|
266
|
+
}
|
|
267
|
+
async list() {
|
|
268
|
+
const infos = [];
|
|
269
|
+
for (const [id, entries] of this.sessions) {
|
|
270
|
+
const info = extractSessionInfo(entries, `memory://${id}`);
|
|
271
|
+
if (info) {
|
|
272
|
+
infos.push(info);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
infos.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
|
276
|
+
return infos;
|
|
277
|
+
}
|
|
278
|
+
async clear() {
|
|
279
|
+
this.sessions.clear();
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// src/storage/file/helpers.ts
|
|
284
|
+
import { appendFile, mkdir, readFile, readdir, stat, unlink, writeFile } from "fs/promises";
|
|
285
|
+
import { existsSync } from "fs";
|
|
286
|
+
import { basename, join } from "path";
|
|
287
|
+
async function ensureStorageDirectory(directory) {
|
|
288
|
+
if (!existsSync(directory)) {
|
|
289
|
+
await mkdir(directory, { recursive: true });
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
function getSessionFilePath(directory, extension, sessionId) {
|
|
293
|
+
const safeId = sessionId.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
294
|
+
return join(directory, `${safeId}${extension}`);
|
|
295
|
+
}
|
|
296
|
+
async function loadEntriesFromDisk(filePath, sessionId) {
|
|
297
|
+
try {
|
|
298
|
+
const content = await readFile(filePath, "utf-8");
|
|
299
|
+
return parseJSONL(content);
|
|
300
|
+
} catch (error) {
|
|
301
|
+
if (error.code === "ENOENT") {
|
|
302
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
303
|
+
}
|
|
304
|
+
throw error;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
async function createSessionFile(filePath, header) {
|
|
308
|
+
await writeFile(filePath, toJSONL(header), "utf-8");
|
|
309
|
+
}
|
|
310
|
+
async function appendSessionEntry(filePath, entry) {
|
|
311
|
+
await appendFile(filePath, toJSONL(entry), "utf-8");
|
|
312
|
+
}
|
|
313
|
+
async function appendSessionEntries(filePath, entries) {
|
|
314
|
+
await appendFile(filePath, toJSONLBatch(entries), "utf-8");
|
|
315
|
+
}
|
|
316
|
+
function sessionFileExists(filePath) {
|
|
317
|
+
return existsSync(filePath);
|
|
318
|
+
}
|
|
319
|
+
async function deleteSessionFile(filePath) {
|
|
320
|
+
if (!existsSync(filePath)) {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
try {
|
|
324
|
+
await unlink(filePath);
|
|
325
|
+
return true;
|
|
326
|
+
} catch {
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
async function listSessionIds(directory, extension) {
|
|
331
|
+
try {
|
|
332
|
+
const files = await readdir(directory);
|
|
333
|
+
return files.filter((file) => file.endsWith(extension)).map((file) => basename(file, extension));
|
|
334
|
+
} catch {
|
|
335
|
+
return [];
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
async function clearSessionFiles(directory, extension) {
|
|
339
|
+
try {
|
|
340
|
+
const files = await readdir(directory);
|
|
341
|
+
await Promise.all(
|
|
342
|
+
files.filter((file) => file.endsWith(extension)).map((file) => unlink(join(directory, file)).catch(() => {
|
|
343
|
+
}))
|
|
344
|
+
);
|
|
345
|
+
} catch {
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
async function getSessionFileStats(filePath) {
|
|
349
|
+
try {
|
|
350
|
+
const info = await stat(filePath);
|
|
351
|
+
return {
|
|
352
|
+
size: info.size,
|
|
353
|
+
mtime: info.mtime
|
|
354
|
+
};
|
|
355
|
+
} catch {
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// src/storage/file/storage.ts
|
|
361
|
+
var FileStorage = class {
|
|
362
|
+
directory;
|
|
363
|
+
extension;
|
|
364
|
+
useCache;
|
|
365
|
+
cache = /* @__PURE__ */ new Map();
|
|
366
|
+
initialized = false;
|
|
367
|
+
constructor(options) {
|
|
368
|
+
this.directory = options.directory;
|
|
369
|
+
this.extension = options.extension ?? ".jsonl";
|
|
370
|
+
this.useCache = options.cache ?? true;
|
|
371
|
+
}
|
|
372
|
+
async ensureDir() {
|
|
373
|
+
if (this.initialized) return;
|
|
374
|
+
await ensureStorageDirectory(this.directory);
|
|
375
|
+
this.initialized = true;
|
|
376
|
+
}
|
|
377
|
+
getPath(sessionId) {
|
|
378
|
+
return getSessionFilePath(this.directory, this.extension, sessionId);
|
|
379
|
+
}
|
|
380
|
+
async getEntries(sessionId) {
|
|
381
|
+
if (this.useCache && this.cache.has(sessionId)) {
|
|
382
|
+
return this.cache.get(sessionId);
|
|
383
|
+
}
|
|
384
|
+
const entries = await loadEntriesFromDisk(this.getPath(sessionId), sessionId);
|
|
385
|
+
if (this.useCache) {
|
|
386
|
+
this.cache.set(sessionId, entries);
|
|
387
|
+
}
|
|
388
|
+
return entries;
|
|
389
|
+
}
|
|
390
|
+
async create(header) {
|
|
391
|
+
await this.ensureDir();
|
|
392
|
+
const filePath = this.getPath(header.id);
|
|
393
|
+
if (sessionFileExists(filePath)) {
|
|
394
|
+
throw new Error(`Session already exists: ${header.id}`);
|
|
395
|
+
}
|
|
396
|
+
await createSessionFile(filePath, header);
|
|
397
|
+
if (this.useCache) {
|
|
398
|
+
this.cache.set(header.id, [header]);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
async append(sessionId, entry) {
|
|
402
|
+
await this.ensureDir();
|
|
403
|
+
const filePath = this.getPath(sessionId);
|
|
404
|
+
if (!sessionFileExists(filePath)) {
|
|
405
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
406
|
+
}
|
|
407
|
+
await appendSessionEntry(filePath, entry);
|
|
408
|
+
if (this.useCache) {
|
|
409
|
+
this.cache.get(sessionId)?.push(entry);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
async appendBatch(sessionId, entries) {
|
|
413
|
+
if (entries.length === 0) return;
|
|
414
|
+
await this.ensureDir();
|
|
415
|
+
const filePath = this.getPath(sessionId);
|
|
416
|
+
if (!sessionFileExists(filePath)) {
|
|
417
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
418
|
+
}
|
|
419
|
+
await appendSessionEntries(filePath, entries);
|
|
420
|
+
if (this.useCache) {
|
|
421
|
+
this.cache.get(sessionId)?.push(...entries);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
async read(sessionId) {
|
|
425
|
+
await this.ensureDir();
|
|
426
|
+
return [...await this.getEntries(sessionId)];
|
|
427
|
+
}
|
|
428
|
+
async delete(sessionId) {
|
|
429
|
+
this.cache.delete(sessionId);
|
|
430
|
+
return deleteSessionFile(this.getPath(sessionId));
|
|
431
|
+
}
|
|
432
|
+
async exists(sessionId) {
|
|
433
|
+
if (this.useCache && this.cache.has(sessionId)) {
|
|
434
|
+
return true;
|
|
435
|
+
}
|
|
436
|
+
return sessionFileExists(this.getPath(sessionId));
|
|
437
|
+
}
|
|
438
|
+
async list() {
|
|
439
|
+
await this.ensureDir();
|
|
440
|
+
const infos = [];
|
|
441
|
+
for (const sessionId of await listSessionIds(this.directory, this.extension)) {
|
|
442
|
+
try {
|
|
443
|
+
const info = extractSessionInfo(await this.getEntries(sessionId), this.getPath(sessionId));
|
|
444
|
+
if (info) {
|
|
445
|
+
infos.push(info);
|
|
446
|
+
}
|
|
447
|
+
} catch {
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
infos.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
|
451
|
+
return infos;
|
|
452
|
+
}
|
|
453
|
+
async clear() {
|
|
454
|
+
this.cache.clear();
|
|
455
|
+
await clearSessionFiles(this.directory, this.extension);
|
|
456
|
+
}
|
|
457
|
+
flushCache() {
|
|
458
|
+
this.cache.clear();
|
|
459
|
+
}
|
|
460
|
+
async reload(sessionId) {
|
|
461
|
+
this.cache.delete(sessionId);
|
|
462
|
+
return this.read(sessionId);
|
|
463
|
+
}
|
|
464
|
+
async listIds() {
|
|
465
|
+
await this.ensureDir();
|
|
466
|
+
return listSessionIds(this.directory, this.extension);
|
|
467
|
+
}
|
|
468
|
+
async getStats(sessionId) {
|
|
469
|
+
return getSessionFileStats(this.getPath(sessionId));
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
// src/storage/paths.ts
|
|
474
|
+
import { homedir } from "os";
|
|
475
|
+
import { join as join2 } from "path";
|
|
476
|
+
import { execSync } from "child_process";
|
|
477
|
+
function getDataDir(appName = "cuylabs") {
|
|
478
|
+
const platform = process.platform;
|
|
479
|
+
if (platform === "darwin") {
|
|
480
|
+
return join2(homedir(), `.${appName}`);
|
|
481
|
+
}
|
|
482
|
+
if (platform === "win32") {
|
|
483
|
+
return join2(process.env.APPDATA || join2(homedir(), "AppData", "Roaming"), appName);
|
|
484
|
+
}
|
|
485
|
+
const xdgData = process.env.XDG_DATA_HOME || join2(homedir(), ".local", "share");
|
|
486
|
+
return join2(xdgData, appName);
|
|
487
|
+
}
|
|
488
|
+
function getSessionsDir(appName = "cuylabs") {
|
|
489
|
+
return join2(getDataDir(appName), "sessions");
|
|
490
|
+
}
|
|
491
|
+
function getGitRootHash(cwd) {
|
|
492
|
+
try {
|
|
493
|
+
const result = execSync("git rev-list --max-parents=0 --all 2>/dev/null", {
|
|
494
|
+
cwd,
|
|
495
|
+
encoding: "utf-8"
|
|
496
|
+
});
|
|
497
|
+
const roots = result.split("\n").filter(Boolean).map((s) => s.trim()).sort();
|
|
498
|
+
return roots[0] || null;
|
|
499
|
+
} catch {
|
|
500
|
+
return null;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
function getProjectId(cwd) {
|
|
504
|
+
const gitHash = getGitRootHash(cwd);
|
|
505
|
+
if (gitHash) {
|
|
506
|
+
return gitHash.slice(0, 12);
|
|
507
|
+
}
|
|
508
|
+
return cwd.replace(/^\//, "").replace(/[/\\:]/g, "-").replace(/\s+/g, "-").toLowerCase().slice(-50);
|
|
509
|
+
}
|
|
510
|
+
function getProjectSessionsDir(cwd, appName = "cuylabs") {
|
|
511
|
+
const projectId = getProjectId(cwd);
|
|
512
|
+
return join2(getSessionsDir(appName), projectId);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// src/storage/manager/session-manager.ts
|
|
516
|
+
var SessionManager = class {
|
|
517
|
+
storage;
|
|
518
|
+
currentSessionId = null;
|
|
519
|
+
currentLeafId = null;
|
|
520
|
+
entriesCache = [];
|
|
521
|
+
idsCache = /* @__PURE__ */ new Set();
|
|
522
|
+
constructor(storage) {
|
|
523
|
+
this.storage = storage ?? new MemoryStorage();
|
|
524
|
+
}
|
|
525
|
+
async create(options) {
|
|
526
|
+
const id = options.id ?? crypto.randomUUID();
|
|
527
|
+
const header = {
|
|
528
|
+
type: "header",
|
|
529
|
+
version: STORAGE_VERSION,
|
|
530
|
+
id,
|
|
531
|
+
cwd: options.cwd,
|
|
532
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
533
|
+
title: options.title,
|
|
534
|
+
parentSessionId: options.parentSessionId
|
|
535
|
+
};
|
|
536
|
+
await this.storage.create(header);
|
|
537
|
+
this.currentSessionId = id;
|
|
538
|
+
this.currentLeafId = null;
|
|
539
|
+
this.entriesCache = [header];
|
|
540
|
+
this.idsCache.clear();
|
|
541
|
+
return id;
|
|
542
|
+
}
|
|
543
|
+
async load(sessionId) {
|
|
544
|
+
const entries = await this.storage.read(sessionId);
|
|
545
|
+
if (entries.length === 0) {
|
|
546
|
+
throw new Error(`Empty session: ${sessionId}`);
|
|
547
|
+
}
|
|
548
|
+
this.currentSessionId = sessionId;
|
|
549
|
+
this.entriesCache = entries;
|
|
550
|
+
this.currentLeafId = getLeafId(entries);
|
|
551
|
+
this.idsCache.clear();
|
|
552
|
+
for (const entry of entries) {
|
|
553
|
+
if (entry.type !== "header") {
|
|
554
|
+
this.idsCache.add(entry.id);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
getSessionId() {
|
|
559
|
+
return this.currentSessionId;
|
|
560
|
+
}
|
|
561
|
+
getLeafId() {
|
|
562
|
+
return this.currentLeafId;
|
|
563
|
+
}
|
|
564
|
+
async addMessage(message) {
|
|
565
|
+
if (!this.currentSessionId) {
|
|
566
|
+
throw new Error("No session loaded");
|
|
567
|
+
}
|
|
568
|
+
const entry = createMessageEntry(message, this.currentLeafId, this.idsCache);
|
|
569
|
+
await this.storage.append(this.currentSessionId, entry);
|
|
570
|
+
this.entriesCache.push(entry);
|
|
571
|
+
this.idsCache.add(entry.id);
|
|
572
|
+
this.currentLeafId = entry.id;
|
|
573
|
+
return entry.id;
|
|
574
|
+
}
|
|
575
|
+
async addMessages(messages) {
|
|
576
|
+
if (!this.currentSessionId) {
|
|
577
|
+
throw new Error("No session loaded");
|
|
578
|
+
}
|
|
579
|
+
if (messages.length === 0) {
|
|
580
|
+
return [];
|
|
581
|
+
}
|
|
582
|
+
const entries = [];
|
|
583
|
+
let parentId = this.currentLeafId;
|
|
584
|
+
for (const message of messages) {
|
|
585
|
+
const entry = createMessageEntry(message, parentId, this.idsCache);
|
|
586
|
+
entries.push(entry);
|
|
587
|
+
this.idsCache.add(entry.id);
|
|
588
|
+
parentId = entry.id;
|
|
589
|
+
}
|
|
590
|
+
await this.storage.appendBatch(this.currentSessionId, entries);
|
|
591
|
+
this.entriesCache.push(...entries);
|
|
592
|
+
this.currentLeafId = entries[entries.length - 1].id;
|
|
593
|
+
return entries.map((entry) => entry.id);
|
|
594
|
+
}
|
|
595
|
+
getMessages(leafId) {
|
|
596
|
+
return buildMessagesFromEntries(
|
|
597
|
+
this.entriesCache,
|
|
598
|
+
leafId ?? this.currentLeafId ?? void 0
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
async addCompaction(options) {
|
|
602
|
+
if (!this.currentSessionId) {
|
|
603
|
+
throw new Error("No session loaded");
|
|
604
|
+
}
|
|
605
|
+
const entry = {
|
|
606
|
+
type: "compaction",
|
|
607
|
+
id: generateEntryId(this.idsCache),
|
|
608
|
+
parentId: this.currentLeafId,
|
|
609
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
610
|
+
...options
|
|
611
|
+
};
|
|
612
|
+
await this.storage.append(this.currentSessionId, entry);
|
|
613
|
+
this.entriesCache.push(entry);
|
|
614
|
+
this.idsCache.add(entry.id);
|
|
615
|
+
this.currentLeafId = entry.id;
|
|
616
|
+
return entry.id;
|
|
617
|
+
}
|
|
618
|
+
async branch(fromEntryId, summary) {
|
|
619
|
+
if (!this.currentSessionId) {
|
|
620
|
+
throw new Error("No session loaded");
|
|
621
|
+
}
|
|
622
|
+
if (!this.idsCache.has(fromEntryId)) {
|
|
623
|
+
throw new Error(`Entry not found: ${fromEntryId}`);
|
|
624
|
+
}
|
|
625
|
+
const entry = {
|
|
626
|
+
type: "branch",
|
|
627
|
+
id: generateEntryId(this.idsCache),
|
|
628
|
+
parentId: fromEntryId,
|
|
629
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
630
|
+
branchFromId: fromEntryId,
|
|
631
|
+
summary
|
|
632
|
+
};
|
|
633
|
+
await this.storage.append(this.currentSessionId, entry);
|
|
634
|
+
this.entriesCache.push(entry);
|
|
635
|
+
this.idsCache.add(entry.id);
|
|
636
|
+
this.currentLeafId = entry.id;
|
|
637
|
+
return entry.id;
|
|
638
|
+
}
|
|
639
|
+
switchToLeaf(leafId) {
|
|
640
|
+
if (!this.idsCache.has(leafId)) {
|
|
641
|
+
throw new Error(`Entry not found: ${leafId}`);
|
|
642
|
+
}
|
|
643
|
+
this.currentLeafId = leafId;
|
|
644
|
+
}
|
|
645
|
+
async updateMetadata(updates) {
|
|
646
|
+
if (!this.currentSessionId) {
|
|
647
|
+
throw new Error("No session loaded");
|
|
648
|
+
}
|
|
649
|
+
const entry = createMetadataEntry(updates, this.currentLeafId, this.idsCache);
|
|
650
|
+
await this.storage.append(this.currentSessionId, entry);
|
|
651
|
+
this.entriesCache.push(entry);
|
|
652
|
+
this.idsCache.add(entry.id);
|
|
653
|
+
this.currentLeafId = entry.id;
|
|
654
|
+
}
|
|
655
|
+
getContext() {
|
|
656
|
+
const header = this.entriesCache[0];
|
|
657
|
+
let title = header.title;
|
|
658
|
+
let name;
|
|
659
|
+
for (const entry of this.entriesCache) {
|
|
660
|
+
if (entry.type === "metadata") {
|
|
661
|
+
if (entry.title !== void 0) {
|
|
662
|
+
title = entry.title;
|
|
663
|
+
}
|
|
664
|
+
if (entry.name !== void 0) {
|
|
665
|
+
name = entry.name;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return {
|
|
670
|
+
messages: this.getMessages(),
|
|
671
|
+
leafId: this.currentLeafId,
|
|
672
|
+
metadata: {
|
|
673
|
+
id: header.id,
|
|
674
|
+
cwd: header.cwd,
|
|
675
|
+
title,
|
|
676
|
+
name
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
getEntries() {
|
|
681
|
+
return [...this.entriesCache];
|
|
682
|
+
}
|
|
683
|
+
getHeader() {
|
|
684
|
+
const header = this.entriesCache[0];
|
|
685
|
+
return header?.type === "header" ? header : null;
|
|
686
|
+
}
|
|
687
|
+
async listSessions() {
|
|
688
|
+
return this.storage.list();
|
|
689
|
+
}
|
|
690
|
+
async deleteSession(sessionId) {
|
|
691
|
+
const result = await this.storage.delete(sessionId);
|
|
692
|
+
if (sessionId === this.currentSessionId) {
|
|
693
|
+
this.currentSessionId = null;
|
|
694
|
+
this.currentLeafId = null;
|
|
695
|
+
this.entriesCache = [];
|
|
696
|
+
this.idsCache.clear();
|
|
697
|
+
}
|
|
698
|
+
return result;
|
|
699
|
+
}
|
|
700
|
+
async sessionExists(sessionId) {
|
|
701
|
+
return this.storage.exists(sessionId);
|
|
702
|
+
}
|
|
703
|
+
getStorage() {
|
|
704
|
+
return this.storage;
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
// src/storage/manager/default.ts
|
|
709
|
+
var defaultManager = null;
|
|
710
|
+
function getDefaultSessionManager() {
|
|
711
|
+
if (!defaultManager) {
|
|
712
|
+
defaultManager = new SessionManager();
|
|
713
|
+
}
|
|
714
|
+
return defaultManager;
|
|
715
|
+
}
|
|
716
|
+
function configureDefaultSessionManager(storage) {
|
|
717
|
+
defaultManager = new SessionManager(storage);
|
|
718
|
+
return defaultManager;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
export {
|
|
722
|
+
STORAGE_VERSION,
|
|
723
|
+
generateEntryId,
|
|
724
|
+
parseJSONL,
|
|
725
|
+
toJSONL,
|
|
726
|
+
toJSONLBatch,
|
|
727
|
+
serializeMessage,
|
|
728
|
+
deserializeMessage,
|
|
729
|
+
createMessageEntry,
|
|
730
|
+
createMetadataEntry,
|
|
731
|
+
extractSessionInfo,
|
|
732
|
+
getLeafId,
|
|
733
|
+
buildEntryPath,
|
|
734
|
+
buildMessagesFromEntries,
|
|
735
|
+
MemoryStorage,
|
|
736
|
+
FileStorage,
|
|
737
|
+
getDataDir,
|
|
738
|
+
getSessionsDir,
|
|
739
|
+
getGitRootHash,
|
|
740
|
+
getProjectId,
|
|
741
|
+
getProjectSessionsDir,
|
|
742
|
+
SessionManager,
|
|
743
|
+
getDefaultSessionManager,
|
|
744
|
+
configureDefaultSessionManager
|
|
745
|
+
};
|