tnega 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +115 -189
- package/dist/agent.js +2907 -0
- package/dist/agent.js.map +7 -0
- package/dist/bin.js +32512 -4419
- package/dist/bin.js.map +4 -4
- package/dist/cli-runtime.js +33404 -0
- package/dist/cli-runtime.js.map +7 -0
- package/dist/coding-agent.js +19088 -0
- package/dist/coding-agent.js.map +7 -0
- package/dist/core.js +1398 -0
- package/dist/core.js.map +7 -0
- package/dist/eval.js +23905 -0
- package/dist/eval.js.map +7 -0
- package/dist/events.js +1215 -0
- package/dist/events.js.map +7 -0
- package/dist/evolve.js +18303 -0
- package/dist/evolve.js.map +7 -0
- package/dist/index.js +33087 -4450
- package/dist/index.js.map +4 -4
- package/dist/llm.js +1016 -0
- package/dist/llm.js.map +7 -0
- package/dist/services.js +1142 -0
- package/dist/services.js.map +7 -0
- package/dist/session.js +1045 -0
- package/dist/session.js.map +7 -0
- package/dist/tools.js +2577 -0
- package/dist/tools.js.map +7 -0
- package/dist/types/packages/agent/src/definition.d.ts +17 -0
- package/dist/types/packages/agent/src/inbox-durable.d.ts +54 -0
- package/dist/types/packages/agent/src/index.d.ts +7 -0
- package/dist/types/packages/agent/src/live.d.ts +115 -0
- package/dist/types/packages/agent/src/llm-service.d.ts +32 -0
- package/dist/types/packages/agent/src/prompt.d.ts +55 -0
- package/dist/types/packages/agent/src/service.d.ts +68 -0
- package/dist/types/packages/agent/src/types.d.ts +215 -0
- package/dist/types/packages/benchmark/src/bigcodebench.d.ts +6 -0
- package/dist/types/packages/benchmark/src/humaneval.d.ts +11 -0
- package/dist/types/packages/benchmark/src/index.d.ts +9 -0
- package/dist/types/packages/benchmark/src/manifest.d.ts +10 -0
- package/dist/types/packages/benchmark/src/mbpp.d.ts +10 -0
- package/dist/types/packages/benchmark/src/parquet.d.ts +2 -0
- package/dist/types/packages/benchmark/src/stdlib.d.ts +3 -0
- package/dist/types/packages/benchmark/src/swebench.d.ts +2 -0
- package/dist/types/packages/benchmark/src/types.d.ts +58 -0
- package/dist/types/packages/cli/src/bin.d.ts +2 -0
- package/dist/types/packages/cli/src/commands.d.ts +146 -0
- package/dist/types/packages/cli/src/config.d.ts +28 -0
- package/dist/types/packages/cli/src/index.d.ts +9 -0
- package/dist/types/packages/cli/src/profile-file.d.ts +26 -0
- package/dist/types/packages/cli/src/profile.d.ts +9 -0
- package/dist/types/packages/cli/src/server.d.ts +14 -0
- package/dist/types/packages/cli/src/store.d.ts +60 -0
- package/dist/types/packages/cli/src/yaml.d.ts +1 -0
- package/dist/types/packages/coding-agent/src/codingAgent.d.ts +25 -0
- package/dist/types/packages/coding-agent/src/index.d.ts +6 -0
- package/dist/types/packages/coding-agent/src/mcp.d.ts +24 -0
- package/dist/types/packages/coding-agent/src/plan.d.ts +13 -0
- package/dist/types/packages/coding-agent/src/skills.d.ts +12 -0
- package/dist/types/packages/coding-agent/src/slash.d.ts +28 -0
- package/dist/types/packages/coding-agent/src/types.d.ts +41 -0
- package/dist/types/packages/core/src/context.d.ts +28 -0
- package/dist/types/packages/core/src/events.d.ts +57 -0
- package/dist/types/packages/core/src/fiber.d.ts +79 -0
- package/dist/types/packages/core/src/index.d.ts +10 -0
- package/dist/types/packages/core/src/invariant.d.ts +45 -0
- package/dist/types/packages/core/src/logger.d.ts +48 -0
- package/dist/types/packages/core/src/reflect.d.ts +42 -0
- package/dist/types/packages/core/src/registry.d.ts +54 -0
- package/dist/types/packages/core/src/service.d.ts +19 -0
- package/dist/types/packages/core/src/utils.d.ts +54 -0
- package/dist/types/packages/eval/src/codingRuntime.d.ts +18 -0
- package/dist/types/packages/eval/src/index.d.ts +5 -0
- package/dist/types/packages/eval/src/policy.d.ts +8 -0
- package/dist/types/packages/eval/src/runner.d.ts +31 -0
- package/dist/types/packages/eval/src/service.d.ts +23 -0
- package/dist/types/packages/eval/src/strategies.d.ts +58 -0
- package/dist/types/packages/eval/src/trace.d.ts +7 -0
- package/dist/types/packages/eval/src/types.d.ts +281 -0
- package/dist/types/packages/eval/src/utils.d.ts +5 -0
- package/dist/types/packages/eval/src/workspace.d.ts +17 -0
- package/dist/types/packages/evolve/src/diagnose.d.ts +9 -0
- package/dist/types/packages/evolve/src/index.d.ts +7 -0
- package/dist/types/packages/evolve/src/llm.d.ts +39 -0
- package/dist/types/packages/evolve/src/log.d.ts +26 -0
- package/dist/types/packages/evolve/src/select.d.ts +3 -0
- package/dist/types/packages/evolve/src/service.d.ts +29 -0
- package/dist/types/packages/evolve/src/types.d.ts +176 -0
- package/dist/types/packages/llm/src/anthropic-messages.d.ts +4 -0
- package/dist/types/packages/llm/src/errors.d.ts +6 -0
- package/dist/types/packages/llm/src/index.d.ts +7 -0
- package/dist/types/packages/llm/src/models.d.ts +29 -0
- package/dist/types/packages/llm/src/openai.d.ts +4 -0
- package/dist/types/packages/llm/src/provider.d.ts +3 -0
- package/dist/types/packages/llm/src/shared.d.ts +15 -0
- package/dist/types/packages/llm/src/types.d.ts +13 -0
- package/dist/types/packages/session/src/index.d.ts +500 -0
- package/dist/types/packages/session/src/invariant.d.ts +33 -0
- package/dist/types/packages/tools/src/builtins.d.ts +25 -0
- package/dist/types/packages/tools/src/calc.d.ts +4 -0
- package/dist/types/packages/tools/src/execution.d.ts +31 -0
- package/dist/types/packages/tools/src/index.d.ts +107 -0
- package/dist/types/packages/tools/src/path.d.ts +4 -0
- package/dist/types/packages/tools/src/policy.d.ts +14 -0
- package/dist/types/src/events.d.ts +1 -0
- package/dist/types/src/index.d.ts +15 -0
- package/dist/types/src/services.d.ts +4 -0
- package/dist/web/assets/index-45pokiBH.js +270 -0
- package/dist/web/assets/index-DCLL69Kz.css +1 -0
- package/dist/web/index.html +13 -0
- package/package.json +92 -35
package/dist/session.js
ADDED
|
@@ -0,0 +1,1045 @@
|
|
|
1
|
+
import { createRequire as __tnegaCreateRequire } from 'node:module'; const require = __tnegaCreateRequire(import.meta.url);
|
|
2
|
+
|
|
3
|
+
// packages/session/src/index.ts
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
6
|
+
import { dirname, resolve } from "node:path";
|
|
7
|
+
|
|
8
|
+
// packages/session/src/invariant.ts
|
|
9
|
+
function checkBalancedTurns(events) {
|
|
10
|
+
const opens = /* @__PURE__ */ new Map();
|
|
11
|
+
const failures = [];
|
|
12
|
+
for (const event of events) {
|
|
13
|
+
if (event.type === "turn/start") {
|
|
14
|
+
opens.set(event.payload.turn, (opens.get(event.payload.turn) ?? 0) + 1);
|
|
15
|
+
} else if (event.type === "turn/end") {
|
|
16
|
+
const remaining = opens.get(event.payload.turn) ?? 0;
|
|
17
|
+
if (remaining <= 0) {
|
|
18
|
+
failures.push({
|
|
19
|
+
name: "turn/end-without-start",
|
|
20
|
+
detail: `turn/end for turn ${event.payload.turn} has no matching turn/start`
|
|
21
|
+
});
|
|
22
|
+
} else {
|
|
23
|
+
opens.set(event.payload.turn, remaining - 1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
for (const [turn, count] of opens) {
|
|
28
|
+
for (let index = 0; index < count; index += 1) {
|
|
29
|
+
failures.push({
|
|
30
|
+
name: "turn/start-without-end",
|
|
31
|
+
detail: `turn/start ${turn} has no matching turn/end`
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return failures;
|
|
36
|
+
}
|
|
37
|
+
function checkBalancedSteps(events) {
|
|
38
|
+
const open = /* @__PURE__ */ new Set();
|
|
39
|
+
const failures = [];
|
|
40
|
+
for (const event of events) {
|
|
41
|
+
if (event.type === "step/start") {
|
|
42
|
+
const key = `${event.payload.turn}:${event.payload.step}`;
|
|
43
|
+
if (open.has(key)) {
|
|
44
|
+
failures.push({
|
|
45
|
+
name: "duplicate-step-start",
|
|
46
|
+
detail: `step/start for ${key} appears twice without a matching step/end`
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
open.add(key);
|
|
50
|
+
} else if (event.type === "step/end") {
|
|
51
|
+
const key = `${event.payload.turn}:${event.payload.step}`;
|
|
52
|
+
if (!open.delete(key)) {
|
|
53
|
+
failures.push({
|
|
54
|
+
name: "step/end-without-start",
|
|
55
|
+
detail: `step/end for ${key} has no matching step/start`
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (const key of open) {
|
|
61
|
+
failures.push({
|
|
62
|
+
name: "step/start-without-end",
|
|
63
|
+
detail: `step/start ${key} has no matching step/end`
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return failures;
|
|
67
|
+
}
|
|
68
|
+
function checkBalancedToolCalls(events) {
|
|
69
|
+
const open = /* @__PURE__ */ new Set();
|
|
70
|
+
const failures = [];
|
|
71
|
+
for (const event of events) {
|
|
72
|
+
if (event.type === "tool/call") {
|
|
73
|
+
open.add(event.payload.id);
|
|
74
|
+
} else if (event.type === "tool/result") {
|
|
75
|
+
const payload = event.payload;
|
|
76
|
+
const id = payload.toolCallId;
|
|
77
|
+
if (!open.delete(id)) {
|
|
78
|
+
failures.push({
|
|
79
|
+
name: "tool/result-without-call",
|
|
80
|
+
detail: `tool/result for call ${id} has no matching tool/call`
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
for (const id of open) {
|
|
86
|
+
failures.push({
|
|
87
|
+
name: "tool/call-without-result",
|
|
88
|
+
detail: `tool/call ${id} has no matching tool/result`
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return failures;
|
|
92
|
+
}
|
|
93
|
+
function checkMonotonicSeq(events) {
|
|
94
|
+
const failures = [];
|
|
95
|
+
for (let index = 1; index < events.length; index += 1) {
|
|
96
|
+
const previous = events[index - 1];
|
|
97
|
+
const current = events[index];
|
|
98
|
+
if (current && previous && current.seq <= previous.seq) {
|
|
99
|
+
failures.push({
|
|
100
|
+
name: "non-monotonic-seq",
|
|
101
|
+
detail: `seq ${current.seq} follows ${previous.seq} out of order at index ${index}`
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return failures;
|
|
106
|
+
}
|
|
107
|
+
function checkSessionInvariants(events) {
|
|
108
|
+
return [
|
|
109
|
+
...checkBalancedTurns(events),
|
|
110
|
+
...checkBalancedSteps(events),
|
|
111
|
+
...checkBalancedToolCalls(events),
|
|
112
|
+
...checkMonotonicSeq(events)
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// packages/session/src/index.ts
|
|
117
|
+
var SESSION_FORMAT_VERSION = 7;
|
|
118
|
+
var SessionFormatError = class extends Error {
|
|
119
|
+
name = "SessionFormatError";
|
|
120
|
+
};
|
|
121
|
+
var liveSessions = /* @__PURE__ */ new Map();
|
|
122
|
+
function sessionFileKey(file) {
|
|
123
|
+
return resolve(file);
|
|
124
|
+
}
|
|
125
|
+
function isMessageEventType(type) {
|
|
126
|
+
return type === "user/message" || type === "assistant/message" || type === "system/message";
|
|
127
|
+
}
|
|
128
|
+
function isMessageEvent(event) {
|
|
129
|
+
return isMessageEventType(event.type);
|
|
130
|
+
}
|
|
131
|
+
var DEFAULT_CONTEXT_LIMIT = 128e3;
|
|
132
|
+
function isSessionEvent(value) {
|
|
133
|
+
if (typeof value !== "object" || value === null) return false;
|
|
134
|
+
const record = value;
|
|
135
|
+
return typeof record.id === "string" && typeof record.seq === "number" && typeof record.ts === "number" && typeof record.type === "string" && "payload" in record;
|
|
136
|
+
}
|
|
137
|
+
function clone(value) {
|
|
138
|
+
return structuredClone(value);
|
|
139
|
+
}
|
|
140
|
+
function previousSurfaceSeq(nodes) {
|
|
141
|
+
return nodes.at(-1);
|
|
142
|
+
}
|
|
143
|
+
function projectEvents(events) {
|
|
144
|
+
const messages = [];
|
|
145
|
+
for (const event of events) {
|
|
146
|
+
if (event.type === "checkpoint") {
|
|
147
|
+
messages.splice(0, messages.length, ...clone(event.payload.messages));
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const message = deriveEventMessage(event);
|
|
151
|
+
if (message) messages.push(message);
|
|
152
|
+
}
|
|
153
|
+
return messages;
|
|
154
|
+
}
|
|
155
|
+
function isAppendSurfaceEvent(event) {
|
|
156
|
+
return isSurfaceEventType(event.type) && event.surfaceOp === "append";
|
|
157
|
+
}
|
|
158
|
+
function isSurfaceEventType(type) {
|
|
159
|
+
return type === "user/message" || type === "system/message" || type === "assistant/message" || type === "tool/result";
|
|
160
|
+
}
|
|
161
|
+
function checkpointShadowRange(event) {
|
|
162
|
+
if (event.type !== "checkpoint") return void 0;
|
|
163
|
+
const op = event.payload.surfaceOp;
|
|
164
|
+
if (typeof op === "object" && op !== null && op.op === "replace") {
|
|
165
|
+
return { start: op.start, end: op.end };
|
|
166
|
+
}
|
|
167
|
+
return void 0;
|
|
168
|
+
}
|
|
169
|
+
function foldSurface(events) {
|
|
170
|
+
const nodes = [];
|
|
171
|
+
const replacements = [];
|
|
172
|
+
const applyReplace = (seq, start, end) => {
|
|
173
|
+
const startIndex = nodes.indexOf(start);
|
|
174
|
+
if (startIndex < 0) {
|
|
175
|
+
nodes.push(seq);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const endIndex = nodes.indexOf(end, startIndex);
|
|
179
|
+
if (endIndex < 0) {
|
|
180
|
+
nodes.push(seq);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const shadowed = nodes.splice(startIndex, endIndex - startIndex + 1);
|
|
184
|
+
nodes.splice(startIndex, 0, seq);
|
|
185
|
+
replacements.push({ seq, start, end, shadowedSeqs: shadowed });
|
|
186
|
+
};
|
|
187
|
+
for (const event of events) {
|
|
188
|
+
if (event.type === "checkpoint") {
|
|
189
|
+
const range = checkpointShadowRange(event);
|
|
190
|
+
if (!range) {
|
|
191
|
+
nodes.push(event.seq);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
applyReplace(event.seq, range.start, range.end);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (!isSurfaceEventType(event.type)) continue;
|
|
198
|
+
const op = event.surfaceOp ?? "append";
|
|
199
|
+
if (op === "append") {
|
|
200
|
+
nodes.push(event.seq);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
applyReplace(event.seq, op.start, op.end);
|
|
204
|
+
}
|
|
205
|
+
return { nodes, replacements };
|
|
206
|
+
}
|
|
207
|
+
function deriveEventMessage(event) {
|
|
208
|
+
switch (event.type) {
|
|
209
|
+
case "user/message": {
|
|
210
|
+
const message = { role: "user", content: event.payload.content };
|
|
211
|
+
if (event.payload.name) message.name = event.payload.name;
|
|
212
|
+
return message;
|
|
213
|
+
}
|
|
214
|
+
case "system/message": {
|
|
215
|
+
const message = { role: "system", content: event.payload.content };
|
|
216
|
+
if (event.payload.name) message.name = event.payload.name;
|
|
217
|
+
return message;
|
|
218
|
+
}
|
|
219
|
+
case "assistant/message": {
|
|
220
|
+
const toolCalls = event.payload.toolCalls;
|
|
221
|
+
if (!event.payload.content && !(toolCalls && toolCalls.length)) return null;
|
|
222
|
+
const message = { role: "assistant", content: event.payload.content };
|
|
223
|
+
if (event.payload.name) message.name = event.payload.name;
|
|
224
|
+
if (toolCalls?.length) message.tool_calls = clone(toolCalls);
|
|
225
|
+
return message;
|
|
226
|
+
}
|
|
227
|
+
case "tool/result": {
|
|
228
|
+
const failed = !event.payload.ok;
|
|
229
|
+
const message = {
|
|
230
|
+
role: "tool",
|
|
231
|
+
content: failed ? `error: ${event.payload.error?.message ?? "unknown"}` : stringify(event.payload.output),
|
|
232
|
+
tool_call_id: event.payload.toolCallId
|
|
233
|
+
};
|
|
234
|
+
message.name = event.payload.name;
|
|
235
|
+
if (failed) {
|
|
236
|
+
message.toolOk = false;
|
|
237
|
+
if (event.payload.error) message.toolError = event.payload.error;
|
|
238
|
+
}
|
|
239
|
+
return message;
|
|
240
|
+
}
|
|
241
|
+
default:
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
function deriveSurfaceMessages(events) {
|
|
246
|
+
const { nodes } = foldSurface(events);
|
|
247
|
+
const bySeq = new Map(events.map((event) => [event.seq, event]));
|
|
248
|
+
const messages = [];
|
|
249
|
+
for (const seq of nodes) {
|
|
250
|
+
const event = bySeq.get(seq);
|
|
251
|
+
if (!event) continue;
|
|
252
|
+
if (event.type === "checkpoint") {
|
|
253
|
+
messages.splice(0, messages.length, ...clone(event.payload.messages));
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
const message = deriveEventMessage(event);
|
|
257
|
+
if (message) messages.push(message);
|
|
258
|
+
}
|
|
259
|
+
return messages;
|
|
260
|
+
}
|
|
261
|
+
function transcriptEvents(events) {
|
|
262
|
+
const bySeq = new Map(events.map((event) => [event.seq, event]));
|
|
263
|
+
const { nodes, replacements } = foldSurface(events);
|
|
264
|
+
const shadowedOf = /* @__PURE__ */ new Map();
|
|
265
|
+
for (const replacement of replacements) {
|
|
266
|
+
shadowedOf.set(replacement.seq, replacement.shadowedSeqs.map((seq) => bySeq.get(seq)).filter((event) => event !== void 0));
|
|
267
|
+
}
|
|
268
|
+
const isVisible = (event) => event.type !== "system/message" && isSurfaceEventType(event.type);
|
|
269
|
+
const skeleton = [];
|
|
270
|
+
const appendHistory = (checkpointSeq) => {
|
|
271
|
+
for (const shadowed of shadowedOf.get(checkpointSeq) ?? []) {
|
|
272
|
+
if (shadowed.type === "checkpoint") appendHistory(shadowed.seq);
|
|
273
|
+
else if (isVisible(shadowed)) skeleton.push(shadowed);
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
for (const seq of nodes) {
|
|
277
|
+
const event = bySeq.get(seq);
|
|
278
|
+
if (!event) continue;
|
|
279
|
+
if (event.type === "checkpoint") appendHistory(event.seq);
|
|
280
|
+
skeleton.push(event);
|
|
281
|
+
}
|
|
282
|
+
const declaredCalls = /* @__PURE__ */ new Set();
|
|
283
|
+
for (const event of events) {
|
|
284
|
+
if (event.type === "assistant/message") {
|
|
285
|
+
for (const call of event.payload.toolCalls ?? []) declaredCalls.add(call.id);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const inSkeleton = new Set(skeleton.map((event) => event.id));
|
|
289
|
+
const structure = events.filter((event) => !inSkeleton.has(event.id)).filter((event) => event.type !== "compaction/start" && event.type !== "compaction/end").filter((event) => event.type !== "checkpoint").filter((event) => !(event.type === "tool/call" && declaredCalls.has(event.payload.id)));
|
|
290
|
+
const ordered = [...skeleton];
|
|
291
|
+
for (const event of structure) {
|
|
292
|
+
let index = ordered.length;
|
|
293
|
+
for (let cursor = ordered.length - 1; cursor >= 0; cursor -= 1) {
|
|
294
|
+
if (ordered[cursor].seq <= event.seq) {
|
|
295
|
+
index = cursor + 1;
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
ordered.splice(index, 0, event);
|
|
300
|
+
}
|
|
301
|
+
return ordered;
|
|
302
|
+
}
|
|
303
|
+
function foldRequestHeader(events) {
|
|
304
|
+
let header;
|
|
305
|
+
for (const event of events) {
|
|
306
|
+
if (event.type === "request/header") header = clone(event.payload);
|
|
307
|
+
}
|
|
308
|
+
return header;
|
|
309
|
+
}
|
|
310
|
+
function foldRequestContext(events) {
|
|
311
|
+
let context;
|
|
312
|
+
for (const event of events) {
|
|
313
|
+
if (event.type === "request/context") context = clone(event.payload);
|
|
314
|
+
}
|
|
315
|
+
return context;
|
|
316
|
+
}
|
|
317
|
+
function foldSessionMeta(events) {
|
|
318
|
+
const meta = {};
|
|
319
|
+
for (const event of events) {
|
|
320
|
+
if (event.type === "meta") {
|
|
321
|
+
const payload = event.payload;
|
|
322
|
+
if (typeof payload.title === "string") meta.title = payload.title;
|
|
323
|
+
if (payload.agentType === "general" || payload.agentType === "coding") {
|
|
324
|
+
meta.agentType = payload.agentType;
|
|
325
|
+
}
|
|
326
|
+
if (payload.mode === "auto" || payload.mode === "plan" || payload.mode === "execute") {
|
|
327
|
+
meta.mode = payload.mode;
|
|
328
|
+
}
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
if (event.type === "meta/patch") {
|
|
332
|
+
const patch = event.payload;
|
|
333
|
+
if (patch.fields.includes("title") && typeof patch.title === "string") {
|
|
334
|
+
meta.title = patch.title;
|
|
335
|
+
}
|
|
336
|
+
if (patch.fields.includes("agentType") && patch.agentType !== void 0) {
|
|
337
|
+
meta.agentType = patch.agentType;
|
|
338
|
+
}
|
|
339
|
+
if (patch.fields.includes("mode") && patch.mode !== void 0) {
|
|
340
|
+
meta.mode = patch.mode;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return meta;
|
|
345
|
+
}
|
|
346
|
+
function foldMetaFromLog(log) {
|
|
347
|
+
return log.read().then(foldSessionMeta);
|
|
348
|
+
}
|
|
349
|
+
function stringify(value) {
|
|
350
|
+
if (typeof value === "string") return value;
|
|
351
|
+
try {
|
|
352
|
+
return JSON.stringify(value) ?? String(value);
|
|
353
|
+
} catch {
|
|
354
|
+
return String(value);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
function estimateMessageTokens(messages) {
|
|
358
|
+
let tokens = 0;
|
|
359
|
+
for (const message of messages) {
|
|
360
|
+
tokens += Math.ceil(message.content.length / 4);
|
|
361
|
+
for (const call of message.tool_calls ?? []) {
|
|
362
|
+
const raw = JSON.stringify(call.arguments ?? {}) ?? "";
|
|
363
|
+
tokens += Math.ceil(raw.length / 4);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return tokens;
|
|
367
|
+
}
|
|
368
|
+
function estimateEventTokens(event) {
|
|
369
|
+
switch (event.type) {
|
|
370
|
+
case "user/message":
|
|
371
|
+
case "system/message":
|
|
372
|
+
return Math.ceil(event.payload.content.length / 4);
|
|
373
|
+
case "assistant/message": {
|
|
374
|
+
let tokens = Math.ceil(event.payload.content.length / 4);
|
|
375
|
+
for (const call of event.payload.toolCalls ?? []) {
|
|
376
|
+
const raw = JSON.stringify(call.arguments ?? {}) ?? "";
|
|
377
|
+
tokens += Math.ceil(raw.length / 4);
|
|
378
|
+
}
|
|
379
|
+
return tokens;
|
|
380
|
+
}
|
|
381
|
+
case "assistant/chunk":
|
|
382
|
+
return Math.ceil(event.payload.content.length / 4);
|
|
383
|
+
case "tool/call": {
|
|
384
|
+
const raw = JSON.stringify(event.payload.arguments ?? {}) ?? "";
|
|
385
|
+
return Math.ceil(raw.length / 4);
|
|
386
|
+
}
|
|
387
|
+
case "tool/result": {
|
|
388
|
+
const raw = event.payload.ok ? stringify(event.payload.output) : event.payload.error?.message ?? "error";
|
|
389
|
+
return Math.ceil(raw.length / 4);
|
|
390
|
+
}
|
|
391
|
+
case "plan":
|
|
392
|
+
return 0;
|
|
393
|
+
case "checkpoint":
|
|
394
|
+
return estimateMessageTokens(event.payload.messages);
|
|
395
|
+
case "compaction/start":
|
|
396
|
+
case "compaction/end":
|
|
397
|
+
case "meta":
|
|
398
|
+
case "meta/patch":
|
|
399
|
+
return 0;
|
|
400
|
+
case "llm/retry":
|
|
401
|
+
case "llm/retry-started":
|
|
402
|
+
return 0;
|
|
403
|
+
case "request/header":
|
|
404
|
+
case "request/context":
|
|
405
|
+
case "agent/inbox/spliced":
|
|
406
|
+
return 0;
|
|
407
|
+
case "turn/start":
|
|
408
|
+
case "turn/end":
|
|
409
|
+
case "step/start":
|
|
410
|
+
case "step/end":
|
|
411
|
+
return 0;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
function suffixStartIndexForTokens(events, targetTokens) {
|
|
415
|
+
if (!events.length || targetTokens <= 0) return 0;
|
|
416
|
+
let tokens = 0;
|
|
417
|
+
let candidate = events.length;
|
|
418
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
419
|
+
tokens += estimateEventTokens(events[index]);
|
|
420
|
+
if (tokens >= targetTokens) {
|
|
421
|
+
candidate = index;
|
|
422
|
+
break;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
if (candidate === events.length) return 0;
|
|
426
|
+
let cut = candidate;
|
|
427
|
+
while (cut < events.length) {
|
|
428
|
+
const event = events[cut];
|
|
429
|
+
if (event?.type === "user/message") return cut;
|
|
430
|
+
cut += 1;
|
|
431
|
+
}
|
|
432
|
+
return candidate;
|
|
433
|
+
}
|
|
434
|
+
function safeCompactSplit(events, splitIndex) {
|
|
435
|
+
const index = Math.max(0, Math.min(splitIndex, events.length));
|
|
436
|
+
const first = events[index];
|
|
437
|
+
if (first?.type === "tool/result") {
|
|
438
|
+
const callId = first.payload.toolCallId;
|
|
439
|
+
for (let cursor = index - 1; cursor >= 0; cursor -= 1) {
|
|
440
|
+
const candidate = events[cursor];
|
|
441
|
+
if (candidate?.type === "tool/call" && candidate.payload.id === callId) {
|
|
442
|
+
return cursor;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
let openCalls = 0;
|
|
447
|
+
for (let cursor = 0; cursor <= events.length; cursor += 1) {
|
|
448
|
+
if (cursor >= index && openCalls === 0) return cursor;
|
|
449
|
+
const event = events[cursor];
|
|
450
|
+
if (!event) continue;
|
|
451
|
+
if (event.type === "tool/call") openCalls += 1;
|
|
452
|
+
if (event.type === "tool/result") openCalls = Math.max(0, openCalls - 1);
|
|
453
|
+
}
|
|
454
|
+
return index;
|
|
455
|
+
}
|
|
456
|
+
function repairUnclosed(events) {
|
|
457
|
+
const openCalls = [];
|
|
458
|
+
const openSteps = [];
|
|
459
|
+
const openTurns = [];
|
|
460
|
+
for (const event of events) {
|
|
461
|
+
switch (event.type) {
|
|
462
|
+
case "tool/call":
|
|
463
|
+
openCalls.push(event);
|
|
464
|
+
break;
|
|
465
|
+
case "tool/result": {
|
|
466
|
+
const callIndex = openCalls.findIndex(
|
|
467
|
+
(call) => call.payload.id === event.payload.toolCallId
|
|
468
|
+
);
|
|
469
|
+
if (callIndex >= 0) openCalls.splice(callIndex, 1);
|
|
470
|
+
else openCalls.pop();
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
case "step/start":
|
|
474
|
+
openSteps.push(event);
|
|
475
|
+
break;
|
|
476
|
+
case "step/end":
|
|
477
|
+
openSteps.pop();
|
|
478
|
+
break;
|
|
479
|
+
case "turn/start":
|
|
480
|
+
openTurns.push(event);
|
|
481
|
+
break;
|
|
482
|
+
case "turn/end":
|
|
483
|
+
openTurns.pop();
|
|
484
|
+
break;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
if (!openCalls.length && !openSteps.length && !openTurns.length) return [];
|
|
488
|
+
const synthetic = [];
|
|
489
|
+
let nextSeq = (events.at(-1)?.seq ?? 0) + 1;
|
|
490
|
+
let nextTs = (events.at(-1)?.ts ?? Date.now()) + 1;
|
|
491
|
+
const push = (type, payload) => {
|
|
492
|
+
synthetic.push({
|
|
493
|
+
id: randomUUID(),
|
|
494
|
+
seq: nextSeq,
|
|
495
|
+
ts: nextTs,
|
|
496
|
+
type,
|
|
497
|
+
payload: clone(payload)
|
|
498
|
+
});
|
|
499
|
+
nextSeq += 1;
|
|
500
|
+
nextTs += 1;
|
|
501
|
+
};
|
|
502
|
+
for (const call of openCalls) {
|
|
503
|
+
push("tool/result", {
|
|
504
|
+
id: call.payload.id,
|
|
505
|
+
toolCallId: call.payload.id,
|
|
506
|
+
name: call.payload.name,
|
|
507
|
+
ok: false,
|
|
508
|
+
error: {
|
|
509
|
+
name: "SessionInterruptedError",
|
|
510
|
+
message: "session interrupted before tool result was recorded"
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
for (const step of openSteps) {
|
|
515
|
+
push("step/end", {
|
|
516
|
+
turn: step.payload.turn,
|
|
517
|
+
step: step.payload.step,
|
|
518
|
+
finishReason: "interrupted",
|
|
519
|
+
interrupted: true
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
for (let i = 0; i < openTurns.length; i++) {
|
|
523
|
+
const turn = openTurns[i].payload.turn;
|
|
524
|
+
push("turn/end", {
|
|
525
|
+
turn,
|
|
526
|
+
finishReason: "interrupted",
|
|
527
|
+
interrupted: true
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
return synthetic;
|
|
531
|
+
}
|
|
532
|
+
function resolveCompactKeep(events, options) {
|
|
533
|
+
if (typeof options.keepTokens === "number" && Number.isFinite(options.keepTokens) && options.keepTokens > 0) {
|
|
534
|
+
return events.length - suffixStartIndexForTokens(events, options.keepTokens);
|
|
535
|
+
}
|
|
536
|
+
const keep = typeof options.keep === "number" && Number.isFinite(options.keep) && options.keep > 0 ? Math.floor(options.keep) : 0;
|
|
537
|
+
return Math.min(keep, events.length);
|
|
538
|
+
}
|
|
539
|
+
function estimateContextUsage(messages, limit = DEFAULT_CONTEXT_LIMIT) {
|
|
540
|
+
const tokens = estimateMessageTokens(messages);
|
|
541
|
+
return {
|
|
542
|
+
tokens,
|
|
543
|
+
limit,
|
|
544
|
+
ratio: limit > 0 ? tokens / limit : 0
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
var SessionLog = class {
|
|
548
|
+
constructor(file, _broadcast) {
|
|
549
|
+
this.file = file;
|
|
550
|
+
this._broadcast = _broadcast;
|
|
551
|
+
}
|
|
552
|
+
file;
|
|
553
|
+
_broadcast;
|
|
554
|
+
_events = [];
|
|
555
|
+
_surface = [];
|
|
556
|
+
_surfaceNodes = [];
|
|
557
|
+
_requestHeader;
|
|
558
|
+
_requestContext;
|
|
559
|
+
_loaded = false;
|
|
560
|
+
_nextSeq = 1;
|
|
561
|
+
_queue = Promise.resolve();
|
|
562
|
+
_writeTail = Promise.resolve();
|
|
563
|
+
_pending = [];
|
|
564
|
+
_drainScheduled = false;
|
|
565
|
+
_writeError;
|
|
566
|
+
init() {
|
|
567
|
+
return this._run(async () => {
|
|
568
|
+
const key = sessionFileKey(this.file);
|
|
569
|
+
if (!liveSessions.has(key)) liveSessions.set(key, this);
|
|
570
|
+
try {
|
|
571
|
+
await this._ensureLoaded();
|
|
572
|
+
await this._drainWrite();
|
|
573
|
+
if (this._writeError) {
|
|
574
|
+
const error = this._writeError;
|
|
575
|
+
this._writeError = void 0;
|
|
576
|
+
throw error;
|
|
577
|
+
}
|
|
578
|
+
} catch (error) {
|
|
579
|
+
if (liveSessions.get(key) === this) liveSessions.delete(key);
|
|
580
|
+
throw error;
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
append(type, payload) {
|
|
585
|
+
return this._run(async () => {
|
|
586
|
+
await this._ensureLoaded();
|
|
587
|
+
const event = this._buildEvent(type, payload);
|
|
588
|
+
this._commitEvent(event);
|
|
589
|
+
return event;
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
_buildEvent(type, payload) {
|
|
593
|
+
const eventPayload = clone(payload);
|
|
594
|
+
const event = {
|
|
595
|
+
id: randomUUID(),
|
|
596
|
+
seq: this._nextSeq,
|
|
597
|
+
ts: Date.now(),
|
|
598
|
+
type,
|
|
599
|
+
payload: eventPayload
|
|
600
|
+
};
|
|
601
|
+
if (isMessageEventType(type)) {
|
|
602
|
+
for (let index = this._events.length - 1; index >= 0; index -= 1) {
|
|
603
|
+
const previous2 = this._events[index];
|
|
604
|
+
if (previous2 && isMessageEvent(previous2)) {
|
|
605
|
+
const messagePayload = eventPayload;
|
|
606
|
+
messagePayload.parentId ??= previous2.id;
|
|
607
|
+
break;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
event.surfaceOp ??= "append";
|
|
611
|
+
const previous = previousSurfaceSeq(this._surfaceNodes);
|
|
612
|
+
event.sourceEventSeqs ??= previous === void 0 ? [] : [previous];
|
|
613
|
+
} else if (type === "request/header") {
|
|
614
|
+
event.sourceEventSeqs ??= [];
|
|
615
|
+
} else if (type === "request/context") {
|
|
616
|
+
event.sourceEventSeqs ??= [];
|
|
617
|
+
} else if (type === "agent/inbox/spliced") {
|
|
618
|
+
event.sourceEventSeqs ??= [];
|
|
619
|
+
}
|
|
620
|
+
this._nextSeq += 1;
|
|
621
|
+
return event;
|
|
622
|
+
}
|
|
623
|
+
_commitEvent(event) {
|
|
624
|
+
this._events.push(event);
|
|
625
|
+
this._refreshSurface();
|
|
626
|
+
if (event.type === "request/header") this._requestHeader = clone(event.payload);
|
|
627
|
+
if (event.type === "request/context") this._requestContext = clone(event.payload);
|
|
628
|
+
this._broadcast?.("event", event);
|
|
629
|
+
this._enqueue(event);
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Rebuild both surface views from the committed log. The nodes come from the
|
|
633
|
+
* positional {@link foldSurface} fold; the messages come from
|
|
634
|
+
* {@link deriveSurfaceMessages}, which walks those nodes in surface order —
|
|
635
|
+
* the model view is a pure function of the surface, never of raw file order.
|
|
636
|
+
*/
|
|
637
|
+
_refreshSurface() {
|
|
638
|
+
this._surfaceNodes = foldSurface(this._events).nodes;
|
|
639
|
+
this._surface = deriveSurfaceMessages(this._events);
|
|
640
|
+
}
|
|
641
|
+
_enqueue(event) {
|
|
642
|
+
this._pending.push(event);
|
|
643
|
+
if (this._drainScheduled) return;
|
|
644
|
+
this._drainScheduled = true;
|
|
645
|
+
setImmediate(() => {
|
|
646
|
+
this._drainScheduled = false;
|
|
647
|
+
void this._drainWrite();
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
_drainWrite() {
|
|
651
|
+
const events = this._pending.splice(0);
|
|
652
|
+
if (!events.length) return this._writeTail;
|
|
653
|
+
const write = this._writeTail.then(() => appendFile(
|
|
654
|
+
this.file,
|
|
655
|
+
`${events.map((event) => JSON.stringify(event)).join("\n")}
|
|
656
|
+
`,
|
|
657
|
+
"utf8"
|
|
658
|
+
));
|
|
659
|
+
this._writeTail = write.catch((error) => {
|
|
660
|
+
this._writeError ??= error;
|
|
661
|
+
});
|
|
662
|
+
return write.then(() => void 0, () => void 0);
|
|
663
|
+
}
|
|
664
|
+
flush() {
|
|
665
|
+
return this._run(async () => {
|
|
666
|
+
await this._ensureLoaded();
|
|
667
|
+
await this._drainWrite();
|
|
668
|
+
if (this._writeError) {
|
|
669
|
+
const error = this._writeError;
|
|
670
|
+
this._writeError = void 0;
|
|
671
|
+
throw error;
|
|
672
|
+
}
|
|
673
|
+
const seq = this._nextSeq - 1;
|
|
674
|
+
this._broadcast?.("flush", { file: this.file, seq });
|
|
675
|
+
return seq;
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
async lineage(messageId) {
|
|
679
|
+
return this._run(async () => {
|
|
680
|
+
await this._ensureLoaded();
|
|
681
|
+
return this._resolveLineage(messageId);
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
/** The next turn number: one more than the highest recorded `turn/start`. */
|
|
685
|
+
async nextTurn() {
|
|
686
|
+
return this._run(async () => {
|
|
687
|
+
await this._ensureLoaded();
|
|
688
|
+
let max = 0;
|
|
689
|
+
for (const event of this._events) {
|
|
690
|
+
if (event.type === "turn/start") max = Math.max(max, event.payload.turn);
|
|
691
|
+
}
|
|
692
|
+
return max + 1;
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
async forkAt(messageId) {
|
|
696
|
+
return this._run(async () => {
|
|
697
|
+
await this._ensureLoaded();
|
|
698
|
+
const lineage = this._resolveLineage(messageId);
|
|
699
|
+
const lineageIds = new Set(lineage.map((event) => event.id));
|
|
700
|
+
const targetIndex = this._events.findIndex(
|
|
701
|
+
(event) => event.id === messageId && isMessageEvent(event)
|
|
702
|
+
);
|
|
703
|
+
if (targetIndex < 0) {
|
|
704
|
+
throw new Error(`message not found: ${messageId}`);
|
|
705
|
+
}
|
|
706
|
+
const selected = [];
|
|
707
|
+
let skippedMessage = false;
|
|
708
|
+
for (let index = 0; index <= targetIndex; index += 1) {
|
|
709
|
+
const event = this._events[index];
|
|
710
|
+
if (isMessageEvent(event)) {
|
|
711
|
+
if (!lineageIds.has(event.id)) {
|
|
712
|
+
skippedMessage = true;
|
|
713
|
+
continue;
|
|
714
|
+
}
|
|
715
|
+
skippedMessage = false;
|
|
716
|
+
selected.push(clone(event));
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
if (event.type === "meta" || event.type === "llm/retry" || event.type === "llm/retry-started" || event.type.startsWith("turn/") || event.type.startsWith("step/")) {
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
if (event.type === "checkpoint") {
|
|
723
|
+
selected.push(clone(event));
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
726
|
+
if (!skippedMessage) {
|
|
727
|
+
selected.push(clone(event));
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return selected;
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
read() {
|
|
734
|
+
return this._run(async () => {
|
|
735
|
+
await this._ensureLoaded();
|
|
736
|
+
return this._events.map((event) => clone(event));
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
deriveMessages() {
|
|
740
|
+
return this._run(async () => {
|
|
741
|
+
await this._ensureLoaded();
|
|
742
|
+
return clone(this._surface);
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
/** The ordered surface events, after compaction replacements are applied. */
|
|
746
|
+
surfaceEvents() {
|
|
747
|
+
return this._run(async () => {
|
|
748
|
+
await this._ensureLoaded();
|
|
749
|
+
const bySeq = new Map(this._events.map((event) => [event.seq, event]));
|
|
750
|
+
return this._surfaceNodes.map((seq) => bySeq.get(seq)).filter((event) => event !== void 0).map((event) => clone(event));
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
/** The latest `request/header` snapshot, or undefined before the first one. */
|
|
754
|
+
requestHeader() {
|
|
755
|
+
return this._requestHeader ? clone(this._requestHeader) : void 0;
|
|
756
|
+
}
|
|
757
|
+
/** The latest resolved route metadata, or undefined before the first one. */
|
|
758
|
+
requestContext() {
|
|
759
|
+
return this._requestContext ? clone(this._requestContext) : void 0;
|
|
760
|
+
}
|
|
761
|
+
/** Display metadata folded from the durable `meta` + `meta/patch` events. */
|
|
762
|
+
meta() {
|
|
763
|
+
return this._run(async () => {
|
|
764
|
+
await this._ensureLoaded();
|
|
765
|
+
return foldSessionMeta(this._events);
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
estimateContext(limit = DEFAULT_CONTEXT_LIMIT) {
|
|
769
|
+
return this._run(async () => {
|
|
770
|
+
await this._ensureLoaded();
|
|
771
|
+
return estimateContextUsage(this._surface, limit);
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
replay(reduce, initial) {
|
|
775
|
+
return this.read().then((events) => {
|
|
776
|
+
if (!reduce) return events;
|
|
777
|
+
let state = initial;
|
|
778
|
+
let pending = Promise.resolve();
|
|
779
|
+
for (const event of events) {
|
|
780
|
+
pending = pending.then(async () => {
|
|
781
|
+
state = await reduce(state, event);
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
return pending.then(() => state);
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
/** Structural violations in the loaded event stream (empty = balanced). */
|
|
788
|
+
runInvariants() {
|
|
789
|
+
return this._run(async () => {
|
|
790
|
+
await this._ensureLoaded();
|
|
791
|
+
return checkSessionInvariants(this._events);
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Compact the session without ever rewriting history.
|
|
796
|
+
*
|
|
797
|
+
* The surface (not the raw log) is the unit of compaction. A kept suffix —
|
|
798
|
+
* measured in tokens via `keepTokens`, or in nodes via `keep`, and always
|
|
799
|
+
* snapped to start on a fresh `user/message` turn so a tool result never
|
|
800
|
+
* outlives the assistant call it answers — stays visible; every earlier
|
|
801
|
+
* surface node is shadowed by the checkpoint's `surfaceOp.replace` range.
|
|
802
|
+
* The three compaction events are appended like any other event, so seqs
|
|
803
|
+
* stay immutable, live observers see the transition, and a crash can only
|
|
804
|
+
* leave an orphaned `compaction/start` rather than a torn rewrite.
|
|
805
|
+
*
|
|
806
|
+
* Returns the new event count, or the unchanged count when there is no
|
|
807
|
+
* useful surface span to shadow.
|
|
808
|
+
*/
|
|
809
|
+
compact(options = {}) {
|
|
810
|
+
return this._run(async () => {
|
|
811
|
+
await this._ensureLoaded();
|
|
812
|
+
const events = this._events;
|
|
813
|
+
const bySeq = new Map(events.map((event) => [event.seq, event]));
|
|
814
|
+
const surfaceEvents = foldSurface(events).nodes.map((seq) => bySeq.get(seq)).filter((event) => event !== void 0);
|
|
815
|
+
const keep = resolveCompactKeep(surfaceEvents, options);
|
|
816
|
+
let split = Math.max(0, surfaceEvents.length - keep);
|
|
817
|
+
if (split < surfaceEvents.length && surfaceEvents[split].type !== "user/message") {
|
|
818
|
+
let next = split;
|
|
819
|
+
while (next < surfaceEvents.length && surfaceEvents[next].type !== "user/message") next += 1;
|
|
820
|
+
if (next < surfaceEvents.length) {
|
|
821
|
+
split = next;
|
|
822
|
+
} else {
|
|
823
|
+
let lastUser = surfaceEvents.length - 1;
|
|
824
|
+
while (lastUser >= 0 && surfaceEvents[lastUser].type !== "user/message") lastUser -= 1;
|
|
825
|
+
split = lastUser >= 0 ? lastUser : surfaceEvents.length;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
const shadowed = surfaceEvents.slice(0, split);
|
|
829
|
+
if (!options.messages?.length) return this._events.length;
|
|
830
|
+
const range = shadowed.length ? { start: shadowed[0].seq, end: shadowed[shadowed.length - 1].seq } : void 0;
|
|
831
|
+
const boundary = split;
|
|
832
|
+
this._commitEvent(this._buildEvent("compaction/start", {
|
|
833
|
+
...boundary > 0 ? { boundary } : {},
|
|
834
|
+
...options.keep !== void 0 ? { keep: options.keep } : {},
|
|
835
|
+
...options.tokensBefore !== void 0 ? { tokensBefore: options.tokensBefore } : {}
|
|
836
|
+
}));
|
|
837
|
+
const checkpoint = this._buildEvent("checkpoint", {
|
|
838
|
+
messages: clone(options.messages),
|
|
839
|
+
...range ? { surfaceOp: { op: "replace", start: range.start, end: range.end } } : {},
|
|
840
|
+
...options.summary ? { summary: options.summary } : {},
|
|
841
|
+
...options.tokensBefore !== void 0 ? { tokensBefore: options.tokensBefore } : {}
|
|
842
|
+
});
|
|
843
|
+
this._commitEvent(checkpoint);
|
|
844
|
+
this._commitEvent(this._buildEvent("compaction/end", {
|
|
845
|
+
checkpointId: checkpoint.id,
|
|
846
|
+
...options.keep !== void 0 ? { keep: options.keep } : {}
|
|
847
|
+
}));
|
|
848
|
+
return this._events.length;
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
close() {
|
|
852
|
+
return this._queue.then(async () => {
|
|
853
|
+
await this._drainWrite();
|
|
854
|
+
if (this._writeError) {
|
|
855
|
+
const error = this._writeError;
|
|
856
|
+
this._writeError = void 0;
|
|
857
|
+
throw error;
|
|
858
|
+
}
|
|
859
|
+
const key = sessionFileKey(this.file);
|
|
860
|
+
if (liveSessions.get(key) === this) liveSessions.delete(key);
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
_run(task) {
|
|
864
|
+
const next = this._queue.then(task, task);
|
|
865
|
+
this._queue = next.then(() => void 0, () => void 0);
|
|
866
|
+
return next;
|
|
867
|
+
}
|
|
868
|
+
async _ensureLoaded() {
|
|
869
|
+
if (this._loaded) return;
|
|
870
|
+
const key = sessionFileKey(this.file);
|
|
871
|
+
const owner = liveSessions.get(key);
|
|
872
|
+
if (owner && owner !== this && owner._loaded) {
|
|
873
|
+
this._events = owner._events.map((event) => clone(event));
|
|
874
|
+
this._surface = clone(owner._surface);
|
|
875
|
+
this._surfaceNodes = [...owner._surfaceNodes];
|
|
876
|
+
this._requestHeader = owner._requestHeader ? clone(owner._requestHeader) : void 0;
|
|
877
|
+
this._requestContext = owner._requestContext ? clone(owner._requestContext) : void 0;
|
|
878
|
+
this._nextSeq = owner._nextSeq;
|
|
879
|
+
this._loaded = true;
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
await mkdir(dirname(resolve(this.file)), { recursive: true });
|
|
883
|
+
const read = await this._read();
|
|
884
|
+
if (!read.existing) {
|
|
885
|
+
const meta = {
|
|
886
|
+
id: randomUUID(),
|
|
887
|
+
seq: 1,
|
|
888
|
+
ts: Date.now(),
|
|
889
|
+
type: "meta",
|
|
890
|
+
payload: { formatVersion: SESSION_FORMAT_VERSION }
|
|
891
|
+
};
|
|
892
|
+
await writeFile(this.file, `${JSON.stringify(meta)}
|
|
893
|
+
`, "utf8");
|
|
894
|
+
this._events = [meta];
|
|
895
|
+
this._refreshSurface();
|
|
896
|
+
this._nextSeq = 2;
|
|
897
|
+
this._loaded = true;
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
this._events = read.events;
|
|
901
|
+
this._refreshSurface();
|
|
902
|
+
this._requestHeader = foldRequestHeader(this._events);
|
|
903
|
+
this._requestContext = foldRequestContext(this._events);
|
|
904
|
+
this._nextSeq = (this._events.at(-1)?.seq ?? 0) + 1;
|
|
905
|
+
this._loaded = true;
|
|
906
|
+
}
|
|
907
|
+
async _read() {
|
|
908
|
+
let text;
|
|
909
|
+
try {
|
|
910
|
+
text = await readFile(this.file, "utf8");
|
|
911
|
+
} catch (error) {
|
|
912
|
+
if (error.code === "ENOENT") {
|
|
913
|
+
return { existing: false, events: [] };
|
|
914
|
+
}
|
|
915
|
+
throw error;
|
|
916
|
+
}
|
|
917
|
+
if (!text.trim()) return { existing: false, events: [] };
|
|
918
|
+
const lines = text.split("\n");
|
|
919
|
+
const events = [];
|
|
920
|
+
let torn = false;
|
|
921
|
+
for (const line of lines) {
|
|
922
|
+
if (!line.trim()) continue;
|
|
923
|
+
if (torn) continue;
|
|
924
|
+
let parsed;
|
|
925
|
+
try {
|
|
926
|
+
parsed = JSON.parse(line);
|
|
927
|
+
} catch {
|
|
928
|
+
torn = true;
|
|
929
|
+
continue;
|
|
930
|
+
}
|
|
931
|
+
if (!isSessionEvent(parsed)) {
|
|
932
|
+
torn = true;
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
events.push(parsed);
|
|
936
|
+
}
|
|
937
|
+
this._assertFormat(events);
|
|
938
|
+
const key = sessionFileKey(this.file);
|
|
939
|
+
const owner = liveSessions.get(key);
|
|
940
|
+
if (owner && owner !== this) {
|
|
941
|
+
return { existing: true, events };
|
|
942
|
+
}
|
|
943
|
+
const synthetic = repairUnclosed(events);
|
|
944
|
+
if (!torn && !synthetic.length) return { existing: true, events };
|
|
945
|
+
const repaired = [...events, ...synthetic];
|
|
946
|
+
await writeFile(
|
|
947
|
+
this.file,
|
|
948
|
+
`${repaired.map((event) => JSON.stringify(event)).join("\n")}
|
|
949
|
+
`,
|
|
950
|
+
"utf8"
|
|
951
|
+
);
|
|
952
|
+
return { existing: true, events: repaired };
|
|
953
|
+
}
|
|
954
|
+
_assertFormat(events) {
|
|
955
|
+
let version;
|
|
956
|
+
for (const event of events) {
|
|
957
|
+
if (event.type !== "meta") continue;
|
|
958
|
+
const value = event.payload.formatVersion;
|
|
959
|
+
if (typeof value === "number") version = value;
|
|
960
|
+
}
|
|
961
|
+
if (version === void 0) {
|
|
962
|
+
throw new SessionFormatError(
|
|
963
|
+
`session log is missing formatVersion; expected ${SESSION_FORMAT_VERSION}`
|
|
964
|
+
);
|
|
965
|
+
}
|
|
966
|
+
if (version !== SESSION_FORMAT_VERSION) {
|
|
967
|
+
throw new SessionFormatError(
|
|
968
|
+
`unsupported session format version ${version}; expected ${SESSION_FORMAT_VERSION}`
|
|
969
|
+
);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
_resolveLineage(messageId) {
|
|
973
|
+
const messages = this._events.filter(
|
|
974
|
+
(event) => isMessageEvent(event)
|
|
975
|
+
);
|
|
976
|
+
const byId = new Map(
|
|
977
|
+
messages.map((event) => [event.id, event])
|
|
978
|
+
);
|
|
979
|
+
if (!byId.has(messageId)) {
|
|
980
|
+
throw new Error(`message not found: ${messageId}`);
|
|
981
|
+
}
|
|
982
|
+
const chain = [];
|
|
983
|
+
const seen = /* @__PURE__ */ new Set();
|
|
984
|
+
let cursorId = messageId;
|
|
985
|
+
while (cursorId && byId.has(cursorId) && !seen.has(cursorId)) {
|
|
986
|
+
seen.add(cursorId);
|
|
987
|
+
const event = byId.get(cursorId);
|
|
988
|
+
chain.unshift(clone(event));
|
|
989
|
+
const parentId = event.payload.parentId;
|
|
990
|
+
if (parentId && byId.has(parentId)) {
|
|
991
|
+
cursorId = parentId;
|
|
992
|
+
continue;
|
|
993
|
+
}
|
|
994
|
+
const index = messages.findIndex((message) => message.id === cursorId);
|
|
995
|
+
cursorId = index > 0 ? messages[index - 1].id : void 0;
|
|
996
|
+
}
|
|
997
|
+
return chain;
|
|
998
|
+
}
|
|
999
|
+
};
|
|
1000
|
+
var session = {
|
|
1001
|
+
name: "session",
|
|
1002
|
+
apply: async (ctx, config) => {
|
|
1003
|
+
const log = new SessionLog(config.file, config.broadcast ?? ((type, payload) => {
|
|
1004
|
+
if (type === "event") ctx.emit("session/event", payload);
|
|
1005
|
+
else ctx.emit("session/flush", payload);
|
|
1006
|
+
}));
|
|
1007
|
+
await log.init();
|
|
1008
|
+
ctx.provide("session", log);
|
|
1009
|
+
return () => log.close();
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
var name = "@tnega/session";
|
|
1013
|
+
export {
|
|
1014
|
+
DEFAULT_CONTEXT_LIMIT,
|
|
1015
|
+
SESSION_FORMAT_VERSION,
|
|
1016
|
+
SessionFormatError,
|
|
1017
|
+
SessionLog,
|
|
1018
|
+
checkBalancedSteps,
|
|
1019
|
+
checkBalancedToolCalls,
|
|
1020
|
+
checkBalancedTurns,
|
|
1021
|
+
checkMonotonicSeq,
|
|
1022
|
+
checkSessionInvariants,
|
|
1023
|
+
checkpointShadowRange,
|
|
1024
|
+
deriveEventMessage,
|
|
1025
|
+
deriveSurfaceMessages,
|
|
1026
|
+
estimateContextUsage,
|
|
1027
|
+
estimateEventTokens,
|
|
1028
|
+
estimateMessageTokens,
|
|
1029
|
+
foldMetaFromLog,
|
|
1030
|
+
foldRequestContext,
|
|
1031
|
+
foldRequestHeader,
|
|
1032
|
+
foldSessionMeta,
|
|
1033
|
+
foldSurface,
|
|
1034
|
+
isAppendSurfaceEvent,
|
|
1035
|
+
isSurfaceEventType,
|
|
1036
|
+
name,
|
|
1037
|
+
projectEvents,
|
|
1038
|
+
repairUnclosed,
|
|
1039
|
+
resolveCompactKeep,
|
|
1040
|
+
safeCompactSplit,
|
|
1041
|
+
session,
|
|
1042
|
+
suffixStartIndexForTokens,
|
|
1043
|
+
transcriptEvents
|
|
1044
|
+
};
|
|
1045
|
+
//# sourceMappingURL=session.js.map
|