replicas-engine 0.1.53 → 0.1.55
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 +222 -113
- package/package.json +2 -1
package/dist/src/index.js
CHANGED
|
@@ -4,9 +4,9 @@ import "./chunk-ZXMDA7VB.js";
|
|
|
4
4
|
// src/index.ts
|
|
5
5
|
import { serve } from "@hono/node-server";
|
|
6
6
|
import { Hono as Hono2 } from "hono";
|
|
7
|
-
import { readFile as
|
|
7
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
8
8
|
import { execSync } from "child_process";
|
|
9
|
-
import { randomUUID as
|
|
9
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
10
10
|
|
|
11
11
|
// src/managers/github-token-manager.ts
|
|
12
12
|
import { promises as fs } from "fs";
|
|
@@ -846,9 +846,13 @@ var SANDBOX_PATHS = {
|
|
|
846
846
|
REPLICAS_DIR: "/home/ubuntu/.replicas",
|
|
847
847
|
REPLICAS_FILES_DIR: "/home/ubuntu/.replicas/files",
|
|
848
848
|
REPLICAS_FILES_DISPLAY_DIR: "~/.replicas/files",
|
|
849
|
-
REPLICAS_RUNTIME_ENV_FILE: "/home/ubuntu/.replicas/runtime-env.sh"
|
|
849
|
+
REPLICAS_RUNTIME_ENV_FILE: "/home/ubuntu/.replicas/runtime-env.sh",
|
|
850
|
+
REPLICAS_PREVIEW_PORTS_FILE: "/home/ubuntu/.replicas/preview-ports.json"
|
|
850
851
|
};
|
|
851
852
|
|
|
853
|
+
// ../shared/src/replicas-config.ts
|
|
854
|
+
import { parse as parseYaml } from "yaml";
|
|
855
|
+
|
|
852
856
|
// ../shared/src/warm-hooks.ts
|
|
853
857
|
var DEFAULT_WARM_HOOK_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
854
858
|
var MAX_WARM_HOOK_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
@@ -869,18 +873,18 @@ function truncateWarmHookOutput(text, maxChars = DEFAULT_WARM_HOOK_OUTPUT_MAX_CH
|
|
|
869
873
|
return `${text.slice(0, maxChars)}
|
|
870
874
|
...[truncated]`;
|
|
871
875
|
}
|
|
872
|
-
function parseWarmHookConfig(value) {
|
|
876
|
+
function parseWarmHookConfig(value, filename = "replicas.json") {
|
|
873
877
|
if (typeof value === "string") {
|
|
874
878
|
return value;
|
|
875
879
|
}
|
|
876
880
|
if (!isRecord2(value)) {
|
|
877
|
-
throw new Error(
|
|
881
|
+
throw new Error(`Invalid ${filename}: "warmHook" must be a string or object`);
|
|
878
882
|
}
|
|
879
883
|
if (!Array.isArray(value.commands) || !value.commands.every((entry) => typeof entry === "string")) {
|
|
880
|
-
throw new Error(
|
|
884
|
+
throw new Error(`Invalid ${filename}: "warmHook.commands" must be an array of shell commands`);
|
|
881
885
|
}
|
|
882
886
|
if (value.timeout !== void 0 && (typeof value.timeout !== "number" || value.timeout <= 0)) {
|
|
883
|
-
throw new Error(
|
|
887
|
+
throw new Error(`Invalid ${filename}: "warmHook.timeout" must be a positive number`);
|
|
884
888
|
}
|
|
885
889
|
return {
|
|
886
890
|
commands: value.commands,
|
|
@@ -902,6 +906,57 @@ function resolveWarmHookConfig(value) {
|
|
|
902
906
|
};
|
|
903
907
|
}
|
|
904
908
|
|
|
909
|
+
// ../shared/src/replicas-config.ts
|
|
910
|
+
var REPLICAS_CONFIG_FILENAMES = ["replicas.json", "replicas.yaml", "replicas.yml"];
|
|
911
|
+
function isRecord3(value) {
|
|
912
|
+
return typeof value === "object" && value !== null;
|
|
913
|
+
}
|
|
914
|
+
function parseReplicasConfig(value, filename = "replicas.json") {
|
|
915
|
+
if (!isRecord3(value)) {
|
|
916
|
+
throw new Error(`Invalid ${filename}: expected an object`);
|
|
917
|
+
}
|
|
918
|
+
const config = {};
|
|
919
|
+
if ("organizationId" in value) {
|
|
920
|
+
if (typeof value.organizationId !== "string") {
|
|
921
|
+
throw new Error(`Invalid ${filename}: "organizationId" must be a string`);
|
|
922
|
+
}
|
|
923
|
+
config.organizationId = value.organizationId;
|
|
924
|
+
}
|
|
925
|
+
if ("systemPrompt" in value) {
|
|
926
|
+
if (typeof value.systemPrompt !== "string") {
|
|
927
|
+
throw new Error(`Invalid ${filename}: "systemPrompt" must be a string`);
|
|
928
|
+
}
|
|
929
|
+
config.systemPrompt = value.systemPrompt;
|
|
930
|
+
}
|
|
931
|
+
if ("startHook" in value) {
|
|
932
|
+
if (!isRecord3(value.startHook)) {
|
|
933
|
+
throw new Error(`Invalid ${filename}: "startHook" must be an object with "commands" array`);
|
|
934
|
+
}
|
|
935
|
+
const { commands, timeout } = value.startHook;
|
|
936
|
+
if (!Array.isArray(commands) || !commands.every((entry) => typeof entry === "string")) {
|
|
937
|
+
throw new Error(`Invalid ${filename}: "startHook.commands" must be an array of shell commands`);
|
|
938
|
+
}
|
|
939
|
+
if (timeout !== void 0 && (typeof timeout !== "number" || timeout <= 0)) {
|
|
940
|
+
throw new Error(`Invalid ${filename}: "startHook.timeout" must be a positive number`);
|
|
941
|
+
}
|
|
942
|
+
config.startHook = { commands, timeout };
|
|
943
|
+
}
|
|
944
|
+
if ("warmHook" in value) {
|
|
945
|
+
config.warmHook = parseWarmHookConfig(value.warmHook, filename);
|
|
946
|
+
}
|
|
947
|
+
return config;
|
|
948
|
+
}
|
|
949
|
+
function parseReplicasConfigString(content, filename) {
|
|
950
|
+
const isYaml = filename.endsWith(".yaml") || filename.endsWith(".yml");
|
|
951
|
+
let parsed;
|
|
952
|
+
if (isYaml) {
|
|
953
|
+
parsed = parseYaml(content);
|
|
954
|
+
} else {
|
|
955
|
+
parsed = JSON.parse(content);
|
|
956
|
+
}
|
|
957
|
+
return parseReplicasConfig(parsed, filename);
|
|
958
|
+
}
|
|
959
|
+
|
|
905
960
|
// ../shared/src/engine/environment.ts
|
|
906
961
|
var REPLICAS_ENGINE_VERSION = "09-03-2026-kipling";
|
|
907
962
|
|
|
@@ -1081,52 +1136,17 @@ These hooks are currently executing in the background. You can:
|
|
|
1081
1136
|
|
|
1082
1137
|
The start hooks may install dependencies, build projects, or perform other setup tasks.
|
|
1083
1138
|
If your task depends on setup being complete, check the log file before proceeding.`;
|
|
1084
|
-
function
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
if ("copy" in value) {
|
|
1090
|
-
if (!Array.isArray(value.copy) || !value.copy.every((entry) => typeof entry === "string")) {
|
|
1091
|
-
throw new Error('Invalid replicas.json: "copy" must be an array of file paths');
|
|
1092
|
-
}
|
|
1093
|
-
config.copy = value.copy;
|
|
1094
|
-
}
|
|
1095
|
-
if ("ports" in value) {
|
|
1096
|
-
if (!Array.isArray(value.ports) || !value.ports.every((entry) => typeof entry === "number")) {
|
|
1097
|
-
throw new Error('Invalid replicas.json: "ports" must be an array of port numbers');
|
|
1098
|
-
}
|
|
1099
|
-
config.ports = value.ports;
|
|
1100
|
-
}
|
|
1101
|
-
if ("organizationId" in value) {
|
|
1102
|
-
if (typeof value.organizationId !== "string") {
|
|
1103
|
-
throw new Error('Invalid replicas.json: "organizationId" must be a string');
|
|
1104
|
-
}
|
|
1105
|
-
config.organizationId = value.organizationId;
|
|
1106
|
-
}
|
|
1107
|
-
if ("systemPrompt" in value) {
|
|
1108
|
-
if (typeof value.systemPrompt !== "string") {
|
|
1109
|
-
throw new Error('Invalid replicas.json: "systemPrompt" must be a string');
|
|
1110
|
-
}
|
|
1111
|
-
config.systemPrompt = value.systemPrompt;
|
|
1112
|
-
}
|
|
1113
|
-
if ("startHook" in value) {
|
|
1114
|
-
if (!isRecord(value.startHook)) {
|
|
1115
|
-
throw new Error('Invalid replicas.json: "startHook" must be an object with "commands" array');
|
|
1116
|
-
}
|
|
1117
|
-
const { commands, timeout } = value.startHook;
|
|
1118
|
-
if (!Array.isArray(commands) || !commands.every((entry) => typeof entry === "string")) {
|
|
1119
|
-
throw new Error('Invalid replicas.json: "startHook.commands" must be an array of shell commands');
|
|
1120
|
-
}
|
|
1121
|
-
if (timeout !== void 0 && (typeof timeout !== "number" || timeout <= 0)) {
|
|
1122
|
-
throw new Error('Invalid replicas.json: "startHook.timeout" must be a positive number');
|
|
1139
|
+
async function readReplicasConfigFromDir(dirPath) {
|
|
1140
|
+
for (const filename of REPLICAS_CONFIG_FILENAMES) {
|
|
1141
|
+
const configPath = join6(dirPath, filename);
|
|
1142
|
+
if (!existsSync4(configPath)) {
|
|
1143
|
+
continue;
|
|
1123
1144
|
}
|
|
1124
|
-
|
|
1145
|
+
const data = await readFile3(configPath, "utf-8");
|
|
1146
|
+
const config = parseReplicasConfigString(data, filename);
|
|
1147
|
+
return { config, filename };
|
|
1125
1148
|
}
|
|
1126
|
-
|
|
1127
|
-
config.warmHook = parseWarmHookConfig(value.warmHook);
|
|
1128
|
-
}
|
|
1129
|
-
return config;
|
|
1149
|
+
return null;
|
|
1130
1150
|
}
|
|
1131
1151
|
var ReplicasConfigService = class {
|
|
1132
1152
|
configs = [];
|
|
@@ -1134,7 +1154,7 @@ var ReplicasConfigService = class {
|
|
|
1134
1154
|
hooksCompleted = false;
|
|
1135
1155
|
hooksFailed = false;
|
|
1136
1156
|
/**
|
|
1137
|
-
* Initialize by reading all replicas
|
|
1157
|
+
* Initialize by reading all replicas config files and running start hooks.
|
|
1138
1158
|
*/
|
|
1139
1159
|
async initialize() {
|
|
1140
1160
|
await this.loadConfigs();
|
|
@@ -1146,30 +1166,29 @@ var ReplicasConfigService = class {
|
|
|
1146
1166
|
});
|
|
1147
1167
|
}
|
|
1148
1168
|
/**
|
|
1149
|
-
* Load and parse replicas
|
|
1169
|
+
* Load and parse replicas config from each discovered repository root.
|
|
1170
|
+
* Checks replicas.json first, then falls back to replicas.yaml/replicas.yml.
|
|
1150
1171
|
*/
|
|
1151
1172
|
async loadConfigs() {
|
|
1152
1173
|
const repos = await gitService.listRepositories();
|
|
1153
1174
|
const configs = [];
|
|
1154
1175
|
for (const repo of repos) {
|
|
1155
|
-
const configPath = join6(repo.path, "replicas.json");
|
|
1156
|
-
if (!existsSync4(configPath)) {
|
|
1157
|
-
continue;
|
|
1158
|
-
}
|
|
1159
1176
|
try {
|
|
1160
|
-
const
|
|
1161
|
-
|
|
1177
|
+
const result = await readReplicasConfigFromDir(repo.path);
|
|
1178
|
+
if (!result) {
|
|
1179
|
+
continue;
|
|
1180
|
+
}
|
|
1162
1181
|
configs.push({
|
|
1163
1182
|
repoName: repo.name,
|
|
1164
1183
|
workingDirectory: repo.path,
|
|
1165
1184
|
defaultBranch: repo.defaultBranch,
|
|
1166
|
-
config
|
|
1185
|
+
config: result.config
|
|
1167
1186
|
});
|
|
1168
1187
|
} catch (error) {
|
|
1169
1188
|
if (error instanceof SyntaxError) {
|
|
1170
|
-
console.error(`[ReplicasConfig] Failed to parse ${repo.name}
|
|
1189
|
+
console.error(`[ReplicasConfig] Failed to parse ${repo.name} config:`, error.message);
|
|
1171
1190
|
} else if (error instanceof Error) {
|
|
1172
|
-
console.error(`[ReplicasConfig] Error loading ${repo.name}
|
|
1191
|
+
console.error(`[ReplicasConfig] Error loading ${repo.name} config:`, error.message);
|
|
1173
1192
|
}
|
|
1174
1193
|
}
|
|
1175
1194
|
}
|
|
@@ -1368,22 +1387,77 @@ var EventService = class {
|
|
|
1368
1387
|
};
|
|
1369
1388
|
var eventService = new EventService();
|
|
1370
1389
|
|
|
1390
|
+
// src/services/preview-service.ts
|
|
1391
|
+
import { mkdir as mkdir6, readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
|
|
1392
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1393
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1394
|
+
import { dirname } from "path";
|
|
1395
|
+
var PREVIEW_PORTS_FILE = SANDBOX_PATHS.REPLICAS_PREVIEW_PORTS_FILE;
|
|
1396
|
+
async function readPreviewsFile() {
|
|
1397
|
+
try {
|
|
1398
|
+
if (!existsSync5(PREVIEW_PORTS_FILE)) {
|
|
1399
|
+
return { previews: [] };
|
|
1400
|
+
}
|
|
1401
|
+
const raw = await readFile4(PREVIEW_PORTS_FILE, "utf-8");
|
|
1402
|
+
return JSON.parse(raw);
|
|
1403
|
+
} catch {
|
|
1404
|
+
return { previews: [] };
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
async function writePreviewsFile(data) {
|
|
1408
|
+
const dir = dirname(PREVIEW_PORTS_FILE);
|
|
1409
|
+
await mkdir6(dir, { recursive: true });
|
|
1410
|
+
await writeFile5(PREVIEW_PORTS_FILE, `${JSON.stringify(data, null, 2)}
|
|
1411
|
+
`, "utf-8");
|
|
1412
|
+
}
|
|
1413
|
+
var PreviewService = class {
|
|
1414
|
+
async initialize() {
|
|
1415
|
+
await writePreviewsFile({ previews: [] });
|
|
1416
|
+
}
|
|
1417
|
+
async addPreview(port2, publicUrl) {
|
|
1418
|
+
const data = await readPreviewsFile();
|
|
1419
|
+
const existing = data.previews.find((p) => p.port === port2);
|
|
1420
|
+
if (existing) {
|
|
1421
|
+
throw new Error(`Preview already exists for port ${port2}`);
|
|
1422
|
+
}
|
|
1423
|
+
const preview = {
|
|
1424
|
+
port: port2,
|
|
1425
|
+
publicUrl,
|
|
1426
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1427
|
+
};
|
|
1428
|
+
data.previews.push(preview);
|
|
1429
|
+
await writePreviewsFile(data);
|
|
1430
|
+
eventService.publish({
|
|
1431
|
+
id: randomUUID2(),
|
|
1432
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1433
|
+
type: "preview.changed",
|
|
1434
|
+
payload: { previews: data.previews }
|
|
1435
|
+
});
|
|
1436
|
+
return preview;
|
|
1437
|
+
}
|
|
1438
|
+
async listPreviews() {
|
|
1439
|
+
const data = await readPreviewsFile();
|
|
1440
|
+
return data.previews;
|
|
1441
|
+
}
|
|
1442
|
+
};
|
|
1443
|
+
var previewService = new PreviewService();
|
|
1444
|
+
|
|
1371
1445
|
// src/services/chat/chat-service.ts
|
|
1372
|
-
import { mkdir as
|
|
1446
|
+
import { mkdir as mkdir9, readFile as readFile7, rm, writeFile as writeFile7 } from "fs/promises";
|
|
1373
1447
|
import { homedir as homedir9 } from "os";
|
|
1374
1448
|
import { join as join10 } from "path";
|
|
1375
|
-
import { randomUUID as
|
|
1449
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
1376
1450
|
|
|
1377
1451
|
// src/managers/claude-manager.ts
|
|
1378
1452
|
import {
|
|
1379
1453
|
query
|
|
1380
1454
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
1381
1455
|
import { join as join8 } from "path";
|
|
1382
|
-
import { mkdir as
|
|
1456
|
+
import { mkdir as mkdir7, appendFile as appendFile4 } from "fs/promises";
|
|
1383
1457
|
import { homedir as homedir7 } from "os";
|
|
1384
1458
|
|
|
1385
1459
|
// src/utils/jsonl-reader.ts
|
|
1386
|
-
import { readFile as
|
|
1460
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
1387
1461
|
function isJsonlEvent(value) {
|
|
1388
1462
|
if (!isRecord(value)) {
|
|
1389
1463
|
return false;
|
|
@@ -1405,7 +1479,7 @@ function parseJsonlEvents(lines) {
|
|
|
1405
1479
|
}
|
|
1406
1480
|
async function readJSONL(filePath) {
|
|
1407
1481
|
try {
|
|
1408
|
-
const content = await
|
|
1482
|
+
const content = await readFile5(filePath, "utf-8");
|
|
1409
1483
|
const lines = content.split("\n").filter((line) => line.trim());
|
|
1410
1484
|
return parseJsonlEvents(lines);
|
|
1411
1485
|
} catch (error) {
|
|
@@ -2281,7 +2355,7 @@ var ClaudeManager = class extends CodingAgentManager {
|
|
|
2281
2355
|
}
|
|
2282
2356
|
async initialize() {
|
|
2283
2357
|
const historyDir = join8(homedir7(), ".replicas", "claude");
|
|
2284
|
-
await
|
|
2358
|
+
await mkdir7(historyDir, { recursive: true });
|
|
2285
2359
|
if (this.initialSessionId) {
|
|
2286
2360
|
this.sessionId = this.initialSessionId;
|
|
2287
2361
|
console.log(`[ClaudeManager] Restored session ID from persisted state: ${this.sessionId}`);
|
|
@@ -2309,9 +2383,9 @@ var ClaudeManager = class extends CodingAgentManager {
|
|
|
2309
2383
|
|
|
2310
2384
|
// src/managers/codex-manager.ts
|
|
2311
2385
|
import { Codex } from "@openai/codex-sdk";
|
|
2312
|
-
import { randomUUID as
|
|
2313
|
-
import { readdir as readdir2, stat as stat2, writeFile as
|
|
2314
|
-
import { existsSync as
|
|
2386
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
2387
|
+
import { readdir as readdir2, stat as stat2, writeFile as writeFile6, mkdir as mkdir8, readFile as readFile6 } from "fs/promises";
|
|
2388
|
+
import { existsSync as existsSync6 } from "fs";
|
|
2315
2389
|
import { join as join9 } from "path";
|
|
2316
2390
|
import { homedir as homedir8 } from "os";
|
|
2317
2391
|
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
@@ -2359,11 +2433,11 @@ var CodexManager = class extends CodingAgentManager {
|
|
|
2359
2433
|
async updateCodexConfig(developerInstructions) {
|
|
2360
2434
|
try {
|
|
2361
2435
|
const codexDir = join9(homedir8(), ".codex");
|
|
2362
|
-
await
|
|
2436
|
+
await mkdir8(codexDir, { recursive: true });
|
|
2363
2437
|
let config = {};
|
|
2364
|
-
if (
|
|
2438
|
+
if (existsSync6(CODEX_CONFIG_PATH)) {
|
|
2365
2439
|
try {
|
|
2366
|
-
const existingContent = await
|
|
2440
|
+
const existingContent = await readFile6(CODEX_CONFIG_PATH, "utf-8");
|
|
2367
2441
|
const parsed = parseToml(existingContent);
|
|
2368
2442
|
if (isRecord(parsed)) {
|
|
2369
2443
|
config = parsed;
|
|
@@ -2378,7 +2452,7 @@ var CodexManager = class extends CodingAgentManager {
|
|
|
2378
2452
|
delete config.developer_instructions;
|
|
2379
2453
|
}
|
|
2380
2454
|
const tomlContent = stringifyToml(config);
|
|
2381
|
-
await
|
|
2455
|
+
await writeFile6(CODEX_CONFIG_PATH, tomlContent, "utf-8");
|
|
2382
2456
|
console.log("[CodexManager] Updated config.toml with developer_instructions");
|
|
2383
2457
|
} catch (error) {
|
|
2384
2458
|
console.error("[CodexManager] Failed to update config.toml:", error);
|
|
@@ -2389,14 +2463,14 @@ var CodexManager = class extends CodingAgentManager {
|
|
|
2389
2463
|
* @returns Array of temp file paths
|
|
2390
2464
|
*/
|
|
2391
2465
|
async saveImagesToTempFiles(images) {
|
|
2392
|
-
await
|
|
2466
|
+
await mkdir8(this.tempImageDir, { recursive: true });
|
|
2393
2467
|
const tempPaths = [];
|
|
2394
2468
|
for (const image of images) {
|
|
2395
2469
|
const ext = image.source.media_type.split("/")[1] || "png";
|
|
2396
|
-
const filename = `img_${
|
|
2470
|
+
const filename = `img_${randomUUID3()}.${ext}`;
|
|
2397
2471
|
const filepath = join9(this.tempImageDir, filename);
|
|
2398
2472
|
const buffer = Buffer.from(image.source.data, "base64");
|
|
2399
|
-
await
|
|
2473
|
+
await writeFile6(filepath, buffer);
|
|
2400
2474
|
tempPaths.push(filepath);
|
|
2401
2475
|
}
|
|
2402
2476
|
return tempPaths;
|
|
@@ -2588,7 +2662,7 @@ var CodexManager = class extends CodingAgentManager {
|
|
|
2588
2662
|
const seenLines = /* @__PURE__ */ new Set();
|
|
2589
2663
|
const seedSeenLines = async () => {
|
|
2590
2664
|
try {
|
|
2591
|
-
const content = await
|
|
2665
|
+
const content = await readFile6(sessionFile, "utf-8");
|
|
2592
2666
|
const lines = content.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
2593
2667
|
for (const line of lines) {
|
|
2594
2668
|
seenLines.add(line);
|
|
@@ -2600,7 +2674,7 @@ var CodexManager = class extends CodingAgentManager {
|
|
|
2600
2674
|
const pump = async () => {
|
|
2601
2675
|
let emitted = 0;
|
|
2602
2676
|
try {
|
|
2603
|
-
const content = await
|
|
2677
|
+
const content = await readFile6(sessionFile, "utf-8");
|
|
2604
2678
|
const lines = content.split("\n");
|
|
2605
2679
|
const completeLines = content.endsWith("\n") ? lines : lines.slice(0, -1);
|
|
2606
2680
|
for (const line of completeLines) {
|
|
@@ -2688,8 +2762,8 @@ var ChatService = class {
|
|
|
2688
2762
|
chats = /* @__PURE__ */ new Map();
|
|
2689
2763
|
writeChain = Promise.resolve();
|
|
2690
2764
|
async initialize() {
|
|
2691
|
-
await
|
|
2692
|
-
await
|
|
2765
|
+
await mkdir9(ENGINE_DIR2, { recursive: true });
|
|
2766
|
+
await mkdir9(CLAUDE_HISTORY_DIR, { recursive: true });
|
|
2693
2767
|
const persisted = await this.loadChats();
|
|
2694
2768
|
for (const chat of persisted) {
|
|
2695
2769
|
const runtime = this.createRuntimeChat(chat);
|
|
@@ -2710,7 +2784,7 @@ var ChatService = class {
|
|
|
2710
2784
|
throw new DuplicateDefaultChatError(request.provider);
|
|
2711
2785
|
}
|
|
2712
2786
|
const persisted = {
|
|
2713
|
-
id:
|
|
2787
|
+
id: randomUUID4(),
|
|
2714
2788
|
provider: request.provider,
|
|
2715
2789
|
title,
|
|
2716
2790
|
createdAt: now,
|
|
@@ -2912,7 +2986,7 @@ var ChatService = class {
|
|
|
2912
2986
|
}
|
|
2913
2987
|
async loadChats() {
|
|
2914
2988
|
try {
|
|
2915
|
-
const content = await
|
|
2989
|
+
const content = await readFile7(CHATS_FILE, "utf-8");
|
|
2916
2990
|
const parsed = JSON.parse(content);
|
|
2917
2991
|
if (!Array.isArray(parsed)) {
|
|
2918
2992
|
return [];
|
|
@@ -2929,13 +3003,13 @@ var ChatService = class {
|
|
|
2929
3003
|
this.writeChain = this.writeChain.catch(() => {
|
|
2930
3004
|
}).then(async () => {
|
|
2931
3005
|
const payload = Array.from(this.chats.values()).map((chat) => chat.persisted);
|
|
2932
|
-
await
|
|
3006
|
+
await writeFile7(CHATS_FILE, JSON.stringify(payload, null, 2), "utf-8");
|
|
2933
3007
|
});
|
|
2934
3008
|
await this.writeChain;
|
|
2935
3009
|
}
|
|
2936
3010
|
async publish(input) {
|
|
2937
3011
|
const event = {
|
|
2938
|
-
id:
|
|
3012
|
+
id: randomUUID4(),
|
|
2939
3013
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2940
3014
|
...input
|
|
2941
3015
|
};
|
|
@@ -2954,7 +3028,7 @@ import { Hono } from "hono";
|
|
|
2954
3028
|
import { z } from "zod";
|
|
2955
3029
|
|
|
2956
3030
|
// src/services/plan-service.ts
|
|
2957
|
-
import { readdir as readdir3, readFile as
|
|
3031
|
+
import { readdir as readdir3, readFile as readFile8 } from "fs/promises";
|
|
2958
3032
|
import { homedir as homedir10 } from "os";
|
|
2959
3033
|
import { basename, join as join11 } from "path";
|
|
2960
3034
|
var PLAN_DIRECTORIES = [
|
|
@@ -2995,7 +3069,7 @@ var PlanService = class {
|
|
|
2995
3069
|
for (const directory of PLAN_DIRECTORIES) {
|
|
2996
3070
|
const filePath = join11(directory, safeFilename);
|
|
2997
3071
|
try {
|
|
2998
|
-
const content = await
|
|
3072
|
+
const content = await readFile8(filePath, "utf-8");
|
|
2999
3073
|
return { filename: safeFilename, content };
|
|
3000
3074
|
} catch {
|
|
3001
3075
|
}
|
|
@@ -3008,7 +3082,8 @@ var planService = new PlanService();
|
|
|
3008
3082
|
// src/services/warm-hooks-service.ts
|
|
3009
3083
|
import { execFile as execFile2 } from "child_process";
|
|
3010
3084
|
import { promisify as promisify3 } from "util";
|
|
3011
|
-
import { readFile as
|
|
3085
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
3086
|
+
import { existsSync as existsSync7 } from "fs";
|
|
3012
3087
|
import { join as join12 } from "path";
|
|
3013
3088
|
var execFileAsync2 = promisify3(execFile2);
|
|
3014
3089
|
async function executeHookScript(params) {
|
|
@@ -3039,21 +3114,27 @@ async function executeHookScript(params) {
|
|
|
3039
3114
|
}
|
|
3040
3115
|
}
|
|
3041
3116
|
async function readRepoWarmHook(repoPath) {
|
|
3042
|
-
const
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
if (!isRecord(parsed) || !("warmHook" in parsed)) {
|
|
3047
|
-
return null;
|
|
3117
|
+
for (const filename of REPLICAS_CONFIG_FILENAMES) {
|
|
3118
|
+
const configPath = join12(repoPath, filename);
|
|
3119
|
+
if (!existsSync7(configPath)) {
|
|
3120
|
+
continue;
|
|
3048
3121
|
}
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3122
|
+
try {
|
|
3123
|
+
const raw = await readFile9(configPath, "utf-8");
|
|
3124
|
+
const config = parseReplicasConfigString(raw, filename);
|
|
3125
|
+
if (!config.warmHook) {
|
|
3126
|
+
return null;
|
|
3127
|
+
}
|
|
3128
|
+
return resolveWarmHookConfig(config.warmHook);
|
|
3129
|
+
} catch (error) {
|
|
3130
|
+
const nodeError = error;
|
|
3131
|
+
if (nodeError?.code === "ENOENT") {
|
|
3132
|
+
continue;
|
|
3133
|
+
}
|
|
3134
|
+
throw error;
|
|
3054
3135
|
}
|
|
3055
|
-
throw error;
|
|
3056
3136
|
}
|
|
3137
|
+
return null;
|
|
3057
3138
|
}
|
|
3058
3139
|
async function collectRepoWarmHooks() {
|
|
3059
3140
|
const repos = await gitService.listRepositories();
|
|
@@ -3152,6 +3233,10 @@ var createChatSchema = z.object({
|
|
|
3152
3233
|
title: z.string().min(1).optional()
|
|
3153
3234
|
});
|
|
3154
3235
|
var imageMediaTypeSchema = z.enum(IMAGE_MEDIA_TYPES);
|
|
3236
|
+
var createPreviewSchema = z.object({
|
|
3237
|
+
port: z.number().int().min(1).max(65535),
|
|
3238
|
+
publicUrl: z.string().min(1)
|
|
3239
|
+
});
|
|
3155
3240
|
var sendMessageSchema = z.object({
|
|
3156
3241
|
message: z.string().min(1),
|
|
3157
3242
|
model: z.string().optional(),
|
|
@@ -3399,7 +3484,6 @@ function createV1Routes(deps) {
|
|
|
3399
3484
|
try {
|
|
3400
3485
|
const body = await c.req.json();
|
|
3401
3486
|
const result = await runWarmHooks({
|
|
3402
|
-
skills: Array.isArray(body.skills) ? body.skills : [],
|
|
3403
3487
|
organizationWarmHook: body.organizationWarmHook,
|
|
3404
3488
|
includeRepoHooks: body.includeRepoHooks,
|
|
3405
3489
|
timeoutMs: body.timeoutMs
|
|
@@ -3416,6 +3500,30 @@ function createV1Routes(deps) {
|
|
|
3416
3500
|
);
|
|
3417
3501
|
}
|
|
3418
3502
|
});
|
|
3503
|
+
app2.get("/previews", async (c) => {
|
|
3504
|
+
try {
|
|
3505
|
+
const previews = await previewService.listPreviews();
|
|
3506
|
+
return c.json({ previews });
|
|
3507
|
+
} catch (error) {
|
|
3508
|
+
return c.json(
|
|
3509
|
+
jsonError("Failed to list previews", error instanceof Error ? error.message : "Unknown error"),
|
|
3510
|
+
500
|
|
3511
|
+
);
|
|
3512
|
+
}
|
|
3513
|
+
});
|
|
3514
|
+
app2.post("/previews", async (c) => {
|
|
3515
|
+
try {
|
|
3516
|
+
const body = createPreviewSchema.parse(await c.req.json());
|
|
3517
|
+
const preview = await previewService.addPreview(body.port, body.publicUrl);
|
|
3518
|
+
return c.json({ preview }, 201);
|
|
3519
|
+
} catch (error) {
|
|
3520
|
+
const details = error instanceof Error ? error.message : "Unknown error";
|
|
3521
|
+
if (details.includes("already exists")) {
|
|
3522
|
+
return c.json(jsonError("Preview already exists", details), 409);
|
|
3523
|
+
}
|
|
3524
|
+
return c.json(jsonError("Failed to create preview", details), 400);
|
|
3525
|
+
}
|
|
3526
|
+
});
|
|
3419
3527
|
return app2;
|
|
3420
3528
|
}
|
|
3421
3529
|
|
|
@@ -3456,7 +3564,7 @@ app.get("/health", async (c) => {
|
|
|
3456
3564
|
return c.json({ status: "initializing", timestamp: (/* @__PURE__ */ new Date()).toISOString() }, 503);
|
|
3457
3565
|
}
|
|
3458
3566
|
try {
|
|
3459
|
-
const logContent = await
|
|
3567
|
+
const logContent = await readFile10("/var/log/cloud-init-output.log", "utf-8");
|
|
3460
3568
|
let status;
|
|
3461
3569
|
if (logContent.includes(COMPLETION_MESSAGE)) {
|
|
3462
3570
|
status = "active";
|
|
@@ -3511,7 +3619,7 @@ function startStatusBroadcaster() {
|
|
|
3511
3619
|
if (serialized !== previousRepoStatus) {
|
|
3512
3620
|
previousRepoStatus = serialized;
|
|
3513
3621
|
eventService.publish({
|
|
3514
|
-
id:
|
|
3622
|
+
id: randomUUID5(),
|
|
3515
3623
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3516
3624
|
type: "repo.status.changed",
|
|
3517
3625
|
payload: { repos }
|
|
@@ -3531,7 +3639,7 @@ function startStatusBroadcaster() {
|
|
|
3531
3639
|
if (engineStatusJson !== previousEngineStatus) {
|
|
3532
3640
|
previousEngineStatus = engineStatusJson;
|
|
3533
3641
|
eventService.publish({
|
|
3534
|
-
id:
|
|
3642
|
+
id: randomUUID5(),
|
|
3535
3643
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3536
3644
|
type: "engine.status.changed",
|
|
3537
3645
|
payload: { status: engineStatus }
|
|
@@ -3552,7 +3660,7 @@ function startStatusBroadcaster() {
|
|
|
3552
3660
|
previousHookStatus = hookSnapshot;
|
|
3553
3661
|
if (!lastHooksRunning && hooksRunning) {
|
|
3554
3662
|
eventService.publish({
|
|
3555
|
-
id:
|
|
3663
|
+
id: randomUUID5(),
|
|
3556
3664
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3557
3665
|
type: "hooks.started",
|
|
3558
3666
|
payload: { running: true, completed: false }
|
|
@@ -3561,7 +3669,7 @@ function startStatusBroadcaster() {
|
|
|
3561
3669
|
}
|
|
3562
3670
|
if (hooksRunning) {
|
|
3563
3671
|
eventService.publish({
|
|
3564
|
-
id:
|
|
3672
|
+
id: randomUUID5(),
|
|
3565
3673
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3566
3674
|
type: "hooks.progress",
|
|
3567
3675
|
payload: { running: true, completed: false }
|
|
@@ -3570,7 +3678,7 @@ function startStatusBroadcaster() {
|
|
|
3570
3678
|
}
|
|
3571
3679
|
if (lastHooksRunning && !hooksRunning && hooksCompleted && !hooksFailed) {
|
|
3572
3680
|
eventService.publish({
|
|
3573
|
-
id:
|
|
3681
|
+
id: randomUUID5(),
|
|
3574
3682
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3575
3683
|
type: "hooks.completed",
|
|
3576
3684
|
payload: { running: false, completed: true }
|
|
@@ -3579,7 +3687,7 @@ function startStatusBroadcaster() {
|
|
|
3579
3687
|
}
|
|
3580
3688
|
if (lastHooksRunning && !hooksRunning && hooksFailed) {
|
|
3581
3689
|
eventService.publish({
|
|
3582
|
-
id:
|
|
3690
|
+
id: randomUUID5(),
|
|
3583
3691
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3584
3692
|
type: "hooks.failed",
|
|
3585
3693
|
payload: { running: false, completed: hooksCompleted }
|
|
@@ -3587,7 +3695,7 @@ function startStatusBroadcaster() {
|
|
|
3587
3695
|
});
|
|
3588
3696
|
}
|
|
3589
3697
|
eventService.publish({
|
|
3590
|
-
id:
|
|
3698
|
+
id: randomUUID5(),
|
|
3591
3699
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3592
3700
|
type: "hooks.status",
|
|
3593
3701
|
payload: {
|
|
@@ -3617,6 +3725,7 @@ serve(
|
|
|
3617
3725
|
}
|
|
3618
3726
|
await replicasConfigService.initialize();
|
|
3619
3727
|
await chatService.initialize();
|
|
3728
|
+
await previewService.initialize();
|
|
3620
3729
|
engineReady = true;
|
|
3621
3730
|
if (!IS_WARMING_MODE) {
|
|
3622
3731
|
await githubTokenManager.start();
|
|
@@ -3625,20 +3734,20 @@ serve(
|
|
|
3625
3734
|
}
|
|
3626
3735
|
const repos = await gitService.listRepos();
|
|
3627
3736
|
await eventService.publish({
|
|
3628
|
-
id:
|
|
3737
|
+
id: randomUUID5(),
|
|
3629
3738
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3630
3739
|
type: "repo.discovered",
|
|
3631
3740
|
payload: { repos }
|
|
3632
3741
|
});
|
|
3633
3742
|
const repoStatuses = await gitService.listRepos();
|
|
3634
3743
|
await eventService.publish({
|
|
3635
|
-
id:
|
|
3744
|
+
id: randomUUID5(),
|
|
3636
3745
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3637
3746
|
type: "repo.status.changed",
|
|
3638
3747
|
payload: { repos: repoStatuses }
|
|
3639
3748
|
});
|
|
3640
3749
|
await eventService.publish({
|
|
3641
|
-
id:
|
|
3750
|
+
id: randomUUID5(),
|
|
3642
3751
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3643
3752
|
type: "engine.ready",
|
|
3644
3753
|
payload: { version: "v1" }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "replicas-engine",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.55",
|
|
4
4
|
"description": "Lightweight API server for Replicas workspaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"@openai/codex-sdk": "^0.111.0",
|
|
33
33
|
"hono": "^4.10.3",
|
|
34
34
|
"smol-toml": "^1.6.0",
|
|
35
|
+
"yaml": "^2.8.2",
|
|
35
36
|
"zod": "^4.0.0"
|
|
36
37
|
},
|
|
37
38
|
"devDependencies": {
|