@qwen-code/arena-agent 0.0.10-dev.0 → 0.0.10-dev.10

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.
Files changed (158) hide show
  1. package/.env.example +57 -14
  2. package/CHANGELOG.md +228 -0
  3. package/README.md +240 -57
  4. package/dist/agent.d.ts +30 -2
  5. package/dist/agent.js +351 -104
  6. package/dist/agent.js.map +1 -1
  7. package/dist/anthropic.js +23 -22
  8. package/dist/anthropic.js.map +1 -1
  9. package/dist/ask-user.js +2 -0
  10. package/dist/ask-user.js.map +1 -1
  11. package/dist/attachments.js +4 -3
  12. package/dist/attachments.js.map +1 -1
  13. package/dist/brave-search.js +1 -3
  14. package/dist/brave-search.js.map +1 -1
  15. package/dist/canonical-data.js +2 -2
  16. package/dist/canonical-data.js.map +1 -1
  17. package/dist/cli.js +197 -34
  18. package/dist/cli.js.map +1 -1
  19. package/dist/code-tools.d.ts +8 -2
  20. package/dist/code-tools.js +709 -117
  21. package/dist/code-tools.js.map +1 -1
  22. package/dist/compaction.d.ts +49 -3
  23. package/dist/compaction.js +383 -170
  24. package/dist/compaction.js.map +1 -1
  25. package/dist/config.d.ts +17 -2
  26. package/dist/config.js +47 -5
  27. package/dist/config.js.map +1 -1
  28. package/dist/fetch-page.js +9 -33
  29. package/dist/fetch-page.js.map +1 -1
  30. package/dist/file-edit.d.ts +8 -0
  31. package/dist/file-edit.js +63 -0
  32. package/dist/file-edit.js.map +1 -0
  33. package/dist/history.d.ts +35 -1
  34. package/dist/history.js +400 -16
  35. package/dist/history.js.map +1 -1
  36. package/dist/legacy-compaction.d.ts +20 -0
  37. package/dist/legacy-compaction.js +209 -0
  38. package/dist/legacy-compaction.js.map +1 -0
  39. package/dist/midturn-checkpoint-hook.d.ts +31 -0
  40. package/dist/midturn-checkpoint-hook.js +38 -0
  41. package/dist/midturn-checkpoint-hook.js.map +1 -0
  42. package/dist/mock.js +5 -0
  43. package/dist/mock.js.map +1 -1
  44. package/dist/mode.js +4 -2
  45. package/dist/mode.js.map +1 -1
  46. package/dist/model-router-media.d.ts +1 -0
  47. package/dist/model-router-media.js +71 -1
  48. package/dist/model-router-media.js.map +1 -1
  49. package/dist/openai-responses.js +2 -0
  50. package/dist/openai-responses.js.map +1 -1
  51. package/dist/process-manager.js +18 -11
  52. package/dist/process-manager.js.map +1 -1
  53. package/dist/prompts.d.ts +2 -0
  54. package/dist/prompts.js +6 -2
  55. package/dist/prompts.js.map +1 -1
  56. package/dist/protocol.d.ts +5 -1
  57. package/dist/protocol.js +24 -2
  58. package/dist/protocol.js.map +1 -1
  59. package/dist/provider.d.ts +12 -5
  60. package/dist/provider.js +60 -52
  61. package/dist/provider.js.map +1 -1
  62. package/dist/repetitive-output.d.ts +12 -0
  63. package/dist/repetitive-output.js +80 -0
  64. package/dist/repetitive-output.js.map +1 -0
  65. package/dist/replay-responses.d.ts +17 -0
  66. package/dist/replay-responses.js +100 -0
  67. package/dist/replay-responses.js.map +1 -0
  68. package/dist/routify-web-fetch.js +20 -7
  69. package/dist/routify-web-fetch.js.map +1 -1
  70. package/dist/sandbox-file-write.d.ts +10 -0
  71. package/dist/sandbox-file-write.js +58 -0
  72. package/dist/sandbox-file-write.js.map +1 -0
  73. package/dist/sdk.js +9 -1
  74. package/dist/sdk.js.map +1 -1
  75. package/dist/server.js +127 -44
  76. package/dist/server.js.map +1 -1
  77. package/dist/session-memory-prompts.d.ts +5 -0
  78. package/dist/session-memory-prompts.js +7 -0
  79. package/dist/session-memory-prompts.js.map +1 -0
  80. package/dist/snapshot-client.d.ts +10 -0
  81. package/dist/snapshot-client.js +39 -0
  82. package/dist/snapshot-client.js.map +1 -0
  83. package/dist/spec.js +18 -10
  84. package/dist/spec.js.map +1 -1
  85. package/dist/speech-audio.d.ts +9 -0
  86. package/dist/speech-audio.js +130 -0
  87. package/dist/speech-audio.js.map +1 -0
  88. package/dist/speech-model.d.ts +3 -0
  89. package/dist/speech-model.js +8 -0
  90. package/dist/speech-model.js.map +1 -0
  91. package/dist/tools.d.ts +5 -0
  92. package/dist/tools.js +744 -121
  93. package/dist/tools.js.map +1 -1
  94. package/dist/tui.js +20 -12
  95. package/dist/tui.js.map +1 -1
  96. package/dist/types.d.ts +28 -3
  97. package/dist/voice-registry.d.ts +2 -0
  98. package/dist/voice-registry.js +10 -2
  99. package/dist/voice-registry.js.map +1 -1
  100. package/dist/web/app.js +17 -3
  101. package/dist/web/app.js.map +1 -1
  102. package/dist/web-fetch-output.d.ts +9 -0
  103. package/dist/web-fetch-output.js +38 -0
  104. package/dist/web-fetch-output.js.map +1 -0
  105. package/dist/workspace.d.ts +2 -0
  106. package/dist/workspace.js +11 -6
  107. package/dist/workspace.js.map +1 -1
  108. package/docs/BINARY_DISTRIBUTION.md +27 -16
  109. package/docs/MODES_AND_RELEASES.md +28 -5
  110. package/docs/SDK.md +10 -4
  111. package/modes/code-arena/capture-provenance.json +57 -0
  112. package/modes/code-arena/full-stack/system-prompt.txt +1 -1
  113. package/modes/code-arena/full-stack/template/eslint.config.mjs +3 -4
  114. package/modes/code-arena/full-stack/template/postcss.config.mjs +2 -2
  115. package/modes/code-arena/full-stack/tools.json +186 -186
  116. package/modes/code-arena/html/system-prompt.txt +1 -1
  117. package/modes/code-arena/html/tools.json +123 -123
  118. package/modes/code-arena/react/system-prompt.txt +4 -3
  119. package/modes/code-arena/react/template/package.json +4 -4
  120. package/modes/code-arena/react/template/src/App.tsx +1 -1
  121. package/modes/code-arena/react/template/src/main.tsx +1 -1
  122. package/modes/code-arena/react/template/tsconfig.json +6 -0
  123. package/modes/code-arena/react/template/vite.config.ts +1 -0
  124. package/modes/code-arena/react/tools.json +178 -184
  125. package/package.json +15 -4
  126. package/scripts/README.md +62 -0
  127. package/scripts/check-changelog.mjs +66 -0
  128. package/vendor/audio/darwin-arm64/FFmpeg.LICENSE +502 -0
  129. package/vendor/audio/darwin-arm64/LAME.LICENSE +481 -0
  130. package/vendor/audio/darwin-arm64/Opus.LICENSE +44 -0
  131. package/vendor/audio/darwin-arm64/ffmpeg.gz +0 -0
  132. package/vendor/audio/darwin-arm64/manifest.json +39 -0
  133. package/vendor/audio/darwin-x64/FFmpeg.LICENSE +502 -0
  134. package/vendor/audio/darwin-x64/LAME.LICENSE +481 -0
  135. package/vendor/audio/darwin-x64/Opus.LICENSE +44 -0
  136. package/vendor/audio/darwin-x64/ffmpeg.gz +0 -0
  137. package/vendor/audio/darwin-x64/manifest.json +39 -0
  138. package/vendor/audio/linux-arm64/FFmpeg.LICENSE +502 -0
  139. package/vendor/audio/linux-arm64/LAME.LICENSE +481 -0
  140. package/vendor/audio/linux-arm64/Opus.LICENSE +44 -0
  141. package/vendor/audio/linux-arm64/ffmpeg.gz +0 -0
  142. package/vendor/audio/linux-arm64/manifest.json +39 -0
  143. package/vendor/audio/linux-x64/FFmpeg.LICENSE +502 -0
  144. package/vendor/audio/linux-x64/LAME.LICENSE +481 -0
  145. package/vendor/audio/linux-x64/Opus.LICENSE +44 -0
  146. package/vendor/audio/linux-x64/ffmpeg.gz +0 -0
  147. package/vendor/audio/linux-x64/manifest.json +39 -0
  148. package/vendor/audio/sources/build-audio-encoder.sh +76 -0
  149. package/vendor/audio/sources/ffmpeg-7.0.2.tar.xz +0 -0
  150. package/vendor/audio/sources/lame-3.100.tar.gz +0 -0
  151. package/vendor/audio/sources/opus-1.5.2.tar.gz +0 -0
  152. package/vendor/audio/sources/pkgconf-2.3.0.tar.xz +0 -0
  153. package/vendor/audio/sources/sources.lock.json +30 -0
  154. package/vendor/audio/win32-x64/FFmpeg.LICENSE +502 -0
  155. package/vendor/audio/win32-x64/LAME.LICENSE +481 -0
  156. package/vendor/audio/win32-x64/Opus.LICENSE +44 -0
  157. package/vendor/audio/win32-x64/ffmpeg.gz +0 -0
  158. package/vendor/audio/win32-x64/manifest.json +39 -0
