@lov3kaizen/agentsea-debugger 0.5.1
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/LICENSE +21 -0
- package/README.md +364 -0
- package/dist/Recorder-Dc9qR2V2.d.ts +138 -0
- package/dist/ReplayEngine-Dyyqy5bw.d.ts +54 -0
- package/dist/analysis/index.d.ts +4 -0
- package/dist/analysis/index.js +1840 -0
- package/dist/analysis/index.js.map +1 -0
- package/dist/diff-CLShBdWe.d.ts +24 -0
- package/dist/index-1W27DYJt.d.ts +366 -0
- package/dist/index-DRrKPSo9.d.ts +233 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +6232 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/agentsea/index.d.ts +8 -0
- package/dist/integrations/agentsea/index.js +5826 -0
- package/dist/integrations/agentsea/index.js.map +1 -0
- package/dist/recording/index.d.ts +91 -0
- package/dist/recording/index.js +1207 -0
- package/dist/recording/index.js.map +1 -0
- package/dist/recording.types-Ck7pbikw.d.ts +281 -0
- package/dist/replay/index.d.ts +80 -0
- package/dist/replay/index.js +1112 -0
- package/dist/replay/index.js.map +1 -0
- package/dist/replay.types-C9hJizI-.d.ts +76 -0
- package/dist/storage/index.d.ts +135 -0
- package/dist/storage/index.js +588 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/visualization/index.d.ts +123 -0
- package/dist/visualization/index.js +789 -0
- package/dist/visualization/index.js.map +1 -0
- package/dist/visualization.types-dWjGE037.d.ts +98 -0
- package/package.json +99 -0
|
@@ -0,0 +1,1112 @@
|
|
|
1
|
+
import { EventEmitter } from 'eventemitter3';
|
|
2
|
+
import { nanoid } from 'nanoid';
|
|
3
|
+
|
|
4
|
+
// src/replay/ReplayEngine.ts
|
|
5
|
+
function generateId(prefix = "") {
|
|
6
|
+
const id = nanoid(12);
|
|
7
|
+
return prefix ? `${prefix}_${id}` : id;
|
|
8
|
+
}
|
|
9
|
+
function now() {
|
|
10
|
+
return Date.now();
|
|
11
|
+
}
|
|
12
|
+
function deepClone(obj) {
|
|
13
|
+
if (obj === null || typeof obj !== "object") {
|
|
14
|
+
return obj;
|
|
15
|
+
}
|
|
16
|
+
if (Array.isArray(obj)) {
|
|
17
|
+
return obj.map((item) => deepClone(item));
|
|
18
|
+
}
|
|
19
|
+
if (obj instanceof Date) {
|
|
20
|
+
return new Date(obj.getTime());
|
|
21
|
+
}
|
|
22
|
+
if (obj instanceof Map) {
|
|
23
|
+
const clonedMap = /* @__PURE__ */ new Map();
|
|
24
|
+
obj.forEach((value, key) => {
|
|
25
|
+
clonedMap.set(deepClone(key), deepClone(value));
|
|
26
|
+
});
|
|
27
|
+
return clonedMap;
|
|
28
|
+
}
|
|
29
|
+
if (obj instanceof Set) {
|
|
30
|
+
const clonedSet = /* @__PURE__ */ new Set();
|
|
31
|
+
obj.forEach((value) => {
|
|
32
|
+
clonedSet.add(deepClone(value));
|
|
33
|
+
});
|
|
34
|
+
return clonedSet;
|
|
35
|
+
}
|
|
36
|
+
const clonedObj = {};
|
|
37
|
+
for (const key in obj) {
|
|
38
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
39
|
+
clonedObj[key] = deepClone(
|
|
40
|
+
obj[key]
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return clonedObj;
|
|
45
|
+
}
|
|
46
|
+
function sleep(ms) {
|
|
47
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/utils/diff.ts
|
|
51
|
+
function diff(lhs, rhs, path = []) {
|
|
52
|
+
const differences = [];
|
|
53
|
+
if (lhs === rhs) {
|
|
54
|
+
return differences;
|
|
55
|
+
}
|
|
56
|
+
if (lhs === null || lhs === void 0) {
|
|
57
|
+
if (rhs !== null && rhs !== void 0) {
|
|
58
|
+
differences.push({ path, kind: "N", rhs });
|
|
59
|
+
}
|
|
60
|
+
return differences;
|
|
61
|
+
}
|
|
62
|
+
if (rhs === null || rhs === void 0) {
|
|
63
|
+
differences.push({ path, kind: "D", lhs });
|
|
64
|
+
return differences;
|
|
65
|
+
}
|
|
66
|
+
const lhsType = typeof lhs;
|
|
67
|
+
const rhsType = typeof rhs;
|
|
68
|
+
if (lhsType !== rhsType) {
|
|
69
|
+
differences.push({ path, kind: "E", lhs, rhs });
|
|
70
|
+
return differences;
|
|
71
|
+
}
|
|
72
|
+
if (lhsType !== "object") {
|
|
73
|
+
if (lhs !== rhs) {
|
|
74
|
+
differences.push({ path, kind: "E", lhs, rhs });
|
|
75
|
+
}
|
|
76
|
+
return differences;
|
|
77
|
+
}
|
|
78
|
+
if (Array.isArray(lhs) && Array.isArray(rhs)) {
|
|
79
|
+
const maxLen = Math.max(lhs.length, rhs.length);
|
|
80
|
+
for (let i = 0; i < maxLen; i++) {
|
|
81
|
+
if (i >= lhs.length) {
|
|
82
|
+
differences.push({
|
|
83
|
+
path,
|
|
84
|
+
kind: "A",
|
|
85
|
+
index: i,
|
|
86
|
+
item: { path: [...path, String(i)], kind: "N", rhs: rhs[i] }
|
|
87
|
+
});
|
|
88
|
+
} else if (i >= rhs.length) {
|
|
89
|
+
differences.push({
|
|
90
|
+
path,
|
|
91
|
+
kind: "A",
|
|
92
|
+
index: i,
|
|
93
|
+
item: { path: [...path, String(i)], kind: "D", lhs: lhs[i] }
|
|
94
|
+
});
|
|
95
|
+
} else {
|
|
96
|
+
const itemDiffs = diff(lhs[i], rhs[i], [...path, String(i)]);
|
|
97
|
+
differences.push(...itemDiffs);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return differences;
|
|
101
|
+
}
|
|
102
|
+
if (typeof lhs === "object" && typeof rhs === "object") {
|
|
103
|
+
const lhsObj = lhs;
|
|
104
|
+
const rhsObj = rhs;
|
|
105
|
+
const allKeys = /* @__PURE__ */ new Set([...Object.keys(lhsObj), ...Object.keys(rhsObj)]);
|
|
106
|
+
for (const key of allKeys) {
|
|
107
|
+
const keyPath = [...path, key];
|
|
108
|
+
if (!(key in lhsObj)) {
|
|
109
|
+
differences.push({ path: keyPath, kind: "N", rhs: rhsObj[key] });
|
|
110
|
+
} else if (!(key in rhsObj)) {
|
|
111
|
+
differences.push({ path: keyPath, kind: "D", lhs: lhsObj[key] });
|
|
112
|
+
} else {
|
|
113
|
+
const propDiffs = diff(lhsObj[key], rhsObj[key], keyPath);
|
|
114
|
+
differences.push(...propDiffs);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return differences;
|
|
119
|
+
}
|
|
120
|
+
function toReplayDifferences(diffs, stepIndex) {
|
|
121
|
+
return diffs.map((d) => ({
|
|
122
|
+
stepIndex,
|
|
123
|
+
path: d.path.join("."),
|
|
124
|
+
original: d.lhs,
|
|
125
|
+
replayed: d.rhs,
|
|
126
|
+
type: d.kind === "N" ? "added" : d.kind === "D" ? "removed" : "changed"
|
|
127
|
+
}));
|
|
128
|
+
}
|
|
129
|
+
function applyPatches(target, patches) {
|
|
130
|
+
let result = target;
|
|
131
|
+
for (const patch of patches) {
|
|
132
|
+
result = applyPatch(result, patch);
|
|
133
|
+
}
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
function applyPatch(target, patch) {
|
|
137
|
+
const result = JSON.parse(JSON.stringify(target));
|
|
138
|
+
if (patch.path.length === 0) {
|
|
139
|
+
return patch.rhs;
|
|
140
|
+
}
|
|
141
|
+
let current = result;
|
|
142
|
+
for (let i = 0; i < patch.path.length - 1; i++) {
|
|
143
|
+
const key = patch.path[i];
|
|
144
|
+
if (!(key in current)) {
|
|
145
|
+
current[key] = {};
|
|
146
|
+
}
|
|
147
|
+
current = current[key];
|
|
148
|
+
}
|
|
149
|
+
const lastKey = patch.path[patch.path.length - 1];
|
|
150
|
+
switch (patch.kind) {
|
|
151
|
+
case "N":
|
|
152
|
+
case "E":
|
|
153
|
+
current[lastKey] = patch.rhs;
|
|
154
|
+
break;
|
|
155
|
+
case "D":
|
|
156
|
+
delete current[lastKey];
|
|
157
|
+
break;
|
|
158
|
+
case "A":
|
|
159
|
+
if (Array.isArray(current[lastKey]) && patch.item) {
|
|
160
|
+
const arr = current[lastKey];
|
|
161
|
+
if (patch.item.kind === "N") {
|
|
162
|
+
arr.splice(patch.index, 0, patch.item.rhs);
|
|
163
|
+
} else if (patch.item.kind === "D") {
|
|
164
|
+
arr.splice(patch.index, 1);
|
|
165
|
+
} else if (patch.item.kind === "E") {
|
|
166
|
+
arr[patch.index] = patch.item.rhs;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
return result;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// src/replay/StateRestorer.ts
|
|
175
|
+
var DEFAULT_OPTIONS = {
|
|
176
|
+
includeMemory: true,
|
|
177
|
+
includeContext: true,
|
|
178
|
+
includeMessages: true,
|
|
179
|
+
includeTools: true
|
|
180
|
+
};
|
|
181
|
+
var StateRestorer = class {
|
|
182
|
+
options;
|
|
183
|
+
stateCache = /* @__PURE__ */ new Map();
|
|
184
|
+
constructor(options) {
|
|
185
|
+
this.options = {
|
|
186
|
+
...DEFAULT_OPTIONS,
|
|
187
|
+
...options
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Restore state at a specific step
|
|
192
|
+
*/
|
|
193
|
+
restore(recording, stepIndex) {
|
|
194
|
+
const cacheKey = recording.id;
|
|
195
|
+
const recordingCache = this.stateCache.get(cacheKey);
|
|
196
|
+
if (recordingCache?.has(stepIndex)) {
|
|
197
|
+
return deepClone(recordingCache.get(stepIndex));
|
|
198
|
+
}
|
|
199
|
+
if (stepIndex < 0) {
|
|
200
|
+
const state2 = deepClone(recording.initialState);
|
|
201
|
+
if (!recordingCache) {
|
|
202
|
+
this.stateCache.set(cacheKey, /* @__PURE__ */ new Map());
|
|
203
|
+
}
|
|
204
|
+
this.stateCache.get(cacheKey).set(stepIndex, deepClone(state2));
|
|
205
|
+
return state2;
|
|
206
|
+
}
|
|
207
|
+
let state;
|
|
208
|
+
let startStep;
|
|
209
|
+
const checkpoint = this.findClosestCheckpoint(recording, stepIndex);
|
|
210
|
+
if (checkpoint) {
|
|
211
|
+
if (checkpoint.stepIndex === stepIndex) {
|
|
212
|
+
state = deepClone(recording.initialState);
|
|
213
|
+
startStep = 0;
|
|
214
|
+
} else {
|
|
215
|
+
state = deepClone(recording.initialState);
|
|
216
|
+
startStep = 0;
|
|
217
|
+
}
|
|
218
|
+
} else {
|
|
219
|
+
state = deepClone(recording.initialState);
|
|
220
|
+
startStep = 0;
|
|
221
|
+
}
|
|
222
|
+
for (let i = startStep; i <= stepIndex && i < recording.steps.length; i++) {
|
|
223
|
+
state = this.applyStep(state, recording.steps[i]);
|
|
224
|
+
}
|
|
225
|
+
if (!recordingCache) {
|
|
226
|
+
this.stateCache.set(cacheKey, /* @__PURE__ */ new Map());
|
|
227
|
+
}
|
|
228
|
+
this.stateCache.get(cacheKey).set(stepIndex, deepClone(state));
|
|
229
|
+
return state;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Restore state from a checkpoint
|
|
233
|
+
*/
|
|
234
|
+
restoreFromCheckpoint(checkpoint) {
|
|
235
|
+
return deepClone(checkpoint.state);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Apply a step to state
|
|
239
|
+
*/
|
|
240
|
+
applyStep(state, step) {
|
|
241
|
+
const newState = deepClone(state);
|
|
242
|
+
switch (step.type) {
|
|
243
|
+
case "input":
|
|
244
|
+
if (this.options.includeMessages) {
|
|
245
|
+
newState.messages.push({
|
|
246
|
+
role: "user",
|
|
247
|
+
content: String(step.input)
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
break;
|
|
251
|
+
case "prompt":
|
|
252
|
+
break;
|
|
253
|
+
case "response":
|
|
254
|
+
if (this.options.includeMessages) {
|
|
255
|
+
newState.messages.push({
|
|
256
|
+
role: "assistant",
|
|
257
|
+
content: String(step.output)
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
break;
|
|
261
|
+
case "tool-call":
|
|
262
|
+
if (this.options.includeMessages && step.toolCall) {
|
|
263
|
+
newState.messages.push({
|
|
264
|
+
role: "assistant",
|
|
265
|
+
content: `[Calling tool: ${step.toolCall.name}]`,
|
|
266
|
+
toolCalls: [
|
|
267
|
+
{
|
|
268
|
+
id: step.toolCall.id,
|
|
269
|
+
name: step.toolCall.name,
|
|
270
|
+
arguments: step.toolCall.arguments
|
|
271
|
+
}
|
|
272
|
+
]
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
break;
|
|
276
|
+
case "tool-result":
|
|
277
|
+
if (this.options.includeMessages && step.toolCall) {
|
|
278
|
+
newState.messages.push({
|
|
279
|
+
role: "tool",
|
|
280
|
+
content: String(step.toolCall.result ?? ""),
|
|
281
|
+
toolCallId: step.toolCall.id
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
break;
|
|
285
|
+
case "memory-write":
|
|
286
|
+
if (this.options.includeMemory && step.output) {
|
|
287
|
+
this.applyMemoryChange(
|
|
288
|
+
newState,
|
|
289
|
+
step.output
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
break;
|
|
293
|
+
case "memory-read":
|
|
294
|
+
break;
|
|
295
|
+
case "decision":
|
|
296
|
+
if (this.options.includeContext && step.decision) {
|
|
297
|
+
newState.context["lastDecision"] = {
|
|
298
|
+
options: step.decision.options.map((o) => o.description),
|
|
299
|
+
chosen: step.decision.chosen.description,
|
|
300
|
+
confidence: step.decision.confidence
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
break;
|
|
304
|
+
case "handoff":
|
|
305
|
+
case "delegation":
|
|
306
|
+
if (this.options.includeContext && step.output) {
|
|
307
|
+
const output = step.output;
|
|
308
|
+
newState.context["delegatedTo"] = output.agentId ?? output.agentName;
|
|
309
|
+
}
|
|
310
|
+
break;
|
|
311
|
+
case "error":
|
|
312
|
+
newState.context["lastError"] = step.error;
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
return newState;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Apply memory changes to state
|
|
319
|
+
*/
|
|
320
|
+
applyMemoryChange(state, changes) {
|
|
321
|
+
for (const [key, value] of Object.entries(changes)) {
|
|
322
|
+
if (key === "working") {
|
|
323
|
+
state.memory.working = {
|
|
324
|
+
...state.memory.working,
|
|
325
|
+
...value
|
|
326
|
+
};
|
|
327
|
+
} else if (key === "shortTerm" && Array.isArray(value)) {
|
|
328
|
+
state.memory.shortTerm = [...state.memory.shortTerm ?? [], ...value];
|
|
329
|
+
} else if (key === "longTermSummary" && typeof value === "string") {
|
|
330
|
+
state.memory.longTermSummary = value;
|
|
331
|
+
} else if (key === "size" && typeof value === "number") {
|
|
332
|
+
state.memory.size = value;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Find closest checkpoint before or at step
|
|
338
|
+
*/
|
|
339
|
+
findClosestCheckpoint(recording, stepIndex) {
|
|
340
|
+
let closest;
|
|
341
|
+
for (const checkpoint of recording.checkpoints) {
|
|
342
|
+
if (checkpoint.stepIndex <= stepIndex) {
|
|
343
|
+
if (!closest || checkpoint.stepIndex > closest.stepIndex) {
|
|
344
|
+
closest = checkpoint;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return closest;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Apply a diff patch to state
|
|
352
|
+
*/
|
|
353
|
+
applyPatch(state, differences) {
|
|
354
|
+
return applyPatches(state, differences);
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Validate restored state
|
|
358
|
+
*/
|
|
359
|
+
validate(state) {
|
|
360
|
+
const errors = [];
|
|
361
|
+
const warnings = [];
|
|
362
|
+
if (!state.agentId) {
|
|
363
|
+
errors.push("Missing agentId");
|
|
364
|
+
}
|
|
365
|
+
if (!state.agentName) {
|
|
366
|
+
warnings.push("Missing agentName");
|
|
367
|
+
}
|
|
368
|
+
if (!state.model) {
|
|
369
|
+
warnings.push("Missing model");
|
|
370
|
+
}
|
|
371
|
+
if (!state.memory) {
|
|
372
|
+
errors.push("Missing memory object");
|
|
373
|
+
} else {
|
|
374
|
+
if (typeof state.memory.size !== "number") {
|
|
375
|
+
warnings.push("Memory size should be a number");
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (!Array.isArray(state.messages)) {
|
|
379
|
+
errors.push("Messages should be an array");
|
|
380
|
+
} else {
|
|
381
|
+
for (let i = 0; i < state.messages.length; i++) {
|
|
382
|
+
const msg = state.messages[i];
|
|
383
|
+
if (!msg.role) {
|
|
384
|
+
errors.push(`Message ${i} missing role`);
|
|
385
|
+
}
|
|
386
|
+
if (msg.content === void 0 && !msg.toolCalls) {
|
|
387
|
+
warnings.push(`Message ${i} has no content or tool calls`);
|
|
388
|
+
}
|
|
389
|
+
if (typeof msg.content === "string" && msg.content.trim() === "") {
|
|
390
|
+
warnings.push(`Message ${i} has empty content`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (!state.context || typeof state.context !== "object") {
|
|
395
|
+
warnings.push("Context should be an object");
|
|
396
|
+
}
|
|
397
|
+
if (!Array.isArray(state.tools)) {
|
|
398
|
+
warnings.push("Tools should be an array");
|
|
399
|
+
}
|
|
400
|
+
return {
|
|
401
|
+
valid: errors.length === 0,
|
|
402
|
+
errors,
|
|
403
|
+
warnings
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Merge two states
|
|
408
|
+
*/
|
|
409
|
+
merge(base, overlay) {
|
|
410
|
+
const merged = deepClone(base);
|
|
411
|
+
if (overlay.agentId) {
|
|
412
|
+
merged.agentId = overlay.agentId;
|
|
413
|
+
}
|
|
414
|
+
if (overlay.agentName) {
|
|
415
|
+
merged.agentName = overlay.agentName;
|
|
416
|
+
}
|
|
417
|
+
if (overlay.model) {
|
|
418
|
+
merged.model = overlay.model;
|
|
419
|
+
}
|
|
420
|
+
if (overlay.memory) {
|
|
421
|
+
merged.memory = {
|
|
422
|
+
...merged.memory,
|
|
423
|
+
...overlay.memory
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
if (overlay.context) {
|
|
427
|
+
merged.context = {
|
|
428
|
+
...merged.context,
|
|
429
|
+
...overlay.context
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
if (overlay.tools) {
|
|
433
|
+
merged.tools = [...overlay.tools];
|
|
434
|
+
}
|
|
435
|
+
if (overlay.messages) {
|
|
436
|
+
merged.messages = [...overlay.messages];
|
|
437
|
+
}
|
|
438
|
+
return merged;
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Create a minimal state
|
|
442
|
+
*/
|
|
443
|
+
createMinimalState(agentId, agentName, model) {
|
|
444
|
+
return {
|
|
445
|
+
agentId,
|
|
446
|
+
agentName,
|
|
447
|
+
model,
|
|
448
|
+
memory: {
|
|
449
|
+
size: 0
|
|
450
|
+
},
|
|
451
|
+
context: {},
|
|
452
|
+
tools: [],
|
|
453
|
+
messages: []
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Clear state cache
|
|
458
|
+
*/
|
|
459
|
+
clearCache() {
|
|
460
|
+
this.stateCache.clear();
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Clear cache for specific recording
|
|
464
|
+
*/
|
|
465
|
+
clearRecordingCache(recordingId) {
|
|
466
|
+
this.stateCache.delete(recordingId);
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Get cache size
|
|
470
|
+
*/
|
|
471
|
+
getCacheSize() {
|
|
472
|
+
let size = 0;
|
|
473
|
+
for (const cache of this.stateCache.values()) {
|
|
474
|
+
size += cache.size;
|
|
475
|
+
}
|
|
476
|
+
return size;
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
function createStateRestorer(options) {
|
|
480
|
+
return new StateRestorer(options);
|
|
481
|
+
}
|
|
482
|
+
var ReplayController = class extends EventEmitter {
|
|
483
|
+
recording;
|
|
484
|
+
session;
|
|
485
|
+
config;
|
|
486
|
+
playbackState;
|
|
487
|
+
stateHistory = /* @__PURE__ */ new Map();
|
|
488
|
+
checkpointMap = /* @__PURE__ */ new Map();
|
|
489
|
+
constructor(recording, session, config) {
|
|
490
|
+
super();
|
|
491
|
+
this.recording = recording;
|
|
492
|
+
this.session = session;
|
|
493
|
+
this.config = config;
|
|
494
|
+
this.playbackState = {
|
|
495
|
+
currentStep: session.currentStep,
|
|
496
|
+
state: deepClone(recording.initialState),
|
|
497
|
+
isPaused: false
|
|
498
|
+
};
|
|
499
|
+
this.stateHistory.set(-1, deepClone(recording.initialState));
|
|
500
|
+
for (const checkpoint of recording.checkpoints) {
|
|
501
|
+
this.checkpointMap.set(checkpoint.id, checkpoint);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Step forward one step
|
|
506
|
+
*/
|
|
507
|
+
stepForward() {
|
|
508
|
+
const nextStep = this.playbackState.currentStep + 1;
|
|
509
|
+
if (nextStep >= this.recording.steps.length) {
|
|
510
|
+
return void 0;
|
|
511
|
+
}
|
|
512
|
+
const step = this.recording.steps[nextStep];
|
|
513
|
+
const newState = this.applyStep(this.playbackState.state, step);
|
|
514
|
+
this.stateHistory.set(nextStep, deepClone(newState));
|
|
515
|
+
this.playbackState.currentStep = nextStep;
|
|
516
|
+
this.playbackState.state = newState;
|
|
517
|
+
this.session.currentStep = nextStep;
|
|
518
|
+
this.emit("step:replayed", step, newState);
|
|
519
|
+
this.checkPauseConditions(step);
|
|
520
|
+
return step;
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Step backward one step
|
|
524
|
+
*/
|
|
525
|
+
stepBackward() {
|
|
526
|
+
if (this.playbackState.currentStep < 0) {
|
|
527
|
+
return void 0;
|
|
528
|
+
}
|
|
529
|
+
const prevStep = this.playbackState.currentStep - 1;
|
|
530
|
+
let state = this.stateHistory.get(prevStep);
|
|
531
|
+
if (!state) {
|
|
532
|
+
state = this.rebuildStateAt(prevStep);
|
|
533
|
+
this.stateHistory.set(prevStep, deepClone(state));
|
|
534
|
+
}
|
|
535
|
+
this.playbackState.currentStep = prevStep;
|
|
536
|
+
this.playbackState.state = deepClone(state);
|
|
537
|
+
this.session.currentStep = Math.max(0, prevStep);
|
|
538
|
+
const stepIndex = prevStep - 1;
|
|
539
|
+
if (stepIndex >= 0 && stepIndex < this.recording.steps.length) {
|
|
540
|
+
const step = this.recording.steps[stepIndex];
|
|
541
|
+
this.emit("step:replayed", step, this.playbackState.state);
|
|
542
|
+
return step;
|
|
543
|
+
}
|
|
544
|
+
return void 0;
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Jump to a specific step
|
|
548
|
+
*/
|
|
549
|
+
jumpToStep(stepIndex) {
|
|
550
|
+
if (stepIndex < -1 || stepIndex >= this.recording.steps.length) {
|
|
551
|
+
return void 0;
|
|
552
|
+
}
|
|
553
|
+
let state = this.stateHistory.get(stepIndex);
|
|
554
|
+
if (!state) {
|
|
555
|
+
state = this.rebuildStateAt(stepIndex);
|
|
556
|
+
this.stateHistory.set(stepIndex, deepClone(state));
|
|
557
|
+
}
|
|
558
|
+
this.playbackState.currentStep = stepIndex;
|
|
559
|
+
this.playbackState.state = deepClone(state);
|
|
560
|
+
this.session.currentStep = Math.max(0, stepIndex);
|
|
561
|
+
if (stepIndex >= 0) {
|
|
562
|
+
const step = this.recording.steps[stepIndex];
|
|
563
|
+
this.emit("step:replayed", step, this.playbackState.state);
|
|
564
|
+
return step;
|
|
565
|
+
}
|
|
566
|
+
return void 0;
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Jump to a checkpoint
|
|
570
|
+
*/
|
|
571
|
+
jumpToCheckpoint(checkpointId) {
|
|
572
|
+
const checkpoint = this.checkpointMap.get(checkpointId);
|
|
573
|
+
if (!checkpoint) {
|
|
574
|
+
return false;
|
|
575
|
+
}
|
|
576
|
+
this.playbackState.currentStep = checkpoint.stepIndex;
|
|
577
|
+
this.playbackState.state = deepClone(checkpoint.state);
|
|
578
|
+
this.session.currentStep = checkpoint.stepIndex;
|
|
579
|
+
this.stateHistory.set(checkpoint.stepIndex, deepClone(checkpoint.state));
|
|
580
|
+
this.emit("checkpoint:reached", checkpoint);
|
|
581
|
+
if (checkpoint.stepIndex >= 0 && checkpoint.stepIndex < this.recording.steps.length) {
|
|
582
|
+
const step = this.recording.steps[checkpoint.stepIndex];
|
|
583
|
+
this.emit("step:replayed", step, this.playbackState.state);
|
|
584
|
+
}
|
|
585
|
+
return true;
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Get next checkpoint
|
|
589
|
+
*/
|
|
590
|
+
getNextCheckpoint() {
|
|
591
|
+
const currentStep = this.playbackState.currentStep;
|
|
592
|
+
for (const checkpoint of this.recording.checkpoints) {
|
|
593
|
+
if (checkpoint.stepIndex > currentStep) {
|
|
594
|
+
return checkpoint;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
return void 0;
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Get previous checkpoint
|
|
601
|
+
*/
|
|
602
|
+
getPreviousCheckpoint() {
|
|
603
|
+
const currentStep = this.playbackState.currentStep;
|
|
604
|
+
let lastCheckpoint;
|
|
605
|
+
for (const checkpoint of this.recording.checkpoints) {
|
|
606
|
+
if (checkpoint.stepIndex < currentStep) {
|
|
607
|
+
lastCheckpoint = checkpoint;
|
|
608
|
+
} else {
|
|
609
|
+
break;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
return lastCheckpoint;
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Pause playback
|
|
616
|
+
*/
|
|
617
|
+
pause(reason) {
|
|
618
|
+
this.playbackState.isPaused = true;
|
|
619
|
+
this.playbackState.pauseReason = reason;
|
|
620
|
+
this.emit("paused", reason ?? "Manual pause");
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Resume playback
|
|
624
|
+
*/
|
|
625
|
+
resume() {
|
|
626
|
+
this.playbackState.isPaused = false;
|
|
627
|
+
this.playbackState.pauseReason = void 0;
|
|
628
|
+
this.emit("resumed");
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Get current step
|
|
632
|
+
*/
|
|
633
|
+
getCurrentStep() {
|
|
634
|
+
const index = this.playbackState.currentStep;
|
|
635
|
+
if (index >= 0 && index < this.recording.steps.length) {
|
|
636
|
+
return this.recording.steps[index];
|
|
637
|
+
}
|
|
638
|
+
return void 0;
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Get current state
|
|
642
|
+
*/
|
|
643
|
+
getCurrentState() {
|
|
644
|
+
return deepClone(this.playbackState.state);
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Get playback state
|
|
648
|
+
*/
|
|
649
|
+
getPlaybackState() {
|
|
650
|
+
return { ...this.playbackState };
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Check if at beginning
|
|
654
|
+
*/
|
|
655
|
+
isAtBeginning() {
|
|
656
|
+
return this.playbackState.currentStep <= 0;
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Check if at end
|
|
660
|
+
*/
|
|
661
|
+
isAtEnd() {
|
|
662
|
+
return this.playbackState.currentStep >= this.recording.steps.length - 1;
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Get progress percentage
|
|
666
|
+
*/
|
|
667
|
+
getProgress() {
|
|
668
|
+
if (this.recording.steps.length === 0) {
|
|
669
|
+
return 100;
|
|
670
|
+
}
|
|
671
|
+
return (this.playbackState.currentStep + 1) / this.recording.steps.length * 100;
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Apply a step to state
|
|
675
|
+
*/
|
|
676
|
+
applyStep(state, step) {
|
|
677
|
+
const newState = deepClone(state);
|
|
678
|
+
switch (step.type) {
|
|
679
|
+
case "input":
|
|
680
|
+
newState.messages.push({
|
|
681
|
+
role: "user",
|
|
682
|
+
content: String(step.input)
|
|
683
|
+
});
|
|
684
|
+
break;
|
|
685
|
+
case "response":
|
|
686
|
+
newState.messages.push({
|
|
687
|
+
role: "assistant",
|
|
688
|
+
content: String(step.output)
|
|
689
|
+
});
|
|
690
|
+
break;
|
|
691
|
+
case "tool-call":
|
|
692
|
+
if (step.toolCall) {
|
|
693
|
+
newState.messages.push({
|
|
694
|
+
role: "assistant",
|
|
695
|
+
content: `[Tool Call: ${step.toolCall.name}]`
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
break;
|
|
699
|
+
case "tool-result":
|
|
700
|
+
if (step.toolCall) {
|
|
701
|
+
newState.messages.push({
|
|
702
|
+
role: "tool",
|
|
703
|
+
content: String(step.toolCall.result)
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
break;
|
|
707
|
+
case "memory-write":
|
|
708
|
+
if (step.output && typeof step.output === "object") {
|
|
709
|
+
Object.assign(newState.memory, step.output);
|
|
710
|
+
}
|
|
711
|
+
break;
|
|
712
|
+
}
|
|
713
|
+
return newState;
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Rebuild state at a specific step
|
|
717
|
+
*/
|
|
718
|
+
rebuildStateAt(targetStep) {
|
|
719
|
+
let startStep = -1;
|
|
720
|
+
let state = deepClone(this.recording.initialState);
|
|
721
|
+
for (let i = targetStep; i >= -1; i--) {
|
|
722
|
+
const cached = this.stateHistory.get(i);
|
|
723
|
+
if (cached) {
|
|
724
|
+
startStep = i;
|
|
725
|
+
state = deepClone(cached);
|
|
726
|
+
break;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
for (const checkpoint of this.recording.checkpoints) {
|
|
730
|
+
if (checkpoint.stepIndex > startStep && checkpoint.stepIndex <= targetStep) {
|
|
731
|
+
startStep = checkpoint.stepIndex;
|
|
732
|
+
state = deepClone(checkpoint.state);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
for (let i = startStep + 1; i <= targetStep; i++) {
|
|
736
|
+
if (i >= 0 && i < this.recording.steps.length) {
|
|
737
|
+
state = this.applyStep(state, this.recording.steps[i]);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
return state;
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Check pause conditions
|
|
744
|
+
*/
|
|
745
|
+
checkPauseConditions(step) {
|
|
746
|
+
if (this.config.pauseOnDecisions && step.type === "decision") {
|
|
747
|
+
this.pause("Decision point");
|
|
748
|
+
}
|
|
749
|
+
if (this.config.pauseOnErrors && step.error) {
|
|
750
|
+
this.pause("Error occurred");
|
|
751
|
+
}
|
|
752
|
+
if (this.config.pauseOnToolCalls && step.type === "tool-call") {
|
|
753
|
+
this.pause("Tool call");
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Get all checkpoints
|
|
758
|
+
*/
|
|
759
|
+
getCheckpoints() {
|
|
760
|
+
return this.recording.checkpoints;
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Get steps count
|
|
764
|
+
*/
|
|
765
|
+
get stepsCount() {
|
|
766
|
+
return this.recording.steps.length;
|
|
767
|
+
}
|
|
768
|
+
};
|
|
769
|
+
function createReplayController(recording, session, config) {
|
|
770
|
+
return new ReplayController(recording, session, config);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// src/replay/ReplayEngine.ts
|
|
774
|
+
var DEFAULT_CONFIG = {
|
|
775
|
+
speedMultiplier: 1,
|
|
776
|
+
pauseOnDecisions: false,
|
|
777
|
+
pauseOnErrors: true,
|
|
778
|
+
pauseOnToolCalls: false,
|
|
779
|
+
executeTools: false,
|
|
780
|
+
executeLLM: false,
|
|
781
|
+
compareResults: true,
|
|
782
|
+
trackDifferences: true
|
|
783
|
+
};
|
|
784
|
+
var ReplayEngine = class extends EventEmitter {
|
|
785
|
+
config;
|
|
786
|
+
sessions = /* @__PURE__ */ new Map();
|
|
787
|
+
currentSession;
|
|
788
|
+
stateRestorer;
|
|
789
|
+
controller;
|
|
790
|
+
isRunning = false;
|
|
791
|
+
constructor(config) {
|
|
792
|
+
super();
|
|
793
|
+
this.config = {
|
|
794
|
+
...DEFAULT_CONFIG,
|
|
795
|
+
...config
|
|
796
|
+
};
|
|
797
|
+
this.stateRestorer = new StateRestorer();
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Start a replay session
|
|
801
|
+
*/
|
|
802
|
+
start(recording, options) {
|
|
803
|
+
const session = {
|
|
804
|
+
id: generateId("replay"),
|
|
805
|
+
recordingId: recording.id,
|
|
806
|
+
state: "idle",
|
|
807
|
+
currentStep: options?.startStep ?? 0,
|
|
808
|
+
totalSteps: recording.steps.length,
|
|
809
|
+
speed: options?.speed ?? "normal",
|
|
810
|
+
startedAt: now(),
|
|
811
|
+
modifications: options?.modifications ?? [],
|
|
812
|
+
differences: []
|
|
813
|
+
};
|
|
814
|
+
this.sessions.set(session.id, session);
|
|
815
|
+
this.currentSession = session;
|
|
816
|
+
this.controller = new ReplayController(recording, session, {
|
|
817
|
+
...this.config,
|
|
818
|
+
speedMultiplier: this.getSpeedMultiplier(session.speed)
|
|
819
|
+
});
|
|
820
|
+
this.controller.on("step:replayed", (step, state) => {
|
|
821
|
+
this.emit("step:replayed", step, state);
|
|
822
|
+
});
|
|
823
|
+
this.controller.on("paused", () => {
|
|
824
|
+
session.state = "paused";
|
|
825
|
+
this.emit("replay:paused", session);
|
|
826
|
+
});
|
|
827
|
+
this.controller.on("error", (error) => {
|
|
828
|
+
this.emit("error", error);
|
|
829
|
+
});
|
|
830
|
+
this.emit("replay:started", session);
|
|
831
|
+
this.runReplay(recording, session, options).catch((error) => {
|
|
832
|
+
this.emit("error", error);
|
|
833
|
+
});
|
|
834
|
+
return session;
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Run the replay loop
|
|
838
|
+
*/
|
|
839
|
+
async runReplay(recording, session, options) {
|
|
840
|
+
this.isRunning = true;
|
|
841
|
+
const startStep = options?.startStep ?? 0;
|
|
842
|
+
const endStep = options?.endStep ?? recording.steps.length - 1;
|
|
843
|
+
let currentState = this.stateRestorer.restore(
|
|
844
|
+
recording,
|
|
845
|
+
startStep > 0 ? startStep - 1 : 0
|
|
846
|
+
);
|
|
847
|
+
const replayedSteps = [];
|
|
848
|
+
const differences = [];
|
|
849
|
+
for (let i = startStep; i <= endStep && this.isRunning; i++) {
|
|
850
|
+
while (session.state === "paused" && this.isRunning) {
|
|
851
|
+
await sleep(100);
|
|
852
|
+
}
|
|
853
|
+
if (!this.isRunning) {
|
|
854
|
+
break;
|
|
855
|
+
}
|
|
856
|
+
const originalStep = recording.steps[i];
|
|
857
|
+
let step = deepClone(originalStep);
|
|
858
|
+
const modification = options?.modifications?.find(
|
|
859
|
+
(m) => m.stepIndex === i
|
|
860
|
+
);
|
|
861
|
+
if (modification) {
|
|
862
|
+
step = this.applyModification(step, modification);
|
|
863
|
+
this.emit("step:modified", originalStep, step);
|
|
864
|
+
}
|
|
865
|
+
const result2 = await this.executeStep(step, currentState, options);
|
|
866
|
+
if (this.config.trackDifferences && result2.executed) {
|
|
867
|
+
const stepDiffs = this.compareResults(originalStep, result2.step);
|
|
868
|
+
if (stepDiffs.length > 0) {
|
|
869
|
+
differences.push(...stepDiffs);
|
|
870
|
+
session.differences = differences;
|
|
871
|
+
this.emit("divergence:detected", stepDiffs);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
replayedSteps.push(result2.step);
|
|
875
|
+
currentState = result2.state;
|
|
876
|
+
session.currentStep = i;
|
|
877
|
+
const delay = this.getStepDelay(step, session.speed);
|
|
878
|
+
if (delay > 0) {
|
|
879
|
+
await sleep(delay);
|
|
880
|
+
}
|
|
881
|
+
this.emit("step:replayed", result2.step, currentState);
|
|
882
|
+
if (this.shouldPause(step)) {
|
|
883
|
+
session.state = "paused";
|
|
884
|
+
this.emit("replay:paused", session);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
const result = {
|
|
888
|
+
sessionId: session.id,
|
|
889
|
+
recordingId: recording.id,
|
|
890
|
+
success: this.isRunning,
|
|
891
|
+
stepsReplayed: replayedSteps.length,
|
|
892
|
+
differences,
|
|
893
|
+
finalState: currentState,
|
|
894
|
+
startedAt: session.startedAt,
|
|
895
|
+
completedAt: now(),
|
|
896
|
+
durationMs: now() - session.startedAt
|
|
897
|
+
};
|
|
898
|
+
session.state = "completed";
|
|
899
|
+
session.completedAt = result.completedAt;
|
|
900
|
+
this.isRunning = false;
|
|
901
|
+
this.emit("replay:completed", result);
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Execute a step during replay
|
|
905
|
+
*/
|
|
906
|
+
async executeStep(step, state, options) {
|
|
907
|
+
const newStep = deepClone(step);
|
|
908
|
+
let newState = deepClone(state);
|
|
909
|
+
let executed = false;
|
|
910
|
+
if (step.type === "tool-call" && options?.executeTools && options?.onToolCall) {
|
|
911
|
+
try {
|
|
912
|
+
const result = await options.onToolCall(step);
|
|
913
|
+
newStep.toolCall = {
|
|
914
|
+
...newStep.toolCall,
|
|
915
|
+
result,
|
|
916
|
+
success: true
|
|
917
|
+
};
|
|
918
|
+
executed = true;
|
|
919
|
+
} catch (error) {
|
|
920
|
+
newStep.toolCall = {
|
|
921
|
+
...newStep.toolCall,
|
|
922
|
+
result: error.message,
|
|
923
|
+
success: false
|
|
924
|
+
};
|
|
925
|
+
executed = true;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
if (step.type === "response" && options?.executeLLM && options?.onLLMCall) {
|
|
929
|
+
try {
|
|
930
|
+
const response = await options.onLLMCall(step);
|
|
931
|
+
newStep.output = response;
|
|
932
|
+
executed = true;
|
|
933
|
+
} catch (error) {
|
|
934
|
+
newStep.error = {
|
|
935
|
+
name: "LLMError",
|
|
936
|
+
message: error.message
|
|
937
|
+
};
|
|
938
|
+
executed = true;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
newState = this.updateState(newState, newStep);
|
|
942
|
+
return { step: newStep, state: newState, executed };
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* Update state based on step
|
|
946
|
+
*/
|
|
947
|
+
updateState(state, step) {
|
|
948
|
+
const newState = deepClone(state);
|
|
949
|
+
if (step.type === "input") {
|
|
950
|
+
newState.messages.push({
|
|
951
|
+
role: "user",
|
|
952
|
+
content: String(step.input)
|
|
953
|
+
});
|
|
954
|
+
} else if (step.type === "response") {
|
|
955
|
+
newState.messages.push({
|
|
956
|
+
role: "assistant",
|
|
957
|
+
content: String(step.output)
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
return newState;
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
963
|
+
* Apply a modification to a step
|
|
964
|
+
*/
|
|
965
|
+
applyModification(step, modification) {
|
|
966
|
+
const modified = deepClone(step);
|
|
967
|
+
switch (modification.type) {
|
|
968
|
+
case "skip":
|
|
969
|
+
modified.metadata = {
|
|
970
|
+
...modified.metadata,
|
|
971
|
+
skipped: true
|
|
972
|
+
};
|
|
973
|
+
break;
|
|
974
|
+
case "modify":
|
|
975
|
+
if (modification.data) {
|
|
976
|
+
Object.assign(modified, modification.data);
|
|
977
|
+
}
|
|
978
|
+
break;
|
|
979
|
+
case "insert":
|
|
980
|
+
break;
|
|
981
|
+
case "replace":
|
|
982
|
+
if (modification.data) {
|
|
983
|
+
return {
|
|
984
|
+
...modified,
|
|
985
|
+
...modification.data
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
break;
|
|
989
|
+
}
|
|
990
|
+
return modified;
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Compare original and replayed results
|
|
994
|
+
*/
|
|
995
|
+
compareResults(original, replayed) {
|
|
996
|
+
const differences = diff(original, replayed);
|
|
997
|
+
return toReplayDifferences(differences, original.index);
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* Check if should pause on this step
|
|
1001
|
+
*/
|
|
1002
|
+
shouldPause(step) {
|
|
1003
|
+
if (this.config.pauseOnDecisions && step.type === "decision") {
|
|
1004
|
+
return true;
|
|
1005
|
+
}
|
|
1006
|
+
if (this.config.pauseOnErrors && step.error) {
|
|
1007
|
+
return true;
|
|
1008
|
+
}
|
|
1009
|
+
if (this.config.pauseOnToolCalls && step.type === "tool-call") {
|
|
1010
|
+
return true;
|
|
1011
|
+
}
|
|
1012
|
+
return false;
|
|
1013
|
+
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Get speed multiplier
|
|
1016
|
+
*/
|
|
1017
|
+
getSpeedMultiplier(speed) {
|
|
1018
|
+
switch (speed) {
|
|
1019
|
+
case "slow":
|
|
1020
|
+
return 0.5;
|
|
1021
|
+
case "normal":
|
|
1022
|
+
return 1;
|
|
1023
|
+
case "fast":
|
|
1024
|
+
return 2;
|
|
1025
|
+
case "instant":
|
|
1026
|
+
return 0;
|
|
1027
|
+
default:
|
|
1028
|
+
return 1;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Get delay for step based on speed
|
|
1033
|
+
*/
|
|
1034
|
+
getStepDelay(step, speed) {
|
|
1035
|
+
if (speed === "instant") {
|
|
1036
|
+
return 0;
|
|
1037
|
+
}
|
|
1038
|
+
const baseDelay = step.durationMs ?? 100;
|
|
1039
|
+
const multiplier = this.getSpeedMultiplier(speed);
|
|
1040
|
+
return baseDelay / multiplier;
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Pause current replay
|
|
1044
|
+
*/
|
|
1045
|
+
pause() {
|
|
1046
|
+
if (this.currentSession && this.currentSession.state !== "paused" && this.currentSession.state !== "stopped") {
|
|
1047
|
+
this.currentSession.state = "paused";
|
|
1048
|
+
this.emit("replay:paused", this.currentSession);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Resume current replay
|
|
1053
|
+
*/
|
|
1054
|
+
resume() {
|
|
1055
|
+
if (this.currentSession && this.currentSession.state === "paused") {
|
|
1056
|
+
this.currentSession.state = "playing";
|
|
1057
|
+
this.emit("replay:resumed", this.currentSession);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Stop current replay
|
|
1062
|
+
*/
|
|
1063
|
+
stop() {
|
|
1064
|
+
this.isRunning = false;
|
|
1065
|
+
if (this.currentSession) {
|
|
1066
|
+
this.currentSession.state = "stopped";
|
|
1067
|
+
this.emit("replay:stopped", this.currentSession);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Set replay speed
|
|
1072
|
+
*/
|
|
1073
|
+
setSpeed(speed) {
|
|
1074
|
+
if (this.currentSession) {
|
|
1075
|
+
this.currentSession.speed = speed;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
/**
|
|
1079
|
+
* Jump to step
|
|
1080
|
+
*/
|
|
1081
|
+
jumpToStep(stepIndex) {
|
|
1082
|
+
if (!this.currentSession) {
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
this.currentSession.currentStep = stepIndex;
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Get current session
|
|
1089
|
+
*/
|
|
1090
|
+
getSession() {
|
|
1091
|
+
return this.currentSession;
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Get session by ID
|
|
1095
|
+
*/
|
|
1096
|
+
getSessionById(id) {
|
|
1097
|
+
return this.sessions.get(id);
|
|
1098
|
+
}
|
|
1099
|
+
/**
|
|
1100
|
+
* Get all sessions
|
|
1101
|
+
*/
|
|
1102
|
+
getSessions() {
|
|
1103
|
+
return Array.from(this.sessions.values());
|
|
1104
|
+
}
|
|
1105
|
+
};
|
|
1106
|
+
function createReplayEngine(config) {
|
|
1107
|
+
return new ReplayEngine(config);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
export { ReplayController, ReplayEngine, StateRestorer, createReplayController, createReplayEngine, createStateRestorer };
|
|
1111
|
+
//# sourceMappingURL=index.js.map
|
|
1112
|
+
//# sourceMappingURL=index.js.map
|