replicas-engine 0.1.485 → 0.1.493

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 (2) hide show
  1. package/dist/src/index.js +210 -60
  2. package/package.json +1 -1
package/dist/src/index.js CHANGED
@@ -408,6 +408,25 @@ function classifyCanvasFilename(filename) {
408
408
  const ext = idx === -1 ? "" : filename.slice(idx).toLowerCase();
409
409
  return CANVAS_KIND_BY_EXTENSION[ext] ?? { kind: "other", mimeType: "application/octet-stream" };
410
410
  }
411
+ function isValidEngineLogSessionId(sessionId) {
412
+ return Boolean(sessionId) && !/[/\\]/.test(sessionId) && !sessionId.includes("..");
413
+ }
414
+ function paginateEngineLogContent(input) {
415
+ const parsedOffset = Number.parseInt(input.offset ?? "0", 10);
416
+ const parsedLimit = Number.parseInt(input.limit ?? "500", 10);
417
+ const offset = Number.isFinite(parsedOffset) && parsedOffset >= 0 ? parsedOffset : 0;
418
+ const limit = Number.isFinite(parsedLimit) && parsedLimit >= 0 ? Math.min(parsedLimit, 5e3) : 500;
419
+ const lines = input.content.split("\n");
420
+ if (lines.at(-1) === "") lines.pop();
421
+ return {
422
+ sessionId: input.sessionId,
423
+ totalLines: lines.length,
424
+ offset,
425
+ limit,
426
+ hasMore: offset + limit < lines.length,
427
+ lines: lines.slice(offset, offset + limit)
428
+ };
429
+ }
411
430
 
412
431
  // ../shared/src/context-usage.ts
