@cydm/pie 1.0.24 → 1.0.25
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 +23 -5
- package/dist/builtin/extensions/ask-user/index.js +3 -4
- package/dist/builtin/extensions/ask-user/package.json +16 -0
- package/dist/builtin/extensions/changelog/index.js +65 -65
- package/dist/builtin/extensions/changelog/package.json +11 -0
- package/dist/builtin/extensions/deploy/package.json +11 -0
- package/dist/builtin/extensions/document-attachments/package.json +11 -0
- package/dist/builtin/extensions/files/package.json +14 -0
- package/dist/builtin/extensions/init/index.js +33 -33
- package/dist/builtin/extensions/init/package.json +11 -0
- package/dist/builtin/extensions/kimi-attachments/index.js +1 -1
- package/dist/builtin/extensions/kimi-attachments/package.json +11 -0
- package/dist/builtin/extensions/plan-mode/index.js +5 -34
- package/dist/builtin/extensions/plan-mode/package.json +11 -0
- package/dist/builtin/extensions/subagent/index.js +4 -5
- package/dist/builtin/extensions/subagent/package.json +14 -0
- package/dist/builtin/extensions/todo/index.js +3 -4
- package/dist/builtin/extensions/todo/package.json +11 -0
- package/dist/chunks/{chunk-X44YTB6R.js → chunk-4B42ZU4B.js} +1 -1
- package/dist/chunks/{chunk-HAAYC2N5.js → chunk-64XJIKVK.js} +334 -99
- package/dist/chunks/{chunk-QYYRJ7JW.js → chunk-BEWVRLCP.js} +375 -252
- package/dist/chunks/{chunk-XWNIIJ3X.js → chunk-O4DPFYYP.js} +7511 -6681
- package/dist/chunks/{src-PENSDHBM.js → src-27UD2ZBZ.js} +2 -2
- package/dist/chunks/{test-stream-PRUYMLXQ.js → test-stream-ZNGAUUVH.js} +6 -2
- package/dist/cli.js +2215 -1698
- package/dist/{chunks/typescript-GSKWJIO4.js → code-intel-worker.cjs} +2507 -2031
- package/models.schema.json +38 -13
- package/package.json +10 -6
- package/dist/chunks/chunk-J4P2LP7K.js +0 -3846
- package/dist/chunks/chunk-QMIIW75M.js +0 -95
- package/dist/session-preview-worker.js +0 -159
|
@@ -1,3846 +0,0 @@
|
|
|
1
|
-
import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
|
|
2
|
-
import {
|
|
3
|
-
FileSystemGateway,
|
|
4
|
-
getPlatformConfig,
|
|
5
|
-
streamSimple
|
|
6
|
-
} from "./chunk-HAAYC2N5.js";
|
|
7
|
-
|
|
8
|
-
// ../../packages/agent-framework/src/session/compaction.ts
|
|
9
|
-
var COMPACT_PROMPT = `You are performing a CONTEXT CHECKPOINT COMPACTION. Create a handoff summary for another LLM that will resume the task.
|
|
10
|
-
|
|
11
|
-
Include:
|
|
12
|
-
- Current progress and key decisions made
|
|
13
|
-
- Important context, constraints, or user preferences
|
|
14
|
-
- What remains to be done (clear next steps)
|
|
15
|
-
- Any critical data, examples, or references needed to continue
|
|
16
|
-
|
|
17
|
-
Be concise, structured, and focused on helping the next LLM seamlessly continue the work.`;
|
|
18
|
-
var SUMMARY_PREFIX = `Another language model started to solve this problem and produced a summary of its thinking process. You also have access to the state of the tools that were used by that language model. Use this to build on the work that has already been done and avoid duplicating work. Here is the summary produced by the other language model, use the information in this summary to assist with your own analysis:`;
|
|
19
|
-
var DEFAULT_COMPACTION_CONFIG = {
|
|
20
|
-
maxMessages: 100,
|
|
21
|
-
maxTokens: 8e3,
|
|
22
|
-
keepRecentUserMessages: 3,
|
|
23
|
-
maxRetainedUserTokens: 2e4,
|
|
24
|
-
reserveTokens: 16384,
|
|
25
|
-
keepRecentTokens: 2e4,
|
|
26
|
-
autoCompact: true,
|
|
27
|
-
autoCompactTokenLimit: void 0,
|
|
28
|
-
compactPrompt: COMPACT_PROMPT,
|
|
29
|
-
summaryPrefix: SUMMARY_PREFIX
|
|
30
|
-
};
|
|
31
|
-
var APPROX_BYTES_PER_TOKEN = 4;
|
|
32
|
-
var RESIZED_IMAGE_BYTES_ESTIMATE = 7373;
|
|
33
|
-
function estimateTokens(message) {
|
|
34
|
-
return approximateBytesAsTokens(estimateModelVisibleBytes(message));
|
|
35
|
-
}
|
|
36
|
-
function calculateTotalTokens(messages) {
|
|
37
|
-
return messages.reduce((sum, m) => sum + estimateTokens(m), 0);
|
|
38
|
-
}
|
|
39
|
-
function calculateContextTokens(usage) {
|
|
40
|
-
return usage.totalTokens || usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
|
|
41
|
-
}
|
|
42
|
-
function getAssistantUsage(message) {
|
|
43
|
-
if (message.role !== "assistant") {
|
|
44
|
-
return void 0;
|
|
45
|
-
}
|
|
46
|
-
if (message.stopReason === "aborted" || message.stopReason === "error") {
|
|
47
|
-
return void 0;
|
|
48
|
-
}
|
|
49
|
-
if (calculateContextTokens(message.usage) <= 0) {
|
|
50
|
-
return void 0;
|
|
51
|
-
}
|
|
52
|
-
return message.usage;
|
|
53
|
-
}
|
|
54
|
-
function estimateContextTokens(messages) {
|
|
55
|
-
const localTokens = calculateTotalTokens(messages);
|
|
56
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
57
|
-
const usage = getAssistantUsage(messages[i]);
|
|
58
|
-
if (!usage) {
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
const providerTokens = calculateContextTokens(usage);
|
|
62
|
-
const trailingTokens = calculateTotalTokens(messages.slice(i + 1));
|
|
63
|
-
const usageTokens = providerTokens;
|
|
64
|
-
return {
|
|
65
|
-
tokens: Math.max(providerTokens + trailingTokens, localTokens),
|
|
66
|
-
usageTokens,
|
|
67
|
-
providerTokens,
|
|
68
|
-
localTokens,
|
|
69
|
-
trailingTokens,
|
|
70
|
-
lastUsageIndex: i
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
return {
|
|
74
|
-
tokens: localTokens,
|
|
75
|
-
usageTokens: 0,
|
|
76
|
-
providerTokens: 0,
|
|
77
|
-
localTokens,
|
|
78
|
-
trailingTokens: localTokens,
|
|
79
|
-
lastUsageIndex: null
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
function getAutoCompactTokenLimit(contextWindow, reserveTokens, autoCompactTokenLimit) {
|
|
83
|
-
const explicitLimit = autoCompactTokenLimit !== void 0 && autoCompactTokenLimit > 0 ? Math.floor(autoCompactTokenLimit) : void 0;
|
|
84
|
-
if (!contextWindow || contextWindow <= 0) {
|
|
85
|
-
return explicitLimit;
|
|
86
|
-
}
|
|
87
|
-
const reserveLimit = reserveTokens >= contextWindow ? Math.floor(contextWindow * 0.8) : contextWindow - reserveTokens;
|
|
88
|
-
const windowLimit = Math.floor(contextWindow * 0.9);
|
|
89
|
-
const defaultLimit = Math.max(1, Math.min(reserveLimit, windowLimit));
|
|
90
|
-
return explicitLimit !== void 0 ? Math.min(defaultLimit, explicitLimit) : defaultLimit;
|
|
91
|
-
}
|
|
92
|
-
function isCompactSummaryMessage(message, summaryPrefix = SUMMARY_PREFIX) {
|
|
93
|
-
return message.startsWith(`${summaryPrefix}
|
|
94
|
-
`);
|
|
95
|
-
}
|
|
96
|
-
function formatCompactSummaryText(summaryText, summaryPrefix = SUMMARY_PREFIX) {
|
|
97
|
-
const compactSummary = summaryText.trim() || "(no summary available)";
|
|
98
|
-
return `${summaryPrefix}
|
|
99
|
-
${compactSummary}`;
|
|
100
|
-
}
|
|
101
|
-
function collectRealUserMessages(messages, summaryPrefix = SUMMARY_PREFIX) {
|
|
102
|
-
return messages.filter((message) => message.role === "user").map((message) => getUserMessageText(message)).filter((text) => Boolean(text)).filter((text) => !isCompactSummaryMessage(text, summaryPrefix)).filter((text) => !isSyntheticUserEnvelope(text));
|
|
103
|
-
}
|
|
104
|
-
function findFirstKeptEntryId(entries, options = {}) {
|
|
105
|
-
if (entries.length === 0) {
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
108
|
-
const fullConfig = { ...DEFAULT_COMPACTION_CONFIG, ...options };
|
|
109
|
-
const validCutPoints = collectValidCutPoints(entries);
|
|
110
|
-
if (validCutPoints.length === 0) {
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
let accumulatedTokens = 0;
|
|
114
|
-
let cutIndex = validCutPoints[0];
|
|
115
|
-
const keepRecentTokens = Math.max(1, fullConfig.keepRecentTokens);
|
|
116
|
-
for (let i = entries.length - 1; i >= 0; i--) {
|
|
117
|
-
const message = getMessageFromEntry(entries[i]);
|
|
118
|
-
if (!message) {
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
accumulatedTokens += estimateTokens(message);
|
|
122
|
-
if (accumulatedTokens >= keepRecentTokens) {
|
|
123
|
-
const nextCutPoint = validCutPoints.find((index) => index >= i);
|
|
124
|
-
const previousCutPoint = [...validCutPoints].reverse().find((index) => index <= i);
|
|
125
|
-
cutIndex = nextCutPoint ?? previousCutPoint ?? validCutPoints[0];
|
|
126
|
-
break;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return entries[cutIndex]?.id ?? null;
|
|
130
|
-
}
|
|
131
|
-
function createFileOps() {
|
|
132
|
-
return {
|
|
133
|
-
read: /* @__PURE__ */ new Set(),
|
|
134
|
-
written: /* @__PURE__ */ new Set(),
|
|
135
|
-
edited: /* @__PURE__ */ new Set()
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
function extractFileOpsFromMessage(message, fileOps) {
|
|
139
|
-
if (message.role !== "assistant") {
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
for (const block of message.content) {
|
|
143
|
-
if (block.type !== "toolCall") {
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
const args = block.arguments;
|
|
147
|
-
const path = typeof args?.path === "string" ? args.path : void 0;
|
|
148
|
-
if (!path) {
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
switch (block.name) {
|
|
152
|
-
case "read_file":
|
|
153
|
-
case "read":
|
|
154
|
-
fileOps.read.add(path);
|
|
155
|
-
break;
|
|
156
|
-
case "write_file":
|
|
157
|
-
case "write":
|
|
158
|
-
fileOps.written.add(path);
|
|
159
|
-
break;
|
|
160
|
-
case "edit_file":
|
|
161
|
-
case "edit":
|
|
162
|
-
fileOps.edited.add(path);
|
|
163
|
-
break;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
function computeFileLists(fileOps) {
|
|
168
|
-
const modified = /* @__PURE__ */ new Set([...fileOps.written, ...fileOps.edited]);
|
|
169
|
-
const readFiles = [...fileOps.read].filter((file) => !modified.has(file)).sort();
|
|
170
|
-
return {
|
|
171
|
-
readFiles,
|
|
172
|
-
modifiedFiles: [...modified].sort()
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
function formatFileOperations(readFiles, modifiedFiles) {
|
|
176
|
-
const sections = [];
|
|
177
|
-
if (readFiles.length > 0) {
|
|
178
|
-
sections.push(`<read-files>
|
|
179
|
-
${readFiles.join("\n")}
|
|
180
|
-
</read-files>`);
|
|
181
|
-
}
|
|
182
|
-
if (modifiedFiles.length > 0) {
|
|
183
|
-
sections.push(`<modified-files>
|
|
184
|
-
${modifiedFiles.join("\n")}
|
|
185
|
-
</modified-files>`);
|
|
186
|
-
}
|
|
187
|
-
return sections.length > 0 ? `
|
|
188
|
-
|
|
189
|
-
${sections.join("\n\n")}` : "";
|
|
190
|
-
}
|
|
191
|
-
async function createCompactionSummary(messages, options = {}) {
|
|
192
|
-
const { model, apiKey, systemPrompt, compactPrompt, signal } = options;
|
|
193
|
-
const throwIfAborted = () => {
|
|
194
|
-
if (signal?.aborted) {
|
|
195
|
-
throw new Error("Compaction aborted");
|
|
196
|
-
}
|
|
197
|
-
};
|
|
198
|
-
if (!model) {
|
|
199
|
-
throwIfAborted();
|
|
200
|
-
return appendFileOperationsToSummary(createSimpleSummary(messages), messages);
|
|
201
|
-
}
|
|
202
|
-
try {
|
|
203
|
-
throwIfAborted();
|
|
204
|
-
const response = await streamSimple(
|
|
205
|
-
model,
|
|
206
|
-
{
|
|
207
|
-
systemPrompt,
|
|
208
|
-
messages: [
|
|
209
|
-
...messages.filter(isCompactionCompatibleMessage),
|
|
210
|
-
{
|
|
211
|
-
role: "user",
|
|
212
|
-
content: [{ type: "text", text: compactPrompt ?? COMPACT_PROMPT }],
|
|
213
|
-
timestamp: Date.now()
|
|
214
|
-
}
|
|
215
|
-
]
|
|
216
|
-
},
|
|
217
|
-
{
|
|
218
|
-
apiKey,
|
|
219
|
-
signal
|
|
220
|
-
}
|
|
221
|
-
).result();
|
|
222
|
-
throwIfAborted();
|
|
223
|
-
return appendFileOperationsToSummary(getAssistantText(response) || createSimpleSummary(messages), messages);
|
|
224
|
-
} catch (error) {
|
|
225
|
-
throwIfAborted();
|
|
226
|
-
console.warn("[Compaction] Failed to create Codex-style summary:", error);
|
|
227
|
-
return appendFileOperationsToSummary(createSimpleSummary(messages), messages);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
function getMessageText(message) {
|
|
231
|
-
if (message.role === "user") {
|
|
232
|
-
return getUserMessageText(message);
|
|
233
|
-
}
|
|
234
|
-
if (message.role === "assistant") {
|
|
235
|
-
return message.content.filter((c) => c.type === "text").map((c) => c.text).join(" ");
|
|
236
|
-
}
|
|
237
|
-
if (message.role === "toolResult") {
|
|
238
|
-
return message.content.filter((c) => c.type === "text").map((c) => c.text).join(" ");
|
|
239
|
-
}
|
|
240
|
-
return "";
|
|
241
|
-
}
|
|
242
|
-
function getMessageFromEntry(entry) {
|
|
243
|
-
if (entry.type === "message") {
|
|
244
|
-
return entry.message;
|
|
245
|
-
}
|
|
246
|
-
return void 0;
|
|
247
|
-
}
|
|
248
|
-
function collectValidCutPoints(entries) {
|
|
249
|
-
const cutPoints = [];
|
|
250
|
-
for (let i = 0; i < entries.length; i++) {
|
|
251
|
-
const entry = entries[i];
|
|
252
|
-
if (entry.type !== "message" || !entry.message) {
|
|
253
|
-
continue;
|
|
254
|
-
}
|
|
255
|
-
if (entry.message.role === "toolResult") {
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
cutPoints.push(i);
|
|
259
|
-
}
|
|
260
|
-
return cutPoints;
|
|
261
|
-
}
|
|
262
|
-
function getUserMessageText(message) {
|
|
263
|
-
if (typeof message.content === "string") {
|
|
264
|
-
return message.content;
|
|
265
|
-
}
|
|
266
|
-
return message.content.filter((c) => c.type === "text").map((c) => c.text).join(" ");
|
|
267
|
-
}
|
|
268
|
-
function getAssistantText(message) {
|
|
269
|
-
return message.content.filter((c) => c.type === "text").map((c) => c.text).join("").trim();
|
|
270
|
-
}
|
|
271
|
-
function createSimpleSummary(messages) {
|
|
272
|
-
const userMessages = collectRealUserMessages(messages);
|
|
273
|
-
const lastUserMessage = userMessages.at(-1);
|
|
274
|
-
const assistantCount = messages.filter((m) => m.role === "assistant").length;
|
|
275
|
-
const toolCount = messages.filter((m) => m.role === "toolResult").length;
|
|
276
|
-
return [
|
|
277
|
-
`- Conversation turns: ${messages.length}`,
|
|
278
|
-
`- Assistant responses: ${assistantCount}`,
|
|
279
|
-
`- Tool results: ${toolCount}`,
|
|
280
|
-
lastUserMessage ? `- Latest user request: ${lastUserMessage}` : ""
|
|
281
|
-
].filter(Boolean).join("\n");
|
|
282
|
-
}
|
|
283
|
-
function appendFileOperationsToSummary(summary, messages) {
|
|
284
|
-
const fileOps = createFileOps();
|
|
285
|
-
for (const message of messages) {
|
|
286
|
-
extractFileOpsFromMessage(message, fileOps);
|
|
287
|
-
}
|
|
288
|
-
const { readFiles, modifiedFiles } = computeFileLists(fileOps);
|
|
289
|
-
return `${summary}${formatFileOperations(readFiles, modifiedFiles)}`;
|
|
290
|
-
}
|
|
291
|
-
function estimateModelVisibleBytes(message) {
|
|
292
|
-
const visibleMessage = normalizeModelVisibleMessage(message);
|
|
293
|
-
const wrapperBytes = utf8ByteLength(safeStringify(visibleMessage));
|
|
294
|
-
return wrapperBytes + estimateMessageMediaBytes(message);
|
|
295
|
-
}
|
|
296
|
-
function normalizeModelVisibleMessage(message) {
|
|
297
|
-
if (message.role === "user") {
|
|
298
|
-
return {
|
|
299
|
-
role: "user",
|
|
300
|
-
content: typeof message.content === "string" ? message.content : message.content.map(normalizeUserContentBlock)
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
if (message.role === "assistant") {
|
|
304
|
-
return {
|
|
305
|
-
role: "assistant",
|
|
306
|
-
content: message.content.map((block) => {
|
|
307
|
-
if (block.type === "text") {
|
|
308
|
-
return {
|
|
309
|
-
type: "text",
|
|
310
|
-
text: block.text,
|
|
311
|
-
...block.textSignature ? { textSignature: block.textSignature } : {}
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
if (block.type === "thinking") {
|
|
315
|
-
return {
|
|
316
|
-
type: "thinking",
|
|
317
|
-
thinking: block.thinking,
|
|
318
|
-
...block.thinkingSignature ? { thinkingSignature: block.thinkingSignature } : {}
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
if (block.type === "toolCall") {
|
|
322
|
-
return {
|
|
323
|
-
type: "toolCall",
|
|
324
|
-
id: block.id,
|
|
325
|
-
name: block.name,
|
|
326
|
-
arguments: block.arguments ?? {},
|
|
327
|
-
...block.thoughtSignature ? { thoughtSignature: block.thoughtSignature } : {}
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
return block;
|
|
331
|
-
})
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
if (message.role === "toolResult") {
|
|
335
|
-
return {
|
|
336
|
-
role: "toolResult",
|
|
337
|
-
toolCallId: message.toolCallId,
|
|
338
|
-
toolName: message.toolName,
|
|
339
|
-
isError: message.isError,
|
|
340
|
-
content: message.content.map(normalizeUserContentBlock)
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
return {
|
|
344
|
-
role: String(message.role ?? "unknown"),
|
|
345
|
-
content: getMessageText(message)
|
|
346
|
-
};
|
|
347
|
-
}
|
|
348
|
-
function normalizeUserContentBlock(block) {
|
|
349
|
-
if (block.type === "text") {
|
|
350
|
-
return { type: "text", text: block.text };
|
|
351
|
-
}
|
|
352
|
-
if (block.type === "image") {
|
|
353
|
-
return { type: "image", mimeType: block.mimeType };
|
|
354
|
-
}
|
|
355
|
-
if (block.type === "audio") {
|
|
356
|
-
return { type: "audio", mimeType: block.mimeType };
|
|
357
|
-
}
|
|
358
|
-
if (block.type === "video") {
|
|
359
|
-
return { type: "video", mimeType: block.mimeType };
|
|
360
|
-
}
|
|
361
|
-
if (block.type === "fileRef") {
|
|
362
|
-
return {
|
|
363
|
-
type: "fileRef",
|
|
364
|
-
url: block.url,
|
|
365
|
-
mimeType: block.mimeType,
|
|
366
|
-
modality: block.modality
|
|
367
|
-
};
|
|
368
|
-
}
|
|
369
|
-
return block;
|
|
370
|
-
}
|
|
371
|
-
function estimateMessageMediaBytes(message) {
|
|
372
|
-
if (message.role === "user" && Array.isArray(message.content)) {
|
|
373
|
-
return message.content.reduce((sum, block) => sum + estimateUserContentBlockMediaBytes(block), 0);
|
|
374
|
-
}
|
|
375
|
-
if (message.role === "toolResult") {
|
|
376
|
-
return message.content.reduce((sum, block) => sum + estimateUserContentBlockMediaBytes(block), 0);
|
|
377
|
-
}
|
|
378
|
-
return 0;
|
|
379
|
-
}
|
|
380
|
-
function estimateUserContentBlockMediaBytes(block) {
|
|
381
|
-
if (block.type === "image") {
|
|
382
|
-
return estimateImageContentBytes(block);
|
|
383
|
-
}
|
|
384
|
-
if (block.type === "audio") {
|
|
385
|
-
return estimateInlineDataBytes(block);
|
|
386
|
-
}
|
|
387
|
-
if (block.type === "video") {
|
|
388
|
-
return estimateInlineDataBytes(block);
|
|
389
|
-
}
|
|
390
|
-
return 0;
|
|
391
|
-
}
|
|
392
|
-
function estimateImageContentBytes(block) {
|
|
393
|
-
return Math.max(RESIZED_IMAGE_BYTES_ESTIMATE, estimateInlineDataBytes(block));
|
|
394
|
-
}
|
|
395
|
-
function estimateInlineDataBytes(block) {
|
|
396
|
-
return utf8ByteLength(block.data || "");
|
|
397
|
-
}
|
|
398
|
-
function approximateBytesAsTokens(bytes) {
|
|
399
|
-
return Math.ceil(bytes / APPROX_BYTES_PER_TOKEN);
|
|
400
|
-
}
|
|
401
|
-
function safeStringify(value) {
|
|
402
|
-
try {
|
|
403
|
-
return JSON.stringify(value) ?? "";
|
|
404
|
-
} catch {
|
|
405
|
-
return String(value ?? "");
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
var utf8Encoder = typeof globalThis.TextEncoder === "function" ? new globalThis.TextEncoder() : void 0;
|
|
409
|
-
function utf8ByteLength(text) {
|
|
410
|
-
if (utf8Encoder) {
|
|
411
|
-
return utf8Encoder.encode(text).length;
|
|
412
|
-
}
|
|
413
|
-
let bytes = 0;
|
|
414
|
-
for (const char of text) {
|
|
415
|
-
const codePoint = char.codePointAt(0) ?? 0;
|
|
416
|
-
if (codePoint <= 127) bytes += 1;
|
|
417
|
-
else if (codePoint <= 2047) bytes += 2;
|
|
418
|
-
else if (codePoint <= 65535) bytes += 3;
|
|
419
|
-
else bytes += 4;
|
|
420
|
-
}
|
|
421
|
-
return bytes;
|
|
422
|
-
}
|
|
423
|
-
function isSyntheticUserEnvelope(text) {
|
|
424
|
-
const trimmed = text.trimStart();
|
|
425
|
-
return trimmed.startsWith("<environment_context>") || trimmed.startsWith("<turn_aborted>") || trimmed.startsWith("# AGENTS.md instructions") || trimmed.startsWith("## Project Context\nProject-specific instructions from AGENTS.md");
|
|
426
|
-
}
|
|
427
|
-
function isCompactionCompatibleMessage(message) {
|
|
428
|
-
if (message.role === "user") return true;
|
|
429
|
-
if (message.role === "assistant") return true;
|
|
430
|
-
if (message.role === "toolResult") return true;
|
|
431
|
-
return false;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
// ../../packages/agent-framework/src/session/types.ts
|
|
435
|
-
function getGeneratedSessionName(createdAt) {
|
|
436
|
-
return `Session ${new Date(createdAt).toLocaleString()}`;
|
|
437
|
-
}
|
|
438
|
-
function generateEntryId() {
|
|
439
|
-
const timestamp = Date.now().toString(36);
|
|
440
|
-
const random = Math.random().toString(36).substring(2, 8);
|
|
441
|
-
return `entry-${timestamp}-${random}`;
|
|
442
|
-
}
|
|
443
|
-
function buildTree(entries, activeEntryId) {
|
|
444
|
-
const byId = /* @__PURE__ */ new Map();
|
|
445
|
-
const childrenMap = /* @__PURE__ */ new Map();
|
|
446
|
-
for (const entry of entries) {
|
|
447
|
-
byId.set(entry.id, entry);
|
|
448
|
-
if (entry.parentId) {
|
|
449
|
-
const siblings = childrenMap.get(entry.parentId) ?? [];
|
|
450
|
-
siblings.push(entry.id);
|
|
451
|
-
childrenMap.set(entry.parentId, siblings);
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
const rootIds = entries.filter((e) => e.parentId === null).map((e) => e.id);
|
|
455
|
-
const activePath = /* @__PURE__ */ new Set();
|
|
456
|
-
if (activeEntryId) {
|
|
457
|
-
let current = activeEntryId;
|
|
458
|
-
while (current) {
|
|
459
|
-
activePath.add(current);
|
|
460
|
-
const entry = byId.get(current);
|
|
461
|
-
current = entry?.parentId ?? null;
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
function buildNode(entryId, depth) {
|
|
465
|
-
const entry = byId.get(entryId);
|
|
466
|
-
const childIds = childrenMap.get(entryId) ?? [];
|
|
467
|
-
return {
|
|
468
|
-
id: entryId,
|
|
469
|
-
parentId: entry.parentId,
|
|
470
|
-
entry,
|
|
471
|
-
children: childIds.map((id) => buildNode(id, depth + 1)),
|
|
472
|
-
depth,
|
|
473
|
-
isActive: activePath.has(entryId)
|
|
474
|
-
};
|
|
475
|
-
}
|
|
476
|
-
return rootIds.map((id) => buildNode(id, 0));
|
|
477
|
-
}
|
|
478
|
-
function flattenTreeToMessages(entries, activeEntryId) {
|
|
479
|
-
const byId = new Map(entries.map((e) => [e.id, e]));
|
|
480
|
-
const messages = [];
|
|
481
|
-
const path = [];
|
|
482
|
-
let current = activeEntryId;
|
|
483
|
-
while (current) {
|
|
484
|
-
path.unshift(current);
|
|
485
|
-
const entry = byId.get(current);
|
|
486
|
-
current = entry?.parentId ?? null;
|
|
487
|
-
}
|
|
488
|
-
const pathEntries = path.map((entryId) => byId.get(entryId)).filter((entry) => Boolean(entry));
|
|
489
|
-
const latestCompactionIndex = findLatestCompactionIndex(pathEntries);
|
|
490
|
-
if (latestCompactionIndex === -1) {
|
|
491
|
-
for (const entry of pathEntries) {
|
|
492
|
-
if (entry.type === "message" && entry.message) {
|
|
493
|
-
messages.push(entry.message);
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
return messages;
|
|
497
|
-
}
|
|
498
|
-
const compactionEntry = pathEntries[latestCompactionIndex];
|
|
499
|
-
const compactionData = compactionEntry.compactionData;
|
|
500
|
-
if (!compactionData) {
|
|
501
|
-
for (const entry of pathEntries) {
|
|
502
|
-
if (entry.type === "message" && entry.message) {
|
|
503
|
-
messages.push(entry.message);
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
return messages;
|
|
507
|
-
}
|
|
508
|
-
messages.push(createCompactionSummaryMessage(compactionData.summary, compactionEntry.timestamp));
|
|
509
|
-
let foundFirstKept = false;
|
|
510
|
-
for (let i = 0; i < latestCompactionIndex; i++) {
|
|
511
|
-
const entry = pathEntries[i];
|
|
512
|
-
if (entry.id === compactionData.firstKeptEntryId) {
|
|
513
|
-
foundFirstKept = true;
|
|
514
|
-
}
|
|
515
|
-
if (foundFirstKept && entry.type === "message" && entry.message) {
|
|
516
|
-
messages.push(entry.message);
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
for (let i = latestCompactionIndex + 1; i < pathEntries.length; i++) {
|
|
520
|
-
const entry = pathEntries[i];
|
|
521
|
-
if (entry.type === "message" && entry.message) {
|
|
522
|
-
messages.push(entry.message);
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
return messages;
|
|
526
|
-
}
|
|
527
|
-
function findLatestCompactionIndex(entries) {
|
|
528
|
-
for (let i = entries.length - 1; i >= 0; i--) {
|
|
529
|
-
if (entries[i].type === "compaction" && entries[i].compactionData) {
|
|
530
|
-
return i;
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
return -1;
|
|
534
|
-
}
|
|
535
|
-
function createCompactionSummaryMessage(summary, timestamp) {
|
|
536
|
-
return {
|
|
537
|
-
role: "user",
|
|
538
|
-
content: [{ type: "text", text: formatCompactSummaryText(summary) }],
|
|
539
|
-
timestamp
|
|
540
|
-
};
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
// ../../packages/agent-framework/src/session/durable-file.ts
|
|
544
|
-
var DEFAULT_FILE_RETRY_DELAYS_MS = [0, 25, 75, 150];
|
|
545
|
-
function durableFilePaths(filePath) {
|
|
546
|
-
return {
|
|
547
|
-
main: filePath,
|
|
548
|
-
backup: `${filePath}.bak`,
|
|
549
|
-
previous: `${filePath}.prev`
|
|
550
|
-
};
|
|
551
|
-
}
|
|
552
|
-
function isTransientFileError(error) {
|
|
553
|
-
const code = typeof error === "object" && error && "code" in error ? String(error.code) : "";
|
|
554
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
555
|
-
return /^(EPERM|EACCES|EBUSY|ENOTEMPTY)$/.test(code) || /\b(EPERM|EACCES|EBUSY|locked|busy)\b/i.test(message);
|
|
556
|
-
}
|
|
557
|
-
function sleep(ms) {
|
|
558
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
559
|
-
}
|
|
560
|
-
async function retryFileOperation(operation, retryDelaysMs) {
|
|
561
|
-
let lastError;
|
|
562
|
-
for (const [index, delayMs] of retryDelaysMs.entries()) {
|
|
563
|
-
if (delayMs > 0) {
|
|
564
|
-
await sleep(delayMs);
|
|
565
|
-
}
|
|
566
|
-
try {
|
|
567
|
-
operation();
|
|
568
|
-
return;
|
|
569
|
-
} catch (error) {
|
|
570
|
-
lastError = error;
|
|
571
|
-
if (!isTransientFileError(error) || index === retryDelaysMs.length - 1) {
|
|
572
|
-
throw error;
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
throw lastError;
|
|
577
|
-
}
|
|
578
|
-
function retryDelays(options) {
|
|
579
|
-
return options.retryDelaysMs ?? DEFAULT_FILE_RETRY_DELAYS_MS;
|
|
580
|
-
}
|
|
581
|
-
function tempPathFor(filePath, operation) {
|
|
582
|
-
return `${filePath}.${operation}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
583
|
-
}
|
|
584
|
-
async function replaceFile(gateway, filePath, content, retryDelaysMs) {
|
|
585
|
-
const tempPath = tempPathFor(filePath, "durable");
|
|
586
|
-
gateway.writeFile(tempPath, content, "utf-8");
|
|
587
|
-
try {
|
|
588
|
-
try {
|
|
589
|
-
await retryFileOperation(() => gateway.renameFile(tempPath, filePath), retryDelaysMs);
|
|
590
|
-
} catch {
|
|
591
|
-
await retryFileOperation(() => gateway.copyFile(tempPath, filePath), retryDelaysMs);
|
|
592
|
-
try {
|
|
593
|
-
gateway.deleteFile(tempPath);
|
|
594
|
-
} catch {
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
} catch (error) {
|
|
598
|
-
try {
|
|
599
|
-
gateway.deleteFile(tempPath);
|
|
600
|
-
} catch {
|
|
601
|
-
}
|
|
602
|
-
throw error;
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
async function copyExistingFile(gateway, sourcePath, destinationPath, retryDelaysMs) {
|
|
606
|
-
if (!gateway.exists(sourcePath)) {
|
|
607
|
-
return false;
|
|
608
|
-
}
|
|
609
|
-
await retryFileOperation(() => gateway.copyFile(sourcePath, destinationPath), retryDelaysMs);
|
|
610
|
-
return true;
|
|
611
|
-
}
|
|
612
|
-
function readCandidate(options, role, path) {
|
|
613
|
-
if (!options.gateway.exists(path)) {
|
|
614
|
-
return null;
|
|
615
|
-
}
|
|
616
|
-
const rawContent = options.gateway.readFile(path, "utf-8");
|
|
617
|
-
const parsed = options.parse(rawContent, path);
|
|
618
|
-
const updatedAt = Number.isFinite(parsed.updatedAt) ? parsed.updatedAt : 0;
|
|
619
|
-
return {
|
|
620
|
-
...parsed,
|
|
621
|
-
updatedAt,
|
|
622
|
-
role,
|
|
623
|
-
path,
|
|
624
|
-
rawContent
|
|
625
|
-
};
|
|
626
|
-
}
|
|
627
|
-
function candidatePriority(role) {
|
|
628
|
-
if (role === "main") return 3;
|
|
629
|
-
if (role === "backup") return 2;
|
|
630
|
-
return 1;
|
|
631
|
-
}
|
|
632
|
-
function chooseBestDurableFileCandidate(candidates) {
|
|
633
|
-
const main = candidates.find((candidate) => candidate.role === "main");
|
|
634
|
-
if (main) {
|
|
635
|
-
const backup = candidates.find((candidate) => candidate.role === "backup");
|
|
636
|
-
const previous = candidates.find((candidate) => candidate.role === "previous");
|
|
637
|
-
if (backup && previous && backup.content !== main.content && previous.content === main.content && backup.updatedAt >= main.updatedAt) {
|
|
638
|
-
return backup;
|
|
639
|
-
}
|
|
640
|
-
return main;
|
|
641
|
-
}
|
|
642
|
-
return [...candidates].sort((a, b) => {
|
|
643
|
-
const updatedAtDiff = b.updatedAt - a.updatedAt;
|
|
644
|
-
if (updatedAtDiff !== 0) return updatedAtDiff;
|
|
645
|
-
return candidatePriority(b.role) - candidatePriority(a.role);
|
|
646
|
-
})[0];
|
|
647
|
-
}
|
|
648
|
-
async function writeDurableFile(options, content) {
|
|
649
|
-
const parsed = options.parse(content, options.filePath);
|
|
650
|
-
const durableContent = parsed.content;
|
|
651
|
-
const paths = durableFilePaths(options.filePath);
|
|
652
|
-
const delays = retryDelays(options);
|
|
653
|
-
if (options.gateway.exists(paths.backup)) {
|
|
654
|
-
await copyExistingFile(options.gateway, paths.backup, paths.previous, delays);
|
|
655
|
-
}
|
|
656
|
-
await replaceFile(options.gateway, paths.backup, durableContent, delays);
|
|
657
|
-
await replaceFile(options.gateway, paths.main, durableContent, delays);
|
|
658
|
-
}
|
|
659
|
-
async function loadDurableFile(options) {
|
|
660
|
-
const paths = durableFilePaths(options.filePath);
|
|
661
|
-
const roles = [
|
|
662
|
-
["main", paths.main],
|
|
663
|
-
["backup", paths.backup],
|
|
664
|
-
["previous", paths.previous]
|
|
665
|
-
];
|
|
666
|
-
const candidates = [];
|
|
667
|
-
for (const [role, path] of roles) {
|
|
668
|
-
try {
|
|
669
|
-
const candidate = readCandidate(options, role, path);
|
|
670
|
-
if (candidate) {
|
|
671
|
-
candidates.push(candidate);
|
|
672
|
-
}
|
|
673
|
-
} catch {
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
if (candidates.length === 0) {
|
|
677
|
-
return null;
|
|
678
|
-
}
|
|
679
|
-
const best = chooseBestDurableFileCandidate(candidates);
|
|
680
|
-
if (!best) {
|
|
681
|
-
return null;
|
|
682
|
-
}
|
|
683
|
-
const main = candidates.find((candidate) => candidate.role === "main");
|
|
684
|
-
const backup = candidates.find((candidate) => candidate.role === "backup");
|
|
685
|
-
const needsMainRestore = !main || main.rawContent !== best.content;
|
|
686
|
-
const needsBackupRefresh = !backup || backup.rawContent !== best.content;
|
|
687
|
-
let restoredMain = false;
|
|
688
|
-
let refreshedBackup = false;
|
|
689
|
-
let repairError;
|
|
690
|
-
const delays = retryDelays(options);
|
|
691
|
-
try {
|
|
692
|
-
if (needsBackupRefresh) {
|
|
693
|
-
if (backup && backup.rawContent !== best.content) {
|
|
694
|
-
await replaceFile(options.gateway, paths.previous, backup.content, delays);
|
|
695
|
-
}
|
|
696
|
-
await replaceFile(options.gateway, paths.backup, best.content, delays);
|
|
697
|
-
refreshedBackup = true;
|
|
698
|
-
}
|
|
699
|
-
if (needsMainRestore) {
|
|
700
|
-
await replaceFile(options.gateway, paths.main, best.content, delays);
|
|
701
|
-
restoredMain = true;
|
|
702
|
-
}
|
|
703
|
-
} catch (error) {
|
|
704
|
-
repairError = error;
|
|
705
|
-
}
|
|
706
|
-
return {
|
|
707
|
-
data: best.data,
|
|
708
|
-
content: best.content,
|
|
709
|
-
source: best.role,
|
|
710
|
-
restoredMain,
|
|
711
|
-
refreshedBackup,
|
|
712
|
-
repairError
|
|
713
|
-
};
|
|
714
|
-
}
|
|
715
|
-
function durableFileExists(gateway, filePath) {
|
|
716
|
-
const paths = durableFilePaths(filePath);
|
|
717
|
-
return gateway.exists(paths.main) || gateway.exists(paths.backup) || gateway.exists(paths.previous);
|
|
718
|
-
}
|
|
719
|
-
function deleteDurableFile(gateway, filePath) {
|
|
720
|
-
const paths = durableFilePaths(filePath);
|
|
721
|
-
let deleted = false;
|
|
722
|
-
for (const path of [paths.main, paths.backup, paths.previous]) {
|
|
723
|
-
if (gateway.exists(path)) {
|
|
724
|
-
gateway.deleteFile(path);
|
|
725
|
-
deleted = true;
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
return deleted;
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
// ../../packages/agent-framework/src/session/store.ts
|
|
732
|
-
var SESSION_VERSION = 2;
|
|
733
|
-
var METADATA_INDEX_VERSION = 3;
|
|
734
|
-
var METADATA_FAST_READ_BYTES = 64 * 1024;
|
|
735
|
-
function isSessionEntry(value) {
|
|
736
|
-
return !!value && typeof value === "object" && "id" in value && "type" in value;
|
|
737
|
-
}
|
|
738
|
-
function normalizeEntriesInput(entriesOrMessages) {
|
|
739
|
-
if (entriesOrMessages.every(isSessionEntry)) {
|
|
740
|
-
return entriesOrMessages;
|
|
741
|
-
}
|
|
742
|
-
const entries = [];
|
|
743
|
-
let parentId = null;
|
|
744
|
-
for (const message of entriesOrMessages) {
|
|
745
|
-
const entry = {
|
|
746
|
-
id: generateEntryId(),
|
|
747
|
-
parentId,
|
|
748
|
-
type: "message",
|
|
749
|
-
timestamp: Date.now(),
|
|
750
|
-
message
|
|
751
|
-
};
|
|
752
|
-
entries.push(entry);
|
|
753
|
-
parentId = entry.id;
|
|
754
|
-
}
|
|
755
|
-
return entries;
|
|
756
|
-
}
|
|
757
|
-
function extractJsonObjectProperty(content, propertyName) {
|
|
758
|
-
const propertyIndex = content.indexOf(`"${propertyName}"`);
|
|
759
|
-
if (propertyIndex < 0) return null;
|
|
760
|
-
const colonIndex = content.indexOf(":", propertyIndex + propertyName.length + 2);
|
|
761
|
-
if (colonIndex < 0) return null;
|
|
762
|
-
let index = colonIndex + 1;
|
|
763
|
-
while (index < content.length && /\s/.test(content[index])) index += 1;
|
|
764
|
-
if (content[index] !== "{") return null;
|
|
765
|
-
let depth = 0;
|
|
766
|
-
let inString = false;
|
|
767
|
-
let escaping = false;
|
|
768
|
-
for (let cursor = index; cursor < content.length; cursor += 1) {
|
|
769
|
-
const char = content[cursor];
|
|
770
|
-
if (inString) {
|
|
771
|
-
if (escaping) {
|
|
772
|
-
escaping = false;
|
|
773
|
-
} else if (char === "\\") {
|
|
774
|
-
escaping = true;
|
|
775
|
-
} else if (char === '"') {
|
|
776
|
-
inString = false;
|
|
777
|
-
}
|
|
778
|
-
continue;
|
|
779
|
-
}
|
|
780
|
-
if (char === '"') {
|
|
781
|
-
inString = true;
|
|
782
|
-
continue;
|
|
783
|
-
}
|
|
784
|
-
if (char === "{") {
|
|
785
|
-
depth += 1;
|
|
786
|
-
} else if (char === "}") {
|
|
787
|
-
depth -= 1;
|
|
788
|
-
if (depth === 0) {
|
|
789
|
-
return content.slice(index, cursor + 1);
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
return null;
|
|
794
|
-
}
|
|
795
|
-
function parseMetadataOnly(content) {
|
|
796
|
-
const metadataText = extractJsonObjectProperty(content, "metadata");
|
|
797
|
-
if (!metadataText) return null;
|
|
798
|
-
const parsed = JSON.parse(metadataText);
|
|
799
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
800
|
-
const metadata = parsed;
|
|
801
|
-
if (typeof metadata.id !== "string" || typeof metadata.name !== "string" || typeof metadata.createdAt !== "number" || typeof metadata.updatedAt !== "number" || typeof metadata.entryCount !== "number" || typeof metadata.messageCount !== "number") {
|
|
802
|
-
return null;
|
|
803
|
-
}
|
|
804
|
-
return metadata;
|
|
805
|
-
}
|
|
806
|
-
function durableRolePriority(role) {
|
|
807
|
-
if (role === "main") return 3;
|
|
808
|
-
if (role === "backup") return 2;
|
|
809
|
-
return 1;
|
|
810
|
-
}
|
|
811
|
-
var LEGACY_VERSION = 1;
|
|
812
|
-
function getDefaultSessionsDir() {
|
|
813
|
-
return getPlatformConfig().sessionsPath;
|
|
814
|
-
}
|
|
815
|
-
function migrateV1ToV2(data) {
|
|
816
|
-
const messages = data.messages ?? [];
|
|
817
|
-
const entries = [];
|
|
818
|
-
let parentId = null;
|
|
819
|
-
for (const message of messages) {
|
|
820
|
-
const entry = {
|
|
821
|
-
id: generateEntryId(),
|
|
822
|
-
parentId,
|
|
823
|
-
type: "message",
|
|
824
|
-
timestamp: Date.now(),
|
|
825
|
-
message
|
|
826
|
-
};
|
|
827
|
-
entries.push(entry);
|
|
828
|
-
parentId = entry.id;
|
|
829
|
-
}
|
|
830
|
-
const activeEntryId = entries.length > 0 ? entries[entries.length - 1].id : null;
|
|
831
|
-
const rootEntryId = entries.length > 0 ? entries[0].id : null;
|
|
832
|
-
return {
|
|
833
|
-
version: SESSION_VERSION,
|
|
834
|
-
metadata: {
|
|
835
|
-
...data.metadata,
|
|
836
|
-
entryCount: entries.length,
|
|
837
|
-
messageCount: messages.length,
|
|
838
|
-
activeEntryId,
|
|
839
|
-
rootEntryId
|
|
840
|
-
},
|
|
841
|
-
entries,
|
|
842
|
-
messages
|
|
843
|
-
// Keep for backward compatibility
|
|
844
|
-
};
|
|
845
|
-
}
|
|
846
|
-
function decodePersistedSessionContent(content) {
|
|
847
|
-
if (!content || content.trim().length === 0) {
|
|
848
|
-
throw new Error("empty session file");
|
|
849
|
-
}
|
|
850
|
-
let data = JSON.parse(content);
|
|
851
|
-
let shouldPersist = false;
|
|
852
|
-
const sourceVersion = data.version;
|
|
853
|
-
let versionMismatch;
|
|
854
|
-
if (sourceVersion === LEGACY_VERSION || !sourceVersion) {
|
|
855
|
-
if (data.metadata === null || data.metadata === void 0) {
|
|
856
|
-
throw new Error("legacy session metadata is missing");
|
|
857
|
-
}
|
|
858
|
-
data = migrateV1ToV2(data);
|
|
859
|
-
shouldPersist = true;
|
|
860
|
-
} else {
|
|
861
|
-
if (sourceVersion !== SESSION_VERSION) {
|
|
862
|
-
versionMismatch = { actual: sourceVersion, expected: SESSION_VERSION };
|
|
863
|
-
}
|
|
864
|
-
if (!data.entries) {
|
|
865
|
-
data.entries = [];
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
return {
|
|
869
|
-
data,
|
|
870
|
-
content: shouldPersist ? JSON.stringify(data, null, 2) : content,
|
|
871
|
-
updatedAt: Number.isFinite(data.metadata?.updatedAt) ? data.metadata.updatedAt : 0,
|
|
872
|
-
...shouldPersist ? { migratedFromVersion: sourceVersion ?? 0 } : {},
|
|
873
|
-
...versionMismatch ? { versionMismatch } : {}
|
|
874
|
-
};
|
|
875
|
-
}
|
|
876
|
-
var FileSessionStore = class {
|
|
877
|
-
sessionsDir;
|
|
878
|
-
gateway;
|
|
879
|
-
constructor(sessionsDir) {
|
|
880
|
-
this.sessionsDir = sessionsDir || getDefaultSessionsDir();
|
|
881
|
-
this.gateway = new FileSystemGateway({
|
|
882
|
-
allowedRoots: [this.sessionsDir, getPlatformConfig().dataPath]
|
|
883
|
-
});
|
|
884
|
-
this.ensureDirectory();
|
|
885
|
-
}
|
|
886
|
-
/**
|
|
887
|
-
* Ensure the sessions directory exists
|
|
888
|
-
*/
|
|
889
|
-
ensureDirectory() {
|
|
890
|
-
try {
|
|
891
|
-
if (!this.gateway.exists(this.sessionsDir)) {
|
|
892
|
-
this.gateway.mkdir(this.sessionsDir, { recursive: true });
|
|
893
|
-
}
|
|
894
|
-
} catch (e) {
|
|
895
|
-
console.warn(`[FileSessionStore] Could not create sessions directory: ${e}`);
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
/**
|
|
899
|
-
* Get the full path for a session file
|
|
900
|
-
*/
|
|
901
|
-
getSessionPath(sessionId) {
|
|
902
|
-
const safeId = sessionId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
903
|
-
return this.gateway.join(this.sessionsDir, `${safeId}.json`);
|
|
904
|
-
}
|
|
905
|
-
getMetadataIndexPath() {
|
|
906
|
-
return this.gateway.join(this.sessionsDir, ".metadata-index.json");
|
|
907
|
-
}
|
|
908
|
-
parseSessionContent(content) {
|
|
909
|
-
const decoded = decodePersistedSessionContent(content);
|
|
910
|
-
if (decoded.migratedFromVersion !== void 0) {
|
|
911
|
-
console.log(`[SessionStore] Migrating session ${decoded.data.metadata.id} from v1 to v2`);
|
|
912
|
-
}
|
|
913
|
-
if (decoded.versionMismatch) {
|
|
914
|
-
console.warn(
|
|
915
|
-
`[SessionStore] Session version mismatch: ${decoded.versionMismatch.actual} vs ${decoded.versionMismatch.expected}`
|
|
916
|
-
);
|
|
917
|
-
}
|
|
918
|
-
return decoded;
|
|
919
|
-
}
|
|
920
|
-
/**
|
|
921
|
-
* Save a session to disk (v2 format)
|
|
922
|
-
*/
|
|
923
|
-
async save(sessionId, entries, metadata) {
|
|
924
|
-
const normalizedEntries = normalizeEntriesInput(entries);
|
|
925
|
-
const now = Date.now();
|
|
926
|
-
const existing = await this.load(sessionId);
|
|
927
|
-
const requestedUpdatedAt = metadata?.updatedAt ?? now;
|
|
928
|
-
const updatedAt = Math.max(requestedUpdatedAt, existing?.metadata.updatedAt ?? 0);
|
|
929
|
-
const nameOrigin = metadata?.nameOrigin ?? (metadata?.name !== void 0 && metadata.name !== existing?.metadata.name ? "explicit" : existing ? existing.metadata.nameOrigin : "generated");
|
|
930
|
-
const messageCount = normalizedEntries.filter((e) => e.type === "message" && e.message).length;
|
|
931
|
-
const rootEntry = normalizedEntries.find((e) => e.parentId === null);
|
|
932
|
-
const activeEntryId = metadata?.activeEntryId ?? existing?.metadata.activeEntryId ?? normalizedEntries[normalizedEntries.length - 1]?.id ?? null;
|
|
933
|
-
const sessionData = {
|
|
934
|
-
version: SESSION_VERSION,
|
|
935
|
-
metadata: {
|
|
936
|
-
id: sessionId,
|
|
937
|
-
name: metadata?.name ?? existing?.metadata.name ?? `Session ${sessionId.slice(0, 8)}`,
|
|
938
|
-
createdAt: existing?.metadata.createdAt ?? now,
|
|
939
|
-
entryCount: normalizedEntries.length,
|
|
940
|
-
messageCount,
|
|
941
|
-
activeEntryId: activeEntryId ?? rootEntry?.id ?? null,
|
|
942
|
-
rootEntryId: rootEntry?.id ?? null,
|
|
943
|
-
tags: metadata?.tags ?? existing?.metadata.tags,
|
|
944
|
-
...metadata,
|
|
945
|
-
nameOrigin,
|
|
946
|
-
updatedAt
|
|
947
|
-
},
|
|
948
|
-
entries: normalizedEntries,
|
|
949
|
-
messages: flattenTreeToMessages(normalizedEntries, activeEntryId ?? null)
|
|
950
|
-
};
|
|
951
|
-
const json = JSON.stringify(sessionData, null, 2);
|
|
952
|
-
const filePath = this.getSessionPath(sessionId);
|
|
953
|
-
await writeDurableFile({
|
|
954
|
-
gateway: this.gateway,
|
|
955
|
-
filePath,
|
|
956
|
-
parse: (content) => this.parseSessionContent(content)
|
|
957
|
-
}, json);
|
|
958
|
-
await this.upsertMetadataIndexBestEffort(sessionId);
|
|
959
|
-
}
|
|
960
|
-
/**
|
|
961
|
-
* Legacy save method for backward compatibility
|
|
962
|
-
* @deprecated Use save() with entries instead
|
|
963
|
-
*/
|
|
964
|
-
async saveLegacy(sessionId, messages, metadata) {
|
|
965
|
-
console.warn("[FileSessionStore] Using deprecated saveLegacy() - consider migrating to v2");
|
|
966
|
-
const entries = [];
|
|
967
|
-
let parentId = null;
|
|
968
|
-
for (const message of messages) {
|
|
969
|
-
const entry = {
|
|
970
|
-
id: generateEntryId(),
|
|
971
|
-
parentId,
|
|
972
|
-
type: "message",
|
|
973
|
-
timestamp: Date.now(),
|
|
974
|
-
message
|
|
975
|
-
};
|
|
976
|
-
entries.push(entry);
|
|
977
|
-
parentId = entry.id;
|
|
978
|
-
}
|
|
979
|
-
await this.save(sessionId, entries, metadata);
|
|
980
|
-
}
|
|
981
|
-
/**
|
|
982
|
-
* Load a session from disk
|
|
983
|
-
*/
|
|
984
|
-
async load(sessionId) {
|
|
985
|
-
const filePath = this.getSessionPath(sessionId);
|
|
986
|
-
try {
|
|
987
|
-
const loaded = await loadDurableFile({
|
|
988
|
-
gateway: this.gateway,
|
|
989
|
-
filePath,
|
|
990
|
-
parse: (content) => this.parseSessionContent(content)
|
|
991
|
-
});
|
|
992
|
-
if (!loaded) {
|
|
993
|
-
return null;
|
|
994
|
-
}
|
|
995
|
-
if (loaded.restoredMain || loaded.refreshedBackup) {
|
|
996
|
-
console.warn(
|
|
997
|
-
`[SessionStore] Repaired session ${sessionId} from ${loaded.source}` + (loaded.repairError ? ` with repair warning: ${loaded.repairError}` : "")
|
|
998
|
-
);
|
|
999
|
-
} else if (loaded.repairError) {
|
|
1000
|
-
console.warn(`[SessionStore] Loaded session ${sessionId} but sidecar refresh failed: ${loaded.repairError}`);
|
|
1001
|
-
}
|
|
1002
|
-
return loaded.data;
|
|
1003
|
-
} catch (e) {
|
|
1004
|
-
console.error(`[SessionStore] Failed to load session: ${sessionId}`, e);
|
|
1005
|
-
return null;
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
/**
|
|
1009
|
-
* Delete a session
|
|
1010
|
-
*/
|
|
1011
|
-
async delete(sessionId) {
|
|
1012
|
-
const filePath = this.getSessionPath(sessionId);
|
|
1013
|
-
try {
|
|
1014
|
-
const deleted = deleteDurableFile(this.gateway, filePath);
|
|
1015
|
-
if (deleted) this.removeMetadataIndexEntryBestEffort(sessionId);
|
|
1016
|
-
return deleted;
|
|
1017
|
-
} catch (e) {
|
|
1018
|
-
console.error(`[SessionStore] Failed to delete session: ${sessionId}`, e);
|
|
1019
|
-
return false;
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
/**
|
|
1023
|
-
* Check if a session exists
|
|
1024
|
-
*/
|
|
1025
|
-
async exists(sessionId) {
|
|
1026
|
-
const filePath = this.getSessionPath(sessionId);
|
|
1027
|
-
return durableFileExists(this.gateway, filePath);
|
|
1028
|
-
}
|
|
1029
|
-
/**
|
|
1030
|
-
* List all session IDs
|
|
1031
|
-
*/
|
|
1032
|
-
async list() {
|
|
1033
|
-
return this.listSessionIdsSync();
|
|
1034
|
-
}
|
|
1035
|
-
listSessionIdsSync() {
|
|
1036
|
-
if (!this.gateway.exists(this.sessionsDir)) {
|
|
1037
|
-
return [];
|
|
1038
|
-
}
|
|
1039
|
-
const files = this.gateway.readdir(this.sessionsDir);
|
|
1040
|
-
const sessionIds = /* @__PURE__ */ new Set();
|
|
1041
|
-
for (const file of files) {
|
|
1042
|
-
if (isSessionSidecarFile(file)) {
|
|
1043
|
-
continue;
|
|
1044
|
-
}
|
|
1045
|
-
if (file.endsWith(".json")) {
|
|
1046
|
-
sessionIds.add(file.replace(/\.json$/i, ""));
|
|
1047
|
-
} else if (file.endsWith(".json.bak")) {
|
|
1048
|
-
sessionIds.add(file.replace(/\.json\.bak$/i, ""));
|
|
1049
|
-
} else if (file.endsWith(".json.prev")) {
|
|
1050
|
-
sessionIds.add(file.replace(/\.json\.prev$/i, ""));
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
return [...sessionIds];
|
|
1054
|
-
}
|
|
1055
|
-
/**
|
|
1056
|
-
* List all session metadata
|
|
1057
|
-
*/
|
|
1058
|
-
async listMetadata() {
|
|
1059
|
-
return await this.listMetadataIndexed();
|
|
1060
|
-
}
|
|
1061
|
-
async listMetadataPage(input) {
|
|
1062
|
-
const limit = Math.max(0, input.limit);
|
|
1063
|
-
if (limit === 0) return { metadata: [], hasMore: false };
|
|
1064
|
-
const result = [];
|
|
1065
|
-
for (const metadata of await this.listMetadataIndexed()) {
|
|
1066
|
-
if (input.after && compareMetadataListPosition(metadata, input.after) <= 0) continue;
|
|
1067
|
-
if (input.filter && !input.filter(metadata)) continue;
|
|
1068
|
-
result.push(metadata);
|
|
1069
|
-
if (result.length > limit) break;
|
|
1070
|
-
}
|
|
1071
|
-
return {
|
|
1072
|
-
metadata: result.slice(0, limit),
|
|
1073
|
-
hasMore: result.length > limit
|
|
1074
|
-
};
|
|
1075
|
-
}
|
|
1076
|
-
/**
|
|
1077
|
-
* Get session metadata without loading full entries
|
|
1078
|
-
*/
|
|
1079
|
-
async getMetadata(sessionId) {
|
|
1080
|
-
const fastMetadata = this.getMetadataFast(sessionId);
|
|
1081
|
-
if (fastMetadata) return fastMetadata;
|
|
1082
|
-
const data = await this.load(sessionId);
|
|
1083
|
-
return data?.metadata ?? null;
|
|
1084
|
-
}
|
|
1085
|
-
getMetadataFast(sessionId) {
|
|
1086
|
-
const paths = durableFilePaths(this.getSessionPath(sessionId));
|
|
1087
|
-
const candidates = [];
|
|
1088
|
-
for (const [role, path] of [
|
|
1089
|
-
["main", paths.main],
|
|
1090
|
-
["backup", paths.backup],
|
|
1091
|
-
["previous", paths.previous]
|
|
1092
|
-
]) {
|
|
1093
|
-
try {
|
|
1094
|
-
if (!this.gateway.exists(path)) continue;
|
|
1095
|
-
const metadata = this.readMetadataOnlyFast(path);
|
|
1096
|
-
if (metadata) candidates.push({ metadata, role });
|
|
1097
|
-
} catch {
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
if (candidates.length === 0) return null;
|
|
1101
|
-
return chooseBestMetadataCandidate(candidates).metadata;
|
|
1102
|
-
}
|
|
1103
|
-
readMetadataOnlyFast(path) {
|
|
1104
|
-
const fileSize = this.gateway.getFileSize(path);
|
|
1105
|
-
if (fileSize <= METADATA_FAST_READ_BYTES) {
|
|
1106
|
-
return parseMetadataOnly(this.gateway.readFile(path, "utf-8"));
|
|
1107
|
-
}
|
|
1108
|
-
const head = this.gateway.readFileHead(path, METADATA_FAST_READ_BYTES, "utf-8");
|
|
1109
|
-
const metadata = parseMetadataOnly(head);
|
|
1110
|
-
if (metadata) return metadata;
|
|
1111
|
-
return parseMetadataOnly(this.gateway.readFile(path, "utf-8"));
|
|
1112
|
-
}
|
|
1113
|
-
async listMetadataIndexed() {
|
|
1114
|
-
for (let attempt = 0; attempt < 2; attempt++) {
|
|
1115
|
-
const sessionIds = await this.list();
|
|
1116
|
-
const sessionIdSet = new Set(sessionIds);
|
|
1117
|
-
const index = this.readMetadataIndexBestEffort();
|
|
1118
|
-
if (index && this.metadataIndexMatchesSessionFiles(index, sessionIdSet)) {
|
|
1119
|
-
return sortMetadata(index.sessions.map((entry) => entry.metadata).filter((metadata2) => sessionIdSet.has(metadata2.id)));
|
|
1120
|
-
}
|
|
1121
|
-
const entries = await this.readStableMetadataIndexEntries(sessionIds);
|
|
1122
|
-
if (entries) {
|
|
1123
|
-
this.writeMetadataIndexEntriesBestEffort(entries);
|
|
1124
|
-
return sortMetadata(entries.map((entry) => entry.metadata));
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
|
-
const metadata = [];
|
|
1128
|
-
for (const id of await this.list()) {
|
|
1129
|
-
const item = await this.getMetadata(id);
|
|
1130
|
-
if (item) metadata.push(item);
|
|
1131
|
-
}
|
|
1132
|
-
return sortMetadata(metadata);
|
|
1133
|
-
}
|
|
1134
|
-
/**
|
|
1135
|
-
* Cheap recent-first listing: when the metadata index is cold, stat-sort by
|
|
1136
|
-
* file mtime and parse metadata only for the most recent `limit` sessions,
|
|
1137
|
-
* then rebuild the full index in the background (chunked, off the critical
|
|
1138
|
-
* path). Avoids parsing thousands of session files on the first /resume.
|
|
1139
|
-
*/
|
|
1140
|
-
async listMetadataRecent(limit) {
|
|
1141
|
-
const sessionIds = await this.list();
|
|
1142
|
-
const sessionIdSet = new Set(sessionIds);
|
|
1143
|
-
const index = this.readMetadataIndexBestEffort();
|
|
1144
|
-
if (index && this.metadataIndexMatchesSessionFiles(index, sessionIdSet)) {
|
|
1145
|
-
const sorted = sortMetadata(index.sessions.map((entry) => entry.metadata).filter((metadata) => sessionIdSet.has(metadata.id)));
|
|
1146
|
-
return { metadata: sorted.slice(0, limit), totalCount: sorted.length };
|
|
1147
|
-
}
|
|
1148
|
-
const ranked = [];
|
|
1149
|
-
for (const id of sessionIds) {
|
|
1150
|
-
const paths = durableFilePaths(this.getSessionPath(id));
|
|
1151
|
-
let mtimeMs = 0;
|
|
1152
|
-
for (const candidate of [paths.main, paths.backup, paths.previous]) {
|
|
1153
|
-
try {
|
|
1154
|
-
if (this.gateway.exists(candidate)) {
|
|
1155
|
-
mtimeMs = this.gateway.getModifiedTime(candidate);
|
|
1156
|
-
break;
|
|
1157
|
-
}
|
|
1158
|
-
} catch {
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
if (mtimeMs > 0) {
|
|
1162
|
-
ranked.push({ id, mtimeMs });
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
ranked.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
1166
|
-
const recent = [];
|
|
1167
|
-
for (const { id } of ranked.slice(0, limit)) {
|
|
1168
|
-
const meta = await this.getMetadata(id);
|
|
1169
|
-
if (meta) recent.push(meta);
|
|
1170
|
-
}
|
|
1171
|
-
this.scheduleIndexRebuild(sessionIds);
|
|
1172
|
-
return { metadata: sortMetadata(recent), totalCount: ranked.length };
|
|
1173
|
-
}
|
|
1174
|
-
indexRebuildInFlight = false;
|
|
1175
|
-
indexRebuildPromise = null;
|
|
1176
|
-
/**
|
|
1177
|
-
* Resolves when the background metadata index rebuild (if any) completes.
|
|
1178
|
-
* Resolves immediately when no rebuild is in flight.
|
|
1179
|
-
*/
|
|
1180
|
-
whenIndexRebuilt() {
|
|
1181
|
-
return this.indexRebuildPromise ?? Promise.resolve();
|
|
1182
|
-
}
|
|
1183
|
-
/** Rebuild the full metadata index in small chunks, off the critical path. */
|
|
1184
|
-
scheduleIndexRebuild(sessionIds) {
|
|
1185
|
-
if (this.indexRebuildInFlight) return;
|
|
1186
|
-
this.indexRebuildInFlight = true;
|
|
1187
|
-
this.indexRebuildPromise = (async () => {
|
|
1188
|
-
try {
|
|
1189
|
-
const entries = await this.readStableMetadataIndexEntries(sessionIds, 50);
|
|
1190
|
-
if (!entries) return;
|
|
1191
|
-
this.writeMetadataIndexEntriesBestEffort(entries);
|
|
1192
|
-
} catch {
|
|
1193
|
-
} finally {
|
|
1194
|
-
this.indexRebuildInFlight = false;
|
|
1195
|
-
this.indexRebuildPromise = null;
|
|
1196
|
-
}
|
|
1197
|
-
})();
|
|
1198
|
-
}
|
|
1199
|
-
async readStableMetadataIndexEntry(sessionId) {
|
|
1200
|
-
try {
|
|
1201
|
-
const before = this.sessionFileSignature(sessionId);
|
|
1202
|
-
const metadata = await this.getMetadata(sessionId);
|
|
1203
|
-
const after = this.sessionFileSignature(sessionId);
|
|
1204
|
-
if (!metadata || !sessionFileSignaturesEqual(before, after)) return null;
|
|
1205
|
-
return { metadata, files: after };
|
|
1206
|
-
} catch {
|
|
1207
|
-
return null;
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
async readStableMetadataIndexEntries(sessionIds, yieldEvery = 0) {
|
|
1211
|
-
const entries = [];
|
|
1212
|
-
for (let index = 0; index < sessionIds.length; index++) {
|
|
1213
|
-
const entry = await this.readStableMetadataIndexEntry(sessionIds[index]);
|
|
1214
|
-
if (!entry) return null;
|
|
1215
|
-
entries.push(entry);
|
|
1216
|
-
if (yieldEvery > 0 && (index + 1) % yieldEvery === 0 && index + 1 < sessionIds.length) {
|
|
1217
|
-
await new Promise((resolve) => setImmediate(resolve));
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
return this.metadataIndexSnapshotStillCurrent(entries, sessionIds) ? entries : null;
|
|
1221
|
-
}
|
|
1222
|
-
metadataIndexSnapshotStillCurrent(entries, expectedSessionIds) {
|
|
1223
|
-
try {
|
|
1224
|
-
const currentIds = new Set(this.listSessionIdsSync());
|
|
1225
|
-
const expectedIds = new Set(expectedSessionIds);
|
|
1226
|
-
if (currentIds.size !== expectedIds.size || expectedIds.size !== entries.length) return false;
|
|
1227
|
-
for (const id of expectedIds) {
|
|
1228
|
-
if (!currentIds.has(id)) return false;
|
|
1229
|
-
}
|
|
1230
|
-
return entries.every(
|
|
1231
|
-
(entry) => expectedIds.has(entry.metadata.id) && sessionFileSignaturesEqual(entry.files, this.sessionFileSignature(entry.metadata.id))
|
|
1232
|
-
);
|
|
1233
|
-
} catch {
|
|
1234
|
-
return false;
|
|
1235
|
-
}
|
|
1236
|
-
}
|
|
1237
|
-
readMetadataIndexBestEffort() {
|
|
1238
|
-
try {
|
|
1239
|
-
if (!this.gateway.exists(this.getMetadataIndexPath())) return null;
|
|
1240
|
-
const parsed = JSON.parse(this.gateway.readFile(this.getMetadataIndexPath(), "utf-8"));
|
|
1241
|
-
if (!isRecord(parsed) || parsed.v !== METADATA_INDEX_VERSION || !Array.isArray(parsed.sessions)) return null;
|
|
1242
|
-
const sessions = parsed.sessions.filter(isMetadataIndexEntry);
|
|
1243
|
-
if (sessions.length !== parsed.sessions.length) return null;
|
|
1244
|
-
return {
|
|
1245
|
-
v: METADATA_INDEX_VERSION,
|
|
1246
|
-
sessions,
|
|
1247
|
-
updatedAt: typeof parsed.updatedAt === "number" ? parsed.updatedAt : 0
|
|
1248
|
-
};
|
|
1249
|
-
} catch {
|
|
1250
|
-
return null;
|
|
1251
|
-
}
|
|
1252
|
-
}
|
|
1253
|
-
writeMetadataIndexEntriesBestEffort(sessions) {
|
|
1254
|
-
try {
|
|
1255
|
-
this.gateway.writeFile(this.getMetadataIndexPath(), JSON.stringify({
|
|
1256
|
-
v: METADATA_INDEX_VERSION,
|
|
1257
|
-
updatedAt: Date.now(),
|
|
1258
|
-
sessions: [...sessions].sort(
|
|
1259
|
-
(left, right) => compareMetadataListPosition(left.metadata, right.metadata)
|
|
1260
|
-
)
|
|
1261
|
-
}, null, 2));
|
|
1262
|
-
} catch {
|
|
1263
|
-
}
|
|
1264
|
-
}
|
|
1265
|
-
async upsertMetadataIndexBestEffort(sessionId) {
|
|
1266
|
-
try {
|
|
1267
|
-
const savedEntry = await this.readStableMetadataIndexEntry(sessionId);
|
|
1268
|
-
if (!savedEntry) {
|
|
1269
|
-
this.removeMetadataIndexFileBestEffort();
|
|
1270
|
-
return;
|
|
1271
|
-
}
|
|
1272
|
-
const current = this.readMetadataIndexBestEffort();
|
|
1273
|
-
const next = (current?.sessions ?? []).filter((entry) => entry.metadata.id !== sessionId);
|
|
1274
|
-
next.push(savedEntry);
|
|
1275
|
-
const sessionIds = this.listSessionIdsSync();
|
|
1276
|
-
if (!this.metadataIndexSnapshotStillCurrent(next, sessionIds)) {
|
|
1277
|
-
this.removeMetadataIndexFileBestEffort();
|
|
1278
|
-
return;
|
|
1279
|
-
}
|
|
1280
|
-
this.writeMetadataIndexEntriesBestEffort(next);
|
|
1281
|
-
} catch {
|
|
1282
|
-
this.removeMetadataIndexFileBestEffort();
|
|
1283
|
-
}
|
|
1284
|
-
}
|
|
1285
|
-
removeMetadataIndexEntryBestEffort(sessionId) {
|
|
1286
|
-
try {
|
|
1287
|
-
const current = this.readMetadataIndexBestEffort();
|
|
1288
|
-
if (!current) return;
|
|
1289
|
-
const next = current.sessions.filter((entry) => entry.metadata.id !== sessionId);
|
|
1290
|
-
if (!this.metadataIndexSnapshotStillCurrent(next, this.listSessionIdsSync())) {
|
|
1291
|
-
this.removeMetadataIndexFileBestEffort();
|
|
1292
|
-
return;
|
|
1293
|
-
}
|
|
1294
|
-
this.writeMetadataIndexEntriesBestEffort(next);
|
|
1295
|
-
} catch {
|
|
1296
|
-
this.removeMetadataIndexFileBestEffort();
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
removeMetadataIndexFileBestEffort() {
|
|
1300
|
-
try {
|
|
1301
|
-
if (this.gateway.exists(this.getMetadataIndexPath())) this.gateway.deleteFile(this.getMetadataIndexPath());
|
|
1302
|
-
} catch {
|
|
1303
|
-
}
|
|
1304
|
-
}
|
|
1305
|
-
metadataIndexMatchesSessionFiles(index, sessionIds) {
|
|
1306
|
-
try {
|
|
1307
|
-
if (index.sessions.length !== sessionIds.size) return false;
|
|
1308
|
-
const indexedIds = new Set(index.sessions.map((entry) => entry.metadata.id));
|
|
1309
|
-
if (indexedIds.size !== index.sessions.length) return false;
|
|
1310
|
-
for (const id of sessionIds) {
|
|
1311
|
-
if (!indexedIds.has(id)) return false;
|
|
1312
|
-
}
|
|
1313
|
-
return index.sessions.every(
|
|
1314
|
-
(entry) => sessionFileSignaturesEqual(entry.files, this.sessionFileSignature(entry.metadata.id))
|
|
1315
|
-
);
|
|
1316
|
-
} catch {
|
|
1317
|
-
return false;
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
sessionFileSignature(sessionId) {
|
|
1321
|
-
const paths = durableFilePaths(this.getSessionPath(sessionId));
|
|
1322
|
-
return {
|
|
1323
|
-
...this.gateway.exists(paths.main) ? { main: this.fileSignature(paths.main) } : {},
|
|
1324
|
-
...this.gateway.exists(paths.backup) ? { backup: this.fileSignature(paths.backup) } : {},
|
|
1325
|
-
...this.gateway.exists(paths.previous) ? { previous: this.fileSignature(paths.previous) } : {}
|
|
1326
|
-
};
|
|
1327
|
-
}
|
|
1328
|
-
fileSignature(path) {
|
|
1329
|
-
return {
|
|
1330
|
-
size: this.gateway.getFileSize(path),
|
|
1331
|
-
modifiedAt: this.gateway.getModifiedTime(path)
|
|
1332
|
-
};
|
|
1333
|
-
}
|
|
1334
|
-
};
|
|
1335
|
-
function isSessionSidecarFile(file) {
|
|
1336
|
-
return file === ".metadata-index.json" || file.endsWith(".trace.json") || file.endsWith(".trace.json.bak") || file.endsWith(".trace.json.prev") || file.endsWith("-files.json") || file.endsWith("-files.json.bak") || file.endsWith("-files.json.prev") || // magic-shell channel state/inbox files share the sessions directory
|
|
1337
|
-
// but are not sessions; counting them breaks the metadata index check
|
|
1338
|
-
// (index size never matches id count, forcing a full rebuild every list).
|
|
1339
|
-
file.includes(".magic-shell-");
|
|
1340
|
-
}
|
|
1341
|
-
function sortMetadata(metadata) {
|
|
1342
|
-
return [...metadata].sort((a, b) => b.updatedAt - a.updatedAt || a.id.localeCompare(b.id));
|
|
1343
|
-
}
|
|
1344
|
-
function chooseBestMetadataCandidate(candidates) {
|
|
1345
|
-
const main = candidates.find((candidate) => candidate.role === "main");
|
|
1346
|
-
if (main) {
|
|
1347
|
-
const backup = candidates.find((candidate) => candidate.role === "backup");
|
|
1348
|
-
const previous = candidates.find((candidate) => candidate.role === "previous");
|
|
1349
|
-
if (backup && previous && !metadataObjectsEqual(backup.metadata, main.metadata) && metadataObjectsEqual(previous.metadata, main.metadata) && backup.metadata.updatedAt >= main.metadata.updatedAt) {
|
|
1350
|
-
return backup;
|
|
1351
|
-
}
|
|
1352
|
-
return main;
|
|
1353
|
-
}
|
|
1354
|
-
return [...candidates].sort((left, right) => {
|
|
1355
|
-
const updatedAtDiff = right.metadata.updatedAt - left.metadata.updatedAt;
|
|
1356
|
-
if (updatedAtDiff !== 0) return updatedAtDiff;
|
|
1357
|
-
return durableRolePriority(right.role) - durableRolePriority(left.role);
|
|
1358
|
-
})[0];
|
|
1359
|
-
}
|
|
1360
|
-
function metadataObjectsEqual(left, right) {
|
|
1361
|
-
return stableJson(left) === stableJson(right);
|
|
1362
|
-
}
|
|
1363
|
-
function stableJson(value) {
|
|
1364
|
-
if (Array.isArray(value)) {
|
|
1365
|
-
return `[${value.map((item) => stableJson(item)).join(",")}]`;
|
|
1366
|
-
}
|
|
1367
|
-
if (isRecord(value)) {
|
|
1368
|
-
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`).join(",")}}`;
|
|
1369
|
-
}
|
|
1370
|
-
return JSON.stringify(value);
|
|
1371
|
-
}
|
|
1372
|
-
function compareMetadataListPosition(metadata, boundary) {
|
|
1373
|
-
const updatedAtDiff = boundary.updatedAt - metadata.updatedAt;
|
|
1374
|
-
if (updatedAtDiff !== 0) return updatedAtDiff;
|
|
1375
|
-
return metadata.id.localeCompare(boundary.id);
|
|
1376
|
-
}
|
|
1377
|
-
function isSessionMetadata(value) {
|
|
1378
|
-
if (!isRecord(value)) return false;
|
|
1379
|
-
return typeof value.id === "string" && typeof value.name === "string" && typeof value.createdAt === "number" && typeof value.updatedAt === "number" && typeof value.entryCount === "number" && typeof value.messageCount === "number";
|
|
1380
|
-
}
|
|
1381
|
-
function isMetadataIndexEntry(value) {
|
|
1382
|
-
if (!isRecord(value) || !isSessionMetadata(value.metadata) || !isRecord(value.files)) return false;
|
|
1383
|
-
return optionalFileSignature(value.files.main) && optionalFileSignature(value.files.backup) && optionalFileSignature(value.files.previous);
|
|
1384
|
-
}
|
|
1385
|
-
function optionalFileSignature(value) {
|
|
1386
|
-
if (value === void 0) return true;
|
|
1387
|
-
return isRecord(value) && typeof value.size === "number" && Number.isFinite(value.size) && typeof value.modifiedAt === "number" && Number.isFinite(value.modifiedAt);
|
|
1388
|
-
}
|
|
1389
|
-
function sessionFileSignaturesEqual(left, right) {
|
|
1390
|
-
return fileSignaturesEqual(left.main, right.main) && fileSignaturesEqual(left.backup, right.backup) && fileSignaturesEqual(left.previous, right.previous);
|
|
1391
|
-
}
|
|
1392
|
-
function fileSignaturesEqual(left, right) {
|
|
1393
|
-
if (!left || !right) return left === right;
|
|
1394
|
-
return left.size === right.size && left.modifiedAt === right.modifiedAt;
|
|
1395
|
-
}
|
|
1396
|
-
function isRecord(value) {
|
|
1397
|
-
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1398
|
-
}
|
|
1399
|
-
|
|
1400
|
-
// ../../packages/agent-framework/src/session/manager.ts
|
|
1401
|
-
function generateSessionId() {
|
|
1402
|
-
const timestamp = Date.now().toString(36);
|
|
1403
|
-
const random = Math.random().toString(36).substring(2, 8);
|
|
1404
|
-
return `${timestamp}-${random}`;
|
|
1405
|
-
}
|
|
1406
|
-
var SessionManager = class {
|
|
1407
|
-
store;
|
|
1408
|
-
activeSession = null;
|
|
1409
|
-
options;
|
|
1410
|
-
autoSaveTimer = null;
|
|
1411
|
-
gateway;
|
|
1412
|
-
constructor(options = {}) {
|
|
1413
|
-
this.options = {
|
|
1414
|
-
sessionsDir: options.sessionsDir ?? getDefaultSessionsDir(),
|
|
1415
|
-
autoSaveInterval: options.autoSaveInterval ?? 0,
|
|
1416
|
-
maxSessions: options.maxSessions ?? 100,
|
|
1417
|
-
version: options.version ?? 2
|
|
1418
|
-
};
|
|
1419
|
-
this.gateway = new FileSystemGateway({
|
|
1420
|
-
allowedRoots: [this.options.sessionsDir, getPlatformConfig().dataPath]
|
|
1421
|
-
});
|
|
1422
|
-
if (!this.gateway.exists(this.options.sessionsDir)) {
|
|
1423
|
-
this.gateway.mkdir(this.options.sessionsDir, { recursive: true });
|
|
1424
|
-
}
|
|
1425
|
-
this.store = new FileSessionStore(this.options.sessionsDir);
|
|
1426
|
-
if (this.options.autoSaveInterval > 0) {
|
|
1427
|
-
this.startAutoSave();
|
|
1428
|
-
}
|
|
1429
|
-
}
|
|
1430
|
-
/**
|
|
1431
|
-
* Get the session store
|
|
1432
|
-
*/
|
|
1433
|
-
getStore() {
|
|
1434
|
-
return this.store;
|
|
1435
|
-
}
|
|
1436
|
-
/**
|
|
1437
|
-
* Start auto-save timer
|
|
1438
|
-
*/
|
|
1439
|
-
startAutoSave() {
|
|
1440
|
-
if (this.autoSaveTimer) {
|
|
1441
|
-
clearInterval(this.autoSaveTimer);
|
|
1442
|
-
}
|
|
1443
|
-
this.autoSaveTimer = setInterval(() => {
|
|
1444
|
-
if (this.activeSession?.isDirty) {
|
|
1445
|
-
this.save().catch((e) => {
|
|
1446
|
-
});
|
|
1447
|
-
}
|
|
1448
|
-
}, this.options.autoSaveInterval);
|
|
1449
|
-
}
|
|
1450
|
-
/**
|
|
1451
|
-
* Stop auto-save timer
|
|
1452
|
-
*/
|
|
1453
|
-
stopAutoSave() {
|
|
1454
|
-
if (this.autoSaveTimer) {
|
|
1455
|
-
clearInterval(this.autoSaveTimer);
|
|
1456
|
-
this.autoSaveTimer = null;
|
|
1457
|
-
}
|
|
1458
|
-
}
|
|
1459
|
-
/**
|
|
1460
|
-
* Create a new session with tree structure
|
|
1461
|
-
*/
|
|
1462
|
-
async createSession(name, tags, sessionId) {
|
|
1463
|
-
const id = sessionId || generateSessionId();
|
|
1464
|
-
const now = Date.now();
|
|
1465
|
-
this.activeSession = {
|
|
1466
|
-
id,
|
|
1467
|
-
metadata: {
|
|
1468
|
-
id,
|
|
1469
|
-
name: name ?? getGeneratedSessionName(now),
|
|
1470
|
-
nameOrigin: name === void 0 ? "generated" : "explicit",
|
|
1471
|
-
createdAt: now,
|
|
1472
|
-
updatedAt: now,
|
|
1473
|
-
entryCount: 0,
|
|
1474
|
-
messageCount: 0,
|
|
1475
|
-
activeEntryId: null,
|
|
1476
|
-
rootEntryId: null,
|
|
1477
|
-
tags
|
|
1478
|
-
},
|
|
1479
|
-
isDirty: true,
|
|
1480
|
-
entries: [],
|
|
1481
|
-
activeEntryId: null
|
|
1482
|
-
};
|
|
1483
|
-
await this.save();
|
|
1484
|
-
return id;
|
|
1485
|
-
}
|
|
1486
|
-
/**
|
|
1487
|
-
* Load an existing session
|
|
1488
|
-
*/
|
|
1489
|
-
async loadSession(sessionId, options) {
|
|
1490
|
-
const data = await this.store.load(sessionId);
|
|
1491
|
-
if (!data) {
|
|
1492
|
-
if (options?.warnIfMissing !== false) {
|
|
1493
|
-
console.warn(`[SessionManager] Session not found: ${sessionId}`);
|
|
1494
|
-
}
|
|
1495
|
-
return false;
|
|
1496
|
-
}
|
|
1497
|
-
if (!data.entries) {
|
|
1498
|
-
data.entries = [];
|
|
1499
|
-
}
|
|
1500
|
-
this.activeSession = {
|
|
1501
|
-
id: sessionId,
|
|
1502
|
-
metadata: data.metadata,
|
|
1503
|
-
isDirty: false,
|
|
1504
|
-
lastSavedAt: data.metadata.updatedAt,
|
|
1505
|
-
entries: data.entries,
|
|
1506
|
-
activeEntryId: data.metadata.activeEntryId ?? null
|
|
1507
|
-
};
|
|
1508
|
-
return true;
|
|
1509
|
-
}
|
|
1510
|
-
/**
|
|
1511
|
-
* Save the current session
|
|
1512
|
-
*/
|
|
1513
|
-
async save() {
|
|
1514
|
-
if (!this.activeSession) {
|
|
1515
|
-
throw new Error("No active session to save");
|
|
1516
|
-
}
|
|
1517
|
-
this.activeSession.metadata.entryCount = this.activeSession.entries.length;
|
|
1518
|
-
this.activeSession.metadata.messageCount = this.activeSession.entries.filter(
|
|
1519
|
-
(e) => e.type === "message" && e.message
|
|
1520
|
-
).length;
|
|
1521
|
-
if (this.activeSession.metadata.messageCount === 0) {
|
|
1522
|
-
if (await this.store.exists(this.activeSession.id)) {
|
|
1523
|
-
await this.store.delete(this.activeSession.id);
|
|
1524
|
-
}
|
|
1525
|
-
this.activeSession.isDirty = false;
|
|
1526
|
-
return;
|
|
1527
|
-
}
|
|
1528
|
-
this.activeSession.metadata.updatedAt = Date.now();
|
|
1529
|
-
this.activeSession.metadata.activeEntryId = this.activeSession.activeEntryId;
|
|
1530
|
-
this.activeSession.metadata.rootEntryId = this.activeSession.entries.find(
|
|
1531
|
-
(e) => e.parentId === null
|
|
1532
|
-
)?.id ?? null;
|
|
1533
|
-
await this.store.save(
|
|
1534
|
-
this.activeSession.id,
|
|
1535
|
-
this.activeSession.entries,
|
|
1536
|
-
this.activeSession.metadata
|
|
1537
|
-
);
|
|
1538
|
-
this.activeSession.isDirty = false;
|
|
1539
|
-
this.activeSession.lastSavedAt = Date.now();
|
|
1540
|
-
}
|
|
1541
|
-
/**
|
|
1542
|
-
* Add a new entry to the session
|
|
1543
|
-
*/
|
|
1544
|
-
addEntry(entryType, message) {
|
|
1545
|
-
if (!this.activeSession) {
|
|
1546
|
-
throw new Error("No active session");
|
|
1547
|
-
}
|
|
1548
|
-
const entry = {
|
|
1549
|
-
id: generateEntryId(),
|
|
1550
|
-
parentId: this.activeSession.activeEntryId,
|
|
1551
|
-
type: entryType,
|
|
1552
|
-
timestamp: Date.now(),
|
|
1553
|
-
message
|
|
1554
|
-
};
|
|
1555
|
-
this.activeSession.entries.push(entry);
|
|
1556
|
-
this.activeSession.activeEntryId = entry.id;
|
|
1557
|
-
this.activeSession.isDirty = true;
|
|
1558
|
-
return entry;
|
|
1559
|
-
}
|
|
1560
|
-
/**
|
|
1561
|
-
* Add a message entry to the session
|
|
1562
|
-
*/
|
|
1563
|
-
addMessage(message) {
|
|
1564
|
-
return this.addEntry("message", message);
|
|
1565
|
-
}
|
|
1566
|
-
/**
|
|
1567
|
-
* Append a compaction checkpoint to the current active path.
|
|
1568
|
-
* Full history remains in entries; active messages are reconstructed from this checkpoint onward.
|
|
1569
|
-
*/
|
|
1570
|
-
appendCompaction(summary, firstKeptEntryId, tokensBefore) {
|
|
1571
|
-
if (!this.activeSession) {
|
|
1572
|
-
throw new Error("No active session");
|
|
1573
|
-
}
|
|
1574
|
-
const entry = {
|
|
1575
|
-
id: generateEntryId(),
|
|
1576
|
-
parentId: this.activeSession.activeEntryId,
|
|
1577
|
-
type: "compaction",
|
|
1578
|
-
timestamp: Date.now(),
|
|
1579
|
-
compactionData: {
|
|
1580
|
-
summary,
|
|
1581
|
-
firstKeptEntryId,
|
|
1582
|
-
tokensBefore
|
|
1583
|
-
}
|
|
1584
|
-
};
|
|
1585
|
-
this.activeSession.entries.push(entry);
|
|
1586
|
-
this.activeSession.activeEntryId = entry.id;
|
|
1587
|
-
this.activeSession.isDirty = true;
|
|
1588
|
-
return entry;
|
|
1589
|
-
}
|
|
1590
|
-
/**
|
|
1591
|
-
* Update messages (legacy compatibility - rebuilds tree)
|
|
1592
|
-
* @deprecated Use addMessage() instead
|
|
1593
|
-
*/
|
|
1594
|
-
updateMessages(messages) {
|
|
1595
|
-
if (!this.activeSession) {
|
|
1596
|
-
throw new Error("No active session");
|
|
1597
|
-
}
|
|
1598
|
-
const newEntries = [];
|
|
1599
|
-
let parentId = null;
|
|
1600
|
-
for (const message of messages) {
|
|
1601
|
-
const entry = {
|
|
1602
|
-
id: generateEntryId(),
|
|
1603
|
-
parentId,
|
|
1604
|
-
type: "message",
|
|
1605
|
-
timestamp: Date.now(),
|
|
1606
|
-
message
|
|
1607
|
-
};
|
|
1608
|
-
newEntries.push(entry);
|
|
1609
|
-
parentId = entry.id;
|
|
1610
|
-
}
|
|
1611
|
-
this.activeSession.entries = newEntries;
|
|
1612
|
-
this.activeSession.activeEntryId = newEntries.length > 0 ? newEntries[newEntries.length - 1].id : null;
|
|
1613
|
-
this.activeSession.isDirty = true;
|
|
1614
|
-
}
|
|
1615
|
-
/**
|
|
1616
|
-
* Get current messages (linear path from root to active leaf)
|
|
1617
|
-
*/
|
|
1618
|
-
getMessages() {
|
|
1619
|
-
if (!this.activeSession) {
|
|
1620
|
-
return [];
|
|
1621
|
-
}
|
|
1622
|
-
return flattenTreeToMessages(this.activeSession.entries, this.activeSession.activeEntryId);
|
|
1623
|
-
}
|
|
1624
|
-
/**
|
|
1625
|
-
* Get current entries
|
|
1626
|
-
*/
|
|
1627
|
-
getEntries() {
|
|
1628
|
-
return this.activeSession?.entries ?? [];
|
|
1629
|
-
}
|
|
1630
|
-
/**
|
|
1631
|
-
* Get active session info
|
|
1632
|
-
*/
|
|
1633
|
-
getActiveSession() {
|
|
1634
|
-
return this.activeSession;
|
|
1635
|
-
}
|
|
1636
|
-
/**
|
|
1637
|
-
* Get active session ID
|
|
1638
|
-
*/
|
|
1639
|
-
getActiveSessionId() {
|
|
1640
|
-
return this.activeSession?.id ?? null;
|
|
1641
|
-
}
|
|
1642
|
-
/**
|
|
1643
|
-
* Get current active entry ID
|
|
1644
|
-
*/
|
|
1645
|
-
getActiveEntryId() {
|
|
1646
|
-
return this.activeSession?.activeEntryId ?? null;
|
|
1647
|
-
}
|
|
1648
|
-
/**
|
|
1649
|
-
* Set active entry (for navigation)
|
|
1650
|
-
*/
|
|
1651
|
-
setActiveEntry(entryId) {
|
|
1652
|
-
if (!this.activeSession) {
|
|
1653
|
-
return false;
|
|
1654
|
-
}
|
|
1655
|
-
if (entryId === null) {
|
|
1656
|
-
this.activeSession.activeEntryId = null;
|
|
1657
|
-
this.activeSession.isDirty = true;
|
|
1658
|
-
return true;
|
|
1659
|
-
}
|
|
1660
|
-
const entry = this.activeSession.entries.find((e) => e.id === entryId);
|
|
1661
|
-
if (!entry) {
|
|
1662
|
-
console.warn(`[SessionManager] Entry not found: ${entryId}`);
|
|
1663
|
-
return false;
|
|
1664
|
-
}
|
|
1665
|
-
this.activeSession.activeEntryId = entryId;
|
|
1666
|
-
this.activeSession.isDirty = true;
|
|
1667
|
-
return true;
|
|
1668
|
-
}
|
|
1669
|
-
/**
|
|
1670
|
-
* Get entry by ID
|
|
1671
|
-
*/
|
|
1672
|
-
getEntry(entryId) {
|
|
1673
|
-
if (!this.activeSession) {
|
|
1674
|
-
return null;
|
|
1675
|
-
}
|
|
1676
|
-
return this.activeSession.entries.find((e) => e.id === entryId) ?? null;
|
|
1677
|
-
}
|
|
1678
|
-
/**
|
|
1679
|
-
* Get children of an entry
|
|
1680
|
-
*/
|
|
1681
|
-
getChildren(parentId) {
|
|
1682
|
-
if (!this.activeSession) {
|
|
1683
|
-
return [];
|
|
1684
|
-
}
|
|
1685
|
-
return this.activeSession.entries.filter((e) => e.parentId === parentId);
|
|
1686
|
-
}
|
|
1687
|
-
/**
|
|
1688
|
-
* Get tree structure
|
|
1689
|
-
*/
|
|
1690
|
-
getTree() {
|
|
1691
|
-
if (!this.activeSession) {
|
|
1692
|
-
return [];
|
|
1693
|
-
}
|
|
1694
|
-
return buildTree(this.activeSession.entries, this.activeSession.activeEntryId);
|
|
1695
|
-
}
|
|
1696
|
-
/**
|
|
1697
|
-
* Branch from a specific entry (in-session branching)
|
|
1698
|
-
* Moves the active entry pointer to the specified entry.
|
|
1699
|
-
* Next addMessage() will create a child of that entry, forming a new branch.
|
|
1700
|
-
* Existing entries are not modified or deleted.
|
|
1701
|
-
*/
|
|
1702
|
-
branch(entryId) {
|
|
1703
|
-
if (!this.activeSession) {
|
|
1704
|
-
return false;
|
|
1705
|
-
}
|
|
1706
|
-
if (entryId === null) {
|
|
1707
|
-
this.activeSession.activeEntryId = null;
|
|
1708
|
-
this.activeSession.isDirty = true;
|
|
1709
|
-
return true;
|
|
1710
|
-
}
|
|
1711
|
-
const entry = this.activeSession.entries.find((e) => e.id === entryId);
|
|
1712
|
-
if (!entry) {
|
|
1713
|
-
console.warn(`[SessionManager] Branch target not found: ${entryId}`);
|
|
1714
|
-
return false;
|
|
1715
|
-
}
|
|
1716
|
-
this.activeSession.activeEntryId = entryId;
|
|
1717
|
-
this.activeSession.isDirty = true;
|
|
1718
|
-
return true;
|
|
1719
|
-
}
|
|
1720
|
-
/**
|
|
1721
|
-
* Get all branch paths (all leaf nodes) in the session
|
|
1722
|
-
* Returns array of entry IDs representing branch tips
|
|
1723
|
-
*/
|
|
1724
|
-
getBranchTips() {
|
|
1725
|
-
if (!this.activeSession) {
|
|
1726
|
-
return [];
|
|
1727
|
-
}
|
|
1728
|
-
const hasChildren = /* @__PURE__ */ new Set();
|
|
1729
|
-
for (const entry of this.activeSession.entries) {
|
|
1730
|
-
if (entry.parentId) {
|
|
1731
|
-
hasChildren.add(entry.parentId);
|
|
1732
|
-
}
|
|
1733
|
-
}
|
|
1734
|
-
const leaves = this.activeSession.entries.filter((e) => !hasChildren.has(e.id)).map((e) => e.id);
|
|
1735
|
-
return leaves;
|
|
1736
|
-
}
|
|
1737
|
-
/**
|
|
1738
|
-
* Get the path from root to a specific entry
|
|
1739
|
-
*/
|
|
1740
|
-
getPathToEntry(entryId) {
|
|
1741
|
-
if (!this.activeSession) {
|
|
1742
|
-
return [];
|
|
1743
|
-
}
|
|
1744
|
-
const path = [];
|
|
1745
|
-
const byId = new Map(this.activeSession.entries.map((e) => [e.id, e]));
|
|
1746
|
-
let current = entryId;
|
|
1747
|
-
while (current) {
|
|
1748
|
-
const entry = byId.get(current);
|
|
1749
|
-
if (!entry) break;
|
|
1750
|
-
path.unshift(entry);
|
|
1751
|
-
current = entry.parentId;
|
|
1752
|
-
}
|
|
1753
|
-
return path;
|
|
1754
|
-
}
|
|
1755
|
-
/**
|
|
1756
|
-
* Get the timestamp of the latest compaction entry on the active path.
|
|
1757
|
-
*/
|
|
1758
|
-
getLatestCompactionTimestamp() {
|
|
1759
|
-
if (!this.activeSession?.activeEntryId) {
|
|
1760
|
-
return null;
|
|
1761
|
-
}
|
|
1762
|
-
const path = this.getPathToEntry(this.activeSession.activeEntryId);
|
|
1763
|
-
for (let i = path.length - 1; i >= 0; i--) {
|
|
1764
|
-
const entry = path[i];
|
|
1765
|
-
if (entry.type === "compaction" && entry.compactionData) {
|
|
1766
|
-
return entry.timestamp;
|
|
1767
|
-
}
|
|
1768
|
-
}
|
|
1769
|
-
return null;
|
|
1770
|
-
}
|
|
1771
|
-
/**
|
|
1772
|
-
* Rename the active session
|
|
1773
|
-
*/
|
|
1774
|
-
async rename(name) {
|
|
1775
|
-
if (!this.activeSession) {
|
|
1776
|
-
throw new Error("No active session");
|
|
1777
|
-
}
|
|
1778
|
-
this.activeSession.metadata.name = name;
|
|
1779
|
-
this.activeSession.metadata.nameOrigin = "explicit";
|
|
1780
|
-
this.activeSession.isDirty = true;
|
|
1781
|
-
await this.save();
|
|
1782
|
-
}
|
|
1783
|
-
/**
|
|
1784
|
-
* Close the current session
|
|
1785
|
-
*/
|
|
1786
|
-
async close() {
|
|
1787
|
-
if (this.activeSession?.isDirty) {
|
|
1788
|
-
await this.save();
|
|
1789
|
-
}
|
|
1790
|
-
this.activeSession = null;
|
|
1791
|
-
}
|
|
1792
|
-
/**
|
|
1793
|
-
* Delete a session (active or inactive)
|
|
1794
|
-
*/
|
|
1795
|
-
async deleteSession(sessionId) {
|
|
1796
|
-
if (this.activeSession?.id === sessionId) {
|
|
1797
|
-
await this.close();
|
|
1798
|
-
}
|
|
1799
|
-
return await this.store.delete(sessionId);
|
|
1800
|
-
}
|
|
1801
|
-
/**
|
|
1802
|
-
* List all available sessions
|
|
1803
|
-
*/
|
|
1804
|
-
async listSessions() {
|
|
1805
|
-
return await this.store.listMetadata();
|
|
1806
|
-
}
|
|
1807
|
-
/**
|
|
1808
|
-
* List the most recently updated sessions cheaply (stat-sorted window).
|
|
1809
|
-
* Falls back to a full listing slice for stores without a fast path.
|
|
1810
|
-
*/
|
|
1811
|
-
async listSessionsRecent(limit) {
|
|
1812
|
-
if (this.store.listMetadataRecent) {
|
|
1813
|
-
return await this.store.listMetadataRecent(limit);
|
|
1814
|
-
}
|
|
1815
|
-
const all = await this.store.listMetadata();
|
|
1816
|
-
return { metadata: all.slice(0, limit), totalCount: all.length };
|
|
1817
|
-
}
|
|
1818
|
-
/**
|
|
1819
|
-
* Resolves when a background metadata index rebuild (triggered by
|
|
1820
|
-
* listSessionsRecent on a cold index) completes. Resolves immediately when
|
|
1821
|
-
* the store has no such rebuild in flight.
|
|
1822
|
-
*/
|
|
1823
|
-
async whenIndexRebuilt() {
|
|
1824
|
-
await this.store.whenIndexRebuilt?.();
|
|
1825
|
-
}
|
|
1826
|
-
/**
|
|
1827
|
-
* Check if a session exists
|
|
1828
|
-
*/
|
|
1829
|
-
async hasSession(sessionId) {
|
|
1830
|
-
return await this.store.exists(sessionId);
|
|
1831
|
-
}
|
|
1832
|
-
/**
|
|
1833
|
-
* Fork the current session at a specific entry point
|
|
1834
|
-
*/
|
|
1835
|
-
async forkAtEntry(entryId, newName) {
|
|
1836
|
-
if (!this.activeSession) {
|
|
1837
|
-
throw new Error("No active session to fork");
|
|
1838
|
-
}
|
|
1839
|
-
const sourceEntry = this.activeSession.entries.find((e) => e.id === entryId);
|
|
1840
|
-
if (!sourceEntry) {
|
|
1841
|
-
throw new Error(`Entry not found: ${entryId}`);
|
|
1842
|
-
}
|
|
1843
|
-
const newSessionId = generateSessionId();
|
|
1844
|
-
const now = Date.now();
|
|
1845
|
-
const subtreeEntries = [];
|
|
1846
|
-
const forkEntry = {
|
|
1847
|
-
id: generateEntryId(),
|
|
1848
|
-
parentId: null,
|
|
1849
|
-
type: "fork",
|
|
1850
|
-
timestamp: now,
|
|
1851
|
-
forkData: {
|
|
1852
|
-
sourceEntryId: entryId,
|
|
1853
|
-
sessionId: this.activeSession.id,
|
|
1854
|
-
name: this.activeSession.metadata.name
|
|
1855
|
-
}
|
|
1856
|
-
};
|
|
1857
|
-
subtreeEntries.push(forkEntry);
|
|
1858
|
-
let currentId = entryId;
|
|
1859
|
-
let newParentId = forkEntry.id;
|
|
1860
|
-
while (currentId) {
|
|
1861
|
-
const entry = this.activeSession.entries.find((e) => e.id === currentId);
|
|
1862
|
-
if (!entry) break;
|
|
1863
|
-
const newEntry = {
|
|
1864
|
-
...entry,
|
|
1865
|
-
id: generateEntryId(),
|
|
1866
|
-
parentId: newParentId,
|
|
1867
|
-
timestamp: now
|
|
1868
|
-
};
|
|
1869
|
-
subtreeEntries.push(newEntry);
|
|
1870
|
-
newParentId = newEntry.id;
|
|
1871
|
-
const children = this.activeSession.entries.filter((e) => e.parentId === currentId);
|
|
1872
|
-
if (children.length === 0) break;
|
|
1873
|
-
const activePathChild = children.find((c) => {
|
|
1874
|
-
let checkId = this.activeSession?.activeEntryId ?? null;
|
|
1875
|
-
while (checkId) {
|
|
1876
|
-
if (checkId === c.id) return true;
|
|
1877
|
-
const checkEntry = this.activeSession?.entries.find((e) => e.id === checkId);
|
|
1878
|
-
checkId = checkEntry?.parentId ?? null;
|
|
1879
|
-
}
|
|
1880
|
-
return false;
|
|
1881
|
-
});
|
|
1882
|
-
currentId = activePathChild?.id ?? children[0]?.id ?? null;
|
|
1883
|
-
}
|
|
1884
|
-
await this.store.save(
|
|
1885
|
-
newSessionId,
|
|
1886
|
-
subtreeEntries,
|
|
1887
|
-
{
|
|
1888
|
-
name: newName ?? `${this.activeSession.metadata.name} (Branch)`,
|
|
1889
|
-
nameOrigin: "explicit",
|
|
1890
|
-
createdAt: now,
|
|
1891
|
-
updatedAt: now,
|
|
1892
|
-
tags: this.activeSession.metadata.tags,
|
|
1893
|
-
...typeof this.activeSession.metadata.cwd === "string" ? { cwd: this.activeSession.metadata.cwd } : {}
|
|
1894
|
-
}
|
|
1895
|
-
);
|
|
1896
|
-
return newSessionId;
|
|
1897
|
-
}
|
|
1898
|
-
/**
|
|
1899
|
-
* Fork the current session (create a copy with current state)
|
|
1900
|
-
*/
|
|
1901
|
-
async fork(newName) {
|
|
1902
|
-
if (!this.activeSession) {
|
|
1903
|
-
throw new Error("No active session to fork");
|
|
1904
|
-
}
|
|
1905
|
-
const newSessionId = generateSessionId();
|
|
1906
|
-
const now = Date.now();
|
|
1907
|
-
const idMap = /* @__PURE__ */ new Map();
|
|
1908
|
-
const newEntries = [];
|
|
1909
|
-
for (const entry of this.activeSession.entries) {
|
|
1910
|
-
idMap.set(entry.id, generateEntryId());
|
|
1911
|
-
}
|
|
1912
|
-
for (const entry of this.activeSession.entries) {
|
|
1913
|
-
const newEntry = {
|
|
1914
|
-
...entry,
|
|
1915
|
-
id: idMap.get(entry.id),
|
|
1916
|
-
parentId: entry.parentId ? idMap.get(entry.parentId) : null,
|
|
1917
|
-
timestamp: now
|
|
1918
|
-
};
|
|
1919
|
-
newEntries.push(newEntry);
|
|
1920
|
-
}
|
|
1921
|
-
const newActiveEntryId = this.activeSession.activeEntryId ? idMap.get(this.activeSession.activeEntryId) ?? null : null;
|
|
1922
|
-
await this.store.save(
|
|
1923
|
-
newSessionId,
|
|
1924
|
-
newEntries,
|
|
1925
|
-
{
|
|
1926
|
-
name: newName ?? `${this.activeSession.metadata.name} (Copy)`,
|
|
1927
|
-
nameOrigin: "explicit",
|
|
1928
|
-
createdAt: now,
|
|
1929
|
-
updatedAt: now,
|
|
1930
|
-
activeEntryId: newActiveEntryId,
|
|
1931
|
-
tags: this.activeSession.metadata.tags,
|
|
1932
|
-
...typeof this.activeSession.metadata.cwd === "string" ? { cwd: this.activeSession.metadata.cwd } : {}
|
|
1933
|
-
}
|
|
1934
|
-
);
|
|
1935
|
-
return newSessionId;
|
|
1936
|
-
}
|
|
1937
|
-
/**
|
|
1938
|
-
* Clean up old sessions if exceeding maxSessions
|
|
1939
|
-
*/
|
|
1940
|
-
async cleanup() {
|
|
1941
|
-
if (this.options.maxSessions <= 0) {
|
|
1942
|
-
return 0;
|
|
1943
|
-
}
|
|
1944
|
-
const sessions = await this.store.listMetadata();
|
|
1945
|
-
if (sessions.length <= this.options.maxSessions) {
|
|
1946
|
-
return 0;
|
|
1947
|
-
}
|
|
1948
|
-
const sorted = sessions.sort((a, b) => a.updatedAt - b.updatedAt);
|
|
1949
|
-
const toDelete = sorted.slice(0, sessions.length - this.options.maxSessions);
|
|
1950
|
-
let deleted = 0;
|
|
1951
|
-
for (const session of toDelete) {
|
|
1952
|
-
if (session.id !== this.activeSession?.id) {
|
|
1953
|
-
await this.store.delete(session.id);
|
|
1954
|
-
deleted++;
|
|
1955
|
-
}
|
|
1956
|
-
}
|
|
1957
|
-
if (deleted > 0) {
|
|
1958
|
-
}
|
|
1959
|
-
return deleted;
|
|
1960
|
-
}
|
|
1961
|
-
/**
|
|
1962
|
-
* Dispose the manager
|
|
1963
|
-
*/
|
|
1964
|
-
dispose() {
|
|
1965
|
-
this.stopAutoSave();
|
|
1966
|
-
}
|
|
1967
|
-
};
|
|
1968
|
-
function createSessionManager(options) {
|
|
1969
|
-
return new SessionManager(options);
|
|
1970
|
-
}
|
|
1971
|
-
|
|
1972
|
-
// ../../packages/agent-framework/src/session/controller/activity.ts
|
|
1973
|
-
var AgentSessionActivity = class {
|
|
1974
|
-
pending = /* @__PURE__ */ new Set();
|
|
1975
|
-
get isBusy() {
|
|
1976
|
-
return this.pending.size > 0;
|
|
1977
|
-
}
|
|
1978
|
-
run(action) {
|
|
1979
|
-
return this.track(action());
|
|
1980
|
-
}
|
|
1981
|
-
track(task) {
|
|
1982
|
-
let tracked;
|
|
1983
|
-
tracked = task.finally(() => this.pending.delete(tracked));
|
|
1984
|
-
this.pending.add(tracked);
|
|
1985
|
-
return tracked;
|
|
1986
|
-
}
|
|
1987
|
-
async waitForIdle() {
|
|
1988
|
-
while (this.pending.size > 0) {
|
|
1989
|
-
await Promise.allSettled([...this.pending]);
|
|
1990
|
-
}
|
|
1991
|
-
}
|
|
1992
|
-
};
|
|
1993
|
-
|
|
1994
|
-
// ../../packages/agent-framework/src/session/controller/auto-compact-policy.ts
|
|
1995
|
-
function defaultSetTimeout(callback, delay) {
|
|
1996
|
-
return setTimeout(callback, delay);
|
|
1997
|
-
}
|
|
1998
|
-
function defaultClearTimeout(handle) {
|
|
1999
|
-
clearTimeout(handle);
|
|
2000
|
-
}
|
|
2001
|
-
var AgentSessionAutoCompactScheduler = class {
|
|
2002
|
-
constructor(options) {
|
|
2003
|
-
this.options = options;
|
|
2004
|
-
}
|
|
2005
|
-
options;
|
|
2006
|
-
autoCompactTimer;
|
|
2007
|
-
pendingAutoCompact;
|
|
2008
|
-
runningAutoCompact;
|
|
2009
|
-
runningAbortController;
|
|
2010
|
-
pendingWait;
|
|
2011
|
-
resolvePendingWait;
|
|
2012
|
-
get hasPendingAutoCompact() {
|
|
2013
|
-
return this.autoCompactTimer !== void 0 || this.pendingAutoCompact !== void 0 || this.runningAutoCompact !== void 0;
|
|
2014
|
-
}
|
|
2015
|
-
waitForAutoCompact() {
|
|
2016
|
-
return this.pendingWait ?? Promise.resolve();
|
|
2017
|
-
}
|
|
2018
|
-
schedule(decision, delayMs = 0) {
|
|
2019
|
-
if (this.options.getRuntimeGuardTriggered()) {
|
|
2020
|
-
return;
|
|
2021
|
-
}
|
|
2022
|
-
this.ensurePendingWait();
|
|
2023
|
-
this.pendingAutoCompact = decision;
|
|
2024
|
-
if (this.autoCompactTimer !== void 0) {
|
|
2025
|
-
return;
|
|
2026
|
-
}
|
|
2027
|
-
const setTimer = this.options.retry?.setTimeout ?? defaultSetTimeout;
|
|
2028
|
-
this.autoCompactTimer = setTimer(() => {
|
|
2029
|
-
this.autoCompactTimer = void 0;
|
|
2030
|
-
const pending = this.pendingAutoCompact;
|
|
2031
|
-
this.pendingAutoCompact = void 0;
|
|
2032
|
-
if (!pending || this.options.getDisposed()) {
|
|
2033
|
-
this.resolvePending();
|
|
2034
|
-
return;
|
|
2035
|
-
}
|
|
2036
|
-
if (this.options.isStreaming()) {
|
|
2037
|
-
this.pendingAutoCompact = pending;
|
|
2038
|
-
this.schedule(pending, 100);
|
|
2039
|
-
return;
|
|
2040
|
-
}
|
|
2041
|
-
const abortController = new AbortController();
|
|
2042
|
-
this.runningAbortController = abortController;
|
|
2043
|
-
const running = this.options.runAutoCompact(pending, abortController.signal).catch(() => {
|
|
2044
|
-
}).finally(() => {
|
|
2045
|
-
if (this.runningAutoCompact === running) {
|
|
2046
|
-
this.runningAutoCompact = void 0;
|
|
2047
|
-
this.runningAbortController = void 0;
|
|
2048
|
-
this.resolvePending();
|
|
2049
|
-
}
|
|
2050
|
-
});
|
|
2051
|
-
this.runningAutoCompact = running;
|
|
2052
|
-
}, delayMs);
|
|
2053
|
-
}
|
|
2054
|
-
cancel() {
|
|
2055
|
-
if (this.autoCompactTimer !== void 0) {
|
|
2056
|
-
const clearTimer = this.options.retry?.clearTimeout ?? defaultClearTimeout;
|
|
2057
|
-
clearTimer(this.autoCompactTimer);
|
|
2058
|
-
this.autoCompactTimer = void 0;
|
|
2059
|
-
}
|
|
2060
|
-
this.pendingAutoCompact = void 0;
|
|
2061
|
-
this.runningAbortController?.abort();
|
|
2062
|
-
this.runningAbortController = void 0;
|
|
2063
|
-
this.runningAutoCompact = void 0;
|
|
2064
|
-
this.resolvePending();
|
|
2065
|
-
}
|
|
2066
|
-
ensurePendingWait() {
|
|
2067
|
-
if (this.pendingWait) return;
|
|
2068
|
-
this.pendingWait = new Promise((resolve) => {
|
|
2069
|
-
this.resolvePendingWait = resolve;
|
|
2070
|
-
});
|
|
2071
|
-
}
|
|
2072
|
-
resolvePending() {
|
|
2073
|
-
const resolve = this.resolvePendingWait;
|
|
2074
|
-
this.pendingWait = void 0;
|
|
2075
|
-
this.resolvePendingWait = void 0;
|
|
2076
|
-
resolve?.();
|
|
2077
|
-
}
|
|
2078
|
-
};
|
|
2079
|
-
|
|
2080
|
-
// ../../packages/agent-framework/src/session/controller/auto-continue-policy.ts
|
|
2081
|
-
function defaultSetTimeout2(callback, delay) {
|
|
2082
|
-
return setTimeout(callback, delay);
|
|
2083
|
-
}
|
|
2084
|
-
function defaultClearTimeout2(handle) {
|
|
2085
|
-
clearTimeout(handle);
|
|
2086
|
-
}
|
|
2087
|
-
var AgentSessionAutoContinueController = class {
|
|
2088
|
-
constructor(options) {
|
|
2089
|
-
this.options = options;
|
|
2090
|
-
}
|
|
2091
|
-
options;
|
|
2092
|
-
autoContinueTimer;
|
|
2093
|
-
pendingAutoContinue;
|
|
2094
|
-
resolvePendingAutoContinue;
|
|
2095
|
-
get hasPendingAutoContinue() {
|
|
2096
|
-
return this.autoContinueTimer !== void 0 || this.pendingAutoContinue !== void 0;
|
|
2097
|
-
}
|
|
2098
|
-
waitForAutoContinue() {
|
|
2099
|
-
return this.pendingAutoContinue ?? Promise.resolve();
|
|
2100
|
-
}
|
|
2101
|
-
schedule(messages) {
|
|
2102
|
-
if (!this.options.getAutoContinueMessage || this.options.getDisposed() || this.autoContinueTimer !== void 0) {
|
|
2103
|
-
return;
|
|
2104
|
-
}
|
|
2105
|
-
this.ensurePending();
|
|
2106
|
-
const setTimer = this.options.retry?.setTimeout ?? defaultSetTimeout2;
|
|
2107
|
-
const snapshot = messages.slice();
|
|
2108
|
-
this.autoContinueTimer = setTimer(() => {
|
|
2109
|
-
this.autoContinueTimer = void 0;
|
|
2110
|
-
void this.maybeScheduleAutoContinue(snapshot).finally(() => this.resolvePending());
|
|
2111
|
-
}, 0);
|
|
2112
|
-
}
|
|
2113
|
-
cancel() {
|
|
2114
|
-
if (this.autoContinueTimer === void 0) {
|
|
2115
|
-
return;
|
|
2116
|
-
}
|
|
2117
|
-
const clearTimer = this.options.retry?.clearTimeout ?? defaultClearTimeout2;
|
|
2118
|
-
clearTimer(this.autoContinueTimer);
|
|
2119
|
-
this.autoContinueTimer = void 0;
|
|
2120
|
-
this.resolvePending();
|
|
2121
|
-
}
|
|
2122
|
-
async maybeScheduleAutoContinue(messages) {
|
|
2123
|
-
if (!this.options.getAutoContinueMessage || this.options.getDisposed() || this.options.getRuntimeGuardTriggered() || this.options.isBlockedByRuntimePolicy() || this.options.hasQueuedMessages() || this.options.isStreaming()) {
|
|
2124
|
-
if (this.options.hasQueuedMessages()) {
|
|
2125
|
-
this.options.emit({ type: "auto_continue_skipped", reason: "queued_messages" });
|
|
2126
|
-
}
|
|
2127
|
-
return;
|
|
2128
|
-
}
|
|
2129
|
-
const lastMessage = messages[messages.length - 1];
|
|
2130
|
-
if (lastMessage?.role === "assistant" && (lastMessage.stopReason === "error" || lastMessage.stopReason === "aborted")) {
|
|
2131
|
-
this.options.emit({ type: "auto_continue_skipped", reason: "last_turn_failed" });
|
|
2132
|
-
return;
|
|
2133
|
-
}
|
|
2134
|
-
const result = await this.options.getAutoContinueMessage({
|
|
2135
|
-
agent: this.options.agent,
|
|
2136
|
-
sessionManager: this.options.sessionManager,
|
|
2137
|
-
cwd: this.options.cwd,
|
|
2138
|
-
messages,
|
|
2139
|
-
hasQueuedMessages: this.options.hasQueuedMessages()
|
|
2140
|
-
});
|
|
2141
|
-
const message = result && "role" in result ? result : result?.message;
|
|
2142
|
-
const reason = result && "role" in result ? void 0 : result?.reason;
|
|
2143
|
-
if (!message) {
|
|
2144
|
-
this.options.emit({ type: "auto_continue_skipped", reason: reason ?? "no_message" });
|
|
2145
|
-
return;
|
|
2146
|
-
}
|
|
2147
|
-
if (this.options.hasQueuedMessages() || this.options.isStreaming() || this.options.isBlockedByRuntimePolicy()) {
|
|
2148
|
-
this.options.emit({ type: "auto_continue_skipped", reason: "runtime_busy" });
|
|
2149
|
-
return;
|
|
2150
|
-
}
|
|
2151
|
-
this.options.emit({ type: "auto_continue_scheduled", reason });
|
|
2152
|
-
this.options.agent.followUp(message);
|
|
2153
|
-
const setTimer = this.options.retry?.setTimeout ?? defaultSetTimeout2;
|
|
2154
|
-
await new Promise((resolve) => {
|
|
2155
|
-
this.autoContinueTimer = setTimer(() => {
|
|
2156
|
-
this.autoContinueTimer = void 0;
|
|
2157
|
-
if (this.options.getDisposed()) {
|
|
2158
|
-
resolve();
|
|
2159
|
-
return;
|
|
2160
|
-
}
|
|
2161
|
-
void this.options.continueTurn().catch(() => {
|
|
2162
|
-
}).finally(resolve);
|
|
2163
|
-
}, 0);
|
|
2164
|
-
});
|
|
2165
|
-
}
|
|
2166
|
-
ensurePending() {
|
|
2167
|
-
if (this.pendingAutoContinue) return;
|
|
2168
|
-
this.pendingAutoContinue = new Promise((resolve) => {
|
|
2169
|
-
this.resolvePendingAutoContinue = resolve;
|
|
2170
|
-
});
|
|
2171
|
-
}
|
|
2172
|
-
resolvePending() {
|
|
2173
|
-
const resolve = this.resolvePendingAutoContinue;
|
|
2174
|
-
this.pendingAutoContinue = void 0;
|
|
2175
|
-
this.resolvePendingAutoContinue = void 0;
|
|
2176
|
-
resolve?.();
|
|
2177
|
-
}
|
|
2178
|
-
};
|
|
2179
|
-
|
|
2180
|
-
// ../../packages/agent-framework/src/session/controller/compaction-policy.ts
|
|
2181
|
-
function removeTrailingAssistantError(params) {
|
|
2182
|
-
const messages = params.agent.state.messages;
|
|
2183
|
-
const lastMessage = messages[messages.length - 1];
|
|
2184
|
-
if (lastMessage?.role === "assistant" && (lastMessage.stopReason === "error" || Boolean(lastMessage.errorMessage))) {
|
|
2185
|
-
params.replaceMessagesAndMarkSynced(messages.slice(0, -1), { updateSession: true });
|
|
2186
|
-
}
|
|
2187
|
-
}
|
|
2188
|
-
async function runAgentSessionCompaction(params) {
|
|
2189
|
-
if (!params.compaction) {
|
|
2190
|
-
throw new Error("No compaction handler configured");
|
|
2191
|
-
}
|
|
2192
|
-
const abortedResult = () => ({ aborted: true });
|
|
2193
|
-
const isAborted = () => params.signal?.aborted === true;
|
|
2194
|
-
params.emit({ type: "auto_compaction_start", reason: params.reason, willRetry: params.willRetry });
|
|
2195
|
-
try {
|
|
2196
|
-
if (isAborted()) {
|
|
2197
|
-
const result2 = abortedResult();
|
|
2198
|
-
params.emit({ type: "auto_compaction_end", reason: params.reason, willRetry: params.willRetry, result: result2 });
|
|
2199
|
-
return result2;
|
|
2200
|
-
}
|
|
2201
|
-
if (params.willRetry) {
|
|
2202
|
-
params.removeTrailingAssistantError();
|
|
2203
|
-
}
|
|
2204
|
-
const result = await params.compaction.compact({
|
|
2205
|
-
reason: params.reason,
|
|
2206
|
-
willRetry: params.willRetry,
|
|
2207
|
-
agent: params.agent,
|
|
2208
|
-
sessionManager: params.sessionManager,
|
|
2209
|
-
messages: params.agent.state.messages.slice(),
|
|
2210
|
-
cwd: params.cwd,
|
|
2211
|
-
signal: params.signal
|
|
2212
|
-
});
|
|
2213
|
-
if (isAborted() || result?.aborted) {
|
|
2214
|
-
const aborted = { ...result, aborted: true };
|
|
2215
|
-
params.emit({ type: "auto_compaction_end", reason: params.reason, willRetry: params.willRetry, result: aborted });
|
|
2216
|
-
return aborted;
|
|
2217
|
-
}
|
|
2218
|
-
if (result?.summary && result.firstKeptEntryId) {
|
|
2219
|
-
params.sessionManager.appendCompaction(result.summary, result.firstKeptEntryId, result.tokensBefore);
|
|
2220
|
-
params.replaceMessagesAndMarkSynced(params.sessionManager.getMessages());
|
|
2221
|
-
} else if (result?.messages) {
|
|
2222
|
-
params.replaceMessagesAndMarkSynced(result.messages, { updateSession: true });
|
|
2223
|
-
}
|
|
2224
|
-
if (isAborted()) {
|
|
2225
|
-
const aborted = { ...result, aborted: true };
|
|
2226
|
-
params.emit({ type: "auto_compaction_end", reason: params.reason, willRetry: params.willRetry, result: aborted });
|
|
2227
|
-
return aborted;
|
|
2228
|
-
}
|
|
2229
|
-
params.markSyncedToAgentState();
|
|
2230
|
-
await params.sessionManager.save();
|
|
2231
|
-
if (isAborted()) {
|
|
2232
|
-
const aborted = { ...result, aborted: true };
|
|
2233
|
-
params.emit({ type: "auto_compaction_end", reason: params.reason, willRetry: params.willRetry, result: aborted });
|
|
2234
|
-
return aborted;
|
|
2235
|
-
}
|
|
2236
|
-
if (params.willRetry && !result?.aborted) {
|
|
2237
|
-
await params.continueTurn();
|
|
2238
|
-
}
|
|
2239
|
-
params.emit({ type: "auto_compaction_end", reason: params.reason, willRetry: params.willRetry, result });
|
|
2240
|
-
return result;
|
|
2241
|
-
} catch (error) {
|
|
2242
|
-
if (isAborted()) {
|
|
2243
|
-
const result = abortedResult();
|
|
2244
|
-
params.emit({ type: "auto_compaction_end", reason: params.reason, willRetry: params.willRetry, result });
|
|
2245
|
-
return result;
|
|
2246
|
-
}
|
|
2247
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2248
|
-
params.emit({ type: "auto_compaction_end", reason: params.reason, willRetry: params.willRetry, errorMessage });
|
|
2249
|
-
throw error;
|
|
2250
|
-
}
|
|
2251
|
-
}
|
|
2252
|
-
|
|
2253
|
-
// ../../packages/agent-framework/src/session/controller/prompt.ts
|
|
2254
|
-
function isAgentMessage(value) {
|
|
2255
|
-
return !!value && typeof value === "object" && "role" in value;
|
|
2256
|
-
}
|
|
2257
|
-
function isUserContentBlock(value) {
|
|
2258
|
-
if (!value || typeof value !== "object" || !("type" in value)) {
|
|
2259
|
-
return false;
|
|
2260
|
-
}
|
|
2261
|
-
const type = value.type;
|
|
2262
|
-
return type === "text" || type === "image" || type === "audio" || type === "video" || type === "fileRef";
|
|
2263
|
-
}
|
|
2264
|
-
function normalizePromptInput(input) {
|
|
2265
|
-
if (typeof input === "string") {
|
|
2266
|
-
return [{ role: "user", content: input, timestamp: Date.now() }];
|
|
2267
|
-
}
|
|
2268
|
-
if (Array.isArray(input)) {
|
|
2269
|
-
if (input.every(isAgentMessage)) {
|
|
2270
|
-
return input;
|
|
2271
|
-
}
|
|
2272
|
-
if (input.every(isUserContentBlock)) {
|
|
2273
|
-
return [{ role: "user", content: input, timestamp: Date.now() }];
|
|
2274
|
-
}
|
|
2275
|
-
throw new Error("Invalid prompt array: expected user content blocks or agent messages.");
|
|
2276
|
-
}
|
|
2277
|
-
if (isAgentMessage(input)) {
|
|
2278
|
-
return [input];
|
|
2279
|
-
}
|
|
2280
|
-
throw new Error("Invalid prompt input.");
|
|
2281
|
-
}
|
|
2282
|
-
function textFromMessage(message) {
|
|
2283
|
-
const content = message.content;
|
|
2284
|
-
if (typeof content === "string") {
|
|
2285
|
-
return content;
|
|
2286
|
-
}
|
|
2287
|
-
if (Array.isArray(content)) {
|
|
2288
|
-
return content.filter((block) => !!block && typeof block === "object" && "type" in block).filter((block) => block.type === "text").map((block) => block.text || "").join("");
|
|
2289
|
-
}
|
|
2290
|
-
return "";
|
|
2291
|
-
}
|
|
2292
|
-
async function preparePromptMessages(params) {
|
|
2293
|
-
if (params.baseSystemPrompt !== void 0) {
|
|
2294
|
-
params.agent.setSystemPrompt(params.baseSystemPrompt);
|
|
2295
|
-
}
|
|
2296
|
-
if (!params.prepareTurn) {
|
|
2297
|
-
return { handled: false, messages: params.messages };
|
|
2298
|
-
}
|
|
2299
|
-
params.emit({ type: "prepare_turn", source: params.options?.source, messageCount: params.messages.length });
|
|
2300
|
-
const result = await params.prepareTurn({
|
|
2301
|
-
source: params.options?.source,
|
|
2302
|
-
streamingBehavior: params.options?.streamingBehavior,
|
|
2303
|
-
messages: params.messages,
|
|
2304
|
-
agent: params.agent,
|
|
2305
|
-
sessionManager: params.sessionManager,
|
|
2306
|
-
cwd: params.cwd,
|
|
2307
|
-
baseSystemPrompt: params.baseSystemPrompt
|
|
2308
|
-
});
|
|
2309
|
-
return {
|
|
2310
|
-
handled: result?.handled === true,
|
|
2311
|
-
messages: result?.messages ?? params.messages,
|
|
2312
|
-
systemPrompt: result?.systemPrompt
|
|
2313
|
-
};
|
|
2314
|
-
}
|
|
2315
|
-
|
|
2316
|
-
// ../../packages/agent-framework/src/session/controller/pre-request-policy.ts
|
|
2317
|
-
async function runPreRequestAutoCompact(params) {
|
|
2318
|
-
if (params.disposed || params.runtimeGuardTriggered || params.isStreaming || params.recoveryActive || params.agent.state.messages.length === 0) {
|
|
2319
|
-
return;
|
|
2320
|
-
}
|
|
2321
|
-
const decision = params.getAutoCompactDecision?.({
|
|
2322
|
-
agent: params.agent,
|
|
2323
|
-
sessionManager: params.sessionManager,
|
|
2324
|
-
cwd: params.cwd,
|
|
2325
|
-
messages: params.agent.state.messages.slice()
|
|
2326
|
-
});
|
|
2327
|
-
if (decision?.reason !== "threshold") {
|
|
2328
|
-
return;
|
|
2329
|
-
}
|
|
2330
|
-
try {
|
|
2331
|
-
await params.runAutoCompact();
|
|
2332
|
-
} catch {
|
|
2333
|
-
params.setRecoveryState("none");
|
|
2334
|
-
}
|
|
2335
|
-
}
|
|
2336
|
-
|
|
2337
|
-
// ../../packages/agent-framework/src/session/controller/queue.ts
|
|
2338
|
-
function cloneQueuedInput(input) {
|
|
2339
|
-
return {
|
|
2340
|
-
...input,
|
|
2341
|
-
messages: input.messages.slice()
|
|
2342
|
-
};
|
|
2343
|
-
}
|
|
2344
|
-
var AgentSessionQueue = class {
|
|
2345
|
-
agent;
|
|
2346
|
-
getIsStreaming;
|
|
2347
|
-
emit;
|
|
2348
|
-
queuedInputs = [];
|
|
2349
|
-
queueIdCounter = 0;
|
|
2350
|
-
constructor(options) {
|
|
2351
|
-
this.agent = options.agent;
|
|
2352
|
-
this.getIsStreaming = options.getIsStreaming;
|
|
2353
|
-
this.emit = options.emit;
|
|
2354
|
-
}
|
|
2355
|
-
get length() {
|
|
2356
|
-
return this.queuedInputs.length;
|
|
2357
|
-
}
|
|
2358
|
-
get hasQueuedMessages() {
|
|
2359
|
-
return this.queuedInputs.length > 0 || this.agent.hasQueuedMessages();
|
|
2360
|
-
}
|
|
2361
|
-
get snapshot() {
|
|
2362
|
-
return this.queuedInputs.map(cloneQueuedInput);
|
|
2363
|
-
}
|
|
2364
|
-
peek() {
|
|
2365
|
-
return this.queuedInputs[0];
|
|
2366
|
-
}
|
|
2367
|
-
shift() {
|
|
2368
|
-
const queuedInput = this.queuedInputs.shift();
|
|
2369
|
-
if (queuedInput) {
|
|
2370
|
-
this.emitQueueChanged();
|
|
2371
|
-
}
|
|
2372
|
-
return queuedInput;
|
|
2373
|
-
}
|
|
2374
|
-
enqueue(messages, mode, options) {
|
|
2375
|
-
if (options.dedupeKey && this.queuedInputs.some((input) => input.dedupeKey === options.dedupeKey)) {
|
|
2376
|
-
return;
|
|
2377
|
-
}
|
|
2378
|
-
for (const message of messages) {
|
|
2379
|
-
const queuedInput = {
|
|
2380
|
-
id: `queued-${++this.queueIdCounter}`,
|
|
2381
|
-
mode,
|
|
2382
|
-
source: options.source,
|
|
2383
|
-
displayText: options.displayText ?? textFromMessage(message),
|
|
2384
|
-
dedupeKey: options.dedupeKey,
|
|
2385
|
-
clearEditor: options.clearEditor ?? true,
|
|
2386
|
-
visibility: options.visibility ?? "user",
|
|
2387
|
-
deliveredToAgent: false,
|
|
2388
|
-
messages: [message],
|
|
2389
|
-
createdAt: Date.now()
|
|
2390
|
-
};
|
|
2391
|
-
this.queuedInputs.push(queuedInput);
|
|
2392
|
-
if (this.getIsStreaming()) {
|
|
2393
|
-
this.deliverToAgent(queuedInput);
|
|
2394
|
-
}
|
|
2395
|
-
}
|
|
2396
|
-
this.emitQueueChanged();
|
|
2397
|
-
}
|
|
2398
|
-
deliverToAgent(queuedInput) {
|
|
2399
|
-
if (queuedInput.deliveredToAgent) {
|
|
2400
|
-
return;
|
|
2401
|
-
}
|
|
2402
|
-
for (const message of queuedInput.messages) {
|
|
2403
|
-
if (queuedInput.mode === "steer") {
|
|
2404
|
-
this.agent.steer(message);
|
|
2405
|
-
} else {
|
|
2406
|
-
this.agent.followUp(message);
|
|
2407
|
-
}
|
|
2408
|
-
}
|
|
2409
|
-
queuedInput.deliveredToAgent = true;
|
|
2410
|
-
}
|
|
2411
|
-
markMessageDelivered(message) {
|
|
2412
|
-
if (this.queuedInputs.length === 0 || message.role !== "user") {
|
|
2413
|
-
return;
|
|
2414
|
-
}
|
|
2415
|
-
const index = this.queuedInputs.findIndex((queued) => queued.messages.includes(message));
|
|
2416
|
-
if (index >= 0) {
|
|
2417
|
-
const [delivered2] = this.queuedInputs.splice(index, 1);
|
|
2418
|
-
if (delivered2) {
|
|
2419
|
-
this.emit({ type: "queued_turn_dispatched", queuedInput: cloneQueuedInput(delivered2) });
|
|
2420
|
-
}
|
|
2421
|
-
this.emitQueueChanged();
|
|
2422
|
-
return;
|
|
2423
|
-
}
|
|
2424
|
-
const delivered = this.queuedInputs.shift();
|
|
2425
|
-
if (delivered) {
|
|
2426
|
-
this.emit({ type: "queued_turn_dispatched", queuedInput: cloneQueuedInput(delivered) });
|
|
2427
|
-
}
|
|
2428
|
-
this.emitQueueChanged();
|
|
2429
|
-
}
|
|
2430
|
-
clear() {
|
|
2431
|
-
if (this.queuedInputs.length === 0 && !this.agent.hasQueuedMessages()) {
|
|
2432
|
-
return;
|
|
2433
|
-
}
|
|
2434
|
-
this.queuedInputs = [];
|
|
2435
|
-
this.agent.clearAllQueues();
|
|
2436
|
-
this.emitQueueChanged();
|
|
2437
|
-
}
|
|
2438
|
-
drainForEditor() {
|
|
2439
|
-
const drained = this.snapshot;
|
|
2440
|
-
this.queuedInputs = [];
|
|
2441
|
-
this.agent.clearAllQueues();
|
|
2442
|
-
this.emitQueueChanged();
|
|
2443
|
-
return drained;
|
|
2444
|
-
}
|
|
2445
|
-
replaceText(search, replacement) {
|
|
2446
|
-
if (!search || this.queuedInputs.length === 0) {
|
|
2447
|
-
return;
|
|
2448
|
-
}
|
|
2449
|
-
let changed = false;
|
|
2450
|
-
for (const input of this.queuedInputs) {
|
|
2451
|
-
if (input.displayText.includes(search)) {
|
|
2452
|
-
input.displayText = input.displayText.split(search).join(replacement);
|
|
2453
|
-
changed = true;
|
|
2454
|
-
}
|
|
2455
|
-
for (const message of input.messages) {
|
|
2456
|
-
if (message.role !== "user") {
|
|
2457
|
-
continue;
|
|
2458
|
-
}
|
|
2459
|
-
const content = message.content;
|
|
2460
|
-
if (typeof content === "string" && content.includes(search)) {
|
|
2461
|
-
message.content = content.split(search).join(replacement);
|
|
2462
|
-
changed = true;
|
|
2463
|
-
} else if (Array.isArray(content)) {
|
|
2464
|
-
for (const block of content) {
|
|
2465
|
-
if (block && typeof block === "object" && "type" in block && block.type === "text" && typeof block.text === "string" && block.text.includes(search)) {
|
|
2466
|
-
block.text = block.text.split(search).join(replacement);
|
|
2467
|
-
changed = true;
|
|
2468
|
-
}
|
|
2469
|
-
}
|
|
2470
|
-
}
|
|
2471
|
-
}
|
|
2472
|
-
}
|
|
2473
|
-
if (changed) {
|
|
2474
|
-
this.emitQueueChanged();
|
|
2475
|
-
}
|
|
2476
|
-
}
|
|
2477
|
-
emitQueueChanged() {
|
|
2478
|
-
this.emit({ type: "queue_changed", queuedInputs: this.snapshot });
|
|
2479
|
-
}
|
|
2480
|
-
};
|
|
2481
|
-
|
|
2482
|
-
// ../../packages/agent-framework/src/session/controller/queue-dispatcher.ts
|
|
2483
|
-
function defaultSetTimeout3(callback, delay) {
|
|
2484
|
-
return setTimeout(callback, delay);
|
|
2485
|
-
}
|
|
2486
|
-
function defaultClearTimeout3(handle) {
|
|
2487
|
-
clearTimeout(handle);
|
|
2488
|
-
}
|
|
2489
|
-
var AgentSessionQueueDispatcher = class {
|
|
2490
|
-
constructor(options) {
|
|
2491
|
-
this.options = options;
|
|
2492
|
-
}
|
|
2493
|
-
options;
|
|
2494
|
-
dispatchTimer;
|
|
2495
|
-
pendingDispatch;
|
|
2496
|
-
resolvePendingDispatch;
|
|
2497
|
-
get hasPendingDispatch() {
|
|
2498
|
-
return this.dispatchTimer !== void 0 || this.pendingDispatch !== void 0;
|
|
2499
|
-
}
|
|
2500
|
-
waitForDispatch() {
|
|
2501
|
-
return this.pendingDispatch ?? Promise.resolve();
|
|
2502
|
-
}
|
|
2503
|
-
schedule(delayMs = 0) {
|
|
2504
|
-
if (this.options.getDisposed() || this.options.getRuntimeGuardTriggered() || this.dispatchTimer !== void 0) {
|
|
2505
|
-
return;
|
|
2506
|
-
}
|
|
2507
|
-
this.ensurePending();
|
|
2508
|
-
const setTimer = this.options.retry?.setTimeout ?? defaultSetTimeout3;
|
|
2509
|
-
this.dispatchTimer = setTimer(() => {
|
|
2510
|
-
this.dispatchTimer = void 0;
|
|
2511
|
-
void this.dispatchNext().finally(() => this.resolvePending());
|
|
2512
|
-
}, delayMs);
|
|
2513
|
-
}
|
|
2514
|
-
cancel() {
|
|
2515
|
-
if (this.dispatchTimer === void 0) {
|
|
2516
|
-
return;
|
|
2517
|
-
}
|
|
2518
|
-
const clearTimer = this.options.retry?.clearTimeout ?? defaultClearTimeout3;
|
|
2519
|
-
clearTimer(this.dispatchTimer);
|
|
2520
|
-
this.dispatchTimer = void 0;
|
|
2521
|
-
this.resolvePending();
|
|
2522
|
-
}
|
|
2523
|
-
async dispatchNext() {
|
|
2524
|
-
if (this.options.getDisposed() || this.options.isStreaming() || this.options.isBlockedByRuntimePolicy()) {
|
|
2525
|
-
return false;
|
|
2526
|
-
}
|
|
2527
|
-
const queuedInput = this.options.queue.peek();
|
|
2528
|
-
if (!queuedInput) {
|
|
2529
|
-
return false;
|
|
2530
|
-
}
|
|
2531
|
-
const decision = await this.options.canDispatchQueuedTurn?.({
|
|
2532
|
-
agent: this.options.agent,
|
|
2533
|
-
sessionManager: this.options.sessionManager,
|
|
2534
|
-
cwd: this.options.cwd,
|
|
2535
|
-
queuedInput: cloneQueuedInput(queuedInput)
|
|
2536
|
-
});
|
|
2537
|
-
const dispatchDecision = typeof decision === "boolean" ? { allowed: decision } : decision ?? { allowed: true };
|
|
2538
|
-
if (!dispatchDecision.allowed) {
|
|
2539
|
-
this.options.emit({
|
|
2540
|
-
type: "queued_turn_blocked",
|
|
2541
|
-
queuedInput: cloneQueuedInput(queuedInput),
|
|
2542
|
-
reason: dispatchDecision.reason
|
|
2543
|
-
});
|
|
2544
|
-
if (dispatchDecision.retryAfterMs !== void 0 && dispatchDecision.retryAfterMs >= 0) {
|
|
2545
|
-
this.schedule(dispatchDecision.retryAfterMs);
|
|
2546
|
-
}
|
|
2547
|
-
return false;
|
|
2548
|
-
}
|
|
2549
|
-
await this.options.runPreRequestAutoCompact();
|
|
2550
|
-
if (this.options.getDisposed() || this.options.isStreaming() || this.options.isBlockedByRuntimePolicy()) {
|
|
2551
|
-
this.schedule(100);
|
|
2552
|
-
return false;
|
|
2553
|
-
}
|
|
2554
|
-
const dispatched = this.options.queue.shift();
|
|
2555
|
-
if (!dispatched) {
|
|
2556
|
-
return false;
|
|
2557
|
-
}
|
|
2558
|
-
this.options.emit({ type: "queued_turn_dispatched", queuedInput: cloneQueuedInput(dispatched) });
|
|
2559
|
-
if (dispatched.deliveredToAgent || this.options.agent.state.messages.at(-1)?.role === "assistant") {
|
|
2560
|
-
if (!dispatched.deliveredToAgent) {
|
|
2561
|
-
this.options.queue.deliverToAgent(dispatched);
|
|
2562
|
-
}
|
|
2563
|
-
await this.options.continueTurn();
|
|
2564
|
-
} else {
|
|
2565
|
-
const prepared = await this.options.preparePromptMessages(dispatched.messages, {
|
|
2566
|
-
source: dispatched.source,
|
|
2567
|
-
streamingBehavior: dispatched.mode,
|
|
2568
|
-
displayText: dispatched.displayText,
|
|
2569
|
-
dedupeKey: dispatched.dedupeKey,
|
|
2570
|
-
clearEditor: dispatched.clearEditor,
|
|
2571
|
-
visibility: dispatched.visibility
|
|
2572
|
-
});
|
|
2573
|
-
if (!prepared.handled) {
|
|
2574
|
-
if (prepared.systemPrompt !== void 0) {
|
|
2575
|
-
this.options.agent.setSystemPrompt(prepared.systemPrompt);
|
|
2576
|
-
}
|
|
2577
|
-
await this.options.agent.prompt(prepared.messages);
|
|
2578
|
-
await this.options.syncNewAgentMessages();
|
|
2579
|
-
}
|
|
2580
|
-
}
|
|
2581
|
-
return true;
|
|
2582
|
-
}
|
|
2583
|
-
ensurePending() {
|
|
2584
|
-
if (this.pendingDispatch) return;
|
|
2585
|
-
this.pendingDispatch = new Promise((resolve) => {
|
|
2586
|
-
this.resolvePendingDispatch = resolve;
|
|
2587
|
-
});
|
|
2588
|
-
}
|
|
2589
|
-
resolvePending() {
|
|
2590
|
-
const resolve = this.resolvePendingDispatch;
|
|
2591
|
-
this.pendingDispatch = void 0;
|
|
2592
|
-
this.resolvePendingDispatch = void 0;
|
|
2593
|
-
resolve?.();
|
|
2594
|
-
}
|
|
2595
|
-
};
|
|
2596
|
-
|
|
2597
|
-
// ../../packages/agent-framework/src/session/controller/failure-classification.ts
|
|
2598
|
-
function isAssistantFailureMessage(message) {
|
|
2599
|
-
return message?.role === "assistant" && (message.stopReason === "error" || message.stopReason === "aborted");
|
|
2600
|
-
}
|
|
2601
|
-
function isAssistantTerminalCompletion(message) {
|
|
2602
|
-
if (message?.role !== "assistant") return false;
|
|
2603
|
-
if (message.stopReason === "error" || message.stopReason === "aborted") return false;
|
|
2604
|
-
if (message.stopReason === "toolUse") return false;
|
|
2605
|
-
return !message.content.some((block) => block.type === "toolCall");
|
|
2606
|
-
}
|
|
2607
|
-
function createFailureClassification(context, retry, classifyFailure) {
|
|
2608
|
-
const hostClassification = classifyFailure?.(context);
|
|
2609
|
-
if (hostClassification) {
|
|
2610
|
-
return hostClassification;
|
|
2611
|
-
}
|
|
2612
|
-
const retryable = retry?.isRetryableError(context.errorMessage) === true;
|
|
2613
|
-
const userAbort = context.message?.role === "assistant" && context.message.stopReason === "aborted";
|
|
2614
|
-
return {
|
|
2615
|
-
kind: userAbort ? "user_abort" : retryable ? "provider_transient" : "unknown",
|
|
2616
|
-
retryable: retryable && !userAbort,
|
|
2617
|
-
compactable: false,
|
|
2618
|
-
userAbort,
|
|
2619
|
-
terminal: !retryable || userAbort,
|
|
2620
|
-
message: context.errorMessage
|
|
2621
|
-
};
|
|
2622
|
-
}
|
|
2623
|
-
function asTerminalFailure(failure, message = failure.message) {
|
|
2624
|
-
return {
|
|
2625
|
-
...failure,
|
|
2626
|
-
retryable: false,
|
|
2627
|
-
compactable: false,
|
|
2628
|
-
terminal: true,
|
|
2629
|
-
message
|
|
2630
|
-
};
|
|
2631
|
-
}
|
|
2632
|
-
|
|
2633
|
-
// ../../packages/agent-framework/src/session/controller/retry-policy.ts
|
|
2634
|
-
function defaultSetTimeout4(callback, delay) {
|
|
2635
|
-
return setTimeout(callback, delay);
|
|
2636
|
-
}
|
|
2637
|
-
function defaultClearTimeout4(handle) {
|
|
2638
|
-
clearTimeout(handle);
|
|
2639
|
-
}
|
|
2640
|
-
var AgentSessionRetryController = class {
|
|
2641
|
-
constructor(retry, emit, removeTrailingAssistantError2, continueTurn, setRecoveryState) {
|
|
2642
|
-
this.retry = retry;
|
|
2643
|
-
this.emit = emit;
|
|
2644
|
-
this.removeTrailingAssistantError = removeTrailingAssistantError2;
|
|
2645
|
-
this.continueTurn = continueTurn;
|
|
2646
|
-
this.setRecoveryState = setRecoveryState;
|
|
2647
|
-
}
|
|
2648
|
-
retry;
|
|
2649
|
-
emit;
|
|
2650
|
-
removeTrailingAssistantError;
|
|
2651
|
-
continueTurn;
|
|
2652
|
-
setRecoveryState;
|
|
2653
|
-
retryAttempt = 0;
|
|
2654
|
-
retryTimer;
|
|
2655
|
-
pendingRetry;
|
|
2656
|
-
resolvePendingRetry;
|
|
2657
|
-
get policy() {
|
|
2658
|
-
return this.retry;
|
|
2659
|
-
}
|
|
2660
|
-
get hasPendingRetry() {
|
|
2661
|
-
return this.retryTimer !== void 0 || this.pendingRetry !== void 0;
|
|
2662
|
-
}
|
|
2663
|
-
waitForRetry() {
|
|
2664
|
-
return this.pendingRetry ?? Promise.resolve();
|
|
2665
|
-
}
|
|
2666
|
-
async handleTurnEnd(message, failure) {
|
|
2667
|
-
if (!this.retry || !message || message.role !== "assistant") {
|
|
2668
|
-
return;
|
|
2669
|
-
}
|
|
2670
|
-
if (!failure) {
|
|
2671
|
-
if (this.retryAttempt > 0) {
|
|
2672
|
-
this.emit({ type: "retry_succeeded", attempts: this.retryAttempt });
|
|
2673
|
-
this.emit({ type: "turn_retry_succeeded", attempts: this.retryAttempt });
|
|
2674
|
-
this.retryAttempt = 0;
|
|
2675
|
-
this.setRecoveryState("none");
|
|
2676
|
-
this.resolvePending();
|
|
2677
|
-
}
|
|
2678
|
-
return;
|
|
2679
|
-
}
|
|
2680
|
-
if (!failure.retryable) {
|
|
2681
|
-
if (this.retryAttempt > 0) {
|
|
2682
|
-
this.retryAttempt = 0;
|
|
2683
|
-
this.setRecoveryState("none");
|
|
2684
|
-
this.resolvePending();
|
|
2685
|
-
}
|
|
2686
|
-
return;
|
|
2687
|
-
}
|
|
2688
|
-
const maxRetries = this.retry.maxRetries ?? 3;
|
|
2689
|
-
this.retryAttempt += 1;
|
|
2690
|
-
if (this.retryAttempt > maxRetries) {
|
|
2691
|
-
const terminalFailure = asTerminalFailure(failure);
|
|
2692
|
-
this.emit({ type: "retry_exhausted", errorMessage: failure.message, maxRetries });
|
|
2693
|
-
this.emit({ type: "turn_failed_terminal", failure: terminalFailure, message });
|
|
2694
|
-
this.emit({ type: "turn_settled" });
|
|
2695
|
-
this.retryAttempt = 0;
|
|
2696
|
-
this.setRecoveryState("terminal");
|
|
2697
|
-
this.resolvePending();
|
|
2698
|
-
return;
|
|
2699
|
-
}
|
|
2700
|
-
const attempt = this.retryAttempt;
|
|
2701
|
-
const delayMs = (this.retry.baseDelayMs ?? 2e3) * 2 ** (attempt - 1);
|
|
2702
|
-
this.ensurePending();
|
|
2703
|
-
this.setRecoveryState("retry_pending");
|
|
2704
|
-
this.emit({ type: "turn_recovery_pending", recovery: "retry", failure, attempt, maxRetries, delayMs });
|
|
2705
|
-
this.emit({ type: "retry_scheduled", errorMessage: failure.message, attempt, maxRetries, delayMs });
|
|
2706
|
-
this.cancelTimerOnly();
|
|
2707
|
-
const setTimer = this.retry.setTimeout ?? defaultSetTimeout4;
|
|
2708
|
-
this.retryTimer = setTimer(() => {
|
|
2709
|
-
this.retryTimer = void 0;
|
|
2710
|
-
this.setRecoveryState("retrying");
|
|
2711
|
-
this.emit({ type: "retry_start", attempt, maxRetries });
|
|
2712
|
-
this.emit({ type: "turn_retry_started", attempt, maxRetries, failure });
|
|
2713
|
-
this.removeTrailingAssistantError();
|
|
2714
|
-
void this.continueTurn().catch((error) => {
|
|
2715
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2716
|
-
const terminalFailure = asTerminalFailure(failure, errorMessage);
|
|
2717
|
-
this.emit({
|
|
2718
|
-
type: "retry_exhausted",
|
|
2719
|
-
errorMessage,
|
|
2720
|
-
maxRetries
|
|
2721
|
-
});
|
|
2722
|
-
this.emit({ type: "turn_failed_terminal", failure: terminalFailure, message });
|
|
2723
|
-
this.emit({ type: "turn_settled" });
|
|
2724
|
-
this.retryAttempt = 0;
|
|
2725
|
-
this.setRecoveryState("terminal");
|
|
2726
|
-
this.resolvePending();
|
|
2727
|
-
});
|
|
2728
|
-
}, delayMs);
|
|
2729
|
-
}
|
|
2730
|
-
cancel() {
|
|
2731
|
-
this.cancelTimerOnly();
|
|
2732
|
-
this.retryAttempt = 0;
|
|
2733
|
-
this.resolvePending();
|
|
2734
|
-
}
|
|
2735
|
-
cancelTimerOnly() {
|
|
2736
|
-
if (this.retryTimer === void 0) {
|
|
2737
|
-
return;
|
|
2738
|
-
}
|
|
2739
|
-
const clearTimer = this.retry?.clearTimeout ?? defaultClearTimeout4;
|
|
2740
|
-
clearTimer(this.retryTimer);
|
|
2741
|
-
this.retryTimer = void 0;
|
|
2742
|
-
}
|
|
2743
|
-
ensurePending() {
|
|
2744
|
-
if (this.pendingRetry) {
|
|
2745
|
-
return;
|
|
2746
|
-
}
|
|
2747
|
-
this.pendingRetry = new Promise((resolve) => {
|
|
2748
|
-
this.resolvePendingRetry = resolve;
|
|
2749
|
-
});
|
|
2750
|
-
}
|
|
2751
|
-
resolvePending() {
|
|
2752
|
-
const resolve = this.resolvePendingRetry;
|
|
2753
|
-
this.pendingRetry = void 0;
|
|
2754
|
-
this.resolvePendingRetry = void 0;
|
|
2755
|
-
resolve?.();
|
|
2756
|
-
}
|
|
2757
|
-
};
|
|
2758
|
-
|
|
2759
|
-
// ../../packages/agent-framework/src/session/controller/runtime-guard.ts
|
|
2760
|
-
var RUNAWAY_TOOLCALL_WHITESPACE_DELTA_CHARS = 16384;
|
|
2761
|
-
var RUNAWAY_TOOLCALL_WHITESPACE_DELTA_EVENTS = 40;
|
|
2762
|
-
var TOOL_FAILURE_SIGNATURE_CHARS = 1e3;
|
|
2763
|
-
var TOOL_FAILURE_SIGNATURE_CONTEXT_LINES = 5;
|
|
2764
|
-
var FAILURE_DIAGNOSTIC_LINE_PATTERN = /\b(E2E_FAILED|AssertionError|TimeoutError|SyntaxError|ReferenceError|TypeError|Error):|(^|\b)(failed|failure|timed out|timeout|exit code|command failed|npm ERR!)/i;
|
|
2765
|
-
function stableStringify(value) {
|
|
2766
|
-
if (value === null || typeof value !== "object") {
|
|
2767
|
-
return JSON.stringify(value) ?? String(value);
|
|
2768
|
-
}
|
|
2769
|
-
if (Array.isArray(value)) {
|
|
2770
|
-
return `[${value.map(stableStringify).join(",")}]`;
|
|
2771
|
-
}
|
|
2772
|
-
const record = value;
|
|
2773
|
-
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`).join(",")}}`;
|
|
2774
|
-
}
|
|
2775
|
-
function summarizeToolResultForGuard(result) {
|
|
2776
|
-
if (!result || typeof result !== "object") {
|
|
2777
|
-
return String(result ?? "");
|
|
2778
|
-
}
|
|
2779
|
-
const content = Array.isArray(result.content) ? result.content : [];
|
|
2780
|
-
const text = content.filter((block) => !!block && typeof block === "object" && "type" in block).filter((block) => block.type === "text").map((block) => block.text || "").join("\n").trim();
|
|
2781
|
-
return text || stableStringify(result);
|
|
2782
|
-
}
|
|
2783
|
-
function summarizeToolFailureSignature(result) {
|
|
2784
|
-
const summary = summarizeToolResultForGuard(result).replace(/\r\n/g, "\n").replace(/\r/g, "\n").trim();
|
|
2785
|
-
if (!summary) {
|
|
2786
|
-
return "";
|
|
2787
|
-
}
|
|
2788
|
-
const lines = summary.split("\n");
|
|
2789
|
-
for (let index = lines.length - 1; index >= 0; index--) {
|
|
2790
|
-
if (!FAILURE_DIAGNOSTIC_LINE_PATTERN.test(lines[index] ?? "")) {
|
|
2791
|
-
continue;
|
|
2792
|
-
}
|
|
2793
|
-
return lines.slice(index, Math.min(lines.length, index + TOOL_FAILURE_SIGNATURE_CONTEXT_LINES)).join("\n").slice(0, TOOL_FAILURE_SIGNATURE_CHARS);
|
|
2794
|
-
}
|
|
2795
|
-
return summary.slice(-TOOL_FAILURE_SIGNATURE_CHARS);
|
|
2796
|
-
}
|
|
2797
|
-
var AgentSessionRuntimeGuard = class {
|
|
2798
|
-
constructor(trigger) {
|
|
2799
|
-
this.trigger = trigger;
|
|
2800
|
-
}
|
|
2801
|
-
trigger;
|
|
2802
|
-
repeatedToolFailures = /* @__PURE__ */ new Map();
|
|
2803
|
-
streamGuard = {
|
|
2804
|
-
whitespaceToolCallDeltaChars: 0,
|
|
2805
|
-
whitespaceToolCallDeltaEvents: 0
|
|
2806
|
-
};
|
|
2807
|
-
isTriggered = false;
|
|
2808
|
-
get triggered() {
|
|
2809
|
-
return this.isTriggered;
|
|
2810
|
-
}
|
|
2811
|
-
reset() {
|
|
2812
|
-
this.repeatedToolFailures.clear();
|
|
2813
|
-
this.streamGuard = {
|
|
2814
|
-
whitespaceToolCallDeltaChars: 0,
|
|
2815
|
-
whitespaceToolCallDeltaEvents: 0
|
|
2816
|
-
};
|
|
2817
|
-
this.isTriggered = false;
|
|
2818
|
-
}
|
|
2819
|
-
markTriggered() {
|
|
2820
|
-
this.isTriggered = true;
|
|
2821
|
-
}
|
|
2822
|
-
recordMessageUpdate(event) {
|
|
2823
|
-
if (this.isTriggered) {
|
|
2824
|
-
return;
|
|
2825
|
-
}
|
|
2826
|
-
const assistantEvent = event.assistantMessageEvent;
|
|
2827
|
-
if (assistantEvent.type !== "toolcall_delta") {
|
|
2828
|
-
this.streamGuard.whitespaceToolCallDeltaChars = 0;
|
|
2829
|
-
this.streamGuard.whitespaceToolCallDeltaEvents = 0;
|
|
2830
|
-
return;
|
|
2831
|
-
}
|
|
2832
|
-
if (assistantEvent.delta.trim().length > 0) {
|
|
2833
|
-
this.streamGuard.whitespaceToolCallDeltaChars = 0;
|
|
2834
|
-
this.streamGuard.whitespaceToolCallDeltaEvents = 0;
|
|
2835
|
-
return;
|
|
2836
|
-
}
|
|
2837
|
-
this.streamGuard.whitespaceToolCallDeltaChars += assistantEvent.delta.length;
|
|
2838
|
-
this.streamGuard.whitespaceToolCallDeltaEvents += 1;
|
|
2839
|
-
const content = event.message.role === "assistant" && Array.isArray(event.message.content) ? event.message.content[assistantEvent.contentIndex] : void 0;
|
|
2840
|
-
const partialArgs = typeof content?.partialArgs === "string" ? content.partialArgs : "";
|
|
2841
|
-
const sparsePartialArgs = partialArgs.length >= RUNAWAY_TOOLCALL_WHITESPACE_DELTA_CHARS && partialArgs.replace(/\s/g, "").length / partialArgs.length < 0.05;
|
|
2842
|
-
if (sparsePartialArgs || this.streamGuard.whitespaceToolCallDeltaChars >= RUNAWAY_TOOLCALL_WHITESPACE_DELTA_CHARS && this.streamGuard.whitespaceToolCallDeltaEvents >= RUNAWAY_TOOLCALL_WHITESPACE_DELTA_EVENTS) {
|
|
2843
|
-
this.isTriggered = true;
|
|
2844
|
-
this.trigger(
|
|
2845
|
-
"assistant_stream",
|
|
2846
|
-
this.streamGuard.whitespaceToolCallDeltaEvents,
|
|
2847
|
-
"runaway_tool_call_arguments"
|
|
2848
|
-
);
|
|
2849
|
-
}
|
|
2850
|
-
}
|
|
2851
|
-
recordToolExecutionEnd(event) {
|
|
2852
|
-
if (!event.isError) {
|
|
2853
|
-
const prefix = `${event.toolName}:`;
|
|
2854
|
-
for (const key2 of this.repeatedToolFailures.keys()) {
|
|
2855
|
-
if (key2.startsWith(prefix)) {
|
|
2856
|
-
this.repeatedToolFailures.delete(key2);
|
|
2857
|
-
}
|
|
2858
|
-
}
|
|
2859
|
-
return;
|
|
2860
|
-
}
|
|
2861
|
-
const resultSummary = summarizeToolFailureSignature(event.result);
|
|
2862
|
-
const key = `${event.toolName}:${stableStringify(event.args)}:${resultSummary}`;
|
|
2863
|
-
const repeatCount = (this.repeatedToolFailures.get(key) ?? 0) + 1;
|
|
2864
|
-
this.repeatedToolFailures.set(key, repeatCount);
|
|
2865
|
-
if (repeatCount < 3 || this.isTriggered) {
|
|
2866
|
-
return;
|
|
2867
|
-
}
|
|
2868
|
-
this.isTriggered = true;
|
|
2869
|
-
this.trigger(event.toolName, repeatCount, "repeated_tool_error");
|
|
2870
|
-
}
|
|
2871
|
-
};
|
|
2872
|
-
|
|
2873
|
-
// ../../packages/agent-framework/src/session/controller/runtime-phase-policy.ts
|
|
2874
|
-
function classifyControllerRuntimeError(params) {
|
|
2875
|
-
const errorMessage = params.error instanceof Error ? params.error.message : String(params.error);
|
|
2876
|
-
return createFailureClassification({ errorMessage, phase: params.phase }, params.retry, params.classifyFailure);
|
|
2877
|
-
}
|
|
2878
|
-
async function runAgentSessionRuntimePhase(params) {
|
|
2879
|
-
try {
|
|
2880
|
-
return await params.action();
|
|
2881
|
-
} catch (error) {
|
|
2882
|
-
const failure = classifyControllerRuntimeError({
|
|
2883
|
-
error,
|
|
2884
|
-
phase: params.phase,
|
|
2885
|
-
retry: params.retry,
|
|
2886
|
-
classifyFailure: params.classifyFailure
|
|
2887
|
-
});
|
|
2888
|
-
if (failure.userAbort) {
|
|
2889
|
-
params.setRecoveryState("cancelled");
|
|
2890
|
-
params.emit({ type: "turn_cancelled", reason: failure.message });
|
|
2891
|
-
} else {
|
|
2892
|
-
params.setRecoveryState("terminal");
|
|
2893
|
-
params.emit({ type: "turn_failed_terminal", failure: asTerminalFailure(failure) });
|
|
2894
|
-
}
|
|
2895
|
-
params.emit({ type: "turn_settled" });
|
|
2896
|
-
throw error;
|
|
2897
|
-
}
|
|
2898
|
-
}
|
|
2899
|
-
|
|
2900
|
-
// ../../packages/agent-framework/src/session/controller/session-sync.ts
|
|
2901
|
-
function getConversationMessages(messages) {
|
|
2902
|
-
return messages.filter(
|
|
2903
|
-
(message) => message.role === "user" || message.role === "assistant" || message.role === "toolResult"
|
|
2904
|
-
);
|
|
2905
|
-
}
|
|
2906
|
-
async function syncNewAgentMessages(params) {
|
|
2907
|
-
const activeSession = params.sessionManager.getActiveSession();
|
|
2908
|
-
const currentAgentCount = params.agent.state.messages.length;
|
|
2909
|
-
if (!activeSession) {
|
|
2910
|
-
params.setLastSyncedMessageCount(currentAgentCount);
|
|
2911
|
-
return;
|
|
2912
|
-
}
|
|
2913
|
-
if (currentAgentCount <= params.lastSyncedMessageCount) {
|
|
2914
|
-
return;
|
|
2915
|
-
}
|
|
2916
|
-
const addedMessages = params.agent.state.messages.slice(params.lastSyncedMessageCount);
|
|
2917
|
-
for (const message of addedMessages) {
|
|
2918
|
-
params.sessionManager.addMessage(message);
|
|
2919
|
-
}
|
|
2920
|
-
params.setLastSyncedMessageCount(currentAgentCount);
|
|
2921
|
-
params.emit({ type: "session_synced", addedMessages, totalMessages: currentAgentCount });
|
|
2922
|
-
await params.sessionManager.save();
|
|
2923
|
-
}
|
|
2924
|
-
|
|
2925
|
-
// ../../packages/agent-framework/src/session/controller/lifecycle-state-machine.ts
|
|
2926
|
-
function createInitialAgentSessionLifecycleState() {
|
|
2927
|
-
return {
|
|
2928
|
-
phase: "idle",
|
|
2929
|
-
queuedInputs: 0,
|
|
2930
|
-
runtimeGuardTriggered: false,
|
|
2931
|
-
retryPending: false,
|
|
2932
|
-
autoCompactPending: false,
|
|
2933
|
-
autoContinuePending: false
|
|
2934
|
-
};
|
|
2935
|
-
}
|
|
2936
|
-
function reduceAgentSessionLifecycle(state, input) {
|
|
2937
|
-
const next = { ...state };
|
|
2938
|
-
switch (input.type) {
|
|
2939
|
-
case "session_synced":
|
|
2940
|
-
return createInitialAgentSessionLifecycleState();
|
|
2941
|
-
case "prompt_started":
|
|
2942
|
-
next.phase = "preparing_turn";
|
|
2943
|
-
return next;
|
|
2944
|
-
case "queued_input_added":
|
|
2945
|
-
next.queuedInputs += 1;
|
|
2946
|
-
next.phase = state.phase === "idle" ? "queued" : state.phase;
|
|
2947
|
-
return next;
|
|
2948
|
-
case "queued_input_dispatched":
|
|
2949
|
-
next.queuedInputs = Math.max(0, next.queuedInputs - 1);
|
|
2950
|
-
next.phase = "preparing_turn";
|
|
2951
|
-
return next;
|
|
2952
|
-
case "agent_start":
|
|
2953
|
-
next.phase = "running";
|
|
2954
|
-
return next;
|
|
2955
|
-
case "turn_end_success":
|
|
2956
|
-
next.phase = next.queuedInputs > 0 ? "queued" : "idle";
|
|
2957
|
-
next.retryPending = false;
|
|
2958
|
-
next.autoCompactPending = false;
|
|
2959
|
-
return next;
|
|
2960
|
-
case "turn_end_retryable_failure":
|
|
2961
|
-
case "retry_scheduled":
|
|
2962
|
-
next.phase = "retry_pending";
|
|
2963
|
-
next.retryPending = true;
|
|
2964
|
-
return next;
|
|
2965
|
-
case "retry_completed":
|
|
2966
|
-
next.retryPending = false;
|
|
2967
|
-
next.phase = next.queuedInputs > 0 ? "queued" : "idle";
|
|
2968
|
-
return next;
|
|
2969
|
-
case "auto_compact_scheduled":
|
|
2970
|
-
next.phase = "auto_compacting";
|
|
2971
|
-
next.autoCompactPending = true;
|
|
2972
|
-
return next;
|
|
2973
|
-
case "auto_compact_completed":
|
|
2974
|
-
next.autoCompactPending = false;
|
|
2975
|
-
next.phase = next.retryPending ? "retry_pending" : next.queuedInputs > 0 ? "queued" : "idle";
|
|
2976
|
-
return next;
|
|
2977
|
-
case "agent_end":
|
|
2978
|
-
next.phase = next.queuedInputs > 0 ? "queued" : "idle";
|
|
2979
|
-
return next;
|
|
2980
|
-
case "auto_continue_scheduled":
|
|
2981
|
-
next.phase = "auto_continuing";
|
|
2982
|
-
next.autoContinuePending = true;
|
|
2983
|
-
return next;
|
|
2984
|
-
case "auto_continue_completed":
|
|
2985
|
-
next.autoContinuePending = false;
|
|
2986
|
-
next.phase = "idle";
|
|
2987
|
-
return next;
|
|
2988
|
-
case "runtime_guard_triggered":
|
|
2989
|
-
next.runtimeGuardTriggered = true;
|
|
2990
|
-
next.phase = "runtime_blocked";
|
|
2991
|
-
next.retryPending = false;
|
|
2992
|
-
next.autoCompactPending = false;
|
|
2993
|
-
next.autoContinuePending = false;
|
|
2994
|
-
return next;
|
|
2995
|
-
case "abort":
|
|
2996
|
-
next.phase = "aborted";
|
|
2997
|
-
next.retryPending = false;
|
|
2998
|
-
next.autoCompactPending = false;
|
|
2999
|
-
next.autoContinuePending = false;
|
|
3000
|
-
return next;
|
|
3001
|
-
}
|
|
3002
|
-
}
|
|
3003
|
-
|
|
3004
|
-
// ../../packages/agent-framework/src/session/controller/turn-lifecycle-policy.ts
|
|
3005
|
-
function isAgentSessionRecoveryActive(state) {
|
|
3006
|
-
return state === "retry_pending" || state === "retrying" || state === "compacting";
|
|
3007
|
-
}
|
|
3008
|
-
function createRuntimeGuardFailure(reason) {
|
|
3009
|
-
return {
|
|
3010
|
-
kind: "runtime_guard",
|
|
3011
|
-
retryable: false,
|
|
3012
|
-
compactable: false,
|
|
3013
|
-
userAbort: false,
|
|
3014
|
-
terminal: true,
|
|
3015
|
-
message: reason
|
|
3016
|
-
};
|
|
3017
|
-
}
|
|
3018
|
-
function classifyControllerTurnFailure(params) {
|
|
3019
|
-
if (!isAssistantFailureMessage(params.message)) {
|
|
3020
|
-
return void 0;
|
|
3021
|
-
}
|
|
3022
|
-
const errorMessage = params.message.errorMessage || (params.message.stopReason === "aborted" ? "Request was aborted" : "assistant response failed");
|
|
3023
|
-
return createFailureClassification(
|
|
3024
|
-
{ errorMessage, message: params.message, phase: "stream" },
|
|
3025
|
-
params.retry,
|
|
3026
|
-
params.classifyFailure
|
|
3027
|
-
);
|
|
3028
|
-
}
|
|
3029
|
-
async function handleAgentSessionTurnEndRuntimePolicies(params) {
|
|
3030
|
-
if (params.disposed || params.runtimeGuardTriggered) {
|
|
3031
|
-
return;
|
|
3032
|
-
}
|
|
3033
|
-
const failure = classifyControllerTurnFailure({
|
|
3034
|
-
message: params.message,
|
|
3035
|
-
retry: params.retry,
|
|
3036
|
-
classifyFailure: params.classifyFailure
|
|
3037
|
-
});
|
|
3038
|
-
if (failure?.userAbort) {
|
|
3039
|
-
params.cancelRetry();
|
|
3040
|
-
params.setRecoveryState("cancelled");
|
|
3041
|
-
params.emit({ type: "turn_cancelled", reason: failure.message, message: params.message });
|
|
3042
|
-
params.emit({ type: "turn_settled" });
|
|
3043
|
-
return;
|
|
3044
|
-
}
|
|
3045
|
-
const compactDecision = params.getAutoCompactDecision?.({
|
|
3046
|
-
agent: params.agent,
|
|
3047
|
-
sessionManager: params.sessionManager,
|
|
3048
|
-
cwd: params.cwd,
|
|
3049
|
-
message: params.message,
|
|
3050
|
-
messages: params.agent.state.messages.slice()
|
|
3051
|
-
});
|
|
3052
|
-
if (failure?.compactable) {
|
|
3053
|
-
params.scheduleAutoCompact(compactDecision ?? { reason: "overflow", willRetry: true }, failure);
|
|
3054
|
-
return;
|
|
3055
|
-
}
|
|
3056
|
-
if (failure && compactDecision?.reason === "overflow") {
|
|
3057
|
-
params.scheduleAutoCompact(compactDecision, failure);
|
|
3058
|
-
return;
|
|
3059
|
-
}
|
|
3060
|
-
if (failure?.retryable && compactDecision?.reason === "threshold") {
|
|
3061
|
-
params.scheduleAutoCompact({ reason: "threshold", willRetry: true }, failure);
|
|
3062
|
-
return;
|
|
3063
|
-
}
|
|
3064
|
-
if (failure) {
|
|
3065
|
-
if (failure.retryable) {
|
|
3066
|
-
await params.handleRetryTurnEnd(params.message, failure);
|
|
3067
|
-
return;
|
|
3068
|
-
}
|
|
3069
|
-
await params.handleRetryTurnEnd(params.message, failure);
|
|
3070
|
-
params.setRecoveryState("terminal");
|
|
3071
|
-
params.emit({ type: "turn_failed_terminal", failure: asTerminalFailure(failure), message: params.message });
|
|
3072
|
-
params.emit({ type: "turn_settled" });
|
|
3073
|
-
return;
|
|
3074
|
-
}
|
|
3075
|
-
await params.handleRetryTurnEnd(params.message);
|
|
3076
|
-
if (isAssistantTerminalCompletion(params.message)) {
|
|
3077
|
-
params.emit({ type: "turn_completed", message: params.message, toolResults: params.toolResults });
|
|
3078
|
-
const postTurnCompactDecision = compactDecision ?? params.getAutoCompactDecision?.({
|
|
3079
|
-
agent: params.agent,
|
|
3080
|
-
sessionManager: params.sessionManager,
|
|
3081
|
-
cwd: params.cwd,
|
|
3082
|
-
message: params.message,
|
|
3083
|
-
messages: params.agent.state.messages.slice()
|
|
3084
|
-
});
|
|
3085
|
-
if (postTurnCompactDecision?.reason === "threshold") {
|
|
3086
|
-
params.scheduleAutoCompact(postTurnCompactDecision);
|
|
3087
|
-
return;
|
|
3088
|
-
}
|
|
3089
|
-
params.setRecoveryState("none");
|
|
3090
|
-
params.emit({ type: "turn_settled" });
|
|
3091
|
-
}
|
|
3092
|
-
}
|
|
3093
|
-
async function runAgentSessionControllerAutoCompact(params) {
|
|
3094
|
-
const emitSettled = params.emitSettled ?? true;
|
|
3095
|
-
const runAfterAgentEndHook = params.runAfterAgentEndHook ?? true;
|
|
3096
|
-
const cancelOnAbort = params.cancelOnAbort ?? true;
|
|
3097
|
-
params.setRecoveryState("compacting");
|
|
3098
|
-
params.emit({ type: "turn_compaction_started", reason: params.reason, willRetry: params.willRetry });
|
|
3099
|
-
try {
|
|
3100
|
-
const result = await runAgentSessionCompaction({
|
|
3101
|
-
reason: params.reason,
|
|
3102
|
-
willRetry: params.willRetry,
|
|
3103
|
-
signal: params.signal,
|
|
3104
|
-
compaction: params.compaction,
|
|
3105
|
-
agent: params.agent,
|
|
3106
|
-
sessionManager: params.sessionManager,
|
|
3107
|
-
cwd: params.cwd,
|
|
3108
|
-
emit: params.emit,
|
|
3109
|
-
removeTrailingAssistantError: params.removeTrailingAssistantError,
|
|
3110
|
-
replaceMessagesAndMarkSynced: params.replaceMessagesAndMarkSynced,
|
|
3111
|
-
markSyncedToAgentState: params.markSyncedToAgentState,
|
|
3112
|
-
continueTurn: params.continueTurn
|
|
3113
|
-
});
|
|
3114
|
-
if (!params.willRetry || result?.aborted) {
|
|
3115
|
-
const alreadyCancelled = params.getRecoveryState?.() === "cancelled" || params.getDisposed?.() === true;
|
|
3116
|
-
params.setRecoveryState(result?.aborted && cancelOnAbort ? "cancelled" : "none");
|
|
3117
|
-
if (result?.aborted) {
|
|
3118
|
-
params.cancelRetry();
|
|
3119
|
-
if (cancelOnAbort && !alreadyCancelled) {
|
|
3120
|
-
params.emit({ type: "turn_cancelled", reason: "compaction_aborted" });
|
|
3121
|
-
}
|
|
3122
|
-
}
|
|
3123
|
-
if (emitSettled && !alreadyCancelled) {
|
|
3124
|
-
params.emit({ type: "turn_settled" });
|
|
3125
|
-
}
|
|
3126
|
-
if (!result?.aborted && runAfterAgentEndHook) {
|
|
3127
|
-
void params.handleAgentEndHook(params.agent.state.messages.slice());
|
|
3128
|
-
}
|
|
3129
|
-
}
|
|
3130
|
-
return result;
|
|
3131
|
-
} catch (error) {
|
|
3132
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3133
|
-
if (!params.willRetry && params.reason !== "overflow") {
|
|
3134
|
-
params.setRecoveryState("none");
|
|
3135
|
-
if (emitSettled) {
|
|
3136
|
-
params.emit({ type: "turn_settled" });
|
|
3137
|
-
}
|
|
3138
|
-
if (runAfterAgentEndHook) {
|
|
3139
|
-
void params.handleAgentEndHook(params.agent.state.messages.slice());
|
|
3140
|
-
}
|
|
3141
|
-
throw error;
|
|
3142
|
-
}
|
|
3143
|
-
const failure = asTerminalFailure(params.pendingFailure ?? {
|
|
3144
|
-
kind: "internal_error",
|
|
3145
|
-
retryable: false,
|
|
3146
|
-
compactable: false,
|
|
3147
|
-
userAbort: false,
|
|
3148
|
-
terminal: true,
|
|
3149
|
-
message: errorMessage
|
|
3150
|
-
}, errorMessage);
|
|
3151
|
-
params.cancelRetry();
|
|
3152
|
-
params.setRecoveryState("terminal");
|
|
3153
|
-
params.emit({ type: "turn_failed_terminal", failure });
|
|
3154
|
-
params.emit({ type: "turn_settled" });
|
|
3155
|
-
throw error;
|
|
3156
|
-
} finally {
|
|
3157
|
-
params.clearPendingFailure();
|
|
3158
|
-
}
|
|
3159
|
-
}
|
|
3160
|
-
|
|
3161
|
-
// ../../packages/agent-framework/src/session/controller.ts
|
|
3162
|
-
var AgentSessionController = class {
|
|
3163
|
-
agent;
|
|
3164
|
-
sessionManager;
|
|
3165
|
-
cwd;
|
|
3166
|
-
compaction;
|
|
3167
|
-
listeners = /* @__PURE__ */ new Set();
|
|
3168
|
-
queue;
|
|
3169
|
-
queueDispatcher;
|
|
3170
|
-
runtimeGuard;
|
|
3171
|
-
retryController;
|
|
3172
|
-
autoCompact;
|
|
3173
|
-
autoContinue;
|
|
3174
|
-
activity = new AgentSessionActivity();
|
|
3175
|
-
unsubscribeAgent;
|
|
3176
|
-
unsubscribeSemantic;
|
|
3177
|
-
lastSyncedMessageCount = 0;
|
|
3178
|
-
disposed = false;
|
|
3179
|
-
baseSystemPrompt;
|
|
3180
|
-
prepareTurn;
|
|
3181
|
-
afterAgentEnd;
|
|
3182
|
-
getAutoCompactDecision;
|
|
3183
|
-
classifyFailure;
|
|
3184
|
-
recoveryState = "none";
|
|
3185
|
-
pendingCompactionFailure;
|
|
3186
|
-
lifecycleState = createInitialAgentSessionLifecycleState();
|
|
3187
|
-
constructor(options) {
|
|
3188
|
-
this.agent = options.agent;
|
|
3189
|
-
this.sessionManager = options.sessionManager;
|
|
3190
|
-
this.cwd = options.cwd;
|
|
3191
|
-
this.compaction = options.compaction;
|
|
3192
|
-
this.baseSystemPrompt = options.baseSystemPrompt ?? this.agent.state.systemPrompt;
|
|
3193
|
-
this.prepareTurn = options.prepareTurn;
|
|
3194
|
-
this.afterAgentEnd = options.afterAgentEnd;
|
|
3195
|
-
this.getAutoCompactDecision = options.getAutoCompactDecision;
|
|
3196
|
-
this.classifyFailure = options.classifyFailure;
|
|
3197
|
-
if (options.onEvent) {
|
|
3198
|
-
this.listeners.add(options.onEvent);
|
|
3199
|
-
}
|
|
3200
|
-
this.runtimeGuard = new AgentSessionRuntimeGuard((toolName, repeatCount, reason) => {
|
|
3201
|
-
this.triggerRuntimeGuard(toolName, repeatCount, reason);
|
|
3202
|
-
});
|
|
3203
|
-
this.queue = new AgentSessionQueue({
|
|
3204
|
-
agent: this.agent,
|
|
3205
|
-
getIsStreaming: () => this.isStreaming,
|
|
3206
|
-
emit: (event) => this.emit(event)
|
|
3207
|
-
});
|
|
3208
|
-
this.retryController = new AgentSessionRetryController(
|
|
3209
|
-
options.retry,
|
|
3210
|
-
(event) => this.emit(event),
|
|
3211
|
-
() => this.removeTrailingAssistantError(),
|
|
3212
|
-
() => this.continue(),
|
|
3213
|
-
(state) => {
|
|
3214
|
-
this.recoveryState = state;
|
|
3215
|
-
}
|
|
3216
|
-
);
|
|
3217
|
-
this.autoCompact = new AgentSessionAutoCompactScheduler({
|
|
3218
|
-
retry: options.retry,
|
|
3219
|
-
getDisposed: () => this.disposed,
|
|
3220
|
-
getRuntimeGuardTriggered: () => this.runtimeGuard.triggered,
|
|
3221
|
-
isStreaming: () => this.isStreaming,
|
|
3222
|
-
runAutoCompact: async (decision, signal) => {
|
|
3223
|
-
await this.runAutoCompact(decision.reason, decision.willRetry, { signal });
|
|
3224
|
-
}
|
|
3225
|
-
});
|
|
3226
|
-
this.autoContinue = new AgentSessionAutoContinueController({
|
|
3227
|
-
agent: this.agent,
|
|
3228
|
-
sessionManager: this.sessionManager,
|
|
3229
|
-
cwd: this.cwd,
|
|
3230
|
-
getAutoContinueMessage: options.getAutoContinueMessage,
|
|
3231
|
-
retry: options.retry,
|
|
3232
|
-
emit: (event) => this.emit(event),
|
|
3233
|
-
getDisposed: () => this.disposed,
|
|
3234
|
-
getRuntimeGuardTriggered: () => this.runtimeGuard.triggered,
|
|
3235
|
-
isBlockedByRuntimePolicy: () => this.isBlockedByRuntimePolicy(),
|
|
3236
|
-
hasQueuedMessages: () => this.hasQueuedMessages,
|
|
3237
|
-
isStreaming: () => this.isStreaming,
|
|
3238
|
-
continueTurn: () => this.continue()
|
|
3239
|
-
});
|
|
3240
|
-
this.queueDispatcher = new AgentSessionQueueDispatcher({
|
|
3241
|
-
agent: this.agent,
|
|
3242
|
-
sessionManager: this.sessionManager,
|
|
3243
|
-
cwd: this.cwd,
|
|
3244
|
-
queue: this.queue,
|
|
3245
|
-
retry: options.retry,
|
|
3246
|
-
canDispatchQueuedTurn: options.canDispatchQueuedTurn,
|
|
3247
|
-
emit: (event) => this.emit(event),
|
|
3248
|
-
getDisposed: () => this.disposed,
|
|
3249
|
-
getRuntimeGuardTriggered: () => this.runtimeGuard.triggered,
|
|
3250
|
-
isStreaming: () => this.isStreaming,
|
|
3251
|
-
isBlockedByRuntimePolicy: () => this.isBlockedByRuntimePolicy(),
|
|
3252
|
-
runPreRequestAutoCompact: () => this.runPreRequestAutoCompactIfNeeded(),
|
|
3253
|
-
preparePromptMessages: (messages, promptOptions) => this.preparePromptMessages(messages, promptOptions),
|
|
3254
|
-
continueTurn: () => this.continue(),
|
|
3255
|
-
syncNewAgentMessages: () => this.syncNewAgentMessages()
|
|
3256
|
-
});
|
|
3257
|
-
this.syncFromActiveSession();
|
|
3258
|
-
this.unsubscribeAgent = this.agent.subscribe((event) => this.handleAgentEvent(event));
|
|
3259
|
-
this.unsubscribeSemantic = this.agent.subscribeSemantic((event) => {
|
|
3260
|
-
this.emit({ type: "semantic_event", event });
|
|
3261
|
-
});
|
|
3262
|
-
}
|
|
3263
|
-
get isStreaming() {
|
|
3264
|
-
return this.agent.state.isStreaming;
|
|
3265
|
-
}
|
|
3266
|
-
get isBusy() {
|
|
3267
|
-
return this.activity.isBusy || this.isStreaming || this.isRecoveryActive() || this.hasQueuedMessages || this.hasPendingRuntimePolicy();
|
|
3268
|
-
}
|
|
3269
|
-
get hasQueuedMessages() {
|
|
3270
|
-
return this.queue.hasQueuedMessages;
|
|
3271
|
-
}
|
|
3272
|
-
get pendingMessageCount() {
|
|
3273
|
-
return this.queue.length;
|
|
3274
|
-
}
|
|
3275
|
-
get queuedInputsSnapshot() {
|
|
3276
|
-
return this.queue.snapshot;
|
|
3277
|
-
}
|
|
3278
|
-
get turnRecoveryState() {
|
|
3279
|
-
return this.recoveryState;
|
|
3280
|
-
}
|
|
3281
|
-
get activeSessionMetadata() {
|
|
3282
|
-
const metadata = this.sessionManager.getActiveSession()?.metadata;
|
|
3283
|
-
return metadata ? { ...metadata } : void 0;
|
|
3284
|
-
}
|
|
3285
|
-
get syncedMessageCount() {
|
|
3286
|
-
return this.lastSyncedMessageCount;
|
|
3287
|
-
}
|
|
3288
|
-
get runtimeContext() {
|
|
3289
|
-
return { systemPrompt: this.agent.state.systemPrompt, tools: this.agent.state.tools.slice() };
|
|
3290
|
-
}
|
|
3291
|
-
getRuntimeSnapshot() {
|
|
3292
|
-
return {
|
|
3293
|
-
isStreaming: this.isStreaming,
|
|
3294
|
-
hasQueuedMessages: this.hasQueuedMessages,
|
|
3295
|
-
pendingMessageCount: this.pendingMessageCount,
|
|
3296
|
-
queuedInputs: this.queuedInputsSnapshot,
|
|
3297
|
-
activeSessionMetadata: this.activeSessionMetadata,
|
|
3298
|
-
syncedMessageCount: this.syncedMessageCount
|
|
3299
|
-
};
|
|
3300
|
-
}
|
|
3301
|
-
subscribe(listener) {
|
|
3302
|
-
this.listeners.add(listener);
|
|
3303
|
-
return () => this.listeners.delete(listener);
|
|
3304
|
-
}
|
|
3305
|
-
syncFromActiveSession(messages) {
|
|
3306
|
-
const activeSession = this.sessionManager.getActiveSession();
|
|
3307
|
-
this.agent.sessionId = activeSession?.id;
|
|
3308
|
-
const sessionMessages = messages ?? this.sessionManager.getMessages();
|
|
3309
|
-
const conversationMessages = getConversationMessages(sessionMessages);
|
|
3310
|
-
this.agent.replaceMessages(conversationMessages);
|
|
3311
|
-
this.lastSyncedMessageCount = conversationMessages.length;
|
|
3312
|
-
this.queue.clear();
|
|
3313
|
-
this.recordLifecycle({ type: "session_synced" });
|
|
3314
|
-
}
|
|
3315
|
-
markSyncedToAgentState() {
|
|
3316
|
-
this.lastSyncedMessageCount = this.agent.state.messages.length;
|
|
3317
|
-
}
|
|
3318
|
-
replaceMessagesAndMarkSynced(messages, options) {
|
|
3319
|
-
this.agent.replaceMessages(messages);
|
|
3320
|
-
if (options?.updateSession) {
|
|
3321
|
-
this.sessionManager.updateMessages(messages);
|
|
3322
|
-
}
|
|
3323
|
-
this.markSyncedToAgentState();
|
|
3324
|
-
}
|
|
3325
|
-
updateRuntimeContext(context) {
|
|
3326
|
-
if (context.systemPrompt !== void 0) {
|
|
3327
|
-
this.baseSystemPrompt = context.systemPrompt;
|
|
3328
|
-
this.agent.setSystemPrompt(context.systemPrompt);
|
|
3329
|
-
}
|
|
3330
|
-
if (context.tools !== void 0) {
|
|
3331
|
-
this.agent.setTools(context.tools);
|
|
3332
|
-
}
|
|
3333
|
-
}
|
|
3334
|
-
prompt(input, options) {
|
|
3335
|
-
return this.activity.run(async () => {
|
|
3336
|
-
const messages = normalizePromptInput(input);
|
|
3337
|
-
if (this.isRecoveryActive()) {
|
|
3338
|
-
this.queue.enqueue(messages, options?.streamingBehavior ?? "followUp", {
|
|
3339
|
-
...options,
|
|
3340
|
-
streamingBehavior: options?.streamingBehavior ?? "followUp"
|
|
3341
|
-
});
|
|
3342
|
-
this.recordLifecycle({ type: "queued_input_added" });
|
|
3343
|
-
return;
|
|
3344
|
-
}
|
|
3345
|
-
if (this.recoveryState === "terminal" || this.recoveryState === "cancelled") this.recoveryState = "none";
|
|
3346
|
-
if (options?.streamingBehavior) {
|
|
3347
|
-
this.queue.enqueue(messages, options.streamingBehavior, options);
|
|
3348
|
-
this.recordLifecycle({ type: "queued_input_added" });
|
|
3349
|
-
if (!this.isStreaming) {
|
|
3350
|
-
this.queueDispatcher.schedule();
|
|
3351
|
-
}
|
|
3352
|
-
return;
|
|
3353
|
-
}
|
|
3354
|
-
if (this.isStreaming) throw new Error("Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message.");
|
|
3355
|
-
await this.runPreRequestAutoCompactIfNeeded();
|
|
3356
|
-
const prepared = await this.runRuntimePhase("prepare", () => this.preparePromptMessages(messages, options));
|
|
3357
|
-
if (prepared.handled) return;
|
|
3358
|
-
this.recordLifecycle({ type: "prompt_started" });
|
|
3359
|
-
if (prepared.systemPrompt !== void 0) {
|
|
3360
|
-
this.agent.setSystemPrompt(prepared.systemPrompt);
|
|
3361
|
-
}
|
|
3362
|
-
await this.runRuntimePhase("request", () => this.agent.prompt(prepared.messages));
|
|
3363
|
-
await this.runRuntimePhase("post_turn", () => this.syncNewAgentMessages());
|
|
3364
|
-
});
|
|
3365
|
-
}
|
|
3366
|
-
continue() {
|
|
3367
|
-
return this.activity.run(async () => {
|
|
3368
|
-
if (this.isStreaming) {
|
|
3369
|
-
throw new Error("Agent is already processing. Wait for completion before continuing.");
|
|
3370
|
-
}
|
|
3371
|
-
await this.runPreRequestAutoCompactIfNeeded();
|
|
3372
|
-
this.recordLifecycle({ type: "prompt_started" });
|
|
3373
|
-
await this.runRuntimePhase("request", () => this.agent.continue());
|
|
3374
|
-
await this.runRuntimePhase("post_turn", () => this.syncNewAgentMessages());
|
|
3375
|
-
});
|
|
3376
|
-
}
|
|
3377
|
-
abort() {
|
|
3378
|
-
this.recordLifecycle({ type: "abort" });
|
|
3379
|
-
const cancelPendingRecovery = this.isRecoveryActive() && !this.isStreaming;
|
|
3380
|
-
this.retryController.cancel();
|
|
3381
|
-
this.autoCompact.cancel();
|
|
3382
|
-
this.queueDispatcher.cancel();
|
|
3383
|
-
this.autoContinue.cancel();
|
|
3384
|
-
if (cancelPendingRecovery) {
|
|
3385
|
-
this.recoveryState = "cancelled";
|
|
3386
|
-
this.emit({ type: "turn_cancelled", reason: "user_abort" });
|
|
3387
|
-
this.emit({ type: "turn_settled" });
|
|
3388
|
-
}
|
|
3389
|
-
this.agent.abort();
|
|
3390
|
-
}
|
|
3391
|
-
waitForIdle() {
|
|
3392
|
-
return this.agent.waitForIdle();
|
|
3393
|
-
}
|
|
3394
|
-
async waitForSettled() {
|
|
3395
|
-
while (true) {
|
|
3396
|
-
await this.agent.waitForIdle();
|
|
3397
|
-
if (!this.activity.isBusy && !this.hasPendingRuntimePolicy()) return;
|
|
3398
|
-
await Promise.all([
|
|
3399
|
-
this.activity.waitForIdle(),
|
|
3400
|
-
this.retryController.waitForRetry(),
|
|
3401
|
-
this.autoCompact.waitForAutoCompact(),
|
|
3402
|
-
this.autoContinue.waitForAutoContinue(),
|
|
3403
|
-
this.queueDispatcher.waitForDispatch()
|
|
3404
|
-
]);
|
|
3405
|
-
}
|
|
3406
|
-
}
|
|
3407
|
-
async runAutoCompact(reason, willRetry = false, options) {
|
|
3408
|
-
return runAgentSessionControllerAutoCompact({
|
|
3409
|
-
reason,
|
|
3410
|
-
willRetry,
|
|
3411
|
-
signal: options?.signal,
|
|
3412
|
-
compaction: this.compaction,
|
|
3413
|
-
agent: this.agent,
|
|
3414
|
-
sessionManager: this.sessionManager,
|
|
3415
|
-
cwd: this.cwd,
|
|
3416
|
-
pendingFailure: this.pendingCompactionFailure,
|
|
3417
|
-
emit: (event) => this.emit(event),
|
|
3418
|
-
setRecoveryState: (state) => {
|
|
3419
|
-
this.recoveryState = state;
|
|
3420
|
-
},
|
|
3421
|
-
getRecoveryState: () => this.recoveryState,
|
|
3422
|
-
getDisposed: () => this.disposed,
|
|
3423
|
-
cancelRetry: () => this.retryController.cancel(),
|
|
3424
|
-
clearPendingFailure: () => {
|
|
3425
|
-
this.pendingCompactionFailure = void 0;
|
|
3426
|
-
},
|
|
3427
|
-
removeTrailingAssistantError: () => this.removeTrailingAssistantError(),
|
|
3428
|
-
replaceMessagesAndMarkSynced: (messages, options2) => this.replaceMessagesAndMarkSynced(messages, options2),
|
|
3429
|
-
markSyncedToAgentState: () => this.markSyncedToAgentState(),
|
|
3430
|
-
continueTurn: () => this.continue(),
|
|
3431
|
-
handleAgentEndHook: (messages) => this.trackAgentEndHook(messages),
|
|
3432
|
-
emitSettled: options?.emitSettled,
|
|
3433
|
-
runAfterAgentEndHook: options?.runAfterAgentEndHook,
|
|
3434
|
-
cancelOnAbort: options?.cancelOnAbort
|
|
3435
|
-
});
|
|
3436
|
-
}
|
|
3437
|
-
dispose() {
|
|
3438
|
-
if (this.disposed) return;
|
|
3439
|
-
this.disposed = true;
|
|
3440
|
-
this.retryController.cancel();
|
|
3441
|
-
this.autoCompact.cancel();
|
|
3442
|
-
this.queueDispatcher.cancel();
|
|
3443
|
-
this.autoContinue.cancel();
|
|
3444
|
-
this.unsubscribeAgent?.();
|
|
3445
|
-
this.unsubscribeSemantic?.();
|
|
3446
|
-
this.listeners.clear();
|
|
3447
|
-
this.queue.clear();
|
|
3448
|
-
}
|
|
3449
|
-
drainQueuedInputsForEditor() {
|
|
3450
|
-
return this.queue.drainForEditor();
|
|
3451
|
-
}
|
|
3452
|
-
replaceQueuedInputText(search, replacement) {
|
|
3453
|
-
this.queue.replaceText(search, replacement);
|
|
3454
|
-
}
|
|
3455
|
-
requestQueueDispatch() {
|
|
3456
|
-
if (this.queue.length > 0) this.queueDispatcher.schedule();
|
|
3457
|
-
}
|
|
3458
|
-
async runPreRequestAutoCompactIfNeeded() {
|
|
3459
|
-
return runPreRequestAutoCompact({
|
|
3460
|
-
disposed: this.disposed,
|
|
3461
|
-
runtimeGuardTriggered: this.runtimeGuard.triggered,
|
|
3462
|
-
isStreaming: this.isStreaming,
|
|
3463
|
-
recoveryActive: this.isRecoveryActive(),
|
|
3464
|
-
agent: this.agent,
|
|
3465
|
-
sessionManager: this.sessionManager,
|
|
3466
|
-
cwd: this.cwd,
|
|
3467
|
-
getAutoCompactDecision: this.getAutoCompactDecision,
|
|
3468
|
-
runAutoCompact: () => this.runAutoCompact("threshold", false, { emitSettled: false, runAfterAgentEndHook: false, cancelOnAbort: false }).then(() => void 0),
|
|
3469
|
-
setRecoveryState: (state) => {
|
|
3470
|
-
this.recoveryState = state;
|
|
3471
|
-
}
|
|
3472
|
-
});
|
|
3473
|
-
}
|
|
3474
|
-
handleAgentEvent(event) {
|
|
3475
|
-
this.emit({ type: "agent_event", event });
|
|
3476
|
-
if (event.type === "agent_start") {
|
|
3477
|
-
this.runtimeGuard.reset();
|
|
3478
|
-
this.recordLifecycle({ type: "agent_start" });
|
|
3479
|
-
this.emit({ type: "streaming_changed", isStreaming: true });
|
|
3480
|
-
}
|
|
3481
|
-
if (event.type === "message_update") {
|
|
3482
|
-
this.recordMessageUpdateForGuard(event);
|
|
3483
|
-
}
|
|
3484
|
-
if (event.type === "tool_execution_end") {
|
|
3485
|
-
this.recordToolExecutionEndForGuard(event);
|
|
3486
|
-
}
|
|
3487
|
-
if (event.type === "message_end") {
|
|
3488
|
-
this.queue.markMessageDelivered(event.message);
|
|
3489
|
-
}
|
|
3490
|
-
if (event.type === "turn_end") {
|
|
3491
|
-
void this.syncNewAgentMessages();
|
|
3492
|
-
this.recordLifecycle({ type: "turn_end_success" });
|
|
3493
|
-
void this.activity.track(this.handleTurnEndRuntimePolicies(event.message, event.toolResults)).catch(() => {
|
|
3494
|
-
});
|
|
3495
|
-
}
|
|
3496
|
-
if (event.type === "agent_end") {
|
|
3497
|
-
this.recordLifecycle({ type: "agent_end" });
|
|
3498
|
-
void this.trackAgentEndHook(event.messages);
|
|
3499
|
-
this.emit({ type: "streaming_changed", isStreaming: false });
|
|
3500
|
-
}
|
|
3501
|
-
}
|
|
3502
|
-
async preparePromptMessages(messages, options) {
|
|
3503
|
-
return preparePromptMessages({
|
|
3504
|
-
agent: this.agent,
|
|
3505
|
-
sessionManager: this.sessionManager,
|
|
3506
|
-
cwd: this.cwd,
|
|
3507
|
-
baseSystemPrompt: this.baseSystemPrompt,
|
|
3508
|
-
prepareTurn: this.prepareTurn,
|
|
3509
|
-
emit: (event) => this.emit(event),
|
|
3510
|
-
messages,
|
|
3511
|
-
options
|
|
3512
|
-
});
|
|
3513
|
-
}
|
|
3514
|
-
async syncNewAgentMessages() {
|
|
3515
|
-
await syncNewAgentMessages({
|
|
3516
|
-
agent: this.agent,
|
|
3517
|
-
sessionManager: this.sessionManager,
|
|
3518
|
-
lastSyncedMessageCount: this.lastSyncedMessageCount,
|
|
3519
|
-
setLastSyncedMessageCount: (count) => {
|
|
3520
|
-
this.lastSyncedMessageCount = count;
|
|
3521
|
-
},
|
|
3522
|
-
emit: (event) => this.emit(event)
|
|
3523
|
-
});
|
|
3524
|
-
}
|
|
3525
|
-
removeTrailingAssistantError() {
|
|
3526
|
-
removeTrailingAssistantError({
|
|
3527
|
-
agent: this.agent,
|
|
3528
|
-
replaceMessagesAndMarkSynced: (messages, options) => this.replaceMessagesAndMarkSynced(messages, options)
|
|
3529
|
-
});
|
|
3530
|
-
}
|
|
3531
|
-
async handleTurnEndRuntimePolicies(message, toolResults = []) {
|
|
3532
|
-
return handleAgentSessionTurnEndRuntimePolicies({
|
|
3533
|
-
disposed: this.disposed,
|
|
3534
|
-
runtimeGuardTriggered: this.runtimeGuard.triggered,
|
|
3535
|
-
message,
|
|
3536
|
-
toolResults,
|
|
3537
|
-
agent: this.agent,
|
|
3538
|
-
sessionManager: this.sessionManager,
|
|
3539
|
-
cwd: this.cwd,
|
|
3540
|
-
getAutoCompactDecision: this.getAutoCompactDecision,
|
|
3541
|
-
classifyFailure: this.classifyFailure,
|
|
3542
|
-
retry: this.retryController.policy,
|
|
3543
|
-
emit: (event) => this.emit(event),
|
|
3544
|
-
setRecoveryState: (state) => {
|
|
3545
|
-
this.recoveryState = state;
|
|
3546
|
-
},
|
|
3547
|
-
handleRetryTurnEnd: (turnMessage, failure) => this.retryController.handleTurnEnd(turnMessage, failure),
|
|
3548
|
-
cancelRetry: () => this.retryController.cancel(),
|
|
3549
|
-
scheduleAutoCompact: (decision, failure) => this.scheduleAutoCompact(decision, failure)
|
|
3550
|
-
});
|
|
3551
|
-
}
|
|
3552
|
-
async handleAgentEndHook(messages) {
|
|
3553
|
-
if (this.disposed || this.runtimeGuard.triggered || this.isBlockedByRuntimePolicy()) {
|
|
3554
|
-
return;
|
|
3555
|
-
}
|
|
3556
|
-
if (this.afterAgentEnd) {
|
|
3557
|
-
this.emit({ type: "agent_end_hook_start", hasQueuedMessages: this.hasQueuedMessages });
|
|
3558
|
-
try {
|
|
3559
|
-
await this.afterAgentEnd({
|
|
3560
|
-
messages,
|
|
3561
|
-
agent: this.agent,
|
|
3562
|
-
sessionManager: this.sessionManager,
|
|
3563
|
-
cwd: this.cwd,
|
|
3564
|
-
hasQueuedMessages: this.hasQueuedMessages
|
|
3565
|
-
});
|
|
3566
|
-
this.emit({ type: "agent_end_hook_end" });
|
|
3567
|
-
} catch (error) {
|
|
3568
|
-
this.emit({
|
|
3569
|
-
type: "agent_end_hook_end",
|
|
3570
|
-
errorMessage: error instanceof Error ? error.message : String(error)
|
|
3571
|
-
});
|
|
3572
|
-
}
|
|
3573
|
-
}
|
|
3574
|
-
if (this.queue.length > 0) {
|
|
3575
|
-
this.queueDispatcher.schedule();
|
|
3576
|
-
}
|
|
3577
|
-
if (!this.hasQueuedMessages) {
|
|
3578
|
-
this.recordLifecycle({ type: "auto_continue_scheduled" });
|
|
3579
|
-
}
|
|
3580
|
-
this.autoContinue.schedule(messages);
|
|
3581
|
-
}
|
|
3582
|
-
trackAgentEndHook(messages) {
|
|
3583
|
-
return this.activity.track(this.handleAgentEndHook(messages));
|
|
3584
|
-
}
|
|
3585
|
-
recordMessageUpdateForGuard(event) {
|
|
3586
|
-
this.runtimeGuard.recordMessageUpdate(event);
|
|
3587
|
-
}
|
|
3588
|
-
recordToolExecutionEndForGuard(event) {
|
|
3589
|
-
this.runtimeGuard.recordToolExecutionEnd(event);
|
|
3590
|
-
}
|
|
3591
|
-
async runRuntimePhase(phase, action) {
|
|
3592
|
-
return runAgentSessionRuntimePhase({
|
|
3593
|
-
phase,
|
|
3594
|
-
action,
|
|
3595
|
-
retry: this.retryController.policy,
|
|
3596
|
-
classifyFailure: this.classifyFailure,
|
|
3597
|
-
emit: (event) => this.emit(event),
|
|
3598
|
-
setRecoveryState: (state) => {
|
|
3599
|
-
this.recoveryState = state;
|
|
3600
|
-
}
|
|
3601
|
-
});
|
|
3602
|
-
}
|
|
3603
|
-
isRecoveryActive() {
|
|
3604
|
-
return isAgentSessionRecoveryActive(this.recoveryState);
|
|
3605
|
-
}
|
|
3606
|
-
isBlockedByRuntimePolicy() {
|
|
3607
|
-
return this.recoveryState !== "none" || this.retryController.hasPendingRetry || this.autoCompact.hasPendingAutoCompact;
|
|
3608
|
-
}
|
|
3609
|
-
hasPendingRuntimePolicy() {
|
|
3610
|
-
return this.retryController.hasPendingRetry || this.autoCompact.hasPendingAutoCompact || this.autoContinue.hasPendingAutoContinue || this.queueDispatcher.hasPendingDispatch;
|
|
3611
|
-
}
|
|
3612
|
-
scheduleAutoCompact(decision, failure) {
|
|
3613
|
-
this.recordLifecycle({ type: "auto_compact_scheduled" });
|
|
3614
|
-
if (failure) {
|
|
3615
|
-
this.pendingCompactionFailure = failure;
|
|
3616
|
-
this.recoveryState = "compacting";
|
|
3617
|
-
this.emit({ type: "turn_recovery_pending", recovery: "compaction", failure });
|
|
3618
|
-
} else if (decision.reason === "threshold") {
|
|
3619
|
-
this.recoveryState = "compacting";
|
|
3620
|
-
}
|
|
3621
|
-
this.autoCompact.schedule(decision);
|
|
3622
|
-
}
|
|
3623
|
-
triggerRuntimeGuard(toolName, repeatCount, reason) {
|
|
3624
|
-
this.recordLifecycle({ type: "runtime_guard_triggered" });
|
|
3625
|
-
if (this.runtimeGuard.triggered) {
|
|
3626
|
-
this.runtimeGuard.markTriggered();
|
|
3627
|
-
}
|
|
3628
|
-
this.retryController.cancel();
|
|
3629
|
-
this.autoCompact.cancel();
|
|
3630
|
-
this.queueDispatcher.cancel();
|
|
3631
|
-
this.autoContinue.cancel();
|
|
3632
|
-
this.recoveryState = "terminal";
|
|
3633
|
-
this.emit({ type: "runtime_guard_triggered", toolName, repeatCount, reason });
|
|
3634
|
-
this.emit({
|
|
3635
|
-
type: "turn_failed_terminal",
|
|
3636
|
-
failure: createRuntimeGuardFailure(reason)
|
|
3637
|
-
});
|
|
3638
|
-
this.emit({ type: "turn_settled" });
|
|
3639
|
-
this.agent.abort();
|
|
3640
|
-
}
|
|
3641
|
-
emit(event) {
|
|
3642
|
-
for (const listener of this.listeners) {
|
|
3643
|
-
listener(event);
|
|
3644
|
-
}
|
|
3645
|
-
}
|
|
3646
|
-
recordLifecycle(input) {
|
|
3647
|
-
this.lifecycleState = reduceAgentSessionLifecycle(this.lifecycleState, input);
|
|
3648
|
-
void this.lifecycleState.phase;
|
|
3649
|
-
}
|
|
3650
|
-
};
|
|
3651
|
-
|
|
3652
|
-
// ../../packages/agent-framework/src/interaction.ts
|
|
3653
|
-
var InteractionUnavailableError = class extends Error {
|
|
3654
|
-
request;
|
|
3655
|
-
constructor(message, request) {
|
|
3656
|
-
super(message);
|
|
3657
|
-
this.name = "InteractionUnavailableError";
|
|
3658
|
-
this.request = request;
|
|
3659
|
-
}
|
|
3660
|
-
};
|
|
3661
|
-
var currentInteractionHandler = null;
|
|
3662
|
-
function registerInteractionHandler(handler) {
|
|
3663
|
-
currentInteractionHandler = handler;
|
|
3664
|
-
}
|
|
3665
|
-
function hasInteractionHandler() {
|
|
3666
|
-
return currentInteractionHandler !== null;
|
|
3667
|
-
}
|
|
3668
|
-
async function requestInteraction(request) {
|
|
3669
|
-
if (!currentInteractionHandler) {
|
|
3670
|
-
throw new InteractionUnavailableError(
|
|
3671
|
-
`No interaction handler registered for request type: ${request.type}`,
|
|
3672
|
-
request
|
|
3673
|
-
);
|
|
3674
|
-
}
|
|
3675
|
-
return await currentInteractionHandler(request);
|
|
3676
|
-
}
|
|
3677
|
-
|
|
3678
|
-
// ../../packages/agent-framework/src/extensions/identity.ts
|
|
3679
|
-
function normalizeName(value) {
|
|
3680
|
-
if (typeof value !== "string") {
|
|
3681
|
-
return void 0;
|
|
3682
|
-
}
|
|
3683
|
-
const trimmed = value.trim();
|
|
3684
|
-
return trimmed.length > 0 ? trimmed : void 0;
|
|
3685
|
-
}
|
|
3686
|
-
function resolveExtensionIdentity(input) {
|
|
3687
|
-
return normalizeName(input.pieName) ?? normalizeName(input.packageName) ?? normalizeName(input.fallbackName);
|
|
3688
|
-
}
|
|
3689
|
-
function extensionFallbackName(pathSegment) {
|
|
3690
|
-
const normalized = normalizeName(pathSegment);
|
|
3691
|
-
if (!normalized) {
|
|
3692
|
-
return void 0;
|
|
3693
|
-
}
|
|
3694
|
-
const base = normalized.replace(/[\\/]+$/, "").split(/[\\/]/).pop() ?? "";
|
|
3695
|
-
const withoutExtension = base.replace(/\.(cjs|mjs|js|ts)$/i, "");
|
|
3696
|
-
return normalizeName(withoutExtension);
|
|
3697
|
-
}
|
|
3698
|
-
|
|
3699
|
-
// ../../packages/agent-framework/src/extensions/enablement.ts
|
|
3700
|
-
function hasOwn(object, key) {
|
|
3701
|
-
return Object.prototype.hasOwnProperty.call(object, key);
|
|
3702
|
-
}
|
|
3703
|
-
function defineOwnDataProperty(target, key, value) {
|
|
3704
|
-
Object.defineProperty(target, key, {
|
|
3705
|
-
value,
|
|
3706
|
-
enumerable: true,
|
|
3707
|
-
configurable: true,
|
|
3708
|
-
writable: true
|
|
3709
|
-
});
|
|
3710
|
-
}
|
|
3711
|
-
function cloneOwnProperties(source) {
|
|
3712
|
-
const clone = {};
|
|
3713
|
-
for (const key of Object.keys(source)) {
|
|
3714
|
-
defineOwnDataProperty(clone, key, source[key]);
|
|
3715
|
-
}
|
|
3716
|
-
return clone;
|
|
3717
|
-
}
|
|
3718
|
-
function getExtensionEnablementEntry(identity, settings) {
|
|
3719
|
-
if (!settings || typeof settings !== "object") {
|
|
3720
|
-
return void 0;
|
|
3721
|
-
}
|
|
3722
|
-
if (!hasOwn(settings, identity)) {
|
|
3723
|
-
return void 0;
|
|
3724
|
-
}
|
|
3725
|
-
const raw = settings[identity];
|
|
3726
|
-
if (typeof raw === "boolean") {
|
|
3727
|
-
return raw;
|
|
3728
|
-
}
|
|
3729
|
-
if (raw && typeof raw === "object" && hasOwn(raw, "enabled") && typeof raw.enabled === "boolean") {
|
|
3730
|
-
return raw.enabled;
|
|
3731
|
-
}
|
|
3732
|
-
return void 0;
|
|
3733
|
-
}
|
|
3734
|
-
function isExtensionEnabled(identity, ...layers) {
|
|
3735
|
-
for (let i = layers.length - 1; i >= 0; i -= 1) {
|
|
3736
|
-
const value = getExtensionEnablementEntry(identity, layers[i]);
|
|
3737
|
-
if (value !== void 0) {
|
|
3738
|
-
return value;
|
|
3739
|
-
}
|
|
3740
|
-
}
|
|
3741
|
-
return true;
|
|
3742
|
-
}
|
|
3743
|
-
function extractExtensionSettings(parsed) {
|
|
3744
|
-
if (!parsed || typeof parsed !== "object") {
|
|
3745
|
-
return {};
|
|
3746
|
-
}
|
|
3747
|
-
if (!hasOwn(parsed, "extensions")) {
|
|
3748
|
-
return {};
|
|
3749
|
-
}
|
|
3750
|
-
const extensions = parsed.extensions;
|
|
3751
|
-
if (!extensions || typeof extensions !== "object" || Array.isArray(extensions)) {
|
|
3752
|
-
return {};
|
|
3753
|
-
}
|
|
3754
|
-
return extensions;
|
|
3755
|
-
}
|
|
3756
|
-
function setExtensionEnabledInSettings(settingsObject, identity, enabled) {
|
|
3757
|
-
const extensions = cloneOwnProperties(extractExtensionSettings(settingsObject));
|
|
3758
|
-
const current = hasOwn(extensions, identity) ? extensions[identity] : void 0;
|
|
3759
|
-
if (enabled === void 0) {
|
|
3760
|
-
if (current && typeof current === "object" && !Array.isArray(current)) {
|
|
3761
|
-
const next = cloneOwnProperties(current);
|
|
3762
|
-
delete next.enabled;
|
|
3763
|
-
if (Object.keys(next).length === 0) {
|
|
3764
|
-
delete extensions[identity];
|
|
3765
|
-
} else {
|
|
3766
|
-
defineOwnDataProperty(extensions, identity, next);
|
|
3767
|
-
}
|
|
3768
|
-
} else {
|
|
3769
|
-
delete extensions[identity];
|
|
3770
|
-
}
|
|
3771
|
-
} else {
|
|
3772
|
-
const next = current && typeof current === "object" && !Array.isArray(current) ? cloneOwnProperties(current) : {};
|
|
3773
|
-
defineOwnDataProperty(next, "enabled", enabled);
|
|
3774
|
-
defineOwnDataProperty(extensions, identity, next);
|
|
3775
|
-
}
|
|
3776
|
-
if (Object.keys(extensions).length === 0) {
|
|
3777
|
-
delete settingsObject.extensions;
|
|
3778
|
-
} else {
|
|
3779
|
-
defineOwnDataProperty(settingsObject, "extensions", extensions);
|
|
3780
|
-
}
|
|
3781
|
-
return settingsObject;
|
|
3782
|
-
}
|
|
3783
|
-
|
|
3784
|
-
// ../../packages/agent-framework/src/context-files.ts
|
|
3785
|
-
var AGENTS_CONTEXT_FILE_NAME = "AGENTS.md";
|
|
3786
|
-
function normalizeContextPath(path) {
|
|
3787
|
-
return String(path || "").replace(/\\/g, "/").replace(/^\/+/, "").trim() || AGENTS_CONTEXT_FILE_NAME;
|
|
3788
|
-
}
|
|
3789
|
-
function normalizeContextContent(content) {
|
|
3790
|
-
return String(content || "").replace(/\r\n/g, "\n").trim();
|
|
3791
|
-
}
|
|
3792
|
-
function dedupeProjectContextFiles(files) {
|
|
3793
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3794
|
-
const result = [];
|
|
3795
|
-
for (const file of files) {
|
|
3796
|
-
const path = normalizeContextPath(file.path);
|
|
3797
|
-
const key = path.toLowerCase();
|
|
3798
|
-
const content = normalizeContextContent(file.content);
|
|
3799
|
-
if (!content || seen.has(key)) {
|
|
3800
|
-
continue;
|
|
3801
|
-
}
|
|
3802
|
-
seen.add(key);
|
|
3803
|
-
result.push({ path, content });
|
|
3804
|
-
}
|
|
3805
|
-
return result;
|
|
3806
|
-
}
|
|
3807
|
-
function buildProjectContextSection(files, options = {}) {
|
|
3808
|
-
const contextFiles = dedupeProjectContextFiles(files);
|
|
3809
|
-
if (contextFiles.length === 0) {
|
|
3810
|
-
return "";
|
|
3811
|
-
}
|
|
3812
|
-
const heading = options.heading?.trim() || "Project Context";
|
|
3813
|
-
const intro = options.intro?.trim() || "Project-specific instructions from AGENTS.md files. Apply them in order; later files may add narrower instructions.";
|
|
3814
|
-
const lines = [`## ${heading}`, intro, ""];
|
|
3815
|
-
for (const file of contextFiles) {
|
|
3816
|
-
lines.push(`### ${file.path}`, file.content, "");
|
|
3817
|
-
}
|
|
3818
|
-
return lines.join("\n").trimEnd();
|
|
3819
|
-
}
|
|
3820
|
-
|
|
3821
|
-
export {
|
|
3822
|
-
estimateTokens,
|
|
3823
|
-
calculateTotalTokens,
|
|
3824
|
-
estimateContextTokens,
|
|
3825
|
-
getAutoCompactTokenLimit,
|
|
3826
|
-
findFirstKeptEntryId,
|
|
3827
|
-
createCompactionSummary,
|
|
3828
|
-
getGeneratedSessionName,
|
|
3829
|
-
generateEntryId,
|
|
3830
|
-
decodePersistedSessionContent,
|
|
3831
|
-
FileSessionStore,
|
|
3832
|
-
createSessionManager,
|
|
3833
|
-
AgentSessionController,
|
|
3834
|
-
resolveExtensionIdentity,
|
|
3835
|
-
extensionFallbackName,
|
|
3836
|
-
getExtensionEnablementEntry,
|
|
3837
|
-
isExtensionEnabled,
|
|
3838
|
-
extractExtensionSettings,
|
|
3839
|
-
setExtensionEnabledInSettings,
|
|
3840
|
-
InteractionUnavailableError,
|
|
3841
|
-
registerInteractionHandler,
|
|
3842
|
-
hasInteractionHandler,
|
|
3843
|
-
requestInteraction,
|
|
3844
|
-
AGENTS_CONTEXT_FILE_NAME,
|
|
3845
|
-
buildProjectContextSection
|
|
3846
|
-
};
|