package/dist/server.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import crypto from "node:crypto";
2
2
  import fs from "node:fs";
3
- import { cp, mkdir, readFile, readdir, realpath, rename, rm, stat, writeFile, } from "node:fs/promises";
3
+ import { cp, copyFile, mkdir, readFile, readdir, realpath, rename, rm, stat, writeFile, } from "node:fs/promises";
4
4
  import http from "node:http";
5
5
  import path from "node:path";
6
6
  import { Agent } from "./agent.js";
7
7
  import { attachmentMimeType, attachmentUserMessage, maxAttachmentBytesPerMessage, maxAttachmentsPerMessage, maxUploadBytes, mimeTypes, saveAttachment, uploadsDirectory, } from "./attachments.js";
8
8
  import { configFromEnv } from "./config.js";
9
9
  import { isCodeArenaMode } from "./mode.js";
10
- import { historyMetadata, loadStoredHistory, loadStoredHistoryState, saveStoredHistory, sessionStorageRoot, } from "./history.js";
10
+ import { historyMetadata, IncrementalHistoryWriter, loadStoredHistory, loadStoredHistoryState, saveStoredHistory, sessionStorageRoot, } from "./history.js";
11
11
  import { ModelBashMetricJudge, SessionMetricsAnalyzer, } from "./metrics.js";
