replicas-engine 0.1.61 → 0.1.62

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/dist/src/index.js CHANGED
@@ -1,10 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import "./chunk-ZXMDA7VB.js";
3
2
 
4
3
  // src/index.ts
5
4
  import { serve } from "@hono/node-server";
6
5
  import { Hono as Hono2 } from "hono";
7
- import { readFile as readFile10 } from "fs/promises";
6
+ import { readFile as readFile12 } from "fs/promises";
8
7
  import { execSync } from "child_process";
9
8
  import { randomUUID as randomUUID5 } from "crypto";
10
9
 
@@ -854,9 +853,9 @@ var EngineLogger = class {
854
853
  var engineLogger = new EngineLogger();
855
854
 
856
855
  // src/services/replicas-config-service.ts
857
- import { readFile as readFile3, appendFile as appendFile2, writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
856
+ import { readFile as readFile4, appendFile as appendFile2, writeFile as writeFile5, mkdir as mkdir5 } from "fs/promises";
858
857
  import { existsSync as existsSync4 } from "fs";
859
- import { join as join6 } from "path";
858
+ import { join as join7 } from "path";
860
859
  import { homedir as homedir5 } from "os";
861
860
  import { exec } from "child_process";
862
861
  import { promisify as promisify2 } from "util";
@@ -1203,9 +1202,57 @@ var EnvironmentDetailsService = class {
1203
1202
  };
1204
1203
  var environmentDetailsService = new EnvironmentDetailsService();
1205
1204
 
1205
+ // src/services/start-hook-logs-service.ts
1206
+ import { createHash } from "crypto";
1207
+ import { mkdir as mkdir4, readFile as readFile3, writeFile as writeFile4, readdir as readdir2 } from "fs/promises";
1208
+ import { join as join6 } from "path";
1209
+ var LOGS_DIR = join6(SANDBOX_PATHS.REPLICAS_DIR, "start-hook-logs");
1210
+ function sanitizeFilename(name) {
1211
+ const safe = name.replace(/[^a-zA-Z0-9._-]/g, "_");
1212
+ const hash = createHash("sha256").update(name).digest("hex").slice(0, 8);
1213
+ return `${safe}-${hash}`;
1214
+ }
1215
+ var StartHookLogsService = class {
1216
+ async ensureDir() {
1217
+ await mkdir4(LOGS_DIR, { recursive: true });
1218
+ }
1219
+ async saveRepoLog(repoName, entry) {
1220
+ await this.ensureDir();
1221
+ const log = { repoName, ...entry };
1222
+ const filename = `repo-${sanitizeFilename(repoName)}.json`;
1223
+ await writeFile4(join6(LOGS_DIR, filename), `${JSON.stringify(log, null, 2)}
1224
+ `, "utf-8");
1225
+ }
1226
+ async getAllLogs() {
1227
+ let files;
1228
+ try {
1229
+ files = await readdir2(LOGS_DIR);
1230
+ } catch (err) {
1231
+ if (err.code === "ENOENT") {
1232
+ return [];
1233
+ }
1234
+ throw err;
1235
+ }
1236
+ const logs = [];
1237
+ for (const file of files) {
1238
+ if (!file.endsWith(".json")) {
1239
+ continue;
1240
+ }
1241
+ try {
1242
+ const raw = await readFile3(join6(LOGS_DIR, file), "utf-8");
1243
+ logs.push(JSON.parse(raw));
1244
+ } catch {
1245
+ }
1246
+ }
1247
+ logs.sort((a, b) => a.repoName.localeCompare(b.repoName));
1248
+ return logs;
1249
+ }
1250
+ };
1251
+ var startHookLogsService = new StartHookLogsService();
1252
+
1206
1253
  // src/services/replicas-config-service.ts
1207
1254
  var execAsync = promisify2(exec);
1208
- var START_HOOKS_LOG = join6(homedir5(), ".replicas", "startHooks.log");
1255
+ var START_HOOKS_LOG = join7(homedir5(), ".replicas", "startHooks.log");
1209
1256
  var START_HOOKS_RUNNING_PROMPT = `IMPORTANT - Start Hooks Running:
1210
1257
  Start hooks are shell commands/scripts set by repository owners that run on workspace startup.
1211
1258
  These hooks are currently executing in the background. You can:
@@ -1216,11 +1263,11 @@ The start hooks may install dependencies, build projects, or perform other setup
1216
1263
  If your task depends on setup being complete, check the log file before proceeding.`;
1217
1264
  async function readReplicasConfigFromDir(dirPath) {
1218
1265
  for (const filename of REPLICAS_CONFIG_FILENAMES) {
1219
- const configPath = join6(dirPath, filename);
1266
+ const configPath = join7(dirPath, filename);
1220
1267
  if (!existsSync4(configPath)) {
1221
1268
  continue;
1222
1269
  }
1223
- const data = await readFile3(configPath, "utf-8");
1270
+ const data = await readFile4(configPath, "utf-8");
1224
1271
  const config = parseReplicasConfigString(data, filename);
1225
1272
  return { config, filename };
1226
1273
  }
@@ -1280,7 +1327,7 @@ var ReplicasConfigService = class {
1280
1327
  const logLine = `[${timestamp}] ${message}
1281
1328
  `;
1282
1329
  try {
1283
- await mkdir4(join6(homedir5(), ".replicas"), { recursive: true });
1330
+ await mkdir5(join7(homedir5(), ".replicas"), { recursive: true });
1284
1331
  await appendFile2(START_HOOKS_LOG, logLine, "utf-8");
1285
1332
  } catch (error) {
1286
1333
  console.error("Failed to write to start hooks log:", error);
@@ -1304,8 +1351,8 @@ var ReplicasConfigService = class {
1304
1351
  this.hooksRunning = true;
1305
1352
  this.hooksCompleted = false;
1306
1353
  try {
1307
- await mkdir4(join6(homedir5(), ".replicas"), { recursive: true });
1308
- await writeFile4(
1354
+ await mkdir5(join7(homedir5(), ".replicas"), { recursive: true });
1355
+ await writeFile5(
1309
1356
  START_HOOKS_LOG,
1310
1357
  `=== Start Hooks Execution Log ===
1311
1358
  Started: ${(/* @__PURE__ */ new Date()).toISOString()}
@@ -1334,6 +1381,9 @@ Repositories: ${hookEntries.length}
1334
1381
  }
1335
1382
  const timeout = startHookConfig.timeout ?? 3e5;
1336
1383
  let repoFailed = false;
1384
+ let lastExitCode = 0;
1385
+ let repoTimedOut = false;
1386
+ const repoOutput = [];
1337
1387
  await this.logToFile(`[${entry.repoName}] Executing ${startHookConfig.commands.length} hook(s) with timeout ${timeout}ms`);
1338
1388
  for (const hook of startHookConfig.commands) {
1339
1389
  try {
@@ -1344,20 +1394,35 @@ Repositories: ${hookEntries.length}
1344
1394
  env: process.env
1345
1395
  });
1346
1396
  if (stdout) {
1397
+ repoOutput.push(stdout);
1347
1398
  await this.logToFile(`[${entry.repoName}] [stdout] ${stdout}`);
1348
1399
  }
1349
1400
  if (stderr) {
1401
+ repoOutput.push(stderr);
1350
1402
  await this.logToFile(`[${entry.repoName}] [stderr] ${stderr}`);
1351
1403
  }
1352
1404
  await this.logToFile(`[${entry.repoName}] --- Completed: ${hook} ---`);
1353
1405
  } catch (error) {
1354
1406
  const errorMessage = error instanceof Error ? error.message : "Unknown error";
1407
+ const execError = error;
1408
+ lastExitCode = execError.code ?? 1;
1409
+ if (execError.killed) {
1410
+ repoTimedOut = true;
1411
+ }
1355
1412
  this.hooksFailed = true;
1356
1413
  repoFailed = true;
1414
+ repoOutput.push(`[ERROR] ${hook} failed: ${errorMessage}`);
1357
1415
  await this.logToFile(`[${entry.repoName}] [ERROR] ${hook} failed: ${errorMessage}`);
1358
1416
  await environmentDetailsService.setRepositoryStartHook(entry.repoName, "no");
1359
1417
  }
1360
1418
  }
1419
+ await startHookLogsService.saveRepoLog(entry.repoName, {
1420
+ hookCommands: startHookConfig.commands,
1421
+ output: repoOutput.join("\n"),
1422
+ exitCode: lastExitCode,
1423
+ timedOut: repoTimedOut,
1424
+ executedAt: (/* @__PURE__ */ new Date()).toISOString()
1425
+ });
1361
1426
  const fallbackRepoState = persistedRepoState ?? {
1362
1427
  name: entry.repoName,
1363
1428
  path: entry.workingDirectory,
@@ -1426,17 +1491,17 @@ Repositories: ${hookEntries.length}
1426
1491
  var replicasConfigService = new ReplicasConfigService();
1427
1492
 
1428
1493
  // src/services/event-service.ts
1429
- import { appendFile as appendFile3, mkdir as mkdir5 } from "fs/promises";
1494
+ import { appendFile as appendFile3, mkdir as mkdir6 } from "fs/promises";
1430
1495
  import { homedir as homedir6 } from "os";
1431
- import { join as join7 } from "path";
1496
+ import { join as join8 } from "path";
1432
1497
  import { randomUUID } from "crypto";
1433
- var ENGINE_DIR = join7(homedir6(), ".replicas", "engine");
1434
- var EVENTS_FILE = join7(ENGINE_DIR, "events.jsonl");
1498
+ var ENGINE_DIR = join8(homedir6(), ".replicas", "engine");
1499
+ var EVENTS_FILE = join8(ENGINE_DIR, "events.jsonl");
1435
1500
  var EventService = class {
1436
1501
  subscribers = /* @__PURE__ */ new Map();
1437
1502
  writeChain = Promise.resolve();
1438
1503
  async initialize() {
1439
- await mkdir5(ENGINE_DIR, { recursive: true });
1504
+ await mkdir6(ENGINE_DIR, { recursive: true });
1440
1505
  }
1441
1506
  subscribe(subscriber) {
1442
1507
  const id = randomUUID();
@@ -1466,7 +1531,7 @@ var EventService = class {
1466
1531
  var eventService = new EventService();
1467
1532
 
1468
1533
  // src/services/preview-service.ts
1469
- import { mkdir as mkdir6, readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
1534
+ import { mkdir as mkdir7, readFile as readFile5, writeFile as writeFile6 } from "fs/promises";
1470
1535
  import { existsSync as existsSync5 } from "fs";
1471
1536
  import { randomUUID as randomUUID2 } from "crypto";
1472
1537
  import { dirname } from "path";
@@ -1476,7 +1541,7 @@ async function readPreviewsFile() {
1476
1541
  if (!existsSync5(PREVIEW_PORTS_FILE)) {
1477
1542
  return { previews: [] };
1478
1543
  }
1479
- const raw = await readFile4(PREVIEW_PORTS_FILE, "utf-8");
1544
+ const raw = await readFile5(PREVIEW_PORTS_FILE, "utf-8");
1480
1545
  return JSON.parse(raw);
1481
1546
  } catch {
1482
1547
  return { previews: [] };
@@ -1484,8 +1549,8 @@ async function readPreviewsFile() {
1484
1549
  }
1485
1550
  async function writePreviewsFile(data) {
1486
1551
  const dir = dirname(PREVIEW_PORTS_FILE);
1487
- await mkdir6(dir, { recursive: true });
1488
- await writeFile5(PREVIEW_PORTS_FILE, `${JSON.stringify(data, null, 2)}
1552
+ await mkdir7(dir, { recursive: true });
1553
+ await writeFile6(PREVIEW_PORTS_FILE, `${JSON.stringify(data, null, 2)}
1489
1554
  `, "utf-8");
1490
1555
  }
1491
1556
  var PreviewService = class {
@@ -1521,21 +1586,21 @@ var PreviewService = class {
1521
1586
  var previewService = new PreviewService();
1522
1587
 
1523
1588
  // src/services/chat/chat-service.ts
1524
- import { mkdir as mkdir9, readFile as readFile7, rm, writeFile as writeFile7 } from "fs/promises";
1589
+ import { mkdir as mkdir10, readFile as readFile8, rm, writeFile as writeFile8 } from "fs/promises";
1525
1590
  import { homedir as homedir9 } from "os";
1526
- import { join as join10 } from "path";
1591
+ import { join as join11 } from "path";
1527
1592
  import { randomUUID as randomUUID4 } from "crypto";
1528
1593
 
1529
1594
  // src/managers/claude-manager.ts
1530
1595
  import {
1531
1596
  query
1532
1597
  } from "@anthropic-ai/claude-agent-sdk";
1533
- import { join as join8 } from "path";
1534
- import { mkdir as mkdir7, appendFile as appendFile4 } from "fs/promises";
1598
+ import { join as join9 } from "path";
1599
+ import { mkdir as mkdir8, appendFile as appendFile4 } from "fs/promises";
1535
1600
  import { homedir as homedir7 } from "os";
1536
1601
 
1537
1602
  // src/utils/jsonl-reader.ts
1538
- import { readFile as readFile5 } from "fs/promises";
1603
+ import { readFile as readFile6 } from "fs/promises";
1539
1604
  function isJsonlEvent(value) {
1540
1605
  if (!isRecord(value)) {
1541
1606
  return false;
@@ -1557,7 +1622,7 @@ function parseJsonlEvents(lines) {
1557
1622
  }
1558
1623
  async function readJSONL(filePath) {
1559
1624
  try {
1560
- const content = await readFile5(filePath, "utf-8");
1625
+ const content = await readFile6(filePath, "utf-8");
1561
1626
  const lines = content.split("\n").filter((line) => line.trim());
1562
1627
  return parseJsonlEvents(lines);
1563
1628
  } catch (error) {
@@ -2294,7 +2359,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
2294
2359
  pendingInterrupt = false;
2295
2360
  constructor(options) {
2296
2361
  super(options);
2297
- this.historyFile = options.historyFilePath ?? join8(homedir7(), ".replicas", "claude", "history.jsonl");
2362
+ this.historyFile = options.historyFilePath ?? join9(homedir7(), ".replicas", "claude", "history.jsonl");
2298
2363
  this.initializeManager(this.processMessageInternal.bind(this));
2299
2364
  }
2300
2365
  async interruptActiveTurn() {
@@ -2463,8 +2528,8 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
2463
2528
  };
2464
2529
  }
2465
2530
  async initialize() {
2466
- const historyDir = join8(homedir7(), ".replicas", "claude");
2467
- await mkdir7(historyDir, { recursive: true });
2531
+ const historyDir = join9(homedir7(), ".replicas", "claude");
2532
+ await mkdir8(historyDir, { recursive: true });
2468
2533
  if (this.initialSessionId) {
2469
2534
  this.sessionId = this.initialSessionId;
2470
2535
  console.log(`[ClaudeManager] Restored session ID from persisted state: ${this.sessionId}`);
@@ -2515,13 +2580,13 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
2515
2580
  // src/managers/codex-manager.ts
2516
2581
  import { Codex } from "@openai/codex-sdk";
2517
2582
  import { randomUUID as randomUUID3 } from "crypto";
2518
- import { readdir as readdir2, stat as stat2, writeFile as writeFile6, mkdir as mkdir8, readFile as readFile6 } from "fs/promises";
2583
+ import { readdir as readdir3, stat as stat2, writeFile as writeFile7, mkdir as mkdir9, readFile as readFile7 } from "fs/promises";
2519
2584
  import { existsSync as existsSync6 } from "fs";
2520
- import { join as join9 } from "path";
2585
+ import { join as join10 } from "path";
2521
2586
  import { homedir as homedir8 } from "os";
2522
2587
  import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
2523
2588
  var DEFAULT_MODEL = "gpt-5.4";
2524
- var CODEX_CONFIG_PATH = join9(homedir8(), ".codex", "config.toml");
2589
+ var CODEX_CONFIG_PATH = join10(homedir8(), ".codex", "config.toml");
2525
2590
  function isLinearThoughtEvent2(event) {
2526
2591
  return event.content.type === "thought";
2527
2592
  }
@@ -2543,7 +2608,7 @@ var CodexManager = class extends CodingAgentManager {
2543
2608
  constructor(options) {
2544
2609
  super(options);
2545
2610
  this.codex = new Codex();
2546
- this.tempImageDir = join9(homedir8(), ".replicas", "codex", "temp-images");
2611
+ this.tempImageDir = join10(homedir8(), ".replicas", "codex", "temp-images");
2547
2612
  this.initializeManager(this.processMessageInternal.bind(this));
2548
2613
  }
2549
2614
  async initialize() {
@@ -2563,12 +2628,12 @@ var CodexManager = class extends CodingAgentManager {
2563
2628
  */
2564
2629
  async updateCodexConfig(developerInstructions) {
2565
2630
  try {
2566
- const codexDir = join9(homedir8(), ".codex");
2567
- await mkdir8(codexDir, { recursive: true });
2631
+ const codexDir = join10(homedir8(), ".codex");
2632
+ await mkdir9(codexDir, { recursive: true });
2568
2633
  let config = {};
2569
2634
  if (existsSync6(CODEX_CONFIG_PATH)) {
2570
2635
  try {
2571
- const existingContent = await readFile6(CODEX_CONFIG_PATH, "utf-8");
2636
+ const existingContent = await readFile7(CODEX_CONFIG_PATH, "utf-8");
2572
2637
  const parsed = parseToml(existingContent);
2573
2638
  if (isRecord(parsed)) {
2574
2639
  config = parsed;
@@ -2583,7 +2648,7 @@ var CodexManager = class extends CodingAgentManager {
2583
2648
  delete config.developer_instructions;
2584
2649
  }
2585
2650
  const tomlContent = stringifyToml(config);
2586
- await writeFile6(CODEX_CONFIG_PATH, tomlContent, "utf-8");
2651
+ await writeFile7(CODEX_CONFIG_PATH, tomlContent, "utf-8");
2587
2652
  console.log("[CodexManager] Updated config.toml with developer_instructions");
2588
2653
  } catch (error) {
2589
2654
  console.error("[CodexManager] Failed to update config.toml:", error);
@@ -2594,14 +2659,14 @@ var CodexManager = class extends CodingAgentManager {
2594
2659
  * @returns Array of temp file paths
2595
2660
  */
2596
2661
  async saveImagesToTempFiles(images) {
2597
- await mkdir8(this.tempImageDir, { recursive: true });
2662
+ await mkdir9(this.tempImageDir, { recursive: true });
2598
2663
  const tempPaths = [];
2599
2664
  for (const image of images) {
2600
2665
  const ext = image.source.media_type.split("/")[1] || "png";
2601
2666
  const filename = `img_${randomUUID3()}.${ext}`;
2602
- const filepath = join9(this.tempImageDir, filename);
2667
+ const filepath = join10(this.tempImageDir, filename);
2603
2668
  const buffer = Buffer.from(image.source.data, "base64");
2604
- await writeFile6(filepath, buffer);
2669
+ await writeFile7(filepath, buffer);
2605
2670
  tempPaths.push(filepath);
2606
2671
  }
2607
2672
  return tempPaths;
@@ -2731,13 +2796,13 @@ var CodexManager = class extends CodingAgentManager {
2731
2796
  }
2732
2797
  // Helper methods for finding session files
2733
2798
  async findSessionFile(threadId) {
2734
- const sessionsDir = join9(homedir8(), ".codex", "sessions");
2799
+ const sessionsDir = join10(homedir8(), ".codex", "sessions");
2735
2800
  try {
2736
2801
  const now = /* @__PURE__ */ new Date();
2737
2802
  const year = now.getFullYear();
2738
2803
  const month = String(now.getMonth() + 1).padStart(2, "0");
2739
2804
  const day = String(now.getDate()).padStart(2, "0");
2740
- const todayDir = join9(sessionsDir, String(year), month, day);
2805
+ const todayDir = join10(sessionsDir, String(year), month, day);
2741
2806
  const file = await this.findFileInDirectory(todayDir, threadId);
2742
2807
  if (file) return file;
2743
2808
  for (let daysAgo = 1; daysAgo <= 7; daysAgo++) {
@@ -2746,7 +2811,7 @@ var CodexManager = class extends CodingAgentManager {
2746
2811
  const searchYear = date.getFullYear();
2747
2812
  const searchMonth = String(date.getMonth() + 1).padStart(2, "0");
2748
2813
  const searchDay = String(date.getDate()).padStart(2, "0");
2749
- const searchDir = join9(sessionsDir, String(searchYear), searchMonth, searchDay);
2814
+ const searchDir = join10(sessionsDir, String(searchYear), searchMonth, searchDay);
2750
2815
  const file2 = await this.findFileInDirectory(searchDir, threadId);
2751
2816
  if (file2) return file2;
2752
2817
  }
@@ -2757,10 +2822,10 @@ var CodexManager = class extends CodingAgentManager {
2757
2822
  }
2758
2823
  async findFileInDirectory(directory, threadId) {
2759
2824
  try {
2760
- const files = await readdir2(directory);
2825
+ const files = await readdir3(directory);
2761
2826
  for (const file of files) {
2762
2827
  if (file.endsWith(".jsonl") && file.includes(threadId)) {
2763
- const fullPath = join9(directory, file);
2828
+ const fullPath = join10(directory, file);
2764
2829
  const stats = await stat2(fullPath);
2765
2830
  if (stats.isFile()) {
2766
2831
  return fullPath;
@@ -2793,7 +2858,7 @@ var CodexManager = class extends CodingAgentManager {
2793
2858
  const seenLines = /* @__PURE__ */ new Set();
2794
2859
  const seedSeenLines = async () => {
2795
2860
  try {
2796
- const content = await readFile6(sessionFile, "utf-8");
2861
+ const content = await readFile7(sessionFile, "utf-8");
2797
2862
  const lines = content.split("\n").map((line) => line.trim()).filter(Boolean);
2798
2863
  for (const line of lines) {
2799
2864
  seenLines.add(line);
@@ -2805,7 +2870,7 @@ var CodexManager = class extends CodingAgentManager {
2805
2870
  const pump = async () => {
2806
2871
  let emitted = 0;
2807
2872
  try {
2808
- const content = await readFile6(sessionFile, "utf-8");
2873
+ const content = await readFile7(sessionFile, "utf-8");
2809
2874
  const lines = content.split("\n");
2810
2875
  const completeLines = content.endsWith("\n") ? lines : lines.slice(0, -1);
2811
2876
  for (const line of completeLines) {
@@ -2876,9 +2941,9 @@ var DuplicateDefaultChatError = class extends Error {
2876
2941
  };
2877
2942
 
2878
2943
  // src/services/chat/chat-service.ts
2879
- var ENGINE_DIR2 = join10(homedir9(), ".replicas", "engine");
2880
- var CHATS_FILE = join10(ENGINE_DIR2, "chats.json");
2881
- var CLAUDE_HISTORY_DIR = join10(ENGINE_DIR2, "claude-histories");
2944
+ var ENGINE_DIR2 = join11(homedir9(), ".replicas", "engine");
2945
+ var CHATS_FILE = join11(ENGINE_DIR2, "chats.json");
2946
+ var CLAUDE_HISTORY_DIR = join11(ENGINE_DIR2, "claude-histories");
2882
2947
  function isPersistedChat(value) {
2883
2948
  if (!isRecord(value)) {
2884
2949
  return false;
@@ -2893,8 +2958,8 @@ var ChatService = class {
2893
2958
  chats = /* @__PURE__ */ new Map();
2894
2959
  writeChain = Promise.resolve();
2895
2960
  async initialize() {
2896
- await mkdir9(ENGINE_DIR2, { recursive: true });
2897
- await mkdir9(CLAUDE_HISTORY_DIR, { recursive: true });
2961
+ await mkdir10(ENGINE_DIR2, { recursive: true });
2962
+ await mkdir10(CLAUDE_HISTORY_DIR, { recursive: true });
2898
2963
  const persisted = await this.loadChats();
2899
2964
  for (const chat of persisted) {
2900
2965
  const runtime = this.createRuntimeChat(chat);
@@ -2990,7 +3055,7 @@ var ChatService = class {
2990
3055
  this.chats.delete(chatId);
2991
3056
  await this.persistAllChats();
2992
3057
  if (chat.persisted.provider === "claude") {
2993
- const historyFilePath = join10(CLAUDE_HISTORY_DIR, `${chatId}.jsonl`);
3058
+ const historyFilePath = join11(CLAUDE_HISTORY_DIR, `${chatId}.jsonl`);
2994
3059
  await rm(historyFilePath, { force: true });
2995
3060
  }
2996
3061
  await this.publish({
@@ -3033,7 +3098,7 @@ var ChatService = class {
3033
3098
  };
3034
3099
  const provider = persisted.provider === "claude" ? new ClaudeManager({
3035
3100
  workingDirectory: this.workingDirectory,
3036
- historyFilePath: join10(CLAUDE_HISTORY_DIR, `${persisted.id}.jsonl`),
3101
+ historyFilePath: join11(CLAUDE_HISTORY_DIR, `${persisted.id}.jsonl`),
3037
3102
  initialSessionId: persisted.providerSessionId,
3038
3103
  onSaveSessionId: saveSession,
3039
3104
  onTurnComplete: onProviderTurnComplete,
@@ -3129,7 +3194,7 @@ var ChatService = class {
3129
3194
  }
3130
3195
  async loadChats() {
3131
3196
  try {
3132
- const content = await readFile7(CHATS_FILE, "utf-8");
3197
+ const content = await readFile8(CHATS_FILE, "utf-8");
3133
3198
  const parsed = JSON.parse(content);
3134
3199
  if (!Array.isArray(parsed)) {
3135
3200
  return [];
@@ -3146,7 +3211,7 @@ var ChatService = class {
3146
3211
  this.writeChain = this.writeChain.catch(() => {
3147
3212
  }).then(async () => {
3148
3213
  const payload = Array.from(this.chats.values()).map((chat) => chat.persisted);
3149
- await writeFile7(CHATS_FILE, JSON.stringify(payload, null, 2), "utf-8");
3214
+ await writeFile8(CHATS_FILE, JSON.stringify(payload, null, 2), "utf-8");
3150
3215
  });
3151
3216
  await this.writeChain;
3152
3217
  }
@@ -3176,12 +3241,12 @@ import { Hono } from "hono";
3176
3241
  import { z } from "zod";
3177
3242
 
3178
3243
  // src/services/plan-service.ts
3179
- import { readdir as readdir3, readFile as readFile8 } from "fs/promises";
3244
+ import { readdir as readdir4, readFile as readFile9 } from "fs/promises";
3180
3245
  import { homedir as homedir10 } from "os";
3181
- import { basename, join as join11 } from "path";
3246
+ import { basename, join as join12 } from "path";
3182
3247
  var PLAN_DIRECTORIES = [
3183
- join11(homedir10(), ".claude", "plans"),
3184
- join11(homedir10(), ".replicas", "plans")
3248
+ join12(homedir10(), ".claude", "plans"),
3249
+ join12(homedir10(), ".replicas", "plans")
3185
3250
  ];
3186
3251
  function isMarkdownFile(filename) {
3187
3252
  return filename.toLowerCase().endsWith(".md");
@@ -3194,7 +3259,7 @@ var PlanService = class {
3194
3259
  const planNames = /* @__PURE__ */ new Set();
3195
3260
  for (const directory of PLAN_DIRECTORIES) {
3196
3261
  try {
3197
- const entries = await readdir3(directory, { withFileTypes: true });
3262
+ const entries = await readdir4(directory, { withFileTypes: true });
3198
3263
  for (const entry of entries) {
3199
3264
  if (!entry.isFile()) {
3200
3265
  continue;
@@ -3215,9 +3280,9 @@ var PlanService = class {
3215
3280
  return null;
3216
3281
  }
3217
3282
  for (const directory of PLAN_DIRECTORIES) {
3218
- const filePath = join11(directory, safeFilename);
3283
+ const filePath = join12(directory, safeFilename);
3219
3284
  try {
3220
- const content = await readFile8(filePath, "utf-8");
3285
+ const content = await readFile9(filePath, "utf-8");
3221
3286
  return { filename: safeFilename, content };
3222
3287
  } catch {
3223
3288
  }
@@ -3230,9 +3295,78 @@ var planService = new PlanService();
3230
3295
  // src/services/warm-hooks-service.ts
3231
3296
  import { execFile as execFile2 } from "child_process";
3232
3297
  import { promisify as promisify3 } from "util";
3233
- import { readFile as readFile9 } from "fs/promises";
3298
+ import { readFile as readFile11 } from "fs/promises";
3234
3299
  import { existsSync as existsSync7 } from "fs";
3235
- import { join as join12 } from "path";
3300
+ import { join as join14 } from "path";
3301
+
3302
+ // src/services/warm-hook-logs-service.ts
3303
+ import { createHash as createHash2 } from "crypto";
3304
+ import { mkdir as mkdir11, readFile as readFile10, writeFile as writeFile9, readdir as readdir5 } from "fs/promises";
3305
+ import { join as join13 } from "path";
3306
+ var LOGS_DIR2 = join13(SANDBOX_PATHS.REPLICAS_DIR, "warm-hook-logs");
3307
+ function sanitizeFilename2(name) {
3308
+ const safe = name.replace(/[^a-zA-Z0-9._-]/g, "_");
3309
+ const hash = createHash2("sha256").update(name).digest("hex").slice(0, 8);
3310
+ return `${safe}-${hash}`;
3311
+ }
3312
+ var WarmHookLogsService = class {
3313
+ async ensureDir() {
3314
+ await mkdir11(LOGS_DIR2, { recursive: true });
3315
+ }
3316
+ async saveGlobalHookLog(entry) {
3317
+ await this.ensureDir();
3318
+ const log = {
3319
+ hookType: "global",
3320
+ hookName: "organization",
3321
+ ...entry
3322
+ };
3323
+ await writeFile9(join13(LOGS_DIR2, "global.json"), `${JSON.stringify(log, null, 2)}
3324
+ `, "utf-8");
3325
+ }
3326
+ async saveRepoHookLog(repoName, entry) {
3327
+ await this.ensureDir();
3328
+ const log = {
3329
+ hookType: "repository",
3330
+ hookName: repoName,
3331
+ ...entry
3332
+ };
3333
+ const filename = `repo-${sanitizeFilename2(repoName)}.json`;
3334
+ await writeFile9(join13(LOGS_DIR2, filename), `${JSON.stringify(log, null, 2)}
3335
+ `, "utf-8");
3336
+ }
3337
+ async getAllLogs() {
3338
+ let files;
3339
+ try {
3340
+ files = await readdir5(LOGS_DIR2);
3341
+ } catch (err) {
3342
+ if (err.code === "ENOENT") {
3343
+ return [];
3344
+ }
3345
+ throw err;
3346
+ }
3347
+ const logs = [];
3348
+ for (const file of files) {
3349
+ if (!file.endsWith(".json")) {
3350
+ continue;
3351
+ }
3352
+ try {
3353
+ const raw = await readFile10(join13(LOGS_DIR2, file), "utf-8");
3354
+ logs.push(JSON.parse(raw));
3355
+ } catch {
3356
+ }
3357
+ }
3358
+ logs.sort((a, b) => {
3359
+ if (a.hookType !== b.hookType) {
3360
+ return a.hookType === "global" ? -1 : 1;
3361
+ }
3362
+ return a.hookName.localeCompare(b.hookName);
3363
+ });
3364
+ return logs;
3365
+ }
3366
+ };
3367
+ var warmHookLogsService = new WarmHookLogsService();
3368
+
3369
+ // src/services/warm-hooks-service.ts
3236
3370
  var execFileAsync2 = promisify3(execFile2);
3237
3371
  async function executeHookScript(params) {
3238
3372
  const timeout = clampWarmHookTimeoutMs(params.timeoutMs);
@@ -3263,12 +3397,12 @@ async function executeHookScript(params) {
3263
3397
  }
3264
3398
  async function readRepoWarmHook(repoPath) {
3265
3399
  for (const filename of REPLICAS_CONFIG_FILENAMES) {
3266
- const configPath = join12(repoPath, filename);
3400
+ const configPath = join14(repoPath, filename);
3267
3401
  if (!existsSync7(configPath)) {
3268
3402
  continue;
3269
3403
  }
3270
3404
  try {
3271
- const raw = await readFile9(configPath, "utf-8");
3405
+ const raw = await readFile11(configPath, "utf-8");
3272
3406
  const config = parseReplicasConfigString(raw, filename);
3273
3407
  if (!config.warmHook) {
3274
3408
  return null;
@@ -3314,6 +3448,13 @@ async function runWarmHooks(params) {
3314
3448
  });
3315
3449
  outputBlocks.push(orgResult.output);
3316
3450
  await environmentDetailsService.setGlobalWarmHook(orgResult.exitCode === 0 ? "yes" : "no", orgResult.output);
3451
+ await warmHookLogsService.saveGlobalHookLog({
3452
+ hookScript: orgHook,
3453
+ output: orgResult.output,
3454
+ exitCode: orgResult.exitCode,
3455
+ timedOut: orgResult.timedOut,
3456
+ executedAt: (/* @__PURE__ */ new Date()).toISOString()
3457
+ });
3317
3458
  if (orgResult.exitCode !== 0) {
3318
3459
  return {
3319
3460
  exitCode: orgResult.exitCode,
@@ -3334,6 +3475,11 @@ async function runWarmHooks(params) {
3334
3475
  }
3335
3476
  }
3336
3477
  for (const repoHook of repoHooks) {
3478
+ const combinedScript = repoHook.commands.join("\n");
3479
+ const repoOutputBlocks = [];
3480
+ let repoFailed = false;
3481
+ let repoTimedOut = false;
3482
+ let repoExitCode = 0;
3337
3483
  for (const command of repoHook.commands) {
3338
3484
  const repoResult = await executeHookScript({
3339
3485
  label: `repo-warm-hook:${repoHook.repoName}`,
@@ -3342,18 +3488,32 @@ async function runWarmHooks(params) {
3342
3488
  timeoutMs: repoHook.timeoutMs
3343
3489
  });
3344
3490
  outputBlocks.push(repoResult.output);
3491
+ repoOutputBlocks.push(repoResult.output);
3345
3492
  await environmentDetailsService.setRepositoryWarmHook(
3346
3493
  repoHook.repoName,
3347
3494
  repoResult.exitCode === 0 ? "yes" : "no"
3348
3495
  );
3349
3496
  if (repoResult.exitCode !== 0) {
3350
- return {
3351
- exitCode: repoResult.exitCode,
3352
- output: outputBlocks.join("\n\n"),
3353
- timedOut: repoResult.timedOut
3354
- };
3497
+ repoFailed = true;
3498
+ repoTimedOut = repoResult.timedOut;
3499
+ repoExitCode = repoResult.exitCode;
3500
+ break;
3355
3501
  }
3356
3502
  }
3503
+ await warmHookLogsService.saveRepoHookLog(repoHook.repoName, {
3504
+ hookScript: combinedScript,
3505
+ output: repoOutputBlocks.join("\n\n"),
3506
+ exitCode: repoFailed ? repoExitCode : 0,
3507
+ timedOut: repoTimedOut,
3508
+ executedAt: (/* @__PURE__ */ new Date()).toISOString()
3509
+ });
3510
+ if (repoFailed) {
3511
+ return {
3512
+ exitCode: repoExitCode,
3513
+ output: outputBlocks.join("\n\n"),
3514
+ timedOut: repoTimedOut
3515
+ };
3516
+ }
3357
3517
  }
3358
3518
  } else {
3359
3519
  const repos = await gitService.listRepositories();
@@ -3651,6 +3811,28 @@ function createV1Routes(deps) {
3651
3811
  );
3652
3812
  }
3653
3813
  });
3814
+ app2.get("/warm-hooks/logs", async (c) => {
3815
+ try {
3816
+ const logs = await warmHookLogsService.getAllLogs();
3817
+ return c.json({ logs });
3818
+ } catch (error) {
3819
+ return c.json(
3820
+ jsonError("Failed to read warm hook logs", error instanceof Error ? error.message : "Unknown error"),
3821
+ 500
3822
+ );
3823
+ }
3824
+ });
3825
+ app2.get("/start-hooks/logs", async (c) => {
3826
+ try {
3827
+ const logs = await startHookLogsService.getAllLogs();
3828
+ return c.json({ logs });
3829
+ } catch (error) {
3830
+ return c.json(
3831
+ jsonError("Failed to read start hook logs", error instanceof Error ? error.message : "Unknown error"),
3832
+ 500
3833
+ );
3834
+ }
3835
+ });
3654
3836
  app2.post("/workspace-name", async (c) => {
3655
3837
  try {
3656
3838
  const body = setWorkspaceNameSchema.parse(await c.req.json());
@@ -3728,7 +3910,7 @@ app.get("/health", async (c) => {
3728
3910
  return c.json({ status: "initializing", timestamp: (/* @__PURE__ */ new Date()).toISOString() }, 503);
3729
3911
  }
3730
3912
  try {
3731
- const logContent = await readFile10("/var/log/cloud-init-output.log", "utf-8");
3913
+ const logContent = await readFile12("/var/log/cloud-init-output.log", "utf-8");
3732
3914
  let status;
3733
3915
  if (logContent.includes(COMPLETION_MESSAGE)) {
3734
3916
  status = "active";