chainlesschain 0.45.11 → 0.45.19
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/package.json +1 -1
- package/src/assets/web-panel/assets/AppLayout-B00RARl2.js +1 -0
- package/src/assets/web-panel/assets/AppLayout-CFP4dGIJ.css +1 -0
- package/src/assets/web-panel/assets/{Chat-5f__rMCR.js → Chat-DXtvKoM0.js} +1 -1
- package/src/assets/web-panel/assets/{Cron-C4mrNC4c.js → Cron-BJ4ODHOy.js} +1 -1
- package/src/assets/web-panel/assets/Dashboard-3iIpp3zd.js +3 -0
- package/src/assets/web-panel/assets/Dashboard-BS-tzGNj.css +1 -0
- package/src/assets/web-panel/assets/{Logs-CC_Zuh66.js → Logs-CSeKZEG_.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-B15GiN3u.js → McpTools-BYQAK11r.js} +2 -2
- package/src/assets/web-panel/assets/{Memory-Dbd7oLOH.js → Memory-gkUAPyuZ.js} +2 -2
- package/src/assets/web-panel/assets/{Notes-CEkc49fY.js → Notes-bjNrQgAo.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-CjyPHW00.js → Providers-Dbf57Tbv.js} +1 -1
- package/src/assets/web-panel/assets/{Services-XFzHMRRd.js → Services-CS0oMdxh.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-D8oxmB3U.js → Skills-B2fgruv8.js} +1 -1
- package/src/assets/web-panel/assets/Tasks-BJjN_YEm.css +1 -0
- package/src/assets/web-panel/assets/Tasks-qULws8pc.js +1 -0
- package/src/assets/web-panel/assets/{antd-ChLPLhSn.js → antd-CJSBocer.js} +1 -1
- package/src/assets/web-panel/assets/chat-DnH09sSR.js +1 -0
- package/src/assets/web-panel/assets/{index-DQ5xXK7O.js → index-CF2CqPYX.js} +2 -2
- package/src/assets/web-panel/assets/{markdown-DtbPhnFe.js → markdown-Bo5cVN4u.js} +1 -1
- package/src/assets/web-panel/assets/ws-DjelKkD6.js +1 -0
- package/src/assets/web-panel/index.html +2 -2
- package/src/commands/agent.js +7 -8
- package/src/commands/chat.js +9 -11
- package/src/commands/serve.js +11 -106
- package/src/commands/session.js +185 -18
- package/src/commands/ui.js +10 -151
- package/src/gateways/repl/agent-repl.js +1 -0
- package/src/gateways/repl/chat-repl.js +1 -0
- package/src/gateways/ui/web-ui-server.js +1 -0
- package/src/gateways/ws/action-protocol.js +83 -0
- package/src/gateways/ws/message-dispatcher.js +73 -0
- package/src/gateways/ws/session-protocol.js +396 -0
- package/src/gateways/ws/task-protocol.js +55 -0
- package/src/gateways/ws/worktree-protocol.js +315 -0
- package/src/gateways/ws/ws-server.js +4 -0
- package/src/gateways/ws/ws-session-gateway.js +1 -0
- package/src/harness/background-task-manager.js +506 -0
- package/src/harness/background-task-worker.js +48 -0
- package/src/harness/compression-telemetry.js +214 -0
- package/src/harness/feature-flags.js +157 -0
- package/src/harness/jsonl-session-store.js +452 -0
- package/src/harness/prompt-compressor.js +416 -0
- package/src/harness/worktree-isolator.js +845 -0
- package/src/lib/agent-core.js +246 -45
- package/src/lib/background-task-manager.js +1 -305
- package/src/lib/background-task-worker.js +1 -50
- package/src/lib/compression-telemetry.js +5 -0
- package/src/lib/feature-flags.js +7 -182
- package/src/lib/interaction-adapter.js +32 -6
- package/src/lib/jsonl-session-store.js +21 -237
- package/src/lib/prompt-compressor.js +10 -351
- package/src/lib/sub-agent-context.js +91 -0
- package/src/lib/worktree-isolator.js +13 -231
- package/src/lib/ws-agent-handler.js +1 -0
- package/src/lib/ws-server.js +155 -359
- package/src/lib/ws-session-manager.js +82 -1
- package/src/repl/agent-repl.js +114 -32
- package/src/runtime/agent-runtime.js +417 -0
- package/src/runtime/contracts/agent-turn.js +11 -0
- package/src/runtime/contracts/session-record.js +31 -0
- package/src/runtime/contracts/task-record.js +18 -0
- package/src/runtime/contracts/telemetry-record.js +23 -0
- package/src/runtime/contracts/worktree-record.js +14 -0
- package/src/runtime/index.js +13 -0
- package/src/runtime/policies/agent-policy.js +45 -0
- package/src/runtime/runtime-context.js +14 -0
- package/src/runtime/runtime-events.js +37 -0
- package/src/runtime/runtime-factory.js +50 -0
- package/src/tools/index.js +22 -0
- package/src/tools/legacy-agent-tools.js +171 -0
- package/src/tools/registry.js +141 -0
- package/src/tools/tool-context.js +28 -0
- package/src/tools/tool-permissions.js +28 -0
- package/src/tools/tool-telemetry.js +39 -0
- package/src/assets/web-panel/assets/AppLayout-19ZC8w11.js +0 -1
- package/src/assets/web-panel/assets/AppLayout-CjgO-ML6.css +0 -1
- package/src/assets/web-panel/assets/Dashboard-CRFnDUFh.css +0 -1
- package/src/assets/web-panel/assets/Dashboard-DsjXpZor.js +0 -3
- package/src/assets/web-panel/assets/chat-C_hu-qNs.js +0 -1
- package/src/assets/web-panel/assets/ws-DwluTqT5.js +0 -1
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSONL Session Store — append-only session persistence.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
existsSync,
|
|
7
|
+
mkdirSync,
|
|
8
|
+
appendFileSync,
|
|
9
|
+
readFileSync,
|
|
10
|
+
readdirSync,
|
|
11
|
+
copyFileSync,
|
|
12
|
+
rmSync,
|
|
13
|
+
} from "node:fs";
|
|
14
|
+
import { join, basename, resolve } from "node:path";
|
|
15
|
+
import { createHash } from "node:crypto";
|
|
16
|
+
import { getHomeDir } from "../lib/paths.js";
|
|
17
|
+
|
|
18
|
+
function getSessionsDir() {
|
|
19
|
+
const dir = join(getHomeDir(), "sessions");
|
|
20
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
21
|
+
return dir;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function sessionPath(sessionId) {
|
|
25
|
+
return join(getSessionsDir(), `${sessionId}.jsonl`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function appendEvent(sessionId, type, data) {
|
|
29
|
+
const line = JSON.stringify({ type, timestamp: Date.now(), data }) + "\n";
|
|
30
|
+
appendFileSync(sessionPath(sessionId), line, "utf-8");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function startSession(sessionId, meta = {}) {
|
|
34
|
+
const id =
|
|
35
|
+
sessionId ||
|
|
36
|
+
`session-${Date.now()}-${createHash("sha256").update(Math.random().toString()).digest("hex").slice(0, 6)}`;
|
|
37
|
+
|
|
38
|
+
appendEvent(id, "session_start", {
|
|
39
|
+
title: meta.title || "Untitled",
|
|
40
|
+
provider: meta.provider || "",
|
|
41
|
+
model: meta.model || "",
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return id;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function appendUserMessage(sessionId, content) {
|
|
48
|
+
appendEvent(sessionId, "user_message", { role: "user", content });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function appendAssistantMessage(sessionId, content) {
|
|
52
|
+
appendEvent(sessionId, "assistant_message", { role: "assistant", content });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function appendToolCall(sessionId, toolName, args) {
|
|
56
|
+
appendEvent(sessionId, "tool_call", { tool: toolName, args });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function appendToolResult(sessionId, toolName, result) {
|
|
60
|
+
appendEvent(sessionId, "tool_result", { tool: toolName, result });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function appendCompactEvent(sessionId, stats) {
|
|
64
|
+
appendEvent(sessionId, "compact", stats);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function readEvents(sessionId) {
|
|
68
|
+
const filePath = sessionPath(sessionId);
|
|
69
|
+
if (!existsSync(filePath)) return [];
|
|
70
|
+
|
|
71
|
+
const content = readFileSync(filePath, "utf-8");
|
|
72
|
+
const events = [];
|
|
73
|
+
|
|
74
|
+
for (const line of content.split("\n")) {
|
|
75
|
+
if (!line.trim()) continue;
|
|
76
|
+
try {
|
|
77
|
+
events.push(JSON.parse(line));
|
|
78
|
+
} catch (_e) {
|
|
79
|
+
// Skip malformed lines
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return events;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function rebuildMessages(sessionId) {
|
|
87
|
+
const events = readEvents(sessionId);
|
|
88
|
+
const messages = [];
|
|
89
|
+
let lastCompactIndex = -1;
|
|
90
|
+
|
|
91
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
92
|
+
if (events[i].type === "compact" && events[i].data.messages) {
|
|
93
|
+
lastCompactIndex = i;
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (lastCompactIndex >= 0 && events[lastCompactIndex].data.messages) {
|
|
99
|
+
messages.push(...events[lastCompactIndex].data.messages);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const startIndex = lastCompactIndex >= 0 ? lastCompactIndex + 1 : 0;
|
|
103
|
+
|
|
104
|
+
for (let i = startIndex; i < events.length; i++) {
|
|
105
|
+
const event = events[i];
|
|
106
|
+
if (
|
|
107
|
+
event.type === "user_message" ||
|
|
108
|
+
event.type === "assistant_message" ||
|
|
109
|
+
event.type === "system"
|
|
110
|
+
) {
|
|
111
|
+
messages.push(event.data);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return messages;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function listJsonlSessions(options = {}) {
|
|
119
|
+
const dir = getSessionsDir();
|
|
120
|
+
if (!existsSync(dir)) return [];
|
|
121
|
+
|
|
122
|
+
const limit = options.limit || 20;
|
|
123
|
+
const files = readdirSync(dir)
|
|
124
|
+
.filter((f) => f.endsWith(".jsonl"))
|
|
125
|
+
.map((f) => {
|
|
126
|
+
const id = basename(f, ".jsonl");
|
|
127
|
+
const events = readEvents(id);
|
|
128
|
+
const startEvent = events.find((e) => e.type === "session_start");
|
|
129
|
+
const lastEvent = events[events.length - 1];
|
|
130
|
+
const messageCount = events.filter(
|
|
131
|
+
(e) => e.type === "user_message" || e.type === "assistant_message",
|
|
132
|
+
).length;
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
id,
|
|
136
|
+
title: startEvent?.data?.title || "Untitled",
|
|
137
|
+
provider: startEvent?.data?.provider || "",
|
|
138
|
+
model: startEvent?.data?.model || "",
|
|
139
|
+
message_count: messageCount,
|
|
140
|
+
created_at: startEvent
|
|
141
|
+
? new Date(startEvent.timestamp).toISOString()
|
|
142
|
+
: "",
|
|
143
|
+
updated_at: lastEvent
|
|
144
|
+
? new Date(lastEvent.timestamp).toISOString()
|
|
145
|
+
: "",
|
|
146
|
+
};
|
|
147
|
+
})
|
|
148
|
+
.sort((a, b) => (b.updated_at > a.updated_at ? 1 : -1))
|
|
149
|
+
.slice(0, limit);
|
|
150
|
+
|
|
151
|
+
return files;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function forkSession(sourceId) {
|
|
155
|
+
const events = readEvents(sourceId);
|
|
156
|
+
if (events.length === 0) return null;
|
|
157
|
+
|
|
158
|
+
const newId = `session-${Date.now()}-${createHash("sha256").update(Math.random().toString()).digest("hex").slice(0, 6)}`;
|
|
159
|
+
const filePath = sessionPath(newId);
|
|
160
|
+
|
|
161
|
+
for (const event of events) {
|
|
162
|
+
const line = JSON.stringify(event) + "\n";
|
|
163
|
+
appendFileSync(filePath, line, "utf-8");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
appendEvent(newId, "system", {
|
|
167
|
+
role: "system",
|
|
168
|
+
content: `[Forked from session ${sourceId}]`,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
return newId;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function sessionExists(sessionId) {
|
|
175
|
+
return existsSync(sessionPath(sessionId));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function getLastSessionId() {
|
|
179
|
+
const sessions = listJsonlSessions({ limit: 1 });
|
|
180
|
+
return sessions.length > 0 ? sessions[0].id : null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function migrateLegacySessions(sourceDir, options = {}) {
|
|
184
|
+
return migrateLegacySessionsBatch(sourceDir, options).results;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function migrateLegacySessionsBatch(sourceDir, options = {}) {
|
|
188
|
+
const directory = resolve(sourceDir || getSessionsDir());
|
|
189
|
+
if (!existsSync(directory)) {
|
|
190
|
+
throw new Error(`Directory not found: ${directory}`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const files = readdirSync(directory).filter(
|
|
194
|
+
(file) =>
|
|
195
|
+
file.endsWith(".json") &&
|
|
196
|
+
!file.endsWith(".jsonl") &&
|
|
197
|
+
!file.endsWith(".migrated.json"),
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
const results = [];
|
|
201
|
+
for (const file of files) {
|
|
202
|
+
const filePath = join(directory, file);
|
|
203
|
+
results.push(migrateLegacySessionFile(filePath, options));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const summary = buildMigrationSummary(results, {
|
|
207
|
+
directory,
|
|
208
|
+
dryRun: Boolean(options.dryRun),
|
|
209
|
+
});
|
|
210
|
+
const sampledValidation = options.dryRun
|
|
211
|
+
? []
|
|
212
|
+
: sampleMigratedSessionsValidation(results, {
|
|
213
|
+
sampleSize: options.sampleSize,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
directory,
|
|
218
|
+
results,
|
|
219
|
+
summary,
|
|
220
|
+
sampledValidation,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function migrateLegacySessionFile(filePath, options = {}) {
|
|
225
|
+
const sourcePath = resolve(filePath);
|
|
226
|
+
const maxAttempts = Math.max(
|
|
227
|
+
1,
|
|
228
|
+
(options.retryFailures ? 2 : 1) + Math.max(0, options.retryCount || 0),
|
|
229
|
+
);
|
|
230
|
+
let lastError = null;
|
|
231
|
+
|
|
232
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
233
|
+
try {
|
|
234
|
+
const result = performLegacySessionMigration(sourcePath, options);
|
|
235
|
+
return {
|
|
236
|
+
...result,
|
|
237
|
+
attempts: attempt,
|
|
238
|
+
retried: attempt > 1,
|
|
239
|
+
};
|
|
240
|
+
} catch (error) {
|
|
241
|
+
lastError = error;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
file: sourcePath,
|
|
247
|
+
sessionId: basename(sourcePath, ".json"),
|
|
248
|
+
migrated: false,
|
|
249
|
+
failed: true,
|
|
250
|
+
dryRun: Boolean(options.dryRun),
|
|
251
|
+
attempts: maxAttempts,
|
|
252
|
+
reason: lastError?.message || "migration failed",
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function validateJsonlSession(sessionId) {
|
|
257
|
+
const filePath = sessionPath(sessionId);
|
|
258
|
+
if (!existsSync(filePath)) {
|
|
259
|
+
return {
|
|
260
|
+
sessionId,
|
|
261
|
+
valid: false,
|
|
262
|
+
reason: "session file not found",
|
|
263
|
+
malformedLines: 0,
|
|
264
|
+
eventCount: 0,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const lines = readFileSync(filePath, "utf-8").split("\n");
|
|
269
|
+
let malformedLines = 0;
|
|
270
|
+
const events = [];
|
|
271
|
+
|
|
272
|
+
for (const line of lines) {
|
|
273
|
+
if (!line.trim()) continue;
|
|
274
|
+
try {
|
|
275
|
+
events.push(JSON.parse(line));
|
|
276
|
+
} catch {
|
|
277
|
+
malformedLines++;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const hasStartEvent = events.some((event) => event.type === "session_start");
|
|
282
|
+
const messageCount = events.filter(
|
|
283
|
+
(event) =>
|
|
284
|
+
event.type === "user_message" || event.type === "assistant_message",
|
|
285
|
+
).length;
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
sessionId,
|
|
289
|
+
valid: malformedLines === 0 && hasStartEvent,
|
|
290
|
+
malformedLines,
|
|
291
|
+
eventCount: events.length,
|
|
292
|
+
messageCount,
|
|
293
|
+
hasStartEvent,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export function validateAllJsonlSessions(options = {}) {
|
|
298
|
+
const files = readdirSync(getSessionsDir())
|
|
299
|
+
.filter((file) => file.endsWith(".jsonl"))
|
|
300
|
+
.slice(0, options.limit || 1000);
|
|
301
|
+
return files.map((file) => validateJsonlSession(basename(file, ".jsonl")));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function sampleMigratedSessionsValidation(results, options = {}) {
|
|
305
|
+
const sampleSize = Math.max(0, parseInt(options.sampleSize || 3, 10));
|
|
306
|
+
const migrated = results.filter((item) => item.migrated && !item.dryRun);
|
|
307
|
+
return migrated.slice(0, sampleSize).map((item) => {
|
|
308
|
+
const validation = validateJsonlSession(item.sessionId);
|
|
309
|
+
return {
|
|
310
|
+
sessionId: item.sessionId,
|
|
311
|
+
file: item.file,
|
|
312
|
+
valid: validation.valid,
|
|
313
|
+
messageCount: validation.messageCount,
|
|
314
|
+
expectedMessageCount: item.messageCount,
|
|
315
|
+
matchesExpectedMessages: validation.messageCount === item.messageCount,
|
|
316
|
+
malformedLines: validation.malformedLines,
|
|
317
|
+
};
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function performLegacySessionMigration(sourcePath, options) {
|
|
322
|
+
const parsed = JSON.parse(readFileSync(sourcePath, "utf-8"));
|
|
323
|
+
const legacy = normalizeLegacySession(parsed, basename(sourcePath, ".json"));
|
|
324
|
+
const sessionId = legacy.id;
|
|
325
|
+
|
|
326
|
+
if (!options.force && sessionExists(sessionId)) {
|
|
327
|
+
return {
|
|
328
|
+
file: sourcePath,
|
|
329
|
+
sessionId,
|
|
330
|
+
skipped: true,
|
|
331
|
+
reason: "jsonl session already exists",
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (!options.dryRun) {
|
|
336
|
+
if (options.force && sessionExists(sessionId)) {
|
|
337
|
+
rmSync(sessionPath(sessionId), { force: true });
|
|
338
|
+
}
|
|
339
|
+
startSession(sessionId, legacy.meta);
|
|
340
|
+
for (const message of legacy.messages) {
|
|
341
|
+
appendLegacyMessage(sessionId, message);
|
|
342
|
+
}
|
|
343
|
+
if (legacy.summary) {
|
|
344
|
+
appendEvent(sessionId, "system", {
|
|
345
|
+
role: "system",
|
|
346
|
+
content: `[Migrated Summary]\n${legacy.summary}`,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const validation = validateJsonlSession(sessionId);
|
|
351
|
+
if (!validation.valid || validation.messageCount !== legacy.messages.length) {
|
|
352
|
+
throw new Error(
|
|
353
|
+
`post-migration validation failed for ${sessionId} (${validation.messageCount}/${legacy.messages.length} messages)`,
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (options.archive !== false) {
|
|
358
|
+
copyFileSync(sourcePath, `${sourcePath}.migrated.json`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return {
|
|
363
|
+
file: sourcePath,
|
|
364
|
+
sessionId,
|
|
365
|
+
migrated: true,
|
|
366
|
+
messageCount: legacy.messages.length,
|
|
367
|
+
archived: options.archive !== false && !options.dryRun,
|
|
368
|
+
dryRun: Boolean(options.dryRun),
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function buildMigrationSummary(results, options = {}) {
|
|
373
|
+
const summary = {
|
|
374
|
+
directory: options.directory || null,
|
|
375
|
+
dryRun: Boolean(options.dryRun),
|
|
376
|
+
scanned: results.length,
|
|
377
|
+
migrated: 0,
|
|
378
|
+
skipped: 0,
|
|
379
|
+
failed: 0,
|
|
380
|
+
retries: 0,
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
for (const result of results) {
|
|
384
|
+
if (result.migrated) summary.migrated += 1;
|
|
385
|
+
if (result.skipped) summary.skipped += 1;
|
|
386
|
+
if (result.failed) summary.failed += 1;
|
|
387
|
+
if (result.retried) summary.retries += 1;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return summary;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function normalizeLegacySession(payload, fallbackId) {
|
|
394
|
+
const messages = Array.isArray(payload)
|
|
395
|
+
? payload
|
|
396
|
+
: Array.isArray(payload?.messages)
|
|
397
|
+
? payload.messages
|
|
398
|
+
: [];
|
|
399
|
+
|
|
400
|
+
return {
|
|
401
|
+
id: payload?.id || fallbackId || `session-${Date.now()}`,
|
|
402
|
+
meta: {
|
|
403
|
+
title: payload?.title || payload?.name || fallbackId || "Migrated Session",
|
|
404
|
+
provider: payload?.provider || "",
|
|
405
|
+
model: payload?.model || "",
|
|
406
|
+
},
|
|
407
|
+
summary: payload?.summary || "",
|
|
408
|
+
messages: messages.map(normalizeLegacyMessage).filter(Boolean),
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function normalizeLegacyMessage(message) {
|
|
413
|
+
if (!message) return null;
|
|
414
|
+
if (typeof message === "string") {
|
|
415
|
+
return { role: "user", content: message };
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const role = message.role || message.sender || message.type || "user";
|
|
419
|
+
const content =
|
|
420
|
+
message.content ??
|
|
421
|
+
message.text ??
|
|
422
|
+
message.message ??
|
|
423
|
+
message.result ??
|
|
424
|
+
"";
|
|
425
|
+
|
|
426
|
+
return {
|
|
427
|
+
role,
|
|
428
|
+
content: typeof content === "string" ? content : JSON.stringify(content),
|
|
429
|
+
tool: message.tool || message.name || null,
|
|
430
|
+
args: message.args || message.arguments || null,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function appendLegacyMessage(sessionId, message) {
|
|
435
|
+
switch (message.role) {
|
|
436
|
+
case "assistant":
|
|
437
|
+
appendAssistantMessage(sessionId, message.content);
|
|
438
|
+
break;
|
|
439
|
+
case "tool":
|
|
440
|
+
appendToolResult(sessionId, message.tool || "legacy-tool", message.content);
|
|
441
|
+
break;
|
|
442
|
+
case "system":
|
|
443
|
+
appendEvent(sessionId, "system", {
|
|
444
|
+
role: "system",
|
|
445
|
+
content: message.content,
|
|
446
|
+
});
|
|
447
|
+
break;
|
|
448
|
+
default:
|
|
449
|
+
appendUserMessage(sessionId, message.content);
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
}
|