413
432
  var CODEX_CATEGORY_COLORS = {
@@ -583,7 +602,7 @@ var WORKSPACE_SIZES = ["small", "large"];
583
602
  var INVALID_WORKSPACE_SIZE_ERROR = `Invalid size: must be one of ${WORKSPACE_SIZES.join(", ")}`;
584
603
 
585
604
  // ../shared/src/e2b.ts
586
- var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-07-23-v5";
605
+ var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-07-24-v1";
587
606
 
588
607
  // ../shared/src/runtime-env.ts
589
608
  function shellQuotePosix(value) {
@@ -2533,6 +2552,7 @@ var DEFAULT_USER_PREFERENCES = {
2533
2552
  respond_to_human_pr_reviews: false,
2534
2553
  pr_user_attribution: false,
2535
2554
  auto_draft_prs: false,
2555
+ open_prs_in_graphite: false,
2536
2556
  default_fast_mode: false,
2537
2557
  agent_defaults: { ...EMPTY_AGENT_DEFAULT_SETTINGS }
2538
2558
  };
@@ -4684,6 +4704,7 @@ var MEDIA_KIND = {
4684
4704
  AUDIO: "audio"
4685
4705
  };
4686
4706
  var MEDIA_KINDS = [MEDIA_KIND.IMAGE, MEDIA_KIND.VIDEO, MEDIA_KIND.AUDIO];
4707
+ var MAX_ENGINE_LOG_BYTES = 50 * 1024 * 1024;
4687
4708
 
4688
4709
  // ../shared/src/skill-registry.ts
4689
4710
  var SKILL_REGISTRY_MANIFEST_VERSION = 1;
@@ -6129,8 +6150,8 @@ var StreamWriter = class {
6129
6150
  }
6130
6151
  return true;
6131
6152
  }
6132
- flush() {
6133
- return new Promise((resolve4) => {
6153
+ flush(signal) {
6154
+ return new Promise((resolve4, reject) => {
6134
6155
  if (!this.stream) {
6135
6156
  resolve4();
6136
6157
  return;
@@ -6140,8 +6161,17 @@ var StreamWriter = class {
6140
6161
  this.flushTimer = null;
6141
6162
  }
6142
6163
  const s = this.stream;
6143
- this.stream = null;
6144
- s.end(() => resolve4());
6164
+ const onAbort = () => reject(signal?.reason);
6165
+ if (signal?.aborted) {
6166
+ reject(signal.reason);
6167
+ return;
6168
+ }
6169
+ signal?.addEventListener("abort", onAbort, { once: true });
6170
+ s.write("", (error) => {
6171
+ signal?.removeEventListener("abort", onAbort);
6172
+ if (error) reject(error);
6173
+ else resolve4();
6174
+ });
6145
6175
  });
6146
6176
  }
6147
6177
  scheduleDropWarning() {
@@ -6202,8 +6232,11 @@ var EngineLogger = class {
6202
6232
  this.writer.write(`[${(/* @__PURE__ */ new Date()).toISOString()}] [${level}] ${message}
6203
6233
  `);
6204
6234
  }
6205
- flush() {
6206
- return this.writer.flush();
6235
+ flush(signal) {
6236
+ return this.writer.flush(signal);
6237
+ }
6238
+ error(...args) {
6239
+ this.log("ERROR", format(...args));
6207
6240
  }
6208
6241
  createSessionId() {
6209
6242
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-");
@@ -7113,7 +7146,7 @@ async function registerDesktopPreview() {
7113
7146
  import { existsSync as existsSync7 } from "fs";
7114
7147
  import { appendFile as appendFile3, copyFile, mkdir as mkdir14, readFile as readFile14, rename as rename2, rm as rm2 } from "fs/promises";
7115
7148
  import { homedir as homedir14 } from "os";
7116
- import { join as join23 } from "path";
7149
+ import { join as join24 } from "path";
7117
7150
  import { randomUUID as randomUUID5 } from "crypto";
7118
7151
 
7119
7152
  // src/managers/claude-manager.ts
@@ -9851,7 +9884,7 @@ var DEFAULT_CODEX_ARGS = ["app-server", "--listen", "stdio://"];
9851
9884
  var MIN_CODEX_CLI_VERSION = "0.144.6";
9852
9885
  var CODEX_UPGRADE_TIMEOUT_MS = 12e4;
9853
9886
  var codexCliVersionEnsured = null;
9854
- var ENGINE_PACKAGE_VERSION = "0.1.485";
9887
+ var ENGINE_PACKAGE_VERSION = "0.1.493";
9855
9888
  var INITIALIZE_METHOD = "initialize";
9856
9889
  var INITIALIZED_NOTIFICATION = "initialized";
9857
9890
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -14255,9 +14288,136 @@ async function flushRepoState() {
14255
14288
  }
14256
14289
  }
14257
14290
 
14291
+ // src/services/upload-engine-logs.ts
14292
+ import { createReadStream } from "fs";
14293
+ import { readdir as readdir8, stat as stat4 } from "fs/promises";
14294
+ import { join as join23 } from "path";
14295
+ var MAX_ENGINE_LOG_FLUSH_SESSIONS = 10;
14296
+ var MAX_ENGINE_LOG_FLUSH_BYTES = 5 * 1024 * 1024;
14297
+ var ENGINE_LOG_FLUSH_TIMEOUT_MS = 2e4;
14298
+ async function flushAllEngineLogs() {
14299
+ let flushed = 0;
14300
+ let skipped = 0;
14301
+ let failed = 0;
14302
+ const deadline = Date.now() + ENGINE_LOG_FLUSH_TIMEOUT_MS;
14303
+ await runBeforeDeadline((signal) => engineLogger.flush(signal), deadline);
14304
+ const files = await runBeforeDeadline(() => readdir8(LOG_DIR), deadline).catch(() => []);
14305
+ const currentFilename = engineLogger.sessionId ? `${engineLogger.sessionId}.log` : null;
14306
+ const filenames = files.filter((filename) => {
14307
+ if (!filename.endsWith(".log")) return false;
14308
+ const sessionId = filename.slice(0, -".log".length);
14309
+ if (!isValidEngineLogSessionId(sessionId)) {
14310
+ skipped++;
14311
+ return false;
14312
+ }
14313
+ return true;
14314
+ }).sort((a, b) => {
14315
+ if (a === currentFilename) return -1;
14316
+ if (b === currentFilename) return 1;
14317
+ return b.localeCompare(a);
14318
+ });
14319
+ skipped += Math.max(0, filenames.length - MAX_ENGINE_LOG_FLUSH_SESSIONS);
14320
+ const candidates = (await Promise.all(filenames.slice(0, MAX_ENGINE_LOG_FLUSH_SESSIONS).map(async (filename) => {
14321
+ try {
14322
+ const sessionId = filename.slice(0, -".log".length);
14323
+ const filePath = join23(LOG_DIR, filename);
14324
+ const fileStat = await runBeforeDeadline(() => stat4(filePath), deadline);
14325
+ if (!fileStat.isFile()) {
14326
+ skipped++;
14327
+ return null;
14328
+ }
14329
+ return { sessionId, filename, filePath, fileStat };
14330
+ } catch (error) {
14331
+ failed++;
14332
+ engineLogger.error("[EngineLogUploader] upload failed:", { filename, error });
14333
+ return null;
14334
+ }
14335
+ }))).filter((candidate) => candidate !== null);
14336
+ const selected = [];
14337
+ let selectedBytes = 0;
14338
+ for (const candidate of candidates) {
14339
+ const length = Math.min(candidate.fileStat.size, MAX_ENGINE_LOG_FLUSH_BYTES);
14340
+ if (selected.length >= MAX_ENGINE_LOG_FLUSH_SESSIONS || selectedBytes + length > MAX_ENGINE_LOG_FLUSH_BYTES) {
14341
+ skipped++;
14342
+ continue;
14343
+ }
14344
+ selected.push({ ...candidate, offset: candidate.fileStat.size - length, length });
14345
+ selectedBytes += length;
14346
+ }
14347
+ for (const [index, candidate] of selected.entries()) {
14348
+ if (Date.now() >= deadline) {
14349
+ skipped += selected.length - index;
14350
+ break;
14351
+ }
14352
+ try {
14353
+ const content = await runBeforeDeadline(async (signal) => {
14354
+ if (candidate.length === 0) return Buffer.alloc(0);
14355
+ const chunks = [];
14356
+ for await (const chunk of createReadStream(candidate.filePath, {
14357
+ start: candidate.offset,
14358
+ end: candidate.offset + candidate.length - 1,
14359
+ signal
14360
+ })) {
14361
+ chunks.push(Buffer.from(chunk));
14362
+ }
14363
+ return Buffer.concat(chunks);
14364
+ }, deadline);
14365
+ const timeoutMs = deadline - Date.now();
14366
+ if (timeoutMs <= 0) {
14367
+ skipped += selected.length - index;
14368
+ break;
14369
+ }
14370
+ await uploadEngineLog({
14371
+ sessionId: candidate.sessionId,
14372
+ filename: candidate.filename,
14373
+ updatedAt: candidate.fileStat.mtime.toISOString(),
14374
+ content
14375
+ }, timeoutMs);
14376
+ flushed++;
14377
+ } catch (error) {
14378
+ failed++;
14379
+ engineLogger.error("[EngineLogUploader] upload failed:", { filename: candidate.filename, error });
14380
+ }
14381
+ }
14382
+ return { flushed, skipped, failed };
14383
+ }
14384
+ function runBeforeDeadline(operation, deadline) {
14385
+ const timeoutMs = deadline - Date.now();
14386
+ if (timeoutMs <= 0) return Promise.reject(new DOMException("engine log flush deadline exceeded", "TimeoutError"));
14387
+ const signal = AbortSignal.timeout(timeoutMs);
14388
+ return new Promise((resolve4, reject) => {
14389
+ const onAbort = () => reject(signal.reason);
14390
+ signal.addEventListener("abort", onAbort, { once: true });
14391
+ operation(signal).then(
14392
+ (value) => {
14393
+ signal.removeEventListener("abort", onAbort);
14394
+ resolve4(value);
14395
+ },
14396
+ (error) => {
14397
+ signal.removeEventListener("abort", onAbort);
14398
+ reject(error);
14399
+ }
14400
+ );
14401
+ });
14402
+ }
14403
+ async function uploadEngineLog(input, timeoutMs) {
14404
+ const form = new FormData();
14405
+ form.append("session_id", input.sessionId);
14406
+ form.append("filename", input.filename);
14407
+ form.append("updated_at", input.updatedAt);
14408
+ form.append("file", new Blob([input.content], { type: "text/plain" }), input.filename);
14409
+ const response = await monolithRequest("/v1/engine/logs", {
14410
+ body: form,
14411
+ signal: AbortSignal.timeout(timeoutMs)
14412
+ });
14413
+ if (!response.ok) {
14414
+ throw new Error(`upload failed: ${response.status} ${await response.text()}`);
14415
+ }
14416
+ }
14417
+
14258
14418
  // src/services/chat/chat-service.ts
14259
- var CODEX_AUTH_PATH2 = join23(homedir14(), ".codex", "auth.json");
14260
- var OPENCODE_AUTH_PATH3 = join23(homedir14(), ".local", "share", "opencode", "auth.json");
14419
+ var CODEX_AUTH_PATH2 = join24(homedir14(), ".codex", "auth.json");
14420
+ var OPENCODE_AUTH_PATH3 = join24(homedir14(), ".local", "share", "opencode", "auth.json");
14261
14421
  var CHATS_BACKUP_FILE = `${CHATS_FILE}.bak`;
14262
14422
  function isCodexAvailable() {
14263
14423
  return existsSync7(CODEX_AUTH_PATH2) || Boolean(ENGINE_ENV.OPENAI_API_KEY);
@@ -14719,7 +14879,7 @@ var ChatService = class {
14719
14879
  return descendants;
14720
14880
  }
14721
14881
  async deleteHistoryFile(persisted) {
14722
- await rm2(join23(HISTORY_DIR_BY_PROVIDER[persisted.provider], `${persisted.id}.jsonl`), { force: true });
14882
+ await rm2(join24(HISTORY_DIR_BY_PROVIDER[persisted.provider], `${persisted.id}.jsonl`), { force: true });
14723
14883
  await rm2(chatMessageSendersFilePath(persisted.id), { force: true });
14724
14884
  }
14725
14885
  async getChatHistory(chatId, page = {}) {
@@ -14762,12 +14922,13 @@ var ChatService = class {
14762
14922
  const chatsById = new Map(
14763
14923
  [...this.chats.entries()].map(([chatId, chat]) => [chatId, this.toSummary(chat)])
14764
14924
  );
14765
- const [chatTranscripts, canvas, repoState] = await Promise.all([
14925
+ const [chatTranscripts, canvas, repoState, engineLogs] = await Promise.all([
14766
14926
  flushAllChatTranscripts(chatsById),
14767
14927
  flushAllCanvasItems(),
14768
- flushRepoState()
14928
+ flushRepoState(),
14929
+ flushAllEngineLogs()
14769
14930
  ]);
14770
- return { chatTranscripts, canvas, repoState };
14931
+ return { chatTranscripts, canvas, repoState, engineLogs };
14771
14932
  }
14772
14933
  createRuntimeChat(persisted) {
14773
14934
  const saveSession = async (sessionId) => {
@@ -14794,7 +14955,7 @@ var ChatService = class {
14794
14955
  if (persisted.provider === "claude") {
14795
14956
  provider = new ClaudeManager({
14796
14957
  workingDirectory: this.workingDirectory,
14797
- historyFilePath: join23(CLAUDE_HISTORY_DIR, `${persisted.id}.jsonl`),
14958
+ historyFilePath: join24(CLAUDE_HISTORY_DIR, `${persisted.id}.jsonl`),
14798
14959
  initialSessionId: persisted.providerSessionId,
14799
14960
  onSaveSessionId: saveSession,
14800
14961
  onTurnComplete: onProviderTurnComplete,
@@ -14803,7 +14964,7 @@ var ChatService = class {
14803
14964
  } else if (persisted.provider === "relay") {
14804
14965
  provider = new RelayManager({
14805
14966
  workingDirectory: this.workingDirectory,
14806
- historyFilePath: join23(RELAY_HISTORY_DIR, `${persisted.id}.jsonl`),
14967
+ historyFilePath: join24(RELAY_HISTORY_DIR, `${persisted.id}.jsonl`),
14807
14968
  initialSessionId: persisted.providerSessionId,
14808
14969
  onSaveSessionId: saveSession,
14809
14970
  onTurnComplete: onProviderTurnComplete,
@@ -14817,7 +14978,7 @@ var ChatService = class {
14817
14978
  } else if (persisted.provider === "cursor") {
14818
14979
  provider = new CursorManager({
14819
14980
  workingDirectory: this.workingDirectory,
14820
- historyFilePath: join23(CURSOR_HISTORY_DIR, `${persisted.id}.jsonl`),
14981
+ historyFilePath: join24(CURSOR_HISTORY_DIR, `${persisted.id}.jsonl`),
14821
14982
  initialSessionId: persisted.providerSessionId,
14822
14983
  onSaveSessionId: saveSession,
14823
14984
  onTurnComplete: onProviderTurnComplete,
@@ -14826,7 +14987,7 @@ var ChatService = class {
14826
14987
  } else if (persisted.provider === "opencode") {
14827
14988
  provider = new OpencodeManager({
14828
14989
  workingDirectory: this.workingDirectory,
14829
- historyFilePath: join23(OPENCODE_HISTORY_DIR, `${persisted.id}.jsonl`),
14990
+ historyFilePath: join24(OPENCODE_HISTORY_DIR, `${persisted.id}.jsonl`),
14830
14991
  initialSessionId: persisted.providerSessionId,
14831
14992
  onSaveSessionId: saveSession,
14832
14993
  onTurnComplete: onProviderTurnComplete,
@@ -14835,7 +14996,7 @@ var ChatService = class {
14835
14996
  } else if (persisted.provider === "pi") {
14836
14997
  provider = new PiManager({
14837
14998
  workingDirectory: this.workingDirectory,
14838
- historyFilePath: join23(PI_HISTORY_DIR, `${persisted.id}.jsonl`),
14999
+ historyFilePath: join24(PI_HISTORY_DIR, `${persisted.id}.jsonl`),
14839
15000
  initialSessionId: persisted.providerSessionId,
14840
15001
  onSaveSessionId: saveSession,
14841
15002
  onTurnComplete: onProviderTurnComplete,
@@ -14844,7 +15005,7 @@ var ChatService = class {
14844
15005
  } else {
14845
15006
  provider = new CodexAspManager({
14846
15007
  workingDirectory: this.workingDirectory,
14847
- historyFilePath: join23(CODEX_HISTORY_DIR, `${persisted.id}.jsonl`),
15008
+ historyFilePath: join24(CODEX_HISTORY_DIR, `${persisted.id}.jsonl`),
14848
15009
  initialSessionId: persisted.providerSessionId,
14849
15010
  onSaveSessionId: saveSession,
14850
15011
  onTurnComplete: onProviderTurnComplete,
@@ -14993,7 +15154,7 @@ var ChatService = class {
14993
15154
  });
14994
15155
  uploadChatTranscript(
14995
15156
  chatId,
14996
- join23(HISTORY_DIR_BY_PROVIDER[chat.persisted.provider], `${chatId}.jsonl`),
15157
+ join24(HISTORY_DIR_BY_PROVIDER[chat.persisted.provider], `${chatId}.jsonl`),
14997
15158
  this.toSummary(chat)
14998
15159
  ).catch((err) => {
14999
15160
  console.error("[ChatService] Failed to upload chat transcript:", { chatId, err });
@@ -15128,8 +15289,8 @@ var ChatService = class {
15128
15289
 
15129
15290
  // src/services/repo-file-service.ts
15130
15291
  import { execFile as execFile2 } from "child_process";
15131
- import { readFile as readFile15, realpath, stat as stat4 } from "fs/promises";
15132
- import { join as join24, resolve as resolve2, extname as extname2 } from "path";
15292
+ import { readFile as readFile15, realpath, stat as stat5 } from "fs/promises";
15293
+ import { join as join25, resolve as resolve2, extname as extname2 } from "path";
15133
15294
  var CACHE_TTL_MS = 3e4;
15134
15295
  var SEARCH_TIMEOUT_MS = 15e3;
15135
15296
  var MAX_CONTENT_BYTES = 256 * 1024;
@@ -15289,11 +15450,11 @@ var RepoFileService = class {
15289
15450
  const repo = repos.find((r) => r.name === repoName);
15290
15451
  if (!repo) return null;
15291
15452
  try {
15292
- const fullPath = await realpath(resolve2(join24(repo.path, filePath)));
15453
+ const fullPath = await realpath(resolve2(join25(repo.path, filePath)));
15293
15454
  const repoRoot = await realpath(repo.path);
15294
15455
  const repoPrefix = repoRoot.endsWith("/") ? repoRoot : repoRoot + "/";
15295
15456
  if (!fullPath.startsWith(repoPrefix) && fullPath !== repoRoot) return null;
15296
- const fileStat = await stat4(fullPath);
15457
+ const fileStat = await stat5(fullPath);
15297
15458
  if (!fileStat.isFile()) return null;
15298
15459
  const sizeBytes = fileStat.size;
15299
15460
  if (isBinaryExtension(filePath)) {
@@ -15396,21 +15557,21 @@ var RepoFileService = class {
15396
15557
  // src/v1-routes.ts
15397
15558
  import { Hono } from "hono";
15398
15559
  import { z as z7 } from "zod";
15399
- import { readdir as readdir9, stat as stat5, readFile as readFile18 } from "fs/promises";
15400
- import { join as join27, resolve as resolve3 } from "path";
15560
+ import { readdir as readdir10, stat as stat6, readFile as readFile18 } from "fs/promises";
15561
+ import { join as join28, resolve as resolve3 } from "path";
15401
15562
 
15402
15563
  // src/services/warm-hooks-service.ts
15403
15564
  import { spawn as spawn4 } from "child_process";
15404
15565
  import { readFile as readFile17 } from "fs/promises";
15405
15566
  import { existsSync as existsSync8 } from "fs";
15406
- import { join as join26 } from "path";
15567
+ import { join as join27 } from "path";
15407
15568
 
15408
15569
  // src/services/warm-hook-logs-service.ts
15409
- import { mkdir as mkdir15, readFile as readFile16, writeFile as writeFile6, readdir as readdir8, appendFile as appendFile4, unlink as unlink3 } from "fs/promises";
15570
+ import { mkdir as mkdir15, readFile as readFile16, writeFile as writeFile6, readdir as readdir9, appendFile as appendFile4, unlink as unlink3 } from "fs/promises";
15410
15571
  import { homedir as homedir15 } from "os";
15411
- import { join as join25 } from "path";
15412
- var LOGS_DIR2 = join25(homedir15(), ".replicas", "warm-hook-logs");
15413
- var CURRENT_RUN_LOG = join25(LOGS_DIR2, "current-run.log");
15572
+ import { join as join26 } from "path";
15573
+ var LOGS_DIR2 = join26(homedir15(), ".replicas", "warm-hook-logs");
15574
+ var CURRENT_RUN_LOG = join26(LOGS_DIR2, "current-run.log");
15414
15575
  var GLOBAL_FILENAME = "global.json";
15415
15576
  function withPreview2(stored) {
15416
15577
  const preview = buildHookOutputPreview(stored.output);
@@ -15427,7 +15588,7 @@ var WarmHookLogsService = class {
15427
15588
  hookName: "organization",
15428
15589
  ...entry
15429
15590
  };
15430
- await writeFile6(join25(LOGS_DIR2, GLOBAL_FILENAME), `${JSON.stringify(log, null, 2)}
15591
+ await writeFile6(join26(LOGS_DIR2, GLOBAL_FILENAME), `${JSON.stringify(log, null, 2)}
15431
15592
  `, "utf-8");
15432
15593
  }
15433
15594
  async saveEnvironmentHookLog(entry) {
@@ -15437,7 +15598,7 @@ var WarmHookLogsService = class {
15437
15598
  hookName: "environment",
15438
15599
  ...entry
15439
15600
  };
15440
- await writeFile6(join25(LOGS_DIR2, ENVIRONMENT_HOOK_LOG_FILENAME), `${JSON.stringify(log, null, 2)}
15601
+ await writeFile6(join26(LOGS_DIR2, ENVIRONMENT_HOOK_LOG_FILENAME), `${JSON.stringify(log, null, 2)}
15441
15602
  `, "utf-8");
15442
15603
  }
15443
15604
  async saveRepoHookLog(repoName, entry) {
@@ -15447,13 +15608,13 @@ var WarmHookLogsService = class {
15447
15608
  hookName: repoName,
15448
15609
  ...entry
15449
15610
  };
15450
- await writeFile6(join25(LOGS_DIR2, repoHookLogFilename(repoName)), `${JSON.stringify(log, null, 2)}
15611
+ await writeFile6(join26(LOGS_DIR2, repoHookLogFilename(repoName)), `${JSON.stringify(log, null, 2)}
15451
15612
  `, "utf-8");
15452
15613
  }
15453
15614
  async getAllLogs() {
15454
15615
  let files;
15455
15616
  try {
15456
- files = await readdir8(LOGS_DIR2);
15617
+ files = await readdir9(LOGS_DIR2);
15457
15618
  } catch (err) {
15458
15619
  if (err.code === "ENOENT") {
15459
15620
  return [];
@@ -15466,7 +15627,7 @@ var WarmHookLogsService = class {
15466
15627
  continue;
15467
15628
  }
15468
15629
  try {
15469
- const raw = await readFile16(join25(LOGS_DIR2, file), "utf-8");
15630
+ const raw = await readFile16(join26(LOGS_DIR2, file), "utf-8");
15470
15631
  const stored = JSON.parse(raw);
15471
15632
  logs.push(withPreview2(stored));
15472
15633
  } catch {
@@ -15504,7 +15665,7 @@ var WarmHookLogsService = class {
15504
15665
  async getFullOutput(hookType, hookName) {
15505
15666
  const filename = hookType === "global" ? GLOBAL_FILENAME : hookType === "environment" ? ENVIRONMENT_HOOK_LOG_FILENAME : repoHookLogFilename(hookName);
15506
15667
  try {
15507
- const raw = await readFile16(join25(LOGS_DIR2, filename), "utf-8");
15668
+ const raw = await readFile16(join26(LOGS_DIR2, filename), "utf-8");
15508
15669
  const stored = JSON.parse(raw);
15509
15670
  if (stored.hookType !== hookType || stored.hookName !== hookName) {
15510
15671
  return null;
@@ -15523,7 +15684,7 @@ var warmHookLogsService = new WarmHookLogsService();
15523
15684
  // src/services/warm-hooks-service.ts
15524
15685
  async function readRepoWarmHook(repoPath) {
15525
15686
  for (const filename of REPLICAS_CONFIG_FILENAMES) {
15526
- const configPath = join26(repoPath, filename);
15687
+ const configPath = join27(repoPath, filename);
15527
15688
  if (!existsSync8(configPath)) {
15528
15689
  continue;
15529
15690
  }
@@ -16752,12 +16913,12 @@ data: ${JSON.stringify("Terminal session not found")}
16752
16913
  });
16753
16914
  app2.get("/logs", async (c) => {
16754
16915
  try {
16755
- const files = await readdir9(LOG_DIR).catch(() => []);
16916
+ const files = await readdir10(LOG_DIR).catch(() => []);
16756
16917
  const logFiles = files.filter((f) => f.endsWith(".log"));
16757
16918
  const sessions = await Promise.all(
16758
16919
  logFiles.map(async (filename) => {
16759
- const filePath = join27(LOG_DIR, filename);
16760
- const fileStat = await stat5(filePath);
16920
+ const filePath = join28(LOG_DIR, filename);
16921
+ const fileStat = await stat6(filePath);
16761
16922
  const sessionId = filename.replace(/\.log$/, "");
16762
16923
  return {
16763
16924
  sessionId,
@@ -16782,36 +16943,25 @@ data: ${JSON.stringify("Terminal session not found")}
16782
16943
  app2.get("/logs/:sessionId", async (c) => {
16783
16944
  try {
16784
16945
  const sessionId = c.req.param("sessionId");
16785
- if (!sessionId || /[/\\]/.test(sessionId) || sessionId.includes("..")) {
16946
+ if (!isValidEngineLogSessionId(sessionId)) {
16786
16947
  return c.json(jsonError("Invalid session ID"), 400);
16787
16948
  }
16788
16949
  const filePath = resolve3(LOG_DIR, `${sessionId}.log`);
16789
16950
  if (!filePath.startsWith(resolve3(LOG_DIR))) {
16790
16951
  return c.json(jsonError("Invalid session ID"), 400);
16791
16952
  }
16792
- const offset = parseInt(c.req.query("offset") || "0", 10);
16793
- const limit = Math.min(parseInt(c.req.query("limit") || "500", 10), 5e3);
16794
16953
  let content;
16795
16954
  try {
16796
16955
  content = await readFile18(filePath, "utf-8");
16797
16956
  } catch {
16798
16957
  return c.json(jsonError("Log session not found"), 404);
16799
16958
  }
16800
- const allLines = content.split("\n");
16801
- if (allLines.length > 0 && allLines[allLines.length - 1] === "") {
16802
- allLines.pop();
16803
- }
16804
- const totalLines = allLines.length;
16805
- const slicedLines = allLines.slice(offset, offset + limit);
16806
- const hasMore = offset + limit < totalLines;
16807
- return c.json({
16959
+ return c.json(paginateEngineLogContent({
16808
16960
  sessionId,
16809
- totalLines,
16810
- offset,
16811
- limit,
16812
- hasMore,
16813
- lines: slicedLines
16814
- });
16961
+ content,
16962
+ offset: c.req.query("offset"),
16963
+ limit: c.req.query("limit")
16964
+ }));
16815
16965
  } catch (error) {
16816
16966
  return c.json(
16817
16967
  jsonError("Failed to read log", error instanceof Error ? error.message : "Unknown error"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.485",
3
+ "version": "0.1.493",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",