replicas-engine 0.1.655 → 0.1.657

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -28,6 +28,7 @@ Chats:
28
28
  - `GET /chats/:chatId`
29
29
  - `DELETE /chats/:chatId`
30
30
  - `GET /chats/:chatId/history`
31
+ - `GET /chats/:chatId/history-page?limit=<count>` (`limit` required)
31
32
  - `GET /chats/:chatId/slash-commands`
32
33
  - `POST /chats/:chatId/messages`
33
34
  - `POST /chats/:chatId/interrupt`
@@ -329,6 +329,8 @@ var DEFAULT_DEEPSEEK_MODEL = DEFAULT_OPENCODE_MODEL;
329
329
  var DEFAULT_PI_MODEL = DEFAULT_OPENCODE_MODEL;
330
330
  var OPENROUTER_MODELS = [
331
331
  DEFAULT_OPENCODE_MODEL,
332
+ "deepseek/deepseek-v4-pro",
333
+ "deepseek/deepseek-v4-flash",
332
334
  "minimax/minimax-m3",
333
335
  "xiaomi/mimo-v2.5-pro",
334
336
  "moonshotai/kimi-k2.6"
@@ -444,6 +446,8 @@ var MODEL_LABELS = {
444
446
  [GPT_5_6_LUNA_MODEL]: "GPT-5.6 Luna",
445
447
  "gpt-5.5": "GPT-5.5",
446
448
  [DEFAULT_OPENCODE_MODEL]: "GLM 5.2 via OpenRouter",
449
+ "deepseek/deepseek-v4-pro": "DeepSeek V4 Pro via OpenRouter",
450
+ "deepseek/deepseek-v4-flash": "DeepSeek V4 Flash via OpenRouter",
447
451
  "minimax/minimax-m3": "MiniMax M3 via OpenRouter",
448
452
  "xiaomi/mimo-v2.5-pro": "MiMo V2.5 Pro via OpenRouter",
449
453
  "moonshotai/kimi-k2.6": "Kimi K2.6 via OpenRouter",
@@ -758,7 +762,7 @@ var sendChatMessageRequestSchema = z3.object({
758
762
  ]).optional()
759
763
  }).passthrough();
760
764
  function isChatMessageSender(value) {
761
- return isRecord(value) && typeof value.senderUserId === "string" && typeof value.senderEmail === "string" && typeof value.recordedAt === "string";
765
+ return isRecord(value) && (value.messageId === void 0 || typeof value.messageId === "string") && typeof value.senderUserId === "string" && typeof value.senderEmail === "string" && typeof value.recordedAt === "string";
762
766
  }
763
767
  function normalizeCodexAspTranscriptStatus(status, failed = false) {
764
768
  if (failed || status === "failed" || status === "declined") return "failed";
@@ -881,6 +885,27 @@ function applyCodexAspTranscriptDelta(current, delta) {
881
885
  turns
882
886
  };
883
887
  }
