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 +254 -72
- package/package.json +3 -3
- package/dist/src/chunk-ZXMDA7VB.js +0 -16
- package/dist/src/lib-WNJM7YOZ.js +0 -3011
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
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
1266
|
+
const configPath = join7(dirPath, filename);
|
|
1220
1267
|
if (!existsSync4(configPath)) {
|
|
1221
1268
|
continue;
|
|
1222
1269
|
}
|
|
1223
|
-
const data = await
|
|
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
|
|
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
|
|
1308
|
-
await
|
|
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
|
|
1494
|
+
import { appendFile as appendFile3, mkdir as mkdir6 } from "fs/promises";
|
|
1430
1495
|
import { homedir as homedir6 } from "os";
|
|
1431
|
-
import { join as
|
|
1496
|
+
import { join as join8 } from "path";
|
|
1432
1497
|
import { randomUUID } from "crypto";
|
|
1433
|
-
var ENGINE_DIR =
|
|
1434
|
-
var EVENTS_FILE =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1488
|
-
await
|
|
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
|
|
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
|
|
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
|
|
1534
|
-
import { mkdir as
|
|
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
|
|
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
|
|
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 ??
|
|
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 =
|
|
2467
|
-
await
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
2567
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
2667
|
+
const filepath = join10(this.tempImageDir, filename);
|
|
2603
2668
|
const buffer = Buffer.from(image.source.data, "base64");
|
|
2604
|
-
await
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
2880
|
-
var CHATS_FILE =
|
|
2881
|
-
var CLAUDE_HISTORY_DIR =
|
|
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
|
|
2897
|
-
await
|
|
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 =
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
3244
|
+
import { readdir as readdir4, readFile as readFile9 } from "fs/promises";
|
|
3180
3245
|
import { homedir as homedir10 } from "os";
|
|
3181
|
-
import { basename, join as
|
|
3246
|
+
import { basename, join as join12 } from "path";
|
|
3182
3247
|
var PLAN_DIRECTORIES = [
|
|
3183
|
-
|
|
3184
|
-
|
|
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
|
|
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 =
|
|
3283
|
+
const filePath = join12(directory, safeFilename);
|
|
3219
3284
|
try {
|
|
3220
|
-
const content = await
|
|
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
|
|
3298
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
3234
3299
|
import { existsSync as existsSync7 } from "fs";
|
|
3235
|
-
import { join as
|
|
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 =
|
|
3400
|
+
const configPath = join14(repoPath, filename);
|
|
3267
3401
|
if (!existsSync7(configPath)) {
|
|
3268
3402
|
continue;
|
|
3269
3403
|
}
|
|
3270
3404
|
try {
|
|
3271
|
-
const raw = await
|
|
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
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
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
|
|
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";
|