replicas-engine 0.1.60 → 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 +382 -75
- 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
|
|
|
@@ -78,7 +77,7 @@ var ENGINE_ENV = loadEngineEnv();
|
|
|
78
77
|
|
|
79
78
|
// src/managers/base-refresh-manager.ts
|
|
80
79
|
var BaseRefreshManager = class {
|
|
81
|
-
constructor(managerName, intervalMs =
|
|
80
|
+
constructor(managerName, intervalMs = 15 * 60 * 1e3) {
|
|
82
81
|
this.managerName = managerName;
|
|
83
82
|
this.intervalMs = intervalMs;
|
|
84
83
|
this.health = {
|
|
@@ -228,6 +227,28 @@ var ClaudeTokenManager = class extends BaseRefreshManager {
|
|
|
228
227
|
await this.updateClaudeCredentials(data);
|
|
229
228
|
console.log(`[ClaudeTokenManager] Credentials refreshed successfully, expires at ${data.expiresAt}`);
|
|
230
229
|
}
|
|
230
|
+
/**
|
|
231
|
+
* Re-fetches credentials from the monolith after an auth failure is detected.
|
|
232
|
+
* With the 1-hour refresh threshold, the monolith will have already
|
|
233
|
+
* proactively refreshed the token, so this just pulls the latest.
|
|
234
|
+
*
|
|
235
|
+
* Returns true if credentials were successfully updated, false otherwise.
|
|
236
|
+
*/
|
|
237
|
+
async fetchFreshCredentials() {
|
|
238
|
+
if (this.getSkipReason()) {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
const config = this.getRuntimeConfig();
|
|
242
|
+
if (!config) return false;
|
|
243
|
+
try {
|
|
244
|
+
console.log("[ClaudeTokenManager] Fetching fresh credentials from monolith after auth failure...");
|
|
245
|
+
await this.doRefresh(config);
|
|
246
|
+
return true;
|
|
247
|
+
} catch (error) {
|
|
248
|
+
console.error("[ClaudeTokenManager] Failed to fetch fresh credentials:", error);
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
231
252
|
async updateClaudeCredentials(credentials) {
|
|
232
253
|
const workspaceHome = ENGINE_ENV.HOME_DIR;
|
|
233
254
|
const claudeDir = path2.join(workspaceHome, ".claude");
|
|
@@ -832,13 +853,63 @@ var EngineLogger = class {
|
|
|
832
853
|
var engineLogger = new EngineLogger();
|
|
833
854
|
|
|
834
855
|
// src/services/replicas-config-service.ts
|
|
835
|
-
import { readFile as
|
|
856
|
+
import { readFile as readFile4, appendFile as appendFile2, writeFile as writeFile5, mkdir as mkdir5 } from "fs/promises";
|
|
836
857
|
import { existsSync as existsSync4 } from "fs";
|
|
837
|
-
import { join as
|
|
858
|
+
import { join as join7 } from "path";
|
|
838
859
|
import { homedir as homedir5 } from "os";
|
|
839
860
|
import { exec } from "child_process";
|
|
840
861
|
import { promisify as promisify2 } from "util";
|
|
841
862
|
|
|
863
|
+
// ../shared/src/pricing.ts
|
|
864
|
+
var PLANS = {
|
|
865
|
+
hobby: {
|
|
866
|
+
id: "hobby",
|
|
867
|
+
name: "Hobby",
|
|
868
|
+
monthlyPrice: 0,
|
|
869
|
+
seatPriceCents: 0,
|
|
870
|
+
creditsIncluded: 20,
|
|
871
|
+
features: ["20 hours of usage"]
|
|
872
|
+
},
|
|
873
|
+
developer: {
|
|
874
|
+
id: "developer",
|
|
875
|
+
name: "Developer",
|
|
876
|
+
monthlyPrice: 30,
|
|
877
|
+
seatPriceCents: 3e3,
|
|
878
|
+
creditsIncluded: 0,
|
|
879
|
+
features: ["Unlimited usage", "API access ($1/hr)"]
|
|
880
|
+
},
|
|
881
|
+
team: {
|
|
882
|
+
id: "team",
|
|
883
|
+
name: "Team",
|
|
884
|
+
monthlyPrice: 120,
|
|
885
|
+
seatPriceCents: 12e3,
|
|
886
|
+
creditsIncluded: 0,
|
|
887
|
+
features: [
|
|
888
|
+
"Unlimited usage",
|
|
889
|
+
"API access",
|
|
890
|
+
"Higher API rate limits",
|
|
891
|
+
"Warm hooks and pool access",
|
|
892
|
+
"Optional add-ons for higher resources, warm pools, rate limits, and SOC 2"
|
|
893
|
+
]
|
|
894
|
+
},
|
|
895
|
+
enterprise: {
|
|
896
|
+
id: "enterprise",
|
|
897
|
+
name: "Enterprise",
|
|
898
|
+
monthlyPrice: 0,
|
|
899
|
+
seatPriceCents: 0,
|
|
900
|
+
creditsIncluded: 0,
|
|
901
|
+
features: [
|
|
902
|
+
"Unlimited usage",
|
|
903
|
+
"Custom API rates",
|
|
904
|
+
"Custom rate limits",
|
|
905
|
+
"Custom warm hooks and pools",
|
|
906
|
+
"SOC 2"
|
|
907
|
+
]
|
|
908
|
+
}
|
|
909
|
+
};
|
|
910
|
+
var TEAM_PLAN = PLANS.team;
|
|
911
|
+
var ENTERPRISE_PLAN = PLANS.enterprise;
|
|
912
|
+
|
|
842
913
|
// ../shared/src/sandbox.ts
|
|
843
914
|
var SANDBOX_LIFECYCLE = {
|
|
844
915
|
AUTO_STOP_MINUTES: 60,
|
|
@@ -1131,9 +1202,57 @@ var EnvironmentDetailsService = class {
|
|
|
1131
1202
|
};
|
|
1132
1203
|
var environmentDetailsService = new EnvironmentDetailsService();
|
|
1133
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
|
+
|
|
1134
1253
|
// src/services/replicas-config-service.ts
|
|
1135
1254
|
var execAsync = promisify2(exec);
|
|
1136
|
-
var START_HOOKS_LOG =
|
|
1255
|
+
var START_HOOKS_LOG = join7(homedir5(), ".replicas", "startHooks.log");
|
|
1137
1256
|
var START_HOOKS_RUNNING_PROMPT = `IMPORTANT - Start Hooks Running:
|
|
1138
1257
|
Start hooks are shell commands/scripts set by repository owners that run on workspace startup.
|
|
1139
1258
|
These hooks are currently executing in the background. You can:
|
|
@@ -1144,11 +1263,11 @@ The start hooks may install dependencies, build projects, or perform other setup
|
|
|
1144
1263
|
If your task depends on setup being complete, check the log file before proceeding.`;
|
|
1145
1264
|
async function readReplicasConfigFromDir(dirPath) {
|
|
1146
1265
|
for (const filename of REPLICAS_CONFIG_FILENAMES) {
|
|
1147
|
-
const configPath =
|
|
1266
|
+
const configPath = join7(dirPath, filename);
|
|
1148
1267
|
if (!existsSync4(configPath)) {
|
|
1149
1268
|
continue;
|
|
1150
1269
|
}
|
|
1151
|
-
const data = await
|
|
1270
|
+
const data = await readFile4(configPath, "utf-8");
|
|
1152
1271
|
const config = parseReplicasConfigString(data, filename);
|
|
1153
1272
|
return { config, filename };
|
|
1154
1273
|
}
|
|
@@ -1208,7 +1327,7 @@ var ReplicasConfigService = class {
|
|
|
1208
1327
|
const logLine = `[${timestamp}] ${message}
|
|
1209
1328
|
`;
|
|
1210
1329
|
try {
|
|
1211
|
-
await
|
|
1330
|
+
await mkdir5(join7(homedir5(), ".replicas"), { recursive: true });
|
|
1212
1331
|
await appendFile2(START_HOOKS_LOG, logLine, "utf-8");
|
|
1213
1332
|
} catch (error) {
|
|
1214
1333
|
console.error("Failed to write to start hooks log:", error);
|
|
@@ -1232,8 +1351,8 @@ var ReplicasConfigService = class {
|
|
|
1232
1351
|
this.hooksRunning = true;
|
|
1233
1352
|
this.hooksCompleted = false;
|
|
1234
1353
|
try {
|
|
1235
|
-
await
|
|
1236
|
-
await
|
|
1354
|
+
await mkdir5(join7(homedir5(), ".replicas"), { recursive: true });
|
|
1355
|
+
await writeFile5(
|
|
1237
1356
|
START_HOOKS_LOG,
|
|
1238
1357
|
`=== Start Hooks Execution Log ===
|
|
1239
1358
|
Started: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
@@ -1262,6 +1381,9 @@ Repositories: ${hookEntries.length}
|
|
|
1262
1381
|
}
|
|
1263
1382
|
const timeout = startHookConfig.timeout ?? 3e5;
|
|
1264
1383
|
let repoFailed = false;
|
|
1384
|
+
let lastExitCode = 0;
|
|
1385
|
+
let repoTimedOut = false;
|
|
1386
|
+
const repoOutput = [];
|
|
1265
1387
|
await this.logToFile(`[${entry.repoName}] Executing ${startHookConfig.commands.length} hook(s) with timeout ${timeout}ms`);
|
|
1266
1388
|
for (const hook of startHookConfig.commands) {
|
|
1267
1389
|
try {
|
|
@@ -1272,20 +1394,35 @@ Repositories: ${hookEntries.length}
|
|
|
1272
1394
|
env: process.env
|
|
1273
1395
|
});
|
|
1274
1396
|
if (stdout) {
|
|
1397
|
+
repoOutput.push(stdout);
|
|
1275
1398
|
await this.logToFile(`[${entry.repoName}] [stdout] ${stdout}`);
|
|
1276
1399
|
}
|
|
1277
1400
|
if (stderr) {
|
|
1401
|
+
repoOutput.push(stderr);
|
|
1278
1402
|
await this.logToFile(`[${entry.repoName}] [stderr] ${stderr}`);
|
|
1279
1403
|
}
|
|
1280
1404
|
await this.logToFile(`[${entry.repoName}] --- Completed: ${hook} ---`);
|
|
1281
1405
|
} catch (error) {
|
|
1282
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
|
+
}
|
|
1283
1412
|
this.hooksFailed = true;
|
|
1284
1413
|
repoFailed = true;
|
|
1414
|
+
repoOutput.push(`[ERROR] ${hook} failed: ${errorMessage}`);
|
|
1285
1415
|
await this.logToFile(`[${entry.repoName}] [ERROR] ${hook} failed: ${errorMessage}`);
|
|
1286
1416
|
await environmentDetailsService.setRepositoryStartHook(entry.repoName, "no");
|
|
1287
1417
|
}
|
|
1288
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
|
+
});
|
|
1289
1426
|
const fallbackRepoState = persistedRepoState ?? {
|
|
1290
1427
|
name: entry.repoName,
|
|
1291
1428
|
path: entry.workingDirectory,
|
|
@@ -1354,17 +1491,17 @@ Repositories: ${hookEntries.length}
|
|
|
1354
1491
|
var replicasConfigService = new ReplicasConfigService();
|
|
1355
1492
|
|
|
1356
1493
|
// src/services/event-service.ts
|
|
1357
|
-
import { appendFile as appendFile3, mkdir as
|
|
1494
|
+
import { appendFile as appendFile3, mkdir as mkdir6 } from "fs/promises";
|
|
1358
1495
|
import { homedir as homedir6 } from "os";
|
|
1359
|
-
import { join as
|
|
1496
|
+
import { join as join8 } from "path";
|
|
1360
1497
|
import { randomUUID } from "crypto";
|
|
1361
|
-
var ENGINE_DIR =
|
|
1362
|
-
var EVENTS_FILE =
|
|
1498
|
+
var ENGINE_DIR = join8(homedir6(), ".replicas", "engine");
|
|
1499
|
+
var EVENTS_FILE = join8(ENGINE_DIR, "events.jsonl");
|
|
1363
1500
|
var EventService = class {
|
|
1364
1501
|
subscribers = /* @__PURE__ */ new Map();
|
|
1365
1502
|
writeChain = Promise.resolve();
|
|
1366
1503
|
async initialize() {
|
|
1367
|
-
await
|
|
1504
|
+
await mkdir6(ENGINE_DIR, { recursive: true });
|
|
1368
1505
|
}
|
|
1369
1506
|
subscribe(subscriber) {
|
|
1370
1507
|
const id = randomUUID();
|
|
@@ -1394,7 +1531,7 @@ var EventService = class {
|
|
|
1394
1531
|
var eventService = new EventService();
|
|
1395
1532
|
|
|
1396
1533
|
// src/services/preview-service.ts
|
|
1397
|
-
import { mkdir as
|
|
1534
|
+
import { mkdir as mkdir7, readFile as readFile5, writeFile as writeFile6 } from "fs/promises";
|
|
1398
1535
|
import { existsSync as existsSync5 } from "fs";
|
|
1399
1536
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1400
1537
|
import { dirname } from "path";
|
|
@@ -1404,7 +1541,7 @@ async function readPreviewsFile() {
|
|
|
1404
1541
|
if (!existsSync5(PREVIEW_PORTS_FILE)) {
|
|
1405
1542
|
return { previews: [] };
|
|
1406
1543
|
}
|
|
1407
|
-
const raw = await
|
|
1544
|
+
const raw = await readFile5(PREVIEW_PORTS_FILE, "utf-8");
|
|
1408
1545
|
return JSON.parse(raw);
|
|
1409
1546
|
} catch {
|
|
1410
1547
|
return { previews: [] };
|
|
@@ -1412,8 +1549,8 @@ async function readPreviewsFile() {
|
|
|
1412
1549
|
}
|
|
1413
1550
|
async function writePreviewsFile(data) {
|
|
1414
1551
|
const dir = dirname(PREVIEW_PORTS_FILE);
|
|
1415
|
-
await
|
|
1416
|
-
await
|
|
1552
|
+
await mkdir7(dir, { recursive: true });
|
|
1553
|
+
await writeFile6(PREVIEW_PORTS_FILE, `${JSON.stringify(data, null, 2)}
|
|
1417
1554
|
`, "utf-8");
|
|
1418
1555
|
}
|
|
1419
1556
|
var PreviewService = class {
|
|
@@ -1449,21 +1586,21 @@ var PreviewService = class {
|
|
|
1449
1586
|
var previewService = new PreviewService();
|
|
1450
1587
|
|
|
1451
1588
|
// src/services/chat/chat-service.ts
|
|
1452
|
-
import { mkdir as
|
|
1589
|
+
import { mkdir as mkdir10, readFile as readFile8, rm, writeFile as writeFile8 } from "fs/promises";
|
|
1453
1590
|
import { homedir as homedir9 } from "os";
|
|
1454
|
-
import { join as
|
|
1591
|
+
import { join as join11 } from "path";
|
|
1455
1592
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
1456
1593
|
|
|
1457
1594
|
// src/managers/claude-manager.ts
|
|
1458
1595
|
import {
|
|
1459
1596
|
query
|
|
1460
1597
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
1461
|
-
import { join as
|
|
1462
|
-
import { mkdir as
|
|
1598
|
+
import { join as join9 } from "path";
|
|
1599
|
+
import { mkdir as mkdir8, appendFile as appendFile4 } from "fs/promises";
|
|
1463
1600
|
import { homedir as homedir7 } from "os";
|
|
1464
1601
|
|
|
1465
1602
|
// src/utils/jsonl-reader.ts
|
|
1466
|
-
import { readFile as
|
|
1603
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
1467
1604
|
function isJsonlEvent(value) {
|
|
1468
1605
|
if (!isRecord(value)) {
|
|
1469
1606
|
return false;
|
|
@@ -1485,7 +1622,7 @@ function parseJsonlEvents(lines) {
|
|
|
1485
1622
|
}
|
|
1486
1623
|
async function readJSONL(filePath) {
|
|
1487
1624
|
try {
|
|
1488
|
-
const content = await
|
|
1625
|
+
const content = await readFile6(filePath, "utf-8");
|
|
1489
1626
|
const lines = content.split("\n").filter((line) => line.trim());
|
|
1490
1627
|
return parseJsonlEvents(lines);
|
|
1491
1628
|
} catch (error) {
|
|
@@ -2214,7 +2351,7 @@ var PromptStream = class {
|
|
|
2214
2351
|
function isLinearThoughtEvent(event) {
|
|
2215
2352
|
return event.content.type === "thought";
|
|
2216
2353
|
}
|
|
2217
|
-
var ClaudeManager = class extends CodingAgentManager {
|
|
2354
|
+
var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
|
|
2218
2355
|
historyFile;
|
|
2219
2356
|
sessionId = null;
|
|
2220
2357
|
activeQuery = null;
|
|
@@ -2222,7 +2359,7 @@ var ClaudeManager = class extends CodingAgentManager {
|
|
|
2222
2359
|
pendingInterrupt = false;
|
|
2223
2360
|
constructor(options) {
|
|
2224
2361
|
super(options);
|
|
2225
|
-
this.historyFile = options.historyFilePath ??
|
|
2362
|
+
this.historyFile = options.historyFilePath ?? join9(homedir7(), ".replicas", "claude", "history.jsonl");
|
|
2226
2363
|
this.initializeManager(this.processMessageInternal.bind(this));
|
|
2227
2364
|
}
|
|
2228
2365
|
async interruptActiveTurn() {
|
|
@@ -2233,9 +2370,28 @@ var ClaudeManager = class extends CodingAgentManager {
|
|
|
2233
2370
|
}
|
|
2234
2371
|
}
|
|
2235
2372
|
/**
|
|
2236
|
-
* Internal method that actually processes the message
|
|
2373
|
+
* Internal method that actually processes the message.
|
|
2374
|
+
* On auth failure, refreshes credentials and retries once.
|
|
2237
2375
|
*/
|
|
2238
2376
|
async processMessageInternal(request) {
|
|
2377
|
+
try {
|
|
2378
|
+
await this.executeQuery(request);
|
|
2379
|
+
} catch (error) {
|
|
2380
|
+
if (_ClaudeManager.isAuthError(error)) {
|
|
2381
|
+
console.warn("[ClaudeManager] Auth failure detected, refreshing credentials and retrying...", error);
|
|
2382
|
+
const refreshed = await claudeTokenManager.fetchFreshCredentials();
|
|
2383
|
+
if (refreshed) {
|
|
2384
|
+
await this.executeQuery(request);
|
|
2385
|
+
return;
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
throw error;
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
/**
|
|
2392
|
+
* Executes a single query attempt against the Claude Agent SDK.
|
|
2393
|
+
*/
|
|
2394
|
+
async executeQuery(request) {
|
|
2239
2395
|
const {
|
|
2240
2396
|
message,
|
|
2241
2397
|
model,
|
|
@@ -2351,6 +2507,18 @@ var ClaudeManager = class extends CodingAgentManager {
|
|
|
2351
2507
|
await this.onTurnComplete();
|
|
2352
2508
|
}
|
|
2353
2509
|
}
|
|
2510
|
+
/**
|
|
2511
|
+
* Determines if an error is an OAuth authentication failure from the Claude SDK.
|
|
2512
|
+
* Known patterns:
|
|
2513
|
+
* - "Not logged in · Please run /login"
|
|
2514
|
+
* - "authentication_failed"
|
|
2515
|
+
* - "unauthorized"
|
|
2516
|
+
*/
|
|
2517
|
+
static isAuthError(error) {
|
|
2518
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2519
|
+
const lower = msg.toLowerCase();
|
|
2520
|
+
return lower.includes("not logged in") || lower.includes("please run /login") || lower.includes("authentication_failed") || lower.includes("authentication failed");
|
|
2521
|
+
}
|
|
2354
2522
|
async getHistory() {
|
|
2355
2523
|
await this.initialized;
|
|
2356
2524
|
const events = await readJSONL(this.historyFile);
|
|
@@ -2360,19 +2528,41 @@ var ClaudeManager = class extends CodingAgentManager {
|
|
|
2360
2528
|
};
|
|
2361
2529
|
}
|
|
2362
2530
|
async initialize() {
|
|
2363
|
-
const historyDir =
|
|
2364
|
-
await
|
|
2531
|
+
const historyDir = join9(homedir7(), ".replicas", "claude");
|
|
2532
|
+
await mkdir8(historyDir, { recursive: true });
|
|
2365
2533
|
if (this.initialSessionId) {
|
|
2366
2534
|
this.sessionId = this.initialSessionId;
|
|
2367
2535
|
console.log(`[ClaudeManager] Restored session ID from persisted state: ${this.sessionId}`);
|
|
2368
2536
|
}
|
|
2369
2537
|
}
|
|
2538
|
+
/**
|
|
2539
|
+
* Checks if an SDK message indicates an OAuth authentication failure.
|
|
2540
|
+
* When detected, triggers a force-refresh of credentials from the monolith.
|
|
2541
|
+
*/
|
|
2542
|
+
async checkForAuthFailure(message) {
|
|
2543
|
+
if (message.type === "assistant" && "error" in message && message.error === "authentication_failed") {
|
|
2544
|
+
console.warn("[ClaudeManager] Detected authentication_failed in assistant message, force-refreshing credentials...");
|
|
2545
|
+
await claudeTokenManager.fetchFreshCredentials();
|
|
2546
|
+
return;
|
|
2547
|
+
}
|
|
2548
|
+
if (message.type === "result" && "is_error" in message && message.is_error && "errors" in message) {
|
|
2549
|
+
const errors = message.errors ?? [];
|
|
2550
|
+
const hasAuthError = errors.some(
|
|
2551
|
+
(err) => err.toLowerCase().includes("authentication") || err.toLowerCase().includes("unauthorized") || err.toLowerCase().includes("not logged in") || err.toLowerCase().includes("login")
|
|
2552
|
+
);
|
|
2553
|
+
if (hasAuthError) {
|
|
2554
|
+
console.warn("[ClaudeManager] Detected auth-related error in result, force-refreshing credentials...");
|
|
2555
|
+
await claudeTokenManager.fetchFreshCredentials();
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2370
2559
|
async handleMessage(message) {
|
|
2371
2560
|
if ("session_id" in message && message.session_id && !this.sessionId) {
|
|
2372
2561
|
this.sessionId = message.session_id;
|
|
2373
2562
|
await this.onSaveSessionId(this.sessionId);
|
|
2374
2563
|
console.log(`[ClaudeManager] Captured and persisted session ID: ${this.sessionId}`);
|
|
2375
2564
|
}
|
|
2565
|
+
await this.checkForAuthFailure(message);
|
|
2376
2566
|
await this.recordEvent(message);
|
|
2377
2567
|
}
|
|
2378
2568
|
async recordEvent(event) {
|
|
@@ -2390,13 +2580,13 @@ var ClaudeManager = class extends CodingAgentManager {
|
|
|
2390
2580
|
// src/managers/codex-manager.ts
|
|
2391
2581
|
import { Codex } from "@openai/codex-sdk";
|
|
2392
2582
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2393
|
-
import { readdir as
|
|
2583
|
+
import { readdir as readdir3, stat as stat2, writeFile as writeFile7, mkdir as mkdir9, readFile as readFile7 } from "fs/promises";
|
|
2394
2584
|
import { existsSync as existsSync6 } from "fs";
|
|
2395
|
-
import { join as
|
|
2585
|
+
import { join as join10 } from "path";
|
|
2396
2586
|
import { homedir as homedir8 } from "os";
|
|
2397
2587
|
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
2398
2588
|
var DEFAULT_MODEL = "gpt-5.4";
|
|
2399
|
-
var CODEX_CONFIG_PATH =
|
|
2589
|
+
var CODEX_CONFIG_PATH = join10(homedir8(), ".codex", "config.toml");
|
|
2400
2590
|
function isLinearThoughtEvent2(event) {
|
|
2401
2591
|
return event.content.type === "thought";
|
|
2402
2592
|
}
|
|
@@ -2418,7 +2608,7 @@ var CodexManager = class extends CodingAgentManager {
|
|
|
2418
2608
|
constructor(options) {
|
|
2419
2609
|
super(options);
|
|
2420
2610
|
this.codex = new Codex();
|
|
2421
|
-
this.tempImageDir =
|
|
2611
|
+
this.tempImageDir = join10(homedir8(), ".replicas", "codex", "temp-images");
|
|
2422
2612
|
this.initializeManager(this.processMessageInternal.bind(this));
|
|
2423
2613
|
}
|
|
2424
2614
|
async initialize() {
|
|
@@ -2438,12 +2628,12 @@ var CodexManager = class extends CodingAgentManager {
|
|
|
2438
2628
|
*/
|
|
2439
2629
|
async updateCodexConfig(developerInstructions) {
|
|
2440
2630
|
try {
|
|
2441
|
-
const codexDir =
|
|
2442
|
-
await
|
|
2631
|
+
const codexDir = join10(homedir8(), ".codex");
|
|
2632
|
+
await mkdir9(codexDir, { recursive: true });
|
|
2443
2633
|
let config = {};
|
|
2444
2634
|
if (existsSync6(CODEX_CONFIG_PATH)) {
|
|
2445
2635
|
try {
|
|
2446
|
-
const existingContent = await
|
|
2636
|
+
const existingContent = await readFile7(CODEX_CONFIG_PATH, "utf-8");
|
|
2447
2637
|
const parsed = parseToml(existingContent);
|
|
2448
2638
|
if (isRecord(parsed)) {
|
|
2449
2639
|
config = parsed;
|
|
@@ -2458,7 +2648,7 @@ var CodexManager = class extends CodingAgentManager {
|
|
|
2458
2648
|
delete config.developer_instructions;
|
|
2459
2649
|
}
|
|
2460
2650
|
const tomlContent = stringifyToml(config);
|
|
2461
|
-
await
|
|
2651
|
+
await writeFile7(CODEX_CONFIG_PATH, tomlContent, "utf-8");
|
|
2462
2652
|
console.log("[CodexManager] Updated config.toml with developer_instructions");
|
|
2463
2653
|
} catch (error) {
|
|
2464
2654
|
console.error("[CodexManager] Failed to update config.toml:", error);
|
|
@@ -2469,14 +2659,14 @@ var CodexManager = class extends CodingAgentManager {
|
|
|
2469
2659
|
* @returns Array of temp file paths
|
|
2470
2660
|
*/
|
|
2471
2661
|
async saveImagesToTempFiles(images) {
|
|
2472
|
-
await
|
|
2662
|
+
await mkdir9(this.tempImageDir, { recursive: true });
|
|
2473
2663
|
const tempPaths = [];
|
|
2474
2664
|
for (const image of images) {
|
|
2475
2665
|
const ext = image.source.media_type.split("/")[1] || "png";
|
|
2476
2666
|
const filename = `img_${randomUUID3()}.${ext}`;
|
|
2477
|
-
const filepath =
|
|
2667
|
+
const filepath = join10(this.tempImageDir, filename);
|
|
2478
2668
|
const buffer = Buffer.from(image.source.data, "base64");
|
|
2479
|
-
await
|
|
2669
|
+
await writeFile7(filepath, buffer);
|
|
2480
2670
|
tempPaths.push(filepath);
|
|
2481
2671
|
}
|
|
2482
2672
|
return tempPaths;
|
|
@@ -2606,13 +2796,13 @@ var CodexManager = class extends CodingAgentManager {
|
|
|
2606
2796
|
}
|
|
2607
2797
|
// Helper methods for finding session files
|
|
2608
2798
|
async findSessionFile(threadId) {
|
|
2609
|
-
const sessionsDir =
|
|
2799
|
+
const sessionsDir = join10(homedir8(), ".codex", "sessions");
|
|
2610
2800
|
try {
|
|
2611
2801
|
const now = /* @__PURE__ */ new Date();
|
|
2612
2802
|
const year = now.getFullYear();
|
|
2613
2803
|
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
2614
2804
|
const day = String(now.getDate()).padStart(2, "0");
|
|
2615
|
-
const todayDir =
|
|
2805
|
+
const todayDir = join10(sessionsDir, String(year), month, day);
|
|
2616
2806
|
const file = await this.findFileInDirectory(todayDir, threadId);
|
|
2617
2807
|
if (file) return file;
|
|
2618
2808
|
for (let daysAgo = 1; daysAgo <= 7; daysAgo++) {
|
|
@@ -2621,7 +2811,7 @@ var CodexManager = class extends CodingAgentManager {
|
|
|
2621
2811
|
const searchYear = date.getFullYear();
|
|
2622
2812
|
const searchMonth = String(date.getMonth() + 1).padStart(2, "0");
|
|
2623
2813
|
const searchDay = String(date.getDate()).padStart(2, "0");
|
|
2624
|
-
const searchDir =
|
|
2814
|
+
const searchDir = join10(sessionsDir, String(searchYear), searchMonth, searchDay);
|
|
2625
2815
|
const file2 = await this.findFileInDirectory(searchDir, threadId);
|
|
2626
2816
|
if (file2) return file2;
|
|
2627
2817
|
}
|
|
@@ -2632,10 +2822,10 @@ var CodexManager = class extends CodingAgentManager {
|
|
|
2632
2822
|
}
|
|
2633
2823
|
async findFileInDirectory(directory, threadId) {
|
|
2634
2824
|
try {
|
|
2635
|
-
const files = await
|
|
2825
|
+
const files = await readdir3(directory);
|
|
2636
2826
|
for (const file of files) {
|
|
2637
2827
|
if (file.endsWith(".jsonl") && file.includes(threadId)) {
|
|
2638
|
-
const fullPath =
|
|
2828
|
+
const fullPath = join10(directory, file);
|
|
2639
2829
|
const stats = await stat2(fullPath);
|
|
2640
2830
|
if (stats.isFile()) {
|
|
2641
2831
|
return fullPath;
|
|
@@ -2668,7 +2858,7 @@ var CodexManager = class extends CodingAgentManager {
|
|
|
2668
2858
|
const seenLines = /* @__PURE__ */ new Set();
|
|
2669
2859
|
const seedSeenLines = async () => {
|
|
2670
2860
|
try {
|
|
2671
|
-
const content = await
|
|
2861
|
+
const content = await readFile7(sessionFile, "utf-8");
|
|
2672
2862
|
const lines = content.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
2673
2863
|
for (const line of lines) {
|
|
2674
2864
|
seenLines.add(line);
|
|
@@ -2680,7 +2870,7 @@ var CodexManager = class extends CodingAgentManager {
|
|
|
2680
2870
|
const pump = async () => {
|
|
2681
2871
|
let emitted = 0;
|
|
2682
2872
|
try {
|
|
2683
|
-
const content = await
|
|
2873
|
+
const content = await readFile7(sessionFile, "utf-8");
|
|
2684
2874
|
const lines = content.split("\n");
|
|
2685
2875
|
const completeLines = content.endsWith("\n") ? lines : lines.slice(0, -1);
|
|
2686
2876
|
for (const line of completeLines) {
|
|
@@ -2751,9 +2941,9 @@ var DuplicateDefaultChatError = class extends Error {
|
|
|
2751
2941
|
};
|
|
2752
2942
|
|
|
2753
2943
|
// src/services/chat/chat-service.ts
|
|
2754
|
-
var ENGINE_DIR2 =
|
|
2755
|
-
var CHATS_FILE =
|
|
2756
|
-
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");
|
|
2757
2947
|
function isPersistedChat(value) {
|
|
2758
2948
|
if (!isRecord(value)) {
|
|
2759
2949
|
return false;
|
|
@@ -2768,8 +2958,8 @@ var ChatService = class {
|
|
|
2768
2958
|
chats = /* @__PURE__ */ new Map();
|
|
2769
2959
|
writeChain = Promise.resolve();
|
|
2770
2960
|
async initialize() {
|
|
2771
|
-
await
|
|
2772
|
-
await
|
|
2961
|
+
await mkdir10(ENGINE_DIR2, { recursive: true });
|
|
2962
|
+
await mkdir10(CLAUDE_HISTORY_DIR, { recursive: true });
|
|
2773
2963
|
const persisted = await this.loadChats();
|
|
2774
2964
|
for (const chat of persisted) {
|
|
2775
2965
|
const runtime = this.createRuntimeChat(chat);
|
|
@@ -2865,7 +3055,7 @@ var ChatService = class {
|
|
|
2865
3055
|
this.chats.delete(chatId);
|
|
2866
3056
|
await this.persistAllChats();
|
|
2867
3057
|
if (chat.persisted.provider === "claude") {
|
|
2868
|
-
const historyFilePath =
|
|
3058
|
+
const historyFilePath = join11(CLAUDE_HISTORY_DIR, `${chatId}.jsonl`);
|
|
2869
3059
|
await rm(historyFilePath, { force: true });
|
|
2870
3060
|
}
|
|
2871
3061
|
await this.publish({
|
|
@@ -2908,7 +3098,7 @@ var ChatService = class {
|
|
|
2908
3098
|
};
|
|
2909
3099
|
const provider = persisted.provider === "claude" ? new ClaudeManager({
|
|
2910
3100
|
workingDirectory: this.workingDirectory,
|
|
2911
|
-
historyFilePath:
|
|
3101
|
+
historyFilePath: join11(CLAUDE_HISTORY_DIR, `${persisted.id}.jsonl`),
|
|
2912
3102
|
initialSessionId: persisted.providerSessionId,
|
|
2913
3103
|
onSaveSessionId: saveSession,
|
|
2914
3104
|
onTurnComplete: onProviderTurnComplete,
|
|
@@ -3004,7 +3194,7 @@ var ChatService = class {
|
|
|
3004
3194
|
}
|
|
3005
3195
|
async loadChats() {
|
|
3006
3196
|
try {
|
|
3007
|
-
const content = await
|
|
3197
|
+
const content = await readFile8(CHATS_FILE, "utf-8");
|
|
3008
3198
|
const parsed = JSON.parse(content);
|
|
3009
3199
|
if (!Array.isArray(parsed)) {
|
|
3010
3200
|
return [];
|
|
@@ -3021,7 +3211,7 @@ var ChatService = class {
|
|
|
3021
3211
|
this.writeChain = this.writeChain.catch(() => {
|
|
3022
3212
|
}).then(async () => {
|
|
3023
3213
|
const payload = Array.from(this.chats.values()).map((chat) => chat.persisted);
|
|
3024
|
-
await
|
|
3214
|
+
await writeFile8(CHATS_FILE, JSON.stringify(payload, null, 2), "utf-8");
|
|
3025
3215
|
});
|
|
3026
3216
|
await this.writeChain;
|
|
3027
3217
|
}
|
|
@@ -3051,12 +3241,12 @@ import { Hono } from "hono";
|
|
|
3051
3241
|
import { z } from "zod";
|
|
3052
3242
|
|
|
3053
3243
|
// src/services/plan-service.ts
|
|
3054
|
-
import { readdir as
|
|
3244
|
+
import { readdir as readdir4, readFile as readFile9 } from "fs/promises";
|
|
3055
3245
|
import { homedir as homedir10 } from "os";
|
|
3056
|
-
import { basename, join as
|
|
3246
|
+
import { basename, join as join12 } from "path";
|
|
3057
3247
|
var PLAN_DIRECTORIES = [
|
|
3058
|
-
|
|
3059
|
-
|
|
3248
|
+
join12(homedir10(), ".claude", "plans"),
|
|
3249
|
+
join12(homedir10(), ".replicas", "plans")
|
|
3060
3250
|
];
|
|
3061
3251
|
function isMarkdownFile(filename) {
|
|
3062
3252
|
return filename.toLowerCase().endsWith(".md");
|
|
@@ -3069,7 +3259,7 @@ var PlanService = class {
|
|
|
3069
3259
|
const planNames = /* @__PURE__ */ new Set();
|
|
3070
3260
|
for (const directory of PLAN_DIRECTORIES) {
|
|
3071
3261
|
try {
|
|
3072
|
-
const entries = await
|
|
3262
|
+
const entries = await readdir4(directory, { withFileTypes: true });
|
|
3073
3263
|
for (const entry of entries) {
|
|
3074
3264
|
if (!entry.isFile()) {
|
|
3075
3265
|
continue;
|
|
@@ -3090,9 +3280,9 @@ var PlanService = class {
|
|
|
3090
3280
|
return null;
|
|
3091
3281
|
}
|
|
3092
3282
|
for (const directory of PLAN_DIRECTORIES) {
|
|
3093
|
-
const filePath =
|
|
3283
|
+
const filePath = join12(directory, safeFilename);
|
|
3094
3284
|
try {
|
|
3095
|
-
const content = await
|
|
3285
|
+
const content = await readFile9(filePath, "utf-8");
|
|
3096
3286
|
return { filename: safeFilename, content };
|
|
3097
3287
|
} catch {
|
|
3098
3288
|
}
|
|
@@ -3105,9 +3295,78 @@ var planService = new PlanService();
|
|
|
3105
3295
|
// src/services/warm-hooks-service.ts
|
|
3106
3296
|
import { execFile as execFile2 } from "child_process";
|
|
3107
3297
|
import { promisify as promisify3 } from "util";
|
|
3108
|
-
import { readFile as
|
|
3298
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
3109
3299
|
import { existsSync as existsSync7 } from "fs";
|
|
3110
|
-
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
|
|
3111
3370
|
var execFileAsync2 = promisify3(execFile2);
|
|
3112
3371
|
async function executeHookScript(params) {
|
|
3113
3372
|
const timeout = clampWarmHookTimeoutMs(params.timeoutMs);
|
|
@@ -3138,12 +3397,12 @@ async function executeHookScript(params) {
|
|
|
3138
3397
|
}
|
|
3139
3398
|
async function readRepoWarmHook(repoPath) {
|
|
3140
3399
|
for (const filename of REPLICAS_CONFIG_FILENAMES) {
|
|
3141
|
-
const configPath =
|
|
3400
|
+
const configPath = join14(repoPath, filename);
|
|
3142
3401
|
if (!existsSync7(configPath)) {
|
|
3143
3402
|
continue;
|
|
3144
3403
|
}
|
|
3145
3404
|
try {
|
|
3146
|
-
const raw = await
|
|
3405
|
+
const raw = await readFile11(configPath, "utf-8");
|
|
3147
3406
|
const config = parseReplicasConfigString(raw, filename);
|
|
3148
3407
|
if (!config.warmHook) {
|
|
3149
3408
|
return null;
|
|
@@ -3189,6 +3448,13 @@ async function runWarmHooks(params) {
|
|
|
3189
3448
|
});
|
|
3190
3449
|
outputBlocks.push(orgResult.output);
|
|
3191
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
|
+
});
|
|
3192
3458
|
if (orgResult.exitCode !== 0) {
|
|
3193
3459
|
return {
|
|
3194
3460
|
exitCode: orgResult.exitCode,
|
|
@@ -3209,6 +3475,11 @@ async function runWarmHooks(params) {
|
|
|
3209
3475
|
}
|
|
3210
3476
|
}
|
|
3211
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;
|
|
3212
3483
|
for (const command of repoHook.commands) {
|
|
3213
3484
|
const repoResult = await executeHookScript({
|
|
3214
3485
|
label: `repo-warm-hook:${repoHook.repoName}`,
|
|
@@ -3217,18 +3488,32 @@ async function runWarmHooks(params) {
|
|
|
3217
3488
|
timeoutMs: repoHook.timeoutMs
|
|
3218
3489
|
});
|
|
3219
3490
|
outputBlocks.push(repoResult.output);
|
|
3491
|
+
repoOutputBlocks.push(repoResult.output);
|
|
3220
3492
|
await environmentDetailsService.setRepositoryWarmHook(
|
|
3221
3493
|
repoHook.repoName,
|
|
3222
3494
|
repoResult.exitCode === 0 ? "yes" : "no"
|
|
3223
3495
|
);
|
|
3224
3496
|
if (repoResult.exitCode !== 0) {
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
};
|
|
3497
|
+
repoFailed = true;
|
|
3498
|
+
repoTimedOut = repoResult.timedOut;
|
|
3499
|
+
repoExitCode = repoResult.exitCode;
|
|
3500
|
+
break;
|
|
3230
3501
|
}
|
|
3231
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
|
+
}
|
|
3232
3517
|
}
|
|
3233
3518
|
} else {
|
|
3234
3519
|
const repos = await gitService.listRepositories();
|
|
@@ -3526,6 +3811,28 @@ function createV1Routes(deps) {
|
|
|
3526
3811
|
);
|
|
3527
3812
|
}
|
|
3528
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
|
+
});
|
|
3529
3836
|
app2.post("/workspace-name", async (c) => {
|
|
3530
3837
|
try {
|
|
3531
3838
|
const body = setWorkspaceNameSchema.parse(await c.req.json());
|
|
@@ -3603,7 +3910,7 @@ app.get("/health", async (c) => {
|
|
|
3603
3910
|
return c.json({ status: "initializing", timestamp: (/* @__PURE__ */ new Date()).toISOString() }, 503);
|
|
3604
3911
|
}
|
|
3605
3912
|
try {
|
|
3606
|
-
const logContent = await
|
|
3913
|
+
const logContent = await readFile12("/var/log/cloud-init-output.log", "utf-8");
|
|
3607
3914
|
let status;
|
|
3608
3915
|
if (logContent.includes(COMPLETION_MESSAGE)) {
|
|
3609
3916
|
status = "active";
|