12
12
  import { ModelConversationMetricJudge } from "./conversation-metrics.js";
13
13
  import { MockModelClient } from "./mock.js";
@@ -171,14 +171,21 @@ function sessionUndoMetadataPath(session) {
171
171
  function sessionUndoWorkspacePath(session) {
172
172
  return path.join(sessionUndoRoot(session), "workspace");
173
173
  }
174
+ function sessionUndoHistoryPath(session) {
175
+ return path.join(sessionUndoRoot(session), "history.json");
176
+ }
174
177
  function sessionRetryPath(session) {
175
178
  return path.join(session.root, "model-retry.json");
176
179
  }
177
180
  function historyHash(messages) {
178
- return crypto
179
- .createHash("sha256")
180
- .update(JSON.stringify({ messages }))
181
- .digest("hex");
181
+ const hash = crypto.createHash("sha256");
182
+ hash.update('{"messages":[');
183
+ for (let index = 0; index < messages.length; index += 1) {
184
+ if (index)
185
+ hash.update(",");
186
+ hash.update(JSON.stringify(messages[index]));
187
+ }
188
+ return hash.update("]}").digest("hex");
182
189
  }
183
190
  async function loadModelRetry(session) {
184
191
  try {
@@ -284,7 +291,6 @@ async function loadUndoCheckpoint(session) {
284
291
  typeof checkpoint.messageId !== "string" ||
285
292
  typeof checkpoint.input !== "string" ||
286
293
  !Array.isArray(checkpoint.attachments) ||
287
- !Array.isArray(checkpoint.messages) ||
288
294
  typeof checkpoint.createdAt !== "number") {
289
295
  throw new Error("invalid persisted undo checkpoint");
290
296
  }
@@ -292,7 +298,36 @@ async function loadUndoCheckpoint(session) {
292
298
  if (!workspace.isDirectory()) {
293
299
  throw new Error("undo checkpoint workspace is not a directory");
294
300
  }
295
- return checkpoint;
301
+ let undoHistoryExists = true;
302
+ try {
303
+ await stat(sessionUndoHistoryPath(session));
304
+ }
305
+ catch (error) {
306
+ if (error.code !== "ENOENT")
307
+ throw error;
308
+ undoHistoryExists = false;
309
+ }
310
+ if (!undoHistoryExists) {
311
+ if (!Array.isArray(checkpoint.messages)) {
312
+ throw new Error("undo checkpoint history is missing");
313
+ }
314
+ // Migrate checkpoints written before undo history became disk-backed.
315
+ await saveStoredHistory(sessionUndoHistoryPath(session), checkpoint.messages, undefined, checkpoint.metrics ?? null);
316
+ await writeFile(sessionUndoMetadataPath(session), JSON.stringify({
317
+ version: 1,
318
+ messageId: checkpoint.messageId,
319
+ input: checkpoint.input,
320
+ attachments: checkpoint.attachments,
321
+ createdAt: checkpoint.createdAt,
322
+ }), "utf8");
323
+ }
324
+ return {
325
+ version: 1,
326
+ messageId: checkpoint.messageId,
327
+ input: checkpoint.input,
328
+ attachments: checkpoint.attachments,
329
+ createdAt: checkpoint.createdAt,
330
+ };
296
331
  }
297
332
  catch (error) {
298
333
  if (error.code === "ENOENT")
@@ -344,16 +379,11 @@ async function installDirectory(staging, target) {
344
379
  }
345
380
  }
346
381
  async function prepareUndoCheckpoint(session, messageId, input, attachments) {
347
- const storedHistory = await loadStoredHistoryState(sessionHistoryPath(session));
348
382
  const checkpoint = {
349
383
  version: 1,
350
384
  messageId,
351
385
  input,
352
386
  attachments: attachments.map(checkpointAttachment),
353
- messages: structuredClone((await readHistory(session)) || []),
354
- ...(storedHistory?.metrics
355
- ? { metrics: structuredClone(storedHistory.metrics) }
356
- : {}),
357
387
  createdAt: Date.now(),
358
388
  };
359
389
  const target = sessionUndoRoot(session);
@@ -366,6 +396,19 @@ async function prepareUndoCheckpoint(session, messageId, input, attachments) {
366
396
  filter: (source) => snapshotIncludes(session.workspace, source),
367
397
  verbatimSymlinks: true,
368
398
  });
399
+ const undoHistory = path.join(staging, "history.json");
400
+ try {
401
+ await copyFile(sessionHistoryPath(session), undoHistory, fs.constants.COPYFILE_FICLONE);
402
+ }
403
+ catch (error) {
404
+ if (error.code !== "ENOENT")
405
+ throw error;
406
+ await saveStoredHistory(undoHistory, [], undefined, null, []);
407
+ }
408
+ await copyFile(`${sessionHistoryPath(session)}.journal.jsonl`, `${undoHistory}.journal.jsonl`, fs.constants.COPYFILE_FICLONE).catch((error) => {
409
+ if (error.code !== "ENOENT")
410
+ throw error;
411
+ });
369
412
  await writeFile(path.join(staging, "checkpoint.json"), JSON.stringify(checkpoint), "utf8");
370
413
  await installDirectory(staging, target);
371
414
  session.undoCheckpoint = checkpoint;
@@ -432,9 +475,9 @@ async function createSession(context, clientContext) {
432
475
  root: paths.root,
433
476
  workspace: paths.workspace,
434
477
  artifacts: paths.artifacts,
435
- history: null,
436
478
  undoCheckpoint: null,
437
479
  events: [],
480
+ eventBytes: 0,
438
481
  nextEventId: 1,
439
482
  subscribers: new Set(),
440
483
  running: null,
@@ -497,9 +540,9 @@ async function getSession(context, sessionId) {
497
540
  root: paths.root,
498
541
  workspace: paths.workspace,
499
542
  artifacts: paths.artifacts,
500
- history: null,
501
543
  undoCheckpoint: null,
502
544
  events: [],
545
+ eventBytes: 0,
503
546
  nextEventId: 1,
504
547
  subscribers: new Set(),
505
548
  running: null,
@@ -553,14 +596,35 @@ function authorize(request, response, session) {
553
596
  session.touchedAt = Date.now();
554
597
  return true;
555
598
  }
599
+ const maxStoredEvents = 1000;
600
+ const maxStoredEventBytes = 16 * 1024 * 1024;
556
601
  function emit(session, data) {
557
- const event = { id: String(session.nextEventId++), data };
602
+ const id = String(session.nextEventId++);
603
+ const frame = `id: ${id}\ndata: ${JSON.stringify(data)}\n\n`;
604
+ const event = {
605
+ id,
606
+ type: data.type,
607
+ frame,
608
+ bytes: Buffer.byteLength(frame),
609
+ };
558
610
  session.events.push(event);
559
- if (session.events.length > 1000)
560
- session.events.shift();
561
- const frame = `id: ${event.id}\ndata: ${JSON.stringify(data)}\n\n`;
562
- for (const response of session.subscribers)
563
- response.write(frame);
611
+ session.eventBytes += event.bytes;
612
+ while (session.events.length > 1 &&
613
+ (session.events.length > maxStoredEvents ||
614
+ session.eventBytes > maxStoredEventBytes)) {
615
+ const removed = session.events.shift();
616
+ if (removed)
617
+ session.eventBytes -= removed.bytes;
618
+ }
619
+ for (const response of [...session.subscribers]) {
620
+ if (response.write(frame))
621
+ continue;
622
+ // Do not let a slow or disconnected SSE client make Node buffer an
623
+ // unbounded copy of a large history stream. The client reconnects with its
624
+ // last event id and replays from the bounded ring.
625
+ session.subscribers.delete(response);
626
+ response.end();
627
+ }
564
628
  return event;
565
629
  }
566
630
  function openEventStream(request, response, session) {
@@ -574,11 +638,19 @@ function openEventStream(request, response, session) {
574
638
  response.flushHeaders();
575
639
  response.write(": connected\n\n");
576
640
  const lastEventId = Number(request.headers["last-event-id"] || 0);
641
+ let replayBackpressured = false;
577
642
  for (const event of session.events) {
578
643
  if (Number(event.id) > lastEventId) {
579
- response.write(`id: ${event.id}\ndata: ${JSON.stringify(event.data)}\n\n`);
644
+ if (!response.write(event.frame)) {
645
+ replayBackpressured = true;
646
+ break;
647
+ }
580
648
  }
581
649
  }
650
+ if (replayBackpressured) {
651
+ response.end();
652
+ return;
653
+ }
582
654
  session.subscribers.add(response);
583
655
  const heartbeat = setInterval(() => response.write(": heartbeat\n\n"), 15_000);
584
656
  const close = () => {
@@ -805,6 +877,13 @@ function createUiStream(session, messageId) {
805
877
  });
806
878
  return;
807
879
  }
880
+ if (event.type === "memory.update") {
881
+ emit(session, { type: "memory.update", status: event.status,
882
+ ...(event.code ? { code: event.code } : {}),
883
+ ...(event.error ? { error: event.error } : {}),
884
+ ...(event.usage ? { usage: event.usage } : {}) });
885
+ return;
886
+ }
808
887
  if (event.type === "compaction") {
809
888
  emit(session, {
810
889
  type: "compaction",
@@ -973,10 +1052,6 @@ function createUiStream(session, messageId) {
973
1052
  }
974
1053
  if (event.type === "result") {
975
1054
  closeOpenParts();
976
- emit(session, {
977
- type: "data-messages",
978
- data: { messages: event.messages || [] },
979
- });
980
1055
  emit(session, {
981
1056
  type: "finish",
982
1057
  finishReason: event.status === "success" ? "stop" : "error",
@@ -1007,16 +1082,10 @@ function createUiStream(session, messageId) {
1007
1082
  };
1008
1083
  }
1009
1084
  async function readHistory(session) {
1010
- if (session.history)
1011
- return structuredClone(session.history);
1012
- const messages = await loadStoredHistory(sessionHistoryPath(session));
1013
- if (messages)
1014
- session.history = structuredClone(messages);
1015
- return messages ? structuredClone(messages) : undefined;
1085
+ return loadStoredHistory(sessionHistoryPath(session));
1016
1086
  }
1017
- async function saveHistory(session, messages, config, metrics, tools) {
1018
- await saveStoredHistory(sessionHistoryPath(session), messages, historyMetadata(config, session.id), metrics, tools);
1019
- session.history = structuredClone([...messages]);
1087
+ async function saveHistory(session, messages, config, metrics, tools, memory) {
1088
+ await saveStoredHistory(sessionHistoryPath(session), messages, historyMetadata(config, session.id), metrics, tools, memory);
1020
1089
  }
1021
1090
  function sessionMetricsAnalyzer(config, signal) {
1022
1091
  const sideQueryModel = config.metricsSideQueryModel || config.model;
@@ -1100,12 +1169,21 @@ async function runAgent(context, session, prompt, messageId, controller, interru
1100
1169
  try {
1101
1170
  const storedHistory = await loadStoredHistoryState(sessionHistoryPath(session));
1102
1171
  const history = storedHistory?.messages;
1172
+ const historyWriter = await IncrementalHistoryWriter.create(sessionHistoryPath(session), storedHistory, historyMetadata(sessionConfig, session.id));
1173
+ session.sessionMemory ||= {};
1174
+ let latestTools = storedHistory?.tools;
1103
1175
  const result = await new Agent(sessionConfig, sink, {
1104
1176
  askUserHandler,
1105
1177
  sessionId: session.id,
1106
1178
  sessionAffinityId: session.sessionAffinityId,
1107
1179
  signal: controller.signal,
1108
- onCheckpoint: (messages, tools) => saveHistory(session, messages, sessionConfig, undefined, tools),
1180
+ reuseHistoryMessages: true,
1181
+ sessionMemory: (session.sessionMemory ||= {}),
1182
+ storedSessionMemory: storedHistory?.session_memory,
1183
+ onCheckpoint: (messages, tools, memory) => {
1184
+ latestTools = [...tools];
1185
+ return historyWriter.checkpoint(messages, tools, memory);
1186
+ },
1109
1187
  onModelFailure: (messages, error) => {
1110
1188
  failedModelCheckpoint = structuredClone([...messages]);
1111
1189
  failedModelError = error;
@@ -1119,6 +1197,7 @@ async function runAgent(context, session, prompt, messageId, controller, interru
1119
1197
  interrupted: interruptionGeneration !== undefined,
1120
1198
  userMessage,
1121
1199
  resumeFromHistory,
1200
+ restoreSessionMemory: sessionConfig.restoreSessionMemory,
1122
1201
  });
1123
1202
  let deliveredResult = result;
1124
1203
  let retryable = false;
@@ -1126,7 +1205,7 @@ async function runAgent(context, session, prompt, messageId, controller, interru
1126
1205
  !controller.signal.aborted &&
1127
1206
  failedModelCheckpoint) {
1128
1207
  const checkpoint = structuredClone(failedModelCheckpoint);
1129
- await saveHistory(session, checkpoint, sessionConfig);
1208
+ await historyWriter.checkpoint(checkpoint, latestTools);
1130
1209
  await saveModelRetry(session, {
1131
1210
  version: 1,
1132
1211
  sessionId: session.id,
@@ -1157,7 +1236,7 @@ async function runAgent(context, session, prompt, messageId, controller, interru
1157
1236
  currentTurnMessages,
1158
1237
  })
1159
1238
  : undefined;
1160
- await saveHistory(session, deliveredResult.messages, sessionConfig, metrics);
1239
+ await historyWriter.finalize(deliveredResult.messages, metrics, latestTools);
1161
1240
  if (result.status === "success" || controller.signal.aborted) {
1162
1241
  if (interruptionGeneration !== undefined &&
1163
1242
  session.interruptionGeneration === interruptionGeneration) {
@@ -1487,8 +1566,12 @@ async function undoLastTurn(config, request, response, session) {
1487
1566
  });
1488
1567
  return;
1489
1568
  }
1569
+ const checkpointHistory = await loadStoredHistoryState(sessionUndoHistoryPath(session));
1570
+ const checkpointMessages = checkpointHistory?.messages || [];
1571
+ const checkpointMetrics = checkpointHistory?.metrics ?? null;
1490
1572
  await restoreUndoWorkspace(session);
1491
- await saveHistory(session, checkpoint.messages, config, checkpoint.metrics ?? null);
1573
+ await saveHistory(session, checkpointMessages, config, checkpointMetrics, checkpointHistory?.tools, checkpointHistory?.session_memory ?? null);
1574
+ session.sessionMemory = {};
1492
1575
  await clearModelRetry(session);
1493
1576
  session.pendingInterruption = false;
1494
1577
  session.activePrompt = null;
@@ -1496,13 +1579,14 @@ async function undoLastTurn(config, request, response, session) {
1496
1579
  await saveSessionMetadata(session);
1497
1580
  let turnStart = -1;
1498
1581
  for (let index = session.events.length - 1; index >= 0; index -= 1) {
1499
- if (session.events[index]?.data.type === "start") {
1582
+ if (session.events[index]?.type === "start") {
1500
1583
  turnStart = index;
1501
1584
  break;
1502
1585
  }
1503
1586
  }
1504
1587
  if (turnStart >= 0)
1505
1588
  session.events.splice(turnStart);
1589
+ session.eventBytes = session.events.reduce((total, stored) => total + stored.bytes, 0);
1506
1590
  session.turnStartEventId = session.events.at(-1)?.id || "0";
1507
1591
  const event = emit(session, {
1508
1592
  type: "data-turn-undone",
@@ -1510,8 +1594,7 @@ async function undoLastTurn(config, request, response, session) {
1510
1594
  messageId: checkpoint.messageId,
1511
1595
  input: checkpoint.input,
1512
1596
  attachments: checkpoint.attachments,
1513
- messages: checkpoint.messages,
1514
- metrics: checkpoint.metrics ?? null,
1597
+ historyUpdated: true,
1515
1598
  canUndo: false,
1516
1599
  },
1517
1600
  });
@@ -1522,8 +1605,8 @@ async function undoLastTurn(config, request, response, session) {
1522
1605
  messageId: checkpoint.messageId,
1523
1606
  input: checkpoint.input,
1524
1607
  attachments: checkpoint.attachments,
1525
- messages: checkpoint.messages,
1526
- metrics: checkpoint.metrics ?? null,
1608
+ messages: checkpointMessages,
1609
+ metrics: checkpointMetrics,
1527
1610
  lastEventId: event.id,
1528
1611
  canUndo: false,
1529
1612
  });