888
+ function isLatestChatHistoryPage(params) {
889
+ return params.beforeCursor === void 0 && params.beforeEvent === void 0 && params.beforeTurn === void 0;
890
+ }
891
+ function getChatHistoryPageSenders(senders, params) {
892
+ return isLatestChatHistoryPage(params) ? senders : senders?.filter((sender) => sender.messageId);
893
+ }
894
+ function parseChatHistoryCursor(value) {
895
+ if (value === void 0) return void 0;
896
+ try {
897
+ const parsed = JSON.parse(value);
898
+ if (!isRecord(parsed) || parsed.mode !== "range" && parsed.mode !== "legacy") return void 0;
899
+ const valid = (part) => part === null || typeof part === "string";
900
+ if (!valid(parsed.events) || !valid(parsed.turns)) return void 0;
901
+ return { mode: parsed.mode, events: parsed.events, turns: parsed.turns };
902
+ } catch {
903
+ return void 0;
904
+ }
905
+ }
906
+ function createChatHistoryCursor(cursor, mode = "range") {
907
+ return cursor.events === null && cursor.turns === null ? null : JSON.stringify({ mode, ...cursor });
908
+ }
884
909
  function chatHistoryPageParamsFromQuery(query) {
885
910
  const parse = (value) => {
886
911
  if (value === void 0) return void 0;
@@ -890,7 +915,8 @@ function chatHistoryPageParamsFromQuery(query) {
890
915
  return {
891
916
  limit: parse(query.limit),
892
917
  beforeEvent: parse(query.beforeEvent),
893
- beforeTurn: parse(query.beforeTurn)
918
+ beforeTurn: parse(query.beforeTurn),
919
+ beforeCursor: query.beforeCursor || void 0
894
920
  };
895
921
  }
896
922
  function getChatHistoryPageWindow(totalItems, limit, before) {
@@ -5723,6 +5749,105 @@ var EMAIL_KEYS = [
5723
5749
  var MANUAL_EMAIL_TEMPLATE_KEYS = ["product_update"];
5724
5750
  var EMAIL_TEMPLATE_KEYS = [...EMAIL_KEYS, ...MANUAL_EMAIL_TEMPLATE_KEYS];
5725
5751
 
5752
+ // ../shared/src/chat-transcript-pages.ts
5753
+ var decoder = new TextDecoder("utf-8", { fatal: true });
5754
+ var MAX_SOURCE_PAGE_BYTES = 4 * 1024 * 1024;
5755
+ var SOURCE_RANGE_BYTES = 256 * 1024;
5756
+ async function readExactRange(readRange, startByte, endByte, error) {
5757
+ const bytes = await readRange(startByte, endByte);
5758
+ if (bytes.length !== endByte - startByte) throw new Error(error);
5759
+ return bytes;
5760
+ }
5761
+ async function readJsonlPage(args) {
5762
+ let endByte = args.size;
5763
+ if (args.before === null) endByte = 0;
5764
+ else if (args.before !== void 0) {
5765
+ endByte = /^(0|[1-9]\d*)$/.test(args.before) ? Number(args.before) : Number.NaN;
5766
+ }
5767
+ if (!Number.isSafeInteger(endByte) || endByte < 0 || endByte > args.size || !Number.isSafeInteger(args.limit) || args.limit < 1) {
5768
+ throw new RangeError("Invalid JSONL page");
5769
+ }
5770
+ let startByte = endByte;
5771
+ let bytes = new Uint8Array();
5772
+ let items = [];
5773
+ while (startByte > 0 && items.length < args.limit && (endByte - startByte < MAX_SOURCE_PAGE_BYTES || bytes.indexOf(10) === -1)) {
5774
+ const nextStartByte = Math.max(0, startByte - SOURCE_RANGE_BYTES);
5775
+ const chunk = await readExactRange(
5776
+ args.readRange,
5777
+ nextStartByte,
5778
+ startByte,
5779
+ "Incomplete JSONL page read"
5780
+ );
5781
+ const combined = new Uint8Array(chunk.length + bytes.length);
5782
+ combined.set(chunk);
5783
+ combined.set(bytes, chunk.length);
5784
+ bytes = combined;
5785
+ startByte = nextStartByte;
5786
+ items = [];
5787
+ let lineStart = 0;
5788
+ for (let lineEnd = bytes.indexOf(10); lineEnd !== -1; lineEnd = bytes.indexOf(10, lineStart)) {
5789
+ if (!(startByte > 0 && lineStart === 0)) {
5790
+ try {
5791
+ const item = args.parse(JSON.parse(decoder.decode(bytes.subarray(lineStart, lineEnd))));
5792
+ if (item !== void 0) items.push({ item, startByte: startByte + lineStart });
5793
+ } catch {
5794
+ }
5795
+ }
5796
+ lineStart = lineEnd + 1;
5797
+ }
5798
+ }
5799
+ const selected = items.slice(-args.limit);
5800
+ const firstCompleteLine = bytes.indexOf(10) + 1;
5801
+ let before = 0;
5802
+ if (selected.length === args.limit) before = selected[0].startByte;
5803
+ else if (startByte > 0) before = startByte + firstCompleteLine;
5804
+ return {
5805
+ items: selected.map(({ item }) => item),
5806
+ before: before > 0 ? String(before) : null
5807
+ };
5808
+ }
5809
+ function parseChatTranscriptHistoryIndex(value) {
5810
+ if (!isRecord(value) || value.version !== 2 || !isNonNegativeInteger(value.sourceSize)) return void 0;
5811
+ if (value.codexAspTranscript === void 0) {
5812
+ return { version: 2, sourceSize: value.sourceSize };
5813
+ }
5814
+ if (!isRecord(value.codexAspTranscript) || typeof value.codexAspTranscript.threadId !== "string" || typeof value.codexAspTranscript.updatedAt !== "string" || !isNonNegativeInteger(value.codexAspTranscript.size)) return void 0;
5815
+ return {
5816
+ version: 2,
5817
+ sourceSize: value.sourceSize,
5818
+ codexAspTranscript: {
5819
+ threadId: value.codexAspTranscript.threadId,
5820
+ updatedAt: value.codexAspTranscript.updatedAt,
5821
+ size: value.codexAspTranscript.size
5822
+ }
5823
+ };
5824
+ }
5825
+ function getChatTranscriptHistorySize(index) {
5826
+ return index.codexAspTranscript?.size ?? 0;
5827
+ }
5828
+ function createChatTranscriptPages(sourceBytes) {
5829
+ const history = parseAgentEventJsonlWithCodexAspTranscript(new TextDecoder("utf-8", { fatal: true }).decode(sourceBytes));
5830
+ const bytes = new TextEncoder().encode(history.transcript?.turns.length ? `${history.transcript.turns.map((turn) => JSON.stringify(turn)).join("\n")}
5831
+ ` : "");
5832
+ return {
5833
+ bytes,
5834
+ index: {
5835
+ version: 2,
5836
+ sourceSize: sourceBytes.byteLength,
5837
+ ...history.transcript ? {
5838
+ codexAspTranscript: {
5839
+ threadId: history.transcript.threadId,
5840
+ updatedAt: history.transcript.updatedAt,
5841
+ size: bytes.byteLength
5842
+ }
5843
+ } : {}
5844
+ }
5845
+ };
5846
+ }
5847
+ function isNonNegativeInteger(value) {
5848
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
5849
+ }
5850
+
5726
5851
  // ../shared/src/object-store/types.ts
5727
5852
  var MEDIA_KIND = {
5728
5853
  IMAGE: "image",
@@ -5737,8 +5862,9 @@ function normalizeChatMessageSenders(value) {
5737
5862
  if (!Array.isArray(value)) return void 0;
5738
5863
  const senders = value.flatMap((sender) => {
5739
5864
  if (!isChatMessageSender(sender)) return [];
5740
- const { senderUserId, senderEmail, senderDisplayName, senderAvatarUrl, recordedAt } = sender;
5865
+ const { messageId, senderUserId, senderEmail, senderDisplayName, senderAvatarUrl, recordedAt } = sender;
5741
5866
  return [{
5867
+ ...typeof messageId === "string" ? { messageId } : {},
5742
5868
  senderUserId,
5743
5869
  senderEmail,
5744
5870
  ...typeof senderDisplayName === "string" ? { senderDisplayName } : {},
@@ -5760,6 +5886,7 @@ function normalizeChatTranscriptMetadata(value) {
5760
5886
  parentChatId,
5761
5887
  deletedAt,
5762
5888
  senders,
5889
+ historyIndex,
5763
5890
  captureId,
5764
5891
  captureReason,
5765
5892
  sha256
@@ -5774,6 +5901,8 @@ function normalizeChatTranscriptMetadata(value) {
5774
5901
  if (typeof deletedAt === "string" || deletedAt === null) metadata.deletedAt = deletedAt;
5775
5902
  const normalizedSenders = normalizeChatMessageSenders(senders);
5776
5903
  if (normalizedSenders) metadata.senders = normalizedSenders;
5904
+ const parsedHistoryIndex = parseChatTranscriptHistoryIndex(historyIndex);
5905
+ if (parsedHistoryIndex) metadata.historyIndex = parsedHistoryIndex;
5777
5906
  if (typeof captureId === "string") metadata.captureId = captureId;
5778
5907
  if (captureReason === "workspace_sleep") metadata.captureReason = captureReason;
5779
5908
  if (typeof sha256 === "string") metadata.sha256 = sha256;
@@ -5823,9 +5952,6 @@ var memoryGenerationManifestSchema = z6.object({
5823
5952
  rolloutSources: z6.record(z6.string(), z6.string()).optional()
5824
5953
  });
5825
5954
 
5826
- // ../shared/src/chat-transcript-pages.ts
5827
- var decoder = new TextDecoder("utf-8", { fatal: true });
5828
-
5829
5955
  // ../shared/src/skill-registry.ts
5830
5956
  var SKILL_REGISTRY_MANIFEST_VERSION = 1;
5831
5957
  function isSkillRegistryManifest(value) {
@@ -6100,7 +6226,7 @@ var DEFAULT_CODEX_ARGS = [
6100
6226
  var MIN_CODEX_CLI_VERSION = "0.144.6";
6101
6227
  var CODEX_UPGRADE_TIMEOUT_MS = 12e4;
6102
6228
  var codexCliVersionEnsured = null;
6103
- var ENGINE_PACKAGE_VERSION = "0.1.655";
6229
+ var ENGINE_PACKAGE_VERSION = "0.1.657";
6104
6230
  var INITIALIZE_METHOD = "initialize";
6105
6231
  var INITIALIZED_NOTIFICATION = "initialized";
6106
6232
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -6434,8 +6560,11 @@ export {
6434
6560
  isCodexAspTranscript,
6435
6561
  isCodexAspTranscriptDelta,
6436
6562
  applyCodexAspTranscriptDelta,
6563
+ isLatestChatHistoryPage,
6564
+ getChatHistoryPageSenders,
6565
+ parseChatHistoryCursor,
6566
+ createChatHistoryCursor,
6437
6567
  chatHistoryPageParamsFromQuery,
6438
- getChatHistoryPageWindow,
6439
6568
  paginateChatHistory,
6440
6569
  CODEX_AUTH_ENV_KEYS,
6441
6570
  codexAuthEnvFromResponse,
@@ -6464,6 +6593,10 @@ export {
6464
6593
  parseLatestCodexAspTranscriptTurn,
6465
6594
  parseAgentEvents,
6466
6595
  parseDisplayMessages,
6596
+ isAgentBackendEvent,
6597
+ readJsonlPage,
6598
+ getChatTranscriptHistorySize,
6599
+ createChatTranscriptPages,
6467
6600
  parseChatTranscriptArtifact,
6468
6601
  MEMORY_ROOT,
6469
6602
  MEMORY_SUMMARY_FILENAME,
@@ -7,7 +7,7 @@ import {
7
7
  headlessAgentRequestSchema,
8
8
  putPresignedFile,
9
9
  recoverCompletedTurn
10
- } from "./chunk-542J7B2X.js";
10
+ } from "./chunk-HF62RZGX.js";
11
11
 
12
12
  // src/headless-agent.ts
13
13
  import { createHash } from "crypto";
package/dist/src/index.js CHANGED
@@ -104,7 +104,9 @@ import {
104
104
  coerceBackgroundTaskPayload,
105
105
  coerceClaudeResultPayload,
106
106
  createAcceptedUserMessageEvent,
107
+ createChatHistoryCursor,
107
108
  createChatRequestSchema,
109
+ createChatTranscriptPages,
108
110
  createErrorResult,
109
111
  createProviderSlashCommand,
110
112
  createSuccessResult,
@@ -119,7 +121,8 @@ import {
119
121
  extractToolResultText,
120
122
  findGitCommitSignals,
121
123
  findPrMergeSignals,
122
- getChatHistoryPageWindow,
124
+ getChatHistoryPageSenders,
125
+ getChatTranscriptHistorySize,
123
126
  getClaudeModelContextWindow,
124
127
  getCodexAspTurnResponse,
125
128
  getDeepseekAssistantMessageText,
@@ -132,6 +135,7 @@ import {
132
135
  gitIdentityConfigCommands,
133
136
  hasChatStarted,
134
137
  imageContentToUserMessageImages,
138
+ isAgentBackendEvent,
135
139
  isAgentChatMcpActivityRecord,
136
140
  isAgentChatSkillActivityRecord,
137
141
  isAgentChatTurnActivityRecord,
@@ -143,6 +147,7 @@ import {
143
147
  isCodexAuthError,
144
148
  isDefaultChat,
145
149
  isGitHubUrl,
150
+ isLatestChatHistoryPage,
146
151
  isRecord,
147
152
  isSkillRegistryManifest,
148
153
  isTerminalBackgroundTaskStatus,
@@ -158,6 +163,7 @@ import {
158
163
  paginateEngineLogContent,
159
164
  parseAgentEventJsonlWithCodexAspTranscript,
160
165
  parseAgentEvents,
166
+ parseChatHistoryCursor,
161
167
  parseChatTranscriptArtifact,
162
168
  parseCodeHostPrUrl,
163
169
  parseDisplayMessages,
@@ -169,6 +175,7 @@ import {
169
175
  percentage,
170
176
  putPresignedFile,
171
177
  raceWithTimeout,
178
+ readJsonlPage,
172
179
  readReplicasRuntimeEnv,
173
180
  recoverCompletedTurn,
174
181
  resolveWarmHookConfig,
@@ -176,13 +183,13 @@ import {
176
183
  serializeCanvasContentResponse,
177
184
  shellQuotePosix,
178
185
  stripAgentDiagnosticErrors
179
- } from "./chunk-542J7B2X.js";
186
+ } from "./chunk-HF62RZGX.js";
180
187
 
181
188
  // src/index.ts
182
189
  import { serve } from "@hono/node-server";
183
190
  import { Hono as Hono2 } from "hono";
184
191
  import { existsSync as existsSync11 } from "fs";
185
- import { randomUUID as randomUUID8 } from "crypto";
192
+ import { randomUUID as randomUUID9 } from "crypto";
186
193
  import { connect } from "net";
187
194
 
188
195
  // src/managers/github-token-manager.ts
@@ -2735,7 +2742,7 @@ import { existsSync as existsSync8 } from "fs";
2735
2742
  import { appendFile as appendFile4, copyFile, mkdir as mkdir16, readFile as readFile15, rename as rename3, rm as rm2 } from "fs/promises";
2736
2743
  import { homedir as homedir15 } from "os";
2737
2744
  import { join as join29 } from "path";
2738
- import { randomUUID as randomUUID6 } from "crypto";
2745
+ import { randomUUID as randomUUID7 } from "crypto";
2739
2746
 
2740
2747
  // src/managers/claude-manager.ts
2741
2748
  import {
@@ -4147,11 +4154,11 @@ var CodexHistoryFile = class {
4147
4154
  }
4148
4155
  filePath;
4149
4156
  writeLock = new AsyncLock();
4150
- eventLineIndex = null;
4151
4157
  /** Best-effort ordered append; failures must not disrupt the turn. */
4152
4158
  append(event) {
4153
4159
  void this.writeLock.run(
4154
- () => appendFile2(this.filePath, JSON.stringify(event) + "\n", "utf-8").catch((error) => {
4160
+ () => appendFile2(this.filePath, `${JSON.stringify(event)}
4161
+ `).catch((error) => {
4155
4162
  console.error("[CodexHistoryFile] Failed to append event:", error);
4156
4163
  })
4157
4164
  );
@@ -4172,85 +4179,51 @@ var CodexHistoryFile = class {
4172
4179
  }
4173
4180
  async loadEventsPage(page) {
4174
4181
  if (page.limit === void 0) {
4175
- const history2 = await this.load();
4176
- return { events: history2.events, eventsStartIndex: 0, totalEvents: history2.events.length };
4182
+ const history = await this.load();
4183
+ return { events: history.events };
4184
+ }
4185
+ const cursor = parseChatHistoryCursor(page.beforeCursor);
4186
+ if (page.beforeCursor !== void 0 && cursor?.mode !== "range") throw new RangeError("Invalid chat history cursor");
4187
+ if (cursor?.events === null) {
4188
+ return {
4189
+ events: [],
4190
+ beforeCursor: createChatHistoryCursor({
4191
+ events: null,
4192
+ turns: cursor.turns
4193
+ })
4194
+ };
4177
4195
  }
4178
4196
  let file;
4179
4197
  try {
4180
4198
  file = await open(this.filePath, "r");
4181
4199
  } catch (error) {
4182
- if (!(error && typeof error === "object" && "code" in error && error.code === "ENOENT")) {
4183
- console.error("[CodexHistoryFile] Failed to open history file:", error);
4200
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
4201
+ return { events: [], beforeCursor: null };
4184
4202
  }
4185
- return { events: [], eventsStartIndex: 0, totalEvents: 0 };
4203
+ throw error;
4186
4204
  }
4187
4205
  try {
4188
- const stats = await file.stat();
4189
- let index = this.eventLineIndex;
4190
- if (!index || index.size !== stats.size || index.mtimeMs !== stats.mtimeMs) {
4191
- const starts = stats.size > 0 ? [0] : [];
4192
- const buffer = Buffer.allocUnsafe(64 * 1024);
4193
- let position = 0;
4194
- while (position < stats.size) {
4195
- const { bytesRead: bytesRead2 } = await file.read(
4196
- buffer,
4197
- 0,
4198
- Math.min(buffer.length, stats.size - position),
4199
- position
4200
- );
4201
- if (bytesRead2 === 0) break;
4202
- for (let offset = 0; offset < bytesRead2; offset += 1) {
4203
- const next = position + offset + 1;
4204
- if (buffer[offset] === 10 && next < stats.size) starts.push(next);
4205
- }
4206
- position += bytesRead2;
4207
- }
4208
- index = { size: stats.size, mtimeMs: stats.mtimeMs, starts };
4209
- this.eventLineIndex = index;
4210
- }
4211
- const totalEvents2 = index.starts.length;
4212
- const { startIndex: eventsStartIndex2, endIndex: eventsEnd2 } = getChatHistoryPageWindow(
4213
- totalEvents2,
4214
- page.limit,
4215
- page.beforeEvent
4216
- );
4217
- const startByte = index.starts[eventsStartIndex2] ?? index.size;
4218
- const endByte = index.starts[eventsEnd2] ?? index.size;
4219
- const content = Buffer.allocUnsafe(endByte - startByte);
4220
- let bytesRead = 0;
4221
- while (bytesRead < content.length) {
4222
- const result = await file.read(
4223
- content,
4224
- bytesRead,
4225
- content.length - bytesRead,
4226
- startByte + bytesRead
4227
- );
4228
- if (result.bytesRead === 0) break;
4229
- bytesRead += result.bytesRead;
4230
- }
4231
- if (bytesRead === content.length) {
4232
- const parsed = parseAgentEventJsonlWithCodexAspTranscript(content.toString("utf-8"));
4233
- if (!parsed.transcript && parsed.events.length === eventsEnd2 - eventsStartIndex2) {
4234
- return { events: parsed.events, eventsStartIndex: eventsStartIndex2, totalEvents: totalEvents2 };
4235
- }
4236
- }
4237
- } catch (error) {
4238
- console.error("[CodexHistoryFile] Failed to load history page:", error);
4206
+ const result = await readJsonlPage({
4207
+ size: (await file.stat()).size,
4208
+ limit: page.limit,
4209
+ before: cursor?.events,
4210
+ readRange: async (startByte, endByte) => {
4211
+ const bytes = Buffer.allocUnsafe(endByte - startByte);
4212
+ const { bytesRead } = await file.read(bytes, 0, bytes.length, startByte);
4213
+ return bytes.subarray(0, bytesRead);
4214
+ },
4215
+ parse: (value) => isAgentBackendEvent(value) && value.type !== CODEX_ASP_TRANSCRIPT_UPDATED_EVENT_TYPE ? value : void 0
4216
+ });
4217
+ return {
4218
+ events: result.items,
4219
+ beforeCursor: createChatHistoryCursor({
4220
+ events: result.before,
4221
+ turns: cursor?.turns ?? null
4222
+ })
4223
+ };
4239
4224
  } finally {
4240
4225
  await file.close();
4241
4226
  }
4242
- const history = await this.load();
4243
- const totalEvents = history.events.length;
4244
- const { startIndex: eventsStartIndex, endIndex: eventsEnd } = getChatHistoryPageWindow(
4245
- totalEvents,
4246
- page.limit,
4247
- page.beforeEvent
4248
- );
4249
- return {
4250
- events: history.events.slice(eventsStartIndex, eventsEnd),
4251
- eventsStartIndex,
4252
- totalEvents
4253
- };
4254
4227
  }
4255
4228
  };
4256
4229
 
@@ -5721,6 +5694,7 @@ var DEFAULT_MODEL = DEFAULT_CODEX_MODEL;
5721
5694
  var THREAD_START_METHOD = "thread/start";
5722
5695
  var THREAD_RESUME_METHOD = "thread/resume";
5723
5696
  var THREAD_READ_METHOD = "thread/read";
5697
+ var THREAD_TURNS_LIST_METHOD = "thread/turns/list";
5724
5698
  var THREAD_GOAL_SET_METHOD = "thread/goal/set";
5725
5699
  var THREAD_GOAL_GET_METHOD = "thread/goal/get";
5726
5700
  var THREAD_GOAL_CLEAR_METHOD = "thread/goal/clear";
@@ -6049,8 +6023,11 @@ function formatTurnFailure(turn) {
6049
6023
  return parts.join(" ");
6050
6024
  }
6051
6025
  function threadToAspTranscript(thread) {
6026
+ return turnsToAspTranscript(thread.id, timestampFromSeconds(thread.updatedAt), thread.turns);
6027
+ }
6028
+ function turnsToAspTranscript(threadId, updatedAt, sourceTurns) {
6052
6029
  let sequence = 0;
6053
- const turns = thread.turns.map((turn, index) => ({ turn, index })).sort((a, b) => (a.turn.startedAt ?? a.turn.completedAt ?? Number.MAX_SAFE_INTEGER) - (b.turn.startedAt ?? b.turn.completedAt ?? Number.MAX_SAFE_INTEGER) || a.index - b.index).map(({ turn }) => {
6030
+ const turns = sourceTurns.map((turn, index) => ({ turn, index })).sort((a, b) => (a.turn.startedAt ?? a.turn.completedAt ?? Number.MAX_SAFE_INTEGER) - (b.turn.startedAt ?? b.turn.completedAt ?? Number.MAX_SAFE_INTEGER) || a.index - b.index).map(({ turn }) => {
6054
6031
  const startedAt = timestampFromSeconds(turn.startedAt);
6055
6032
  const completedAt = turn.completedAt === null ? null : timestampFromSeconds(turn.completedAt);
6056
6033
  const itemTimestamp = completedAt ?? startedAt;
@@ -6080,8 +6057,8 @@ function threadToAspTranscript(thread) {
6080
6057
  };
6081
6058
  });
6082
6059
  return {
6083
- threadId: thread.id,
6084
- updatedAt: timestampFromSeconds(thread.updatedAt),
6060
+ threadId,
6061
+ updatedAt,
6085
6062
  turns
6086
6063
  };
6087
6064
  }
@@ -6403,7 +6380,7 @@ async function readCodexAspThreadHistory(threadId) {
6403
6380
  }
6404
6381
  }
6405
6382
  for (const entry of entries) {
6406
- if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
6383
+ if (!entry.isFile() || !entry.name.endsWith(".jsonl") || entry.name.endsWith(".pages.jsonl")) continue;
6407
6384
  const history = await new CodexHistoryFile(join19(CODEX_HISTORY_DIR, entry.name)).load();
6408
6385
  const transcript = history.transcriptsByThreadId.get(threadId);
6409
6386
  if (transcript) {
@@ -6452,7 +6429,6 @@ var CodexAspManager = class extends CodingAgentManager {
6452
6429
  activeTurnId = null;
6453
6430
  threadAttached = false;
6454
6431
  historyFile;
6455
- historyEvents = [];
6456
6432
  codexAspTranscript = null;
6457
6433
  lastEmittedTranscripts = /* @__PURE__ */ new Map();
6458
6434
  codexAspSequence = 0;
@@ -6474,9 +6450,12 @@ var CodexAspManager = class extends CodingAgentManager {
6474
6450
  this.initializeManager(this.processMessageInternal.bind(this));
6475
6451
  }
6476
6452
  async initialize() {
6453
+ if (this.initialSessionId) {
6454
+ this.currentThreadId = this.initialSessionId;
6455
+ return;
6456
+ }
6477
6457
  const replayed = await this.historyFile?.load();
6478
6458
  if (replayed) {
6479
- this.historyEvents.push(...replayed.events);
6480
6459
  if (replayed.transcript) {
6481
6460
  this.mergeTranscriptSnapshot(replayed.transcript);
6482
6461
  }
@@ -6565,51 +6544,87 @@ var CodexAspManager = class extends CodingAgentManager {
6565
6544
  return true;
6566
6545
  }
6567
6546
  async getHistory(page = {}) {
6568
- if (!this.currentThreadId) {
6569
- return paginateChatHistory({ thread_id: null, events: [], goal: null }, page);
6570
- }
6571
- if (this.codexAspTranscript?.threadId === this.currentThreadId) {
6572
- if (page.beforeEvent === void 0 && page.beforeTurn === void 0) {
6573
- try {
6574
- const host = await getCodexAspHost();
6575
- await this.refreshThreadGoal(host, this.currentThreadId);
6576
- } catch {
6577
- }
6578
- }
6579
- return paginateChatHistory({
6580
- thread_id: this.currentThreadId,
6581
- events: [...this.historyEvents],
6582
- codexAspTranscript: this.codexAspTranscript,
6583
- goal: this.currentGoal
6584
- }, page);
6585
- }
6586
- try {
6547
+ await this.historyFile?.flush();
6548
+ if (page.limit === void 0) {
6549
+ const local = async () => {
6550
+ const history = await this.historyFile?.load();
6551
+ return {
6552
+ thread_id: this.currentThreadId,
6553
+ events: history?.events ?? [],
6554
+ codexAspTranscript: this.currentThreadId ? history?.transcriptsByThreadId.get(this.currentThreadId) ?? null : null,
6555
+ goal: this.currentGoal
6556
+ };
6557
+ };
6558
+ if (!this.currentThreadId) return local();
6559
+ return this.readFullNativeHistory().catch(local);
6560
+ }
6561
+ const events = await this.historyFile?.loadEventsPage(page) ?? { events: [], beforeCursor: null };
6562
+ if (!this.currentThreadId) return { thread_id: null, ...events, goal: this.currentGoal };
6563
+ const cursor = parseChatHistoryCursor(page.beforeCursor);
6564
+ if (page.beforeCursor !== void 0 && cursor?.mode !== "range") throw new RangeError("Invalid chat history cursor");
6565
+ let transcript = null;
6566
+ let turnsBefore = null;
6567
+ if (cursor?.turns !== null) {
6587
6568
  const host = await getCodexAspHost();
6588
- const [response] = await Promise.all([
6589
- host.client.request(
6590
- THREAD_READ_METHOD,
6591
- { threadId: this.currentThreadId, includeTurns: true }
6592
- ),
6593
- this.refreshThreadGoal(host, this.currentThreadId)
6594
- ]);
6595
- const transcript = this.mergeTranscriptSnapshot(threadToAspTranscript(response.thread));
6596
- if (transcript) {
6597
- transcript.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
6598
- }
6599
- return paginateChatHistory({
6600
- thread_id: this.currentThreadId,
6601
- events: [...this.historyEvents],
6602
- codexAspTranscript: transcript,
6603
- goal: this.currentGoal
6604
- }, page);
6605
- } catch {
6569
+ if (page.beforeCursor === void 0) await this.refreshThreadGoal(host, this.currentThreadId);
6570
+ const turns = await this.readNativeTurnsPage(page.limit, cursor?.turns, host);
6571
+ transcript = turns.transcript;
6572
+ turnsBefore = turns.before;
6606
6573
  }
6607
- return paginateChatHistory({
6574
+ const eventCursor = parseChatHistoryCursor(events.beforeCursor ?? void 0);
6575
+ return {
6576
+ thread_id: this.currentThreadId,
6577
+ ...events,
6578
+ codexAspTranscript: transcript,
6579
+ goal: this.currentGoal,
6580
+ beforeCursor: createChatHistoryCursor({
6581
+ events: eventCursor?.events ?? null,
6582
+ turns: turnsBefore
6583
+ })
6584
+ };
6585
+ }
6586
+ async readFullNativeHistory() {
6587
+ if (!this.currentThreadId) {
6588
+ return { thread_id: null, events: [], goal: this.currentGoal };
6589
+ }
6590
+ const host = await getCodexAspHost();
6591
+ const [response, history] = await Promise.all([
6592
+ host.client.request(
6593
+ THREAD_READ_METHOD,
6594
+ { threadId: this.currentThreadId, includeTurns: true }
6595
+ ),
6596
+ this.historyFile?.load()
6597
+ ]);
6598
+ const transcript = this.mergeTranscriptSnapshot(threadToAspTranscript(response.thread));
6599
+ return {
6608
6600
  thread_id: this.currentThreadId,
6609
- events: [...this.historyEvents],
6610
- codexAspTranscript: this.codexAspTranscript,
6601
+ events: history?.events ?? [],
6602
+ codexAspTranscript: transcript,
6611
6603
  goal: this.currentGoal
6612
- }, page);
6604
+ };
6605
+ }
6606
+ async readNativeTurnsPage(limit, cursor, host) {
6607
+ if (!this.currentThreadId) throw new Error("A thread is required for native turn pagination");
6608
+ const response = await host.client.request(
6609
+ THREAD_TURNS_LIST_METHOD,
6610
+ {
6611
+ threadId: this.currentThreadId,
6612
+ cursor: cursor ?? null,
6613
+ limit,
6614
+ sortDirection: "desc",
6615
+ itemsView: "full"
6616
+ }
6617
+ );
6618
+ const transcript = turnsToAspTranscript(
6619
+ this.currentThreadId,
6620
+ (/* @__PURE__ */ new Date()).toISOString(),
6621
+ response.data
6622
+ );
6623
+ if (cursor === void 0) {
6624
+ this.codexAspTranscript = transcript;
6625
+ this.syncTranscriptSequence(transcript);
6626
+ }
6627
+ return { transcript, before: response.nextCursor };
6613
6628
  }
6614
6629
  getGoal() {
6615
6630
  return this.currentGoal;
@@ -7538,7 +7553,6 @@ var CodexAspManager = class extends CodingAgentManager {
7538
7553
  return event;
7539
7554
  }
7540
7555
  trackHistoryEvent(event) {
7541
- this.historyEvents.push(event);
7542
7556
  this.historyFile?.append(event);
7543
7557
  }
7544
7558
  emitTranscriptUpdated(threadId, options = {}) {
@@ -8094,6 +8108,7 @@ ${instructions}
8094
8108
 
8095
8109
  // src/managers/deepseek-manager.ts
8096
8110
  import { spawn as spawn3 } from "child_process";
8111
+ import { randomUUID as randomUUID5 } from "crypto";
8097
8112
  import { createRequire } from "module";
8098
8113
  import { dirname as dirname6, join as join21 } from "path";
8099
8114
  import { fileURLToPath } from "url";
@@ -8103,7 +8118,7 @@ import { z as z2 } from "zod";
8103
8118
  import WebSocket from "ws";
8104
8119
  import { AbstractApiClient } from "@deepseek-ai/dsh-host-apiproxy/client";
8105
8120
  import { hostFrameSchema, muxFrameSchema } from "@deepseek-ai/dsh-host-apiproxy/api/events.schema";
8106
- import { serverRequestSchema } from "@deepseek-ai/dsh-host-apiproxy/api/rpc.schema";
8121
+ import { serverRequestSchema, serverResponseSchema } from "@deepseek-ai/dsh-host-apiproxy/api/rpc.schema";
8107
8122
  import { sessionIdSchema } from "@deepseek-ai/dsh-host-apiproxy/api/sessions.schema";
8108
8123
  var questionSelectionSchema = z2.record(z2.string(), z2.object({
8109
8124
  options: z2.array(z2.string()).optional(),
@@ -8134,6 +8149,26 @@ var DeepseekApiClient = class extends AbstractApiClient {
8134
8149
  openHost(_payload, signal, onOpen) {
8135
8150
  return this.readWebSocket("/api/events.host", signal, hostFrameSchema, onOpen);
8136
8151
  }
8152
+ async executeCommand(sessionId, line) {
8153
+ const rpcId = randomUUID5();
8154
+ const response = await this.doFetch(new URL("/api/commands/execute", this.baseUrl), {
8155
+ method: "POST",
8156
+ headers: { "content-type": "application/json" },
8157
+ body: JSON.stringify({
8158
+ type: "client-request",
8159
+ rpcId,
8160
+ method: "commands/execute",
8161
+ payload: { args: { agentId: sessionId, line } }
8162
+ })
8163
+ });
8164
+ if (!response.ok) throw new Error(`DeepSeek Harness command failed with HTTP ${response.status}.`);
8165
+ const result = serverResponseSchema.parse(await response.json());
8166
+ if (result.rpcId !== rpcId) throw new Error("DeepSeek Harness command response did not match its request.");
8167
+ if (!result.result.ok) throw new Error(result.result.error.message);
8168
+ const execution = z2.object({ result: z2.object({ kind: z2.enum(["success", "error"]), text: z2.string().optional() }) }).safeParse(result.result.value);
8169
+ if (!execution.success) throw new Error(`DeepSeek Harness did not recognize ${line}.`);
8170
+ if (execution.data.result.kind === "error") throw new Error(execution.data.result.text ?? `DeepSeek Harness rejected ${line}.`);
8171
+ }
8137
8172
  async *readWebSocket(path6, signal, schema, onOpen) {
8138
8173
  const url = new URL(path6, this.baseUrl);
8139
8174
  url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
@@ -8452,18 +8487,12 @@ ${combinedInstructions}
8452
8487
  </system_instructions>
8453
8488
 
8454
8489
  ${request.message}` : request.message;
8455
- if (!request.planMode) {
8456
- unwrap(await client.sessions.prompt({
8457
- sessionId: this.sessionId,
8458
- mode: "queue",
8459
- content: [{ type: "text", text: "/plan off" }]
8460
- }));
8461
- }
8490
+ await client.executeCommand(this.sessionId, request.planMode ? "/plan" : "/plan off");
8462
8491
  unwrap(await client.sessions.prompt({
8463
8492
  sessionId: this.sessionId,
8464
8493
  mode: "queue",
8465
8494
  content: [
8466
- { type: "text", text: request.planMode ? `/plan ${prompt}` : prompt },
8495
+ { type: "text", text: prompt },
8467
8496
  ...images.map((image) => ({
8468
8497
  type: "image",
8469
8498
  mediaType: image.source.media_type,
@@ -10184,7 +10213,7 @@ import {
10184
10213
  unlink as unlink3
10185
10214
  } from "fs/promises";
10186
10215
  import { join as join24 } from "path";
10187
- import { randomUUID as randomUUID5 } from "crypto";
10216
+ import { randomUUID as randomUUID6 } from "crypto";
10188
10217
 
10189
10218
  // src/analytics/agent/activity/skill-mcp-call-extractor.ts
10190
10219
  var NON_MCP_SERVERS = /* @__PURE__ */ new Set(["claude", "cursor", "deepseek", "opencode", "pi", "custom", "dynamic"]);
@@ -10405,7 +10434,7 @@ var AgentChatTurnActivityTracker = class {
10405
10434
  const credential = ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS[provider];
10406
10435
  this.pendingMessages.delete(messageId);
10407
10436
  this.activeTurns.set(chatId, {
10408
- turnId: randomUUID5(),
10437
+ turnId: randomUUID6(),
10409
10438
  startedAtMs: Date.now(),
10410
10439
  provider,
10411
10440
  model: attributes.model ?? getDefaultAgentModel(provider),
@@ -10800,7 +10829,7 @@ async function reconcileCanvasItems(filenames) {
10800
10829
  // src/services/upload-chat-transcripts.ts
10801
10830
  import { createReadStream } from "fs";
10802
10831
  import { createHash as createHash2 } from "crypto";
10803
- import { readdir as readdir8, readFile as readFile14, stat as stat4 } from "fs/promises";
10832
+ import { readFile as readFile14, readdir as readdir8, stat as stat4 } from "fs/promises";
10804
10833
  import { basename as basename2, join as join27 } from "path";
10805
10834
 
10806
10835
  // src/services/chat/chat-senders.ts
@@ -10859,6 +10888,7 @@ async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map(), ca
10859
10888
  async function uploadChatTranscript(chatId, filePath, chat, capture) {
10860
10889
  const { size } = await stat4(filePath);
10861
10890
  if (size === 0) return null;
10891
+ const historyPages = createChatTranscriptPages(await readFile14(filePath));
10862
10892
  const metadata = chat ? {
10863
10893
  provider: chat.provider,
10864
10894
  credential: ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS[chat.provider],
@@ -10882,6 +10912,7 @@ async function uploadChatTranscript(chatId, filePath, chat, capture) {
10882
10912
  } catch (error) {
10883
10913
  if (!(error && typeof error === "object" && "code" in error && error.code === "ENOENT")) throw error;
10884
10914
  }
10915
+ if (historyPages.index.codexAspTranscript) metadata.historyIndex = historyPages.index;
10885
10916
  const uploadRequest = { chatId, size, metadata };
10886
10917
  const prepareResponse = await monolithRequest("/v1/engine/chat-transcripts/upload-url", {
10887
10918
  body: uploadRequest
@@ -10890,11 +10921,25 @@ async function uploadChatTranscript(chatId, filePath, chat, capture) {
10890
10921
  throw new Error(`prepare failed: ${prepareResponse.status} ${await prepareResponse.text()}`);
10891
10922
  }
10892
10923
  const prepareBody = await prepareResponse.json();
10893
- if (!isRecord(prepareBody) || typeof prepareBody.uploadUrl !== "string" && prepareBody.uploadUrl !== null) {
10924
+ if (!isRecord(prepareBody) || typeof prepareBody.uploadUrl !== "string" && prepareBody.uploadUrl !== null || prepareBody.historyPagesUploadUrl !== void 0 && typeof prepareBody.historyPagesUploadUrl !== "string") {
10894
10925
  throw new Error("prepare failed: invalid response");
10895
10926
  }
10896
10927
  if (prepareBody.uploadUrl === null) return null;
10897
- await putPresignedFile(prepareBody.uploadUrl, filePath, size, "application/x-ndjson");
10928
+ const uploads = [putPresignedFile(prepareBody.uploadUrl, filePath, size, "application/x-ndjson")];
10929
+ const historyPagesSize = getChatTranscriptHistorySize(historyPages.index);
10930
+ if (historyPagesSize > 0) {
10931
+ if (typeof prepareBody.historyPagesUploadUrl !== "string") {
10932
+ throw new Error("prepare failed: missing history pages upload URL");
10933
+ }
10934
+ uploads.push(fetch(prepareBody.historyPagesUploadUrl, {
10935
+ method: "PUT",
10936
+ headers: { "content-type": "application/x-ndjson" },
10937
+ body: new Uint8Array(historyPages.bytes).buffer
10938
+ }).then((response) => {
10939
+ if (!response.ok) throw new Error(`history pages upload failed: ${response.status}`);
10940
+ }));
10941
+ }
10942
+ await Promise.all(uploads);
10898
10943
  const finalizeResponse = await monolithRequest("/v1/engine/chat-transcripts/finalize", {
10899
10944
  body: uploadRequest
10900
10945
  });
@@ -11273,7 +11318,7 @@ var ChatService = class {
11273
11318
  throw new ChatNotFoundError(parentChatId);
11274
11319
  }
11275
11320
  const persisted = {
11276
- id: request.id ?? randomUUID6(),
11321
+ id: request.id ?? randomUUID7(),
11277
11322
  provider: request.provider,
11278
11323
  title,
11279
11324
  createdAt: now,
@@ -11310,10 +11355,11 @@ var ChatService = class {
11310
11355
  }
11311
11356
  chat.persisted.acceptedSendResponses = Object.fromEntries(chat.acceptedSendResponses);
11312
11357
  }
11358
+ const submittedAt = request.submittedAt ?? (/* @__PURE__ */ new Date()).toISOString();
11313
11359
  const acceptedEvent = createAcceptedUserMessageEvent(
11314
11360
  request.message,
11315
11361
  result.messageId,
11316
- request.submittedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
11362
+ submittedAt,
11317
11363
  request.images
11318
11364
  );
11319
11365
  if (!chat.pendingMessageIds.includes(result.messageId)) {
@@ -11329,11 +11375,12 @@ var ChatService = class {
11329
11375
  let recordedSender;
11330
11376
  if (request.senderUserId && request.senderEmail) {
11331
11377
  recordedSender = {
11378
+ messageId: result.messageId,
11332
11379
  senderUserId: request.senderUserId,
11333
11380
  senderEmail: request.senderEmail,
11334
11381
  ...request.senderDisplayName ? { senderDisplayName: request.senderDisplayName } : {},
11335
11382
  ...request.senderAvatarUrl ? { senderAvatarUrl: request.senderAvatarUrl } : {},
11336
- recordedAt: request.submittedAt ?? (/* @__PURE__ */ new Date()).toISOString()
11383
+ recordedAt: submittedAt
11337
11384
  };
11338
11385
  await this.appendSender(chatId, recordedSender);
11339
11386
  }
@@ -11362,15 +11409,11 @@ var ChatService = class {
11362
11409
  }
11363
11410
  }
11364
11411
  async readSenders(chatId) {
11365
- try {
11366
- return parseChatMessageSendersJsonl(await readFile15(chatMessageSendersFilePath(chatId), "utf-8"));
11367
- } catch (error) {
11368
- if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
11369
- return [];
11370
- }
11412
+ return readFile15(chatMessageSendersFilePath(chatId), "utf-8").then(parseChatMessageSendersJsonl).catch((error) => {
11413
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") return [];
11371
11414
  console.error("[ChatService] Failed to read sender records:", error);
11372
11415
  return [];
11373
- }
11416
+ });
11374
11417
  }
11375
11418
  async interrupt(chatId) {
11376
11419
  const chat = this.requireChat(chatId);
@@ -11592,7 +11635,12 @@ var ChatService = class {
11592
11635
  return descendants;
11593
11636
  }
11594
11637
  async deleteHistoryFile(persisted) {
11595
- await rm2(join29(HISTORY_DIR_BY_PROVIDER[persisted.provider], `${persisted.id}.jsonl`), { force: true });
11638
+ const historyPath = join29(HISTORY_DIR_BY_PROVIDER[persisted.provider], `${persisted.id}.jsonl`);
11639
+ await Promise.all([
11640
+ historyPath,
11641
+ `${historyPath}.pages.jsonl`,
11642
+ `${historyPath}.pages.index.json`
11643
+ ].map((path6) => rm2(path6, { force: true })));
11596
11644
  await rm2(chatMessageSendersFilePath(persisted.id), { force: true });
11597
11645
  }
11598
11646
  async getChatHistory(chatId, page = {}) {
@@ -11606,10 +11654,23 @@ var ChatService = class {
11606
11654
  chat.acceptedUserEvents.delete(messageId);
11607
11655
  }
11608
11656
  }
11609
- const isLatestPage = page.beforeEvent === void 0 && page.beforeTurn === void 0;
11657
+ const isLatestPage = isLatestChatHistoryPage(page);
11610
11658
  const queuedMessageIds = new Set(chat.provider.getQueue().map((message) => message.id));
11611
11659
  const acceptedEvents = (isLatestPage ? [...chat.acceptedUserEvents.entries()] : []).filter(([messageId]) => messageId === chat.activeMessageId || !chat.hasActiveTurn && chat.pendingMessageIds[0] === messageId && !queuedMessageIds.has(messageId)).map(([, acceptedEvent]) => acceptedEvent).filter((acceptedEvent) => !acceptedEventInCodexTranscript(acceptedEvent, history.codexAspTranscript) && !history.events.some((event) => isSameAcceptedUserEvent(event, acceptedEvent)));
11612
11660
  const events = [...history.events, ...acceptedEvents].sort((a, b) => getEventTimestampMs(a) - getEventTimestampMs(b));
11661
+ if (page.limit !== void 0 && "beforeCursor" in history) {
11662
+ const cursor = parseChatHistoryCursor(history.beforeCursor ?? void 0);
11663
+ return {
11664
+ ...history,
11665
+ events,
11666
+ goal: history.goal ?? chat.provider.getGoal?.() ?? null,
11667
+ senders: getChatHistoryPageSenders(senders, page),
11668
+ beforeCursor: createChatHistoryCursor({
11669
+ events: cursor?.events ?? null,
11670
+ turns: cursor?.turns ?? null
11671
+ })
11672
+ };
11673
+ }
11613
11674
  if (history.eventsStartIndex !== void 0) {
11614
11675
  return {
11615
11676
  ...history,
@@ -12002,7 +12063,7 @@ var ChatService = class {
12002
12063
  }
12003
12064
  async publish(input) {
12004
12065
  const event = {
12005
- id: randomUUID6(),
12066
+ id: randomUUID7(),
12006
12067
  ts: (/* @__PURE__ */ new Date()).toISOString(),
12007
12068
  ...input
12008
12069
  };
@@ -12744,7 +12805,7 @@ ${combinedScript}` : combinedScript;
12744
12805
  }
12745
12806
 
12746
12807
  // src/services/terminal-service.ts
12747
- import { randomUUID as randomUUID7 } from "crypto";
12808
+ import { randomUUID as randomUUID8 } from "crypto";
12748
12809
  import { existsSync as existsSync10 } from "fs";
12749
12810
  import { spawn as spawn5 } from "node-pty";
12750
12811
  var MAX_REPLAY_CHARS = 1024 * 1024;
@@ -12763,7 +12824,7 @@ var TerminalService = class {
12763
12824
  code: "limit"
12764
12825
  });
12765
12826
  }
12766
- const id = randomUUID7();
12827
+ const id = randomUUID8();
12767
12828
  const shell = process.env.SHELL && existsSync10(process.env.SHELL) ? process.env.SHELL : "/bin/bash";
12768
12829
  const pty = spawn5(shell, ["-l"], {
12769
12830
  name: "xterm-256color",
@@ -12940,6 +13001,22 @@ function jsonError(message, details) {
12940
13001
  }
12941
13002
  function createV1Routes(deps) {
12942
13003
  const app2 = new Hono();
13004
+ const loadChatHistory = async (c, chatId, page, failureMessage, unexpectedStatus, legacy = false) => {
13005
+ try {
13006
+ const history = await deps.chatService.getChatHistory(chatId, legacy && page.limit !== void 0 ? {} : page);
13007
+ if (!legacy) return c.json(history);
13008
+ return c.json(paginateChatHistory({
13009
+ ...history,
13010
+ senders: getChatHistoryPageSenders(history.senders, page)
13011
+ }, page));
13012
+ } catch (error) {
13013
+ const status = error instanceof ChatNotFoundError ? 404 : unexpectedStatus;
13014
+ return c.json(jsonError(
13015
+ failureMessage,
13016
+ error instanceof Error ? error.message : "Unknown error"
13017
+ ), status);
13018
+ }
13019
+ };
12943
13020
  app2.get("/events", async () => {
12944
13021
  const encoder = new TextEncoder();
12945
13022
  let unsubscribe = null;
@@ -13059,19 +13136,23 @@ function createV1Routes(deps) {
13059
13136
  return c.json(jsonError("Failed to restore chat", details), 500);
13060
13137
  }
13061
13138
  });
13062
- app2.get("/chats/:chatId/history", async (c) => {
13063
- try {
13064
- const history = await deps.chatService.getChatHistory(
13065
- c.req.param("chatId"),
13066
- chatHistoryPageParamsFromQuery(c.req.query())
13067
- );
13068
- return c.json(history);
13069
- } catch (error) {
13070
- if (error instanceof ChatNotFoundError) {
13071
- return c.json(jsonError("Failed to load chat history", error.message), 404);
13072
- }
13073
- return c.json(jsonError("Failed to load chat history", error instanceof Error ? error.message : "Unknown error"), 404);
13139
+ app2.get("/chats/:chatId/history", (c) => loadChatHistory(
13140
+ c,
13141
+ c.req.param("chatId"),
13142
+ chatHistoryPageParamsFromQuery(c.req.query()),
13143
+ "Failed to load chat history",
13144
+ 404,
13145
+ true
13146
+ ));
13147
+ app2.get("/chats/:chatId/history-page", async (c) => {
13148
+ const page = chatHistoryPageParamsFromQuery(c.req.query());
13149
+ if (page.limit === void 0) {
13150
+ return c.json(jsonError("Failed to load chat history page", "limit is required"), 400);
13151
+ }
13152
+ if (page.beforeCursor !== void 0 && !parseChatHistoryCursor(page.beforeCursor)) {
13153
+ return c.json(jsonError("Failed to load chat history page", "invalid cursor"), 400);
13074
13154
  }
13155
+ return loadChatHistory(c, c.req.param("chatId"), page, "Failed to load chat history page", 500);
13075
13156
  });
13076
13157
  app2.get("/chats/:chatId/slash-commands", async (c) => {
13077
13158
  try {
@@ -14058,7 +14139,7 @@ function startStatusBroadcaster() {
14058
14139
  if (serialized !== previousRepoStatus) {
14059
14140
  previousRepoStatus = serialized;
14060
14141
  eventService.publish({
14061
- id: randomUUID8(),
14142
+ id: randomUUID9(),
14062
14143
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14063
14144
  type: "repo.status.changed",
14064
14145
  payload: { repos }
@@ -14079,7 +14160,7 @@ function startStatusBroadcaster() {
14079
14160
  if (engineStatusJson !== previousEngineStatus) {
14080
14161
  previousEngineStatus = engineStatusJson;
14081
14162
  eventService.publish({
14082
- id: randomUUID8(),
14163
+ id: randomUUID9(),
14083
14164
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14084
14165
  type: "engine.status.changed",
14085
14166
  payload: { status: engineStatus }
@@ -14098,7 +14179,7 @@ function startStatusBroadcaster() {
14098
14179
  previousHookStatus = hookSnapshot;
14099
14180
  if (!lastHooksRunning && hooksRunning) {
14100
14181
  eventService.publish({
14101
- id: randomUUID8(),
14182
+ id: randomUUID9(),
14102
14183
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14103
14184
  type: "hooks.started",
14104
14185
  payload: { running: true, completed: false }
@@ -14107,7 +14188,7 @@ function startStatusBroadcaster() {
14107
14188
  }
14108
14189
  if (hooksRunning) {
14109
14190
  eventService.publish({
14110
- id: randomUUID8(),
14191
+ id: randomUUID9(),
14111
14192
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14112
14193
  type: "hooks.progress",
14113
14194
  payload: { running: true, completed: false }
@@ -14116,7 +14197,7 @@ function startStatusBroadcaster() {
14116
14197
  }
14117
14198
  if (lastHooksRunning && !hooksRunning && hooksCompleted && !hooksFailed) {
14118
14199
  eventService.publish({
14119
- id: randomUUID8(),
14200
+ id: randomUUID9(),
14120
14201
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14121
14202
  type: "hooks.completed",
14122
14203
  payload: { running: false, completed: true }
@@ -14125,7 +14206,7 @@ function startStatusBroadcaster() {
14125
14206
  }
14126
14207
  if (lastHooksRunning && !hooksRunning && hooksFailed) {
14127
14208
  eventService.publish({
14128
- id: randomUUID8(),
14209
+ id: randomUUID9(),
14129
14210
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14130
14211
  type: "hooks.failed",
14131
14212
  payload: { running: false, completed: hooksCompleted }
@@ -14133,7 +14214,7 @@ function startStatusBroadcaster() {
14133
14214
  });
14134
14215
  }
14135
14216
  eventService.publish({
14136
- id: randomUUID8(),
14217
+ id: randomUUID9(),
14137
14218
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14138
14219
  type: "hooks.status",
14139
14220
  payload: {
@@ -14188,20 +14269,20 @@ serve(
14188
14269
  }
14189
14270
  const repos = await gitService.listRepos();
14190
14271
  await eventService.publish({
14191
- id: randomUUID8(),
14272
+ id: randomUUID9(),
14192
14273
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14193
14274
  type: "repo.discovered",
14194
14275
  payload: { repos }
14195
14276
  });
14196
14277
  const repoStatuses = await gitService.listRepos();
14197
14278
  await eventService.publish({
14198
- id: randomUUID8(),
14279
+ id: randomUUID9(),
14199
14280
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14200
14281
  type: "repo.status.changed",
14201
14282
  payload: { repos: repoStatuses }
14202
14283
  });
14203
14284
  await eventService.publish({
14204
- id: randomUUID8(),
14285
+ id: randomUUID9(),
14205
14286
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14206
14287
  type: "engine.ready",
14207
14288
  payload: { version: "v1" }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.655",
3
+ "version": "0.1.657",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",