@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.
@@ -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