replicas-engine 0.1.144 → 0.1.146
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/{chunk-XCFRYWEV.js → chunk-2Y7PD56W.js} +12 -12
- package/dist/src/{chunk-JFXVYYQY.js → chunk-5VUQ55WW.js} +19 -19
- package/dist/src/{chunk-NPXMDMPW.js → chunk-AAVVX4U5.js} +6 -6
- package/dist/src/{chunk-J3WZZOOL.js → chunk-BUG7ZAQW.js} +72 -355
- package/dist/src/{chunk-TY2FR253.js → chunk-SGITM26Q.js} +309 -22
- package/dist/src/{chunk-YDW6RZI2.js → chunk-ST5JVROM.js} +2 -2
- package/dist/src/{chunk-WCAERHFE.js → chunk-SWX24AGM.js} +1 -1
- package/dist/src/{chunk-7RS3TOFT.js → chunk-TU2SAX7Z.js} +1072 -1228
- package/dist/src/{chunk-MCYTXPBZ.js → chunk-WPAL27HA.js} +2 -2
- package/dist/src/{chunk-N2BGF5AZ.js → chunk-ZNARSMJG.js} +1 -1
- package/dist/src/{dist-es-UV765YRF.js → dist-es-BUG4C6LP.js} +18 -18
- package/dist/src/{dist-es-3LYC7DO3.js → dist-es-F6RG3S4O.js} +12 -14
- package/dist/src/{dist-es-KHBICPEM.js → dist-es-GGHPJZ3K.js} +19 -19
- package/dist/src/{dist-es-7DTYZ3KD.js → dist-es-GZBSW2H3.js} +27 -27
- package/dist/src/{dist-es-YWO2URNL.js → dist-es-JIV2SPEM.js} +7 -7
- package/dist/src/{dist-es-BNJQGHZR.js → dist-es-Q3YZVSCP.js} +7 -7
- package/dist/src/{dist-es-EVINFDC2.js → dist-es-YKS4LXBO.js} +3 -3
- package/dist/src/index.js +480 -510
- package/dist/src/{loadSso-ZRFAKENS.js → loadSso-3WFGTQV7.js} +109 -148
- package/dist/src/{signin-PNR24U2H.js → signin-RSKKVVIS.js} +123 -177
- package/dist/src/{sso-oidc-O55BGCVQ.js → sso-oidc-LBVW2ORP.js} +111 -150
- package/dist/src/{sts-MRC7OH6Z.js → sts-DKGAU6YE.js} +2911 -718
- package/package.json +1 -1
- package/dist/src/chunk-5V5ZNFZK.js +0 -178
- package/dist/src/event-streams-IFAW3KBX.js +0 -244
package/dist/src/index.js
CHANGED
|
@@ -14,6 +14,313 @@ import path from "path";
|
|
|
14
14
|
// src/engine-env.ts
|
|
15
15
|
import { homedir } from "os";
|
|
16
16
|
import { join } from "path";
|
|
17
|
+
|
|
18
|
+
// src/runtime-env-loader.ts
|
|
19
|
+
import { readFileSync } from "fs";
|
|
20
|
+
|
|
21
|
+
// ../shared/src/event.ts
|
|
22
|
+
var CODEX_QUOTA_STATUS_EVENT_TYPE = "codex-quota-status";
|
|
23
|
+
|
|
24
|
+
// ../shared/src/pricing.ts
|
|
25
|
+
var PLANS = {
|
|
26
|
+
hobby: {
|
|
27
|
+
id: "hobby",
|
|
28
|
+
name: "Hobby",
|
|
29
|
+
monthlyPrice: 0,
|
|
30
|
+
seatPriceCents: 0,
|
|
31
|
+
creditsIncluded: 1200,
|
|
32
|
+
features: [
|
|
33
|
+
"1,200 minutes of human-initiated workspace usage (one-time)",
|
|
34
|
+
"1,200 minutes of API + automation usage (one-time)",
|
|
35
|
+
"Automations (limited to 2)",
|
|
36
|
+
"Warm pools and warm hooks",
|
|
37
|
+
"Up to 3 repositories"
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
developer: {
|
|
41
|
+
id: "developer",
|
|
42
|
+
name: "Developer",
|
|
43
|
+
monthlyPrice: 120,
|
|
44
|
+
seatPriceCents: 12e3,
|
|
45
|
+
creditsIncluded: 0,
|
|
46
|
+
features: [
|
|
47
|
+
"Unlimited human-initiated workspaces",
|
|
48
|
+
"5,000 included automation/API minutes per month",
|
|
49
|
+
"Up to 10 repositories",
|
|
50
|
+
"Up to 5 automations",
|
|
51
|
+
"Warm pools and warm hooks",
|
|
52
|
+
"API access"
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
team: {
|
|
56
|
+
id: "team",
|
|
57
|
+
name: "Team",
|
|
58
|
+
monthlyPrice: 300,
|
|
59
|
+
seatPriceCents: 3e4,
|
|
60
|
+
creditsIncluded: 0,
|
|
61
|
+
features: [
|
|
62
|
+
"Unlimited human-initiated workspaces",
|
|
63
|
+
"15,000 included automation/API minutes per month",
|
|
64
|
+
"Unlimited repositories",
|
|
65
|
+
"Unlimited automations",
|
|
66
|
+
"Higher API rate limits",
|
|
67
|
+
"Warm pools and warm hooks",
|
|
68
|
+
"Auto-upgraded sandbox resources (32 GB disk, 16 GB memory)",
|
|
69
|
+
"Shared Slack support channel"
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
enterprise: {
|
|
73
|
+
id: "enterprise",
|
|
74
|
+
name: "Enterprise",
|
|
75
|
+
monthlyPrice: 0,
|
|
76
|
+
seatPriceCents: 0,
|
|
77
|
+
creditsIncluded: 0,
|
|
78
|
+
features: [
|
|
79
|
+
"Unlimited usage",
|
|
80
|
+
"Unlimited automations",
|
|
81
|
+
"Custom API rates",
|
|
82
|
+
"Custom rate limits",
|
|
83
|
+
"Custom warm hooks and pools",
|
|
84
|
+
"Shared Slack support channel",
|
|
85
|
+
"SOC 2"
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
var TEAM_PLAN = PLANS.team;
|
|
90
|
+
var ENTERPRISE_PLAN = PLANS.enterprise;
|
|
91
|
+
|
|
92
|
+
// ../shared/src/sandbox.ts
|
|
93
|
+
var SANDBOX_LIFECYCLE = {
|
|
94
|
+
AUTO_STOP_MINUTES: 60,
|
|
95
|
+
AUTO_ARCHIVE_MINUTES: 60 * 24 * 7,
|
|
96
|
+
AUTO_DELETE_MINUTES: -1,
|
|
97
|
+
SSH_TOKEN_EXPIRATION_MINUTES: 3 * 60
|
|
98
|
+
};
|
|
99
|
+
var SANDBOX_PATHS = {
|
|
100
|
+
HOME_DIR: "/home/ubuntu",
|
|
101
|
+
WORKSPACES_DIR: "/home/ubuntu/workspaces",
|
|
102
|
+
REPLICAS_DIR: "/home/ubuntu/.replicas",
|
|
103
|
+
REPLICAS_FILES_DIR: "/home/ubuntu/.replicas/files",
|
|
104
|
+
REPLICAS_FILES_DISPLAY_DIR: "~/.replicas/files",
|
|
105
|
+
REPLICAS_RUNTIME_ENV_FILE: "/home/ubuntu/.replicas/runtime-env.sh",
|
|
106
|
+
REPLICAS_PREVIEW_PORTS_FILE: "/home/ubuntu/.replicas/preview-ports.json"
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// ../shared/src/runtime-env.ts
|
|
110
|
+
function parsePosixEnvFile(content) {
|
|
111
|
+
const result = {};
|
|
112
|
+
let pos = 0;
|
|
113
|
+
while (pos < content.length) {
|
|
114
|
+
let lineStart;
|
|
115
|
+
if (pos === 0 || content[pos - 1] === "\n") {
|
|
116
|
+
lineStart = pos;
|
|
117
|
+
} else {
|
|
118
|
+
const nl2 = content.indexOf("\n", pos);
|
|
119
|
+
if (nl2 === -1) break;
|
|
120
|
+
lineStart = nl2 + 1;
|
|
121
|
+
}
|
|
122
|
+
if (!content.startsWith("export ", lineStart)) {
|
|
123
|
+
const nl2 = content.indexOf("\n", lineStart);
|
|
124
|
+
if (nl2 === -1) break;
|
|
125
|
+
pos = nl2 + 1;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const nameStart = lineStart + "export ".length;
|
|
129
|
+
const eqIdx = content.indexOf("=", nameStart);
|
|
130
|
+
if (eqIdx === -1) break;
|
|
131
|
+
const name = content.slice(nameStart, eqIdx).trim();
|
|
132
|
+
let cursor = eqIdx + 1;
|
|
133
|
+
if (content[cursor] !== "'") {
|
|
134
|
+
const nl2 = content.indexOf("\n", cursor);
|
|
135
|
+
pos = nl2 === -1 ? content.length : nl2 + 1;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
cursor += 1;
|
|
139
|
+
let value = "";
|
|
140
|
+
let closed = false;
|
|
141
|
+
while (cursor < content.length) {
|
|
142
|
+
const ch = content[cursor];
|
|
143
|
+
if (ch === "'") {
|
|
144
|
+
if (content.startsWith("\\''", cursor + 1)) {
|
|
145
|
+
value += "'";
|
|
146
|
+
cursor += 4;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
cursor += 1;
|
|
150
|
+
closed = true;
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
value += ch;
|
|
154
|
+
cursor += 1;
|
|
155
|
+
}
|
|
156
|
+
if (closed && name) {
|
|
157
|
+
result[name] = value;
|
|
158
|
+
}
|
|
159
|
+
const nl = content.indexOf("\n", cursor);
|
|
160
|
+
pos = nl === -1 ? content.length : nl + 1;
|
|
161
|
+
}
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ../shared/src/replicas-config.ts
|
|
166
|
+
import { parse as parseYaml } from "yaml";
|
|
167
|
+
|
|
168
|
+
// ../shared/src/warm-hooks.ts
|
|
169
|
+
var DEFAULT_WARM_HOOK_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
170
|
+
var MAX_WARM_HOOK_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
171
|
+
var DEFAULT_HOOK_OUTPUT_PREVIEW_CHARS = 1e5;
|
|
172
|
+
var HOOK_EXEC_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
173
|
+
function isRecord(value) {
|
|
174
|
+
return typeof value === "object" && value !== null;
|
|
175
|
+
}
|
|
176
|
+
function clampWarmHookTimeoutMs(timeoutMs) {
|
|
177
|
+
if (!timeoutMs || Number.isNaN(timeoutMs) || timeoutMs <= 0) {
|
|
178
|
+
return DEFAULT_WARM_HOOK_TIMEOUT_MS;
|
|
179
|
+
}
|
|
180
|
+
return Math.min(timeoutMs, MAX_WARM_HOOK_TIMEOUT_MS);
|
|
181
|
+
}
|
|
182
|
+
function buildHookOutputPreview(text, maxChars = DEFAULT_HOOK_OUTPUT_PREVIEW_CHARS) {
|
|
183
|
+
if (text.length <= maxChars) {
|
|
184
|
+
return { output: text, outputTruncated: false, outputTotalChars: text.length };
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
output: `${text.slice(0, maxChars)}
|
|
188
|
+
...[truncated \u2014 download the full log to see the rest]`,
|
|
189
|
+
outputTruncated: true,
|
|
190
|
+
outputTotalChars: text.length
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function parseWarmHookConfig(value, filename = "replicas.json") {
|
|
194
|
+
if (typeof value === "string") {
|
|
195
|
+
return value;
|
|
196
|
+
}
|
|
197
|
+
if (!isRecord(value)) {
|
|
198
|
+
throw new Error(`Invalid ${filename}: "warmHook" must be a string or object`);
|
|
199
|
+
}
|
|
200
|
+
if (!Array.isArray(value.commands) || !value.commands.every((entry) => typeof entry === "string")) {
|
|
201
|
+
throw new Error(`Invalid ${filename}: "warmHook.commands" must be an array of shell commands`);
|
|
202
|
+
}
|
|
203
|
+
if (value.timeout !== void 0 && (typeof value.timeout !== "number" || value.timeout <= 0)) {
|
|
204
|
+
throw new Error(`Invalid ${filename}: "warmHook.timeout" must be a positive number`);
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
commands: value.commands,
|
|
208
|
+
timeout: value.timeout
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function resolveWarmHookConfig(value) {
|
|
212
|
+
if (value === void 0) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
const parsed = parseWarmHookConfig(value);
|
|
216
|
+
const commands = typeof parsed === "string" ? [parsed.trim()].filter(Boolean) : parsed.commands.map((command) => command.trim()).filter(Boolean);
|
|
217
|
+
if (commands.length === 0) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
commands,
|
|
222
|
+
timeoutMs: typeof parsed === "string" ? void 0 : parsed.timeout
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ../shared/src/replicas-config.ts
|
|
227
|
+
var REPLICAS_CONFIG_FILENAMES = ["replicas.json", "replicas.yaml", "replicas.yml"];
|
|
228
|
+
function isRecord2(value) {
|
|
229
|
+
return typeof value === "object" && value !== null;
|
|
230
|
+
}
|
|
231
|
+
function parseReplicasConfig(value, filename = "replicas.json") {
|
|
232
|
+
if (!isRecord2(value)) {
|
|
233
|
+
throw new Error(`Invalid ${filename}: expected an object`);
|
|
234
|
+
}
|
|
235
|
+
const config = {};
|
|
236
|
+
if ("organizationId" in value) {
|
|
237
|
+
if (typeof value.organizationId !== "string") {
|
|
238
|
+
throw new Error(`Invalid ${filename}: "organizationId" must be a string`);
|
|
239
|
+
}
|
|
240
|
+
config.organizationId = value.organizationId;
|
|
241
|
+
}
|
|
242
|
+
if ("systemPrompt" in value) {
|
|
243
|
+
if (typeof value.systemPrompt !== "string") {
|
|
244
|
+
throw new Error(`Invalid ${filename}: "systemPrompt" must be a string`);
|
|
245
|
+
}
|
|
246
|
+
config.systemPrompt = value.systemPrompt;
|
|
247
|
+
}
|
|
248
|
+
if ("startHook" in value) {
|
|
249
|
+
if (!isRecord2(value.startHook)) {
|
|
250
|
+
throw new Error(`Invalid ${filename}: "startHook" must be an object with "commands" array`);
|
|
251
|
+
}
|
|
252
|
+
const { commands, timeout } = value.startHook;
|
|
253
|
+
if (!Array.isArray(commands) || !commands.every((entry) => typeof entry === "string")) {
|
|
254
|
+
throw new Error(`Invalid ${filename}: "startHook.commands" must be an array of shell commands`);
|
|
255
|
+
}
|
|
256
|
+
if (timeout !== void 0 && (typeof timeout !== "number" || timeout <= 0)) {
|
|
257
|
+
throw new Error(`Invalid ${filename}: "startHook.timeout" must be a positive number`);
|
|
258
|
+
}
|
|
259
|
+
config.startHook = { commands, timeout };
|
|
260
|
+
}
|
|
261
|
+
if ("warmHook" in value) {
|
|
262
|
+
config.warmHook = parseWarmHookConfig(value.warmHook, filename);
|
|
263
|
+
}
|
|
264
|
+
return config;
|
|
265
|
+
}
|
|
266
|
+
function parseReplicasConfigString(content, filename) {
|
|
267
|
+
const isYaml = filename.endsWith(".yaml") || filename.endsWith(".yml");
|
|
268
|
+
let parsed;
|
|
269
|
+
if (isYaml) {
|
|
270
|
+
parsed = parseYaml(content);
|
|
271
|
+
} else {
|
|
272
|
+
parsed = JSON.parse(content);
|
|
273
|
+
}
|
|
274
|
+
return parseReplicasConfig(parsed, filename);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ../shared/src/engine/environment.ts
|
|
278
|
+
var DAYTONA_SNAPSHOT_ID = "07-05-2026-royal-york-v3";
|
|
279
|
+
|
|
280
|
+
// ../shared/src/engine/types.ts
|
|
281
|
+
var DEFAULT_CHAT_TITLES = {
|
|
282
|
+
claude: "Claude Code",
|
|
283
|
+
codex: "Codex",
|
|
284
|
+
relay: "Relay"
|
|
285
|
+
};
|
|
286
|
+
var IMAGE_MEDIA_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
|
|
287
|
+
|
|
288
|
+
// ../shared/src/routes/workspaces.ts
|
|
289
|
+
var WORKSPACE_FILE_UPLOAD_MAX_SIZE_BYTES = 20 * 1024 * 1024;
|
|
290
|
+
var WORKSPACE_FILE_CONTENT_MAX_SIZE_BYTES = 1 * 1024 * 1024;
|
|
291
|
+
|
|
292
|
+
// ../shared/src/audit-log.ts
|
|
293
|
+
var AUDIT_LOG_ACTION = {
|
|
294
|
+
CREATE: "create",
|
|
295
|
+
UPDATE: "update",
|
|
296
|
+
DELETE: "delete",
|
|
297
|
+
CONNECT: "connect",
|
|
298
|
+
DISCONNECT: "disconnect"
|
|
299
|
+
};
|
|
300
|
+
var AUDIT_LOG_ACTIONS = Object.values(AUDIT_LOG_ACTION);
|
|
301
|
+
|
|
302
|
+
// ../shared/src/object-store/types.ts
|
|
303
|
+
var MEDIA_KIND = {
|
|
304
|
+
IMAGE: "image",
|
|
305
|
+
VIDEO: "video",
|
|
306
|
+
AUDIO: "audio"
|
|
307
|
+
};
|
|
308
|
+
var MEDIA_KINDS = [MEDIA_KIND.IMAGE, MEDIA_KIND.VIDEO, MEDIA_KIND.AUDIO];
|
|
309
|
+
|
|
310
|
+
// src/runtime-env-loader.ts
|
|
311
|
+
function loadRuntimeEnvFile() {
|
|
312
|
+
let content;
|
|
313
|
+
try {
|
|
314
|
+
content = readFileSync(SANDBOX_PATHS.REPLICAS_RUNTIME_ENV_FILE, "utf-8");
|
|
315
|
+
} catch {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
for (const [key, value] of Object.entries(parsePosixEnvFile(content))) {
|
|
319
|
+
process.env[key] = value;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// src/engine-env.ts
|
|
17
324
|
function readEnv(name) {
|
|
18
325
|
const value = process.env[name]?.trim();
|
|
19
326
|
return value ? value : void 0;
|
|
@@ -56,6 +363,7 @@ function parseCodexAuthMethod(value) {
|
|
|
56
363
|
}
|
|
57
364
|
var IS_WARMING_MODE = process.argv.includes("--warming");
|
|
58
365
|
function loadEngineEnv() {
|
|
366
|
+
loadRuntimeEnvFile();
|
|
59
367
|
const HOME_DIR = homedir();
|
|
60
368
|
const env = {
|
|
61
369
|
// Defined: always available
|
|
@@ -386,7 +694,7 @@ import { join as join2 } from "path";
|
|
|
386
694
|
import { homedir as homedir2 } from "os";
|
|
387
695
|
|
|
388
696
|
// src/utils/type-guards.ts
|
|
389
|
-
function
|
|
697
|
+
function isRecord3(value) {
|
|
390
698
|
return typeof value === "object" && value !== null;
|
|
391
699
|
}
|
|
392
700
|
|
|
@@ -418,10 +726,10 @@ async function updateEngineState(updater) {
|
|
|
418
726
|
});
|
|
419
727
|
}
|
|
420
728
|
function isEngineRepoDiff(value) {
|
|
421
|
-
return
|
|
729
|
+
return isRecord3(value) && typeof value.added === "number" && typeof value.removed === "number";
|
|
422
730
|
}
|
|
423
731
|
function coerceRepoState(value) {
|
|
424
|
-
if (!
|
|
732
|
+
if (!isRecord3(value)) {
|
|
425
733
|
return null;
|
|
426
734
|
}
|
|
427
735
|
if (typeof value.name !== "string") return null;
|
|
@@ -447,11 +755,11 @@ function coerceRepoState(value) {
|
|
|
447
755
|
};
|
|
448
756
|
}
|
|
449
757
|
function coerceEngineState(value) {
|
|
450
|
-
if (!
|
|
758
|
+
if (!isRecord3(value)) {
|
|
451
759
|
return {};
|
|
452
760
|
}
|
|
453
761
|
const partial = {};
|
|
454
|
-
if (
|
|
762
|
+
if (isRecord3(value.repos)) {
|
|
455
763
|
const repos = {};
|
|
456
764
|
for (const [repoName, repoState] of Object.entries(value.repos)) {
|
|
457
765
|
const coerced = coerceRepoState(repoState);
|
|
@@ -502,7 +810,7 @@ async function saveRepoState(repoName, state, fallbackState) {
|
|
|
502
810
|
|
|
503
811
|
// src/git/commands.ts
|
|
504
812
|
import { execFileSync } from "child_process";
|
|
505
|
-
import { readFileSync } from "fs";
|
|
813
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
506
814
|
import { join as join3 } from "path";
|
|
507
815
|
function runGitCommand(args, cwd) {
|
|
508
816
|
return execFileSync("git", args, {
|
|
@@ -513,7 +821,7 @@ function runGitCommand(args, cwd) {
|
|
|
513
821
|
}
|
|
514
822
|
function readRepoHeadBranch(repoPath) {
|
|
515
823
|
try {
|
|
516
|
-
const contents =
|
|
824
|
+
const contents = readFileSync2(join3(repoPath, ".git", "HEAD"), "utf-8").trim();
|
|
517
825
|
const match = contents.match(/^ref:\s+refs\/heads\/(.+)$/);
|
|
518
826
|
return match ? match[1] : null;
|
|
519
827
|
} catch {
|
|
@@ -799,414 +1107,174 @@ var GitService = class {
|
|
|
799
1107
|
encoding: "utf-8",
|
|
800
1108
|
stdio: ["pipe", "pipe", "pipe"]
|
|
801
1109
|
}).trim();
|
|
802
|
-
if (!remoteRef) {
|
|
803
|
-
return { status: "not_found" };
|
|
804
|
-
}
|
|
805
|
-
} catch {
|
|
806
|
-
return { status: "error" };
|
|
807
|
-
}
|
|
808
|
-
try {
|
|
809
|
-
const prInfo = execFileSync2("gh", ["pr", "view", branch, "--json", "url", "--jq", ".url"], {
|
|
810
|
-
cwd: repoPath,
|
|
811
|
-
encoding: "utf-8",
|
|
812
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
813
|
-
}).trim();
|
|
814
|
-
return prInfo ? { status: "found", url: prInfo } : { status: "not_found" };
|
|
815
|
-
} catch (error) {
|
|
816
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
817
|
-
console.warn(`[GitService] gh pr view ${branch} failed for ${repoName}: ${message}`);
|
|
818
|
-
return { status: "error" };
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
resolveDefaultBranch(repoPath) {
|
|
822
|
-
const cached = this.defaultBranchCache.get(repoPath);
|
|
823
|
-
if (cached) {
|
|
824
|
-
return cached;
|
|
825
|
-
}
|
|
826
|
-
const fromSymbolicRef = this.resolveDefaultBranchFromSymbolicRef(repoPath);
|
|
827
|
-
if (fromSymbolicRef) {
|
|
828
|
-
this.defaultBranchCache.set(repoPath, fromSymbolicRef);
|
|
829
|
-
return fromSymbolicRef;
|
|
830
|
-
}
|
|
831
|
-
const fallback = "main";
|
|
832
|
-
this.defaultBranchCache.set(repoPath, fallback);
|
|
833
|
-
return fallback;
|
|
834
|
-
}
|
|
835
|
-
resolveDefaultBranchFromSymbolicRef(repoPath) {
|
|
836
|
-
try {
|
|
837
|
-
const output = runGitCommand(["symbolic-ref", "--short", "refs/remotes/origin/HEAD"], repoPath);
|
|
838
|
-
const match = output.match(/^origin\/(.+)$/);
|
|
839
|
-
return match ? match[1] : null;
|
|
840
|
-
} catch {
|
|
841
|
-
return null;
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
findAvailableBranchName(baseName, cwd) {
|
|
845
|
-
const sanitizedBaseName = this.sanitizeBranchName(baseName);
|
|
846
|
-
if (!branchExists(sanitizedBaseName, cwd)) {
|
|
847
|
-
return sanitizedBaseName;
|
|
848
|
-
}
|
|
849
|
-
return `${sanitizedBaseName}-${Date.now()}`;
|
|
850
|
-
}
|
|
851
|
-
sanitizeBranchName(name) {
|
|
852
|
-
const normalized = name.toLowerCase().replace(/[^a-z0-9._/-]+/g, "-").replace(/\/{2,}/g, "/").replace(/^-+|-+$/g, "");
|
|
853
|
-
return normalized || "replicas";
|
|
854
|
-
}
|
|
855
|
-
async refreshRepoMetadata(repo, currentBranch, startHooksCompleted, persistedState, observedBranches) {
|
|
856
|
-
const prResult = await this.getPullRequestUrl(repo.name, repo.path, currentBranch, persistedState);
|
|
857
|
-
let prUrls = persistedState?.prUrls ?? [];
|
|
858
|
-
if (prResult.status === "found") {
|
|
859
|
-
prUrls = appendUniqueUrl(prUrls, prResult.url);
|
|
860
|
-
}
|
|
861
|
-
if (observedBranches) {
|
|
862
|
-
for (const branch of observedBranches) {
|
|
863
|
-
if (branch === currentBranch) continue;
|
|
864
|
-
const branchResult = this.lookupPrOnRemote(repo.name, repo.path, branch);
|
|
865
|
-
if (branchResult.status === "found") {
|
|
866
|
-
prUrls = appendUniqueUrl(prUrls, branchResult.url);
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
const state = {
|
|
871
|
-
name: repo.name,
|
|
872
|
-
path: repo.path,
|
|
873
|
-
defaultBranch: repo.defaultBranch,
|
|
874
|
-
currentBranch,
|
|
875
|
-
prUrls,
|
|
876
|
-
gitDiff: this.getGitDiffStats(repo.path, repo.defaultBranch),
|
|
877
|
-
startHooksCompleted
|
|
878
|
-
};
|
|
879
|
-
await saveRepoState(repo.name, state, state);
|
|
880
|
-
return state;
|
|
881
|
-
}
|
|
882
|
-
pathExists(path4) {
|
|
883
|
-
return existsSync2(path4);
|
|
884
|
-
}
|
|
885
|
-
async safeStat(path4) {
|
|
886
|
-
try {
|
|
887
|
-
return await stat(path4);
|
|
888
|
-
} catch {
|
|
889
|
-
return null;
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
};
|
|
893
|
-
var gitService = new GitService();
|
|
894
|
-
|
|
895
|
-
// src/utils/logger.ts
|
|
896
|
-
import { appendFile, mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
897
|
-
import { homedir as homedir3 } from "os";
|
|
898
|
-
import { join as join5 } from "path";
|
|
899
|
-
import { format } from "util";
|
|
900
|
-
import { randomBytes } from "crypto";
|
|
901
|
-
var LOG_DIR = join5(homedir3(), ".replicas", "logs");
|
|
902
|
-
var EngineLogger = class {
|
|
903
|
-
_sessionId = null;
|
|
904
|
-
filePath = null;
|
|
905
|
-
writeChain = Promise.resolve();
|
|
906
|
-
patched = false;
|
|
907
|
-
get sessionId() {
|
|
908
|
-
return this._sessionId;
|
|
909
|
-
}
|
|
910
|
-
async initialize() {
|
|
911
|
-
await mkdir2(LOG_DIR, { recursive: true });
|
|
912
|
-
this._sessionId = this.createSessionId();
|
|
913
|
-
this.filePath = join5(LOG_DIR, `${this._sessionId}.log`);
|
|
914
|
-
await writeFile2(this.filePath, `=== Replicas Engine Session ${this._sessionId} ===
|
|
915
|
-
`, "utf-8");
|
|
916
|
-
this.patchConsole();
|
|
917
|
-
this.log("INFO", `Engine logging initialized at ${this.filePath}`);
|
|
918
|
-
}
|
|
919
|
-
patchConsole() {
|
|
920
|
-
if (this.patched) return;
|
|
921
|
-
this.patched = true;
|
|
922
|
-
console.log = (...args) => {
|
|
923
|
-
this.log("INFO", format(...args));
|
|
924
|
-
};
|
|
925
|
-
console.info = (...args) => {
|
|
926
|
-
this.log("INFO", format(...args));
|
|
927
|
-
};
|
|
928
|
-
console.warn = (...args) => {
|
|
929
|
-
this.log("WARN", format(...args));
|
|
930
|
-
};
|
|
931
|
-
console.error = (...args) => {
|
|
932
|
-
this.log("ERROR", format(...args));
|
|
933
|
-
};
|
|
934
|
-
console.debug = (...args) => {
|
|
935
|
-
this.log("DEBUG", format(...args));
|
|
936
|
-
};
|
|
937
|
-
}
|
|
938
|
-
log(level, message) {
|
|
939
|
-
if (!this.filePath) return;
|
|
940
|
-
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [${level}] ${message}
|
|
941
|
-
`;
|
|
942
|
-
this.writeChain = this.writeChain.then(() => appendFile(this.filePath, line, "utf-8")).catch((error) => {
|
|
943
|
-
try {
|
|
944
|
-
const err = error instanceof Error ? error.message : "Unknown error";
|
|
945
|
-
process.stderr.write(`[EngineLogger] Failed to append log line: ${err}
|
|
946
|
-
`);
|
|
947
|
-
} catch {
|
|
948
|
-
}
|
|
949
|
-
});
|
|
950
|
-
}
|
|
951
|
-
/** Wait for all queued log writes to complete. */
|
|
952
|
-
flush() {
|
|
953
|
-
return this.writeChain;
|
|
954
|
-
}
|
|
955
|
-
createSessionId() {
|
|
956
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-");
|
|
957
|
-
const suffix = randomBytes(3).toString("hex");
|
|
958
|
-
return `${timestamp}-${suffix}`;
|
|
959
|
-
}
|
|
960
|
-
};
|
|
961
|
-
var engineLogger = new EngineLogger();
|
|
962
|
-
|
|
963
|
-
// src/services/replicas-config-service.ts
|
|
964
|
-
import { readFile as readFile4, appendFile as appendFile2, writeFile as writeFile5, mkdir as mkdir5 } from "fs/promises";
|
|
965
|
-
import { existsSync as existsSync4 } from "fs";
|
|
966
|
-
import { join as join8 } from "path";
|
|
967
|
-
import { homedir as homedir5 } from "os";
|
|
968
|
-
import { exec } from "child_process";
|
|
969
|
-
import { promisify as promisify2 } from "util";
|
|
970
|
-
|
|
971
|
-
// ../shared/src/event.ts
|
|
972
|
-
var CODEX_QUOTA_STATUS_EVENT_TYPE = "codex-quota-status";
|
|
973
|
-
|
|
974
|
-
// ../shared/src/pricing.ts
|
|
975
|
-
var PLANS = {
|
|
976
|
-
hobby: {
|
|
977
|
-
id: "hobby",
|
|
978
|
-
name: "Hobby",
|
|
979
|
-
monthlyPrice: 0,
|
|
980
|
-
seatPriceCents: 0,
|
|
981
|
-
creditsIncluded: 1200,
|
|
982
|
-
features: [
|
|
983
|
-
"1,200 minutes of human-initiated workspace usage (one-time)",
|
|
984
|
-
"1,200 minutes of API + automation usage (one-time)",
|
|
985
|
-
"Automations (limited to 2)",
|
|
986
|
-
"Warm pools and warm hooks",
|
|
987
|
-
"Up to 3 repositories"
|
|
988
|
-
]
|
|
989
|
-
},
|
|
990
|
-
developer: {
|
|
991
|
-
id: "developer",
|
|
992
|
-
name: "Developer",
|
|
993
|
-
monthlyPrice: 120,
|
|
994
|
-
seatPriceCents: 12e3,
|
|
995
|
-
creditsIncluded: 0,
|
|
996
|
-
features: [
|
|
997
|
-
"Unlimited human-initiated workspaces",
|
|
998
|
-
"5,000 included automation/API minutes per month",
|
|
999
|
-
"Up to 10 repositories",
|
|
1000
|
-
"Up to 5 automations",
|
|
1001
|
-
"Warm pools and warm hooks",
|
|
1002
|
-
"API access"
|
|
1003
|
-
]
|
|
1004
|
-
},
|
|
1005
|
-
team: {
|
|
1006
|
-
id: "team",
|
|
1007
|
-
name: "Team",
|
|
1008
|
-
monthlyPrice: 300,
|
|
1009
|
-
seatPriceCents: 3e4,
|
|
1010
|
-
creditsIncluded: 0,
|
|
1011
|
-
features: [
|
|
1012
|
-
"Unlimited human-initiated workspaces",
|
|
1013
|
-
"15,000 included automation/API minutes per month",
|
|
1014
|
-
"Unlimited repositories",
|
|
1015
|
-
"Unlimited automations",
|
|
1016
|
-
"Higher API rate limits",
|
|
1017
|
-
"Warm pools and warm hooks",
|
|
1018
|
-
"Auto-upgraded sandbox resources (32 GB disk, 16 GB memory)",
|
|
1019
|
-
"Shared Slack support channel"
|
|
1020
|
-
]
|
|
1021
|
-
},
|
|
1022
|
-
enterprise: {
|
|
1023
|
-
id: "enterprise",
|
|
1024
|
-
name: "Enterprise",
|
|
1025
|
-
monthlyPrice: 0,
|
|
1026
|
-
seatPriceCents: 0,
|
|
1027
|
-
creditsIncluded: 0,
|
|
1028
|
-
features: [
|
|
1029
|
-
"Unlimited usage",
|
|
1030
|
-
"Unlimited automations",
|
|
1031
|
-
"Custom API rates",
|
|
1032
|
-
"Custom rate limits",
|
|
1033
|
-
"Custom warm hooks and pools",
|
|
1034
|
-
"Shared Slack support channel",
|
|
1035
|
-
"SOC 2"
|
|
1036
|
-
]
|
|
1037
|
-
}
|
|
1038
|
-
};
|
|
1039
|
-
var TEAM_PLAN = PLANS.team;
|
|
1040
|
-
var ENTERPRISE_PLAN = PLANS.enterprise;
|
|
1041
|
-
|
|
1042
|
-
// ../shared/src/sandbox.ts
|
|
1043
|
-
var SANDBOX_LIFECYCLE = {
|
|
1044
|
-
AUTO_STOP_MINUTES: 60,
|
|
1045
|
-
AUTO_ARCHIVE_MINUTES: 60 * 24 * 7,
|
|
1046
|
-
AUTO_DELETE_MINUTES: -1,
|
|
1047
|
-
SSH_TOKEN_EXPIRATION_MINUTES: 3 * 60
|
|
1048
|
-
};
|
|
1049
|
-
var SANDBOX_PATHS = {
|
|
1050
|
-
HOME_DIR: "/home/ubuntu",
|
|
1051
|
-
WORKSPACES_DIR: "/home/ubuntu/workspaces",
|
|
1052
|
-
REPLICAS_DIR: "/home/ubuntu/.replicas",
|
|
1053
|
-
REPLICAS_FILES_DIR: "/home/ubuntu/.replicas/files",
|
|
1054
|
-
REPLICAS_FILES_DISPLAY_DIR: "~/.replicas/files",
|
|
1055
|
-
REPLICAS_RUNTIME_ENV_FILE: "/home/ubuntu/.replicas/runtime-env.sh",
|
|
1056
|
-
REPLICAS_PREVIEW_PORTS_FILE: "/home/ubuntu/.replicas/preview-ports.json",
|
|
1057
|
-
REPLICAS_MCPS_FILE: "/home/ubuntu/.replicas/mcps.json"
|
|
1058
|
-
};
|
|
1059
|
-
|
|
1060
|
-
// ../shared/src/replicas-config.ts
|
|
1061
|
-
import { parse as parseYaml } from "yaml";
|
|
1062
|
-
|
|
1063
|
-
// ../shared/src/warm-hooks.ts
|
|
1064
|
-
var DEFAULT_WARM_HOOK_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
1065
|
-
var MAX_WARM_HOOK_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
1066
|
-
var DEFAULT_HOOK_OUTPUT_PREVIEW_CHARS = 1e5;
|
|
1067
|
-
var HOOK_EXEC_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
1068
|
-
function isRecord2(value) {
|
|
1069
|
-
return typeof value === "object" && value !== null;
|
|
1070
|
-
}
|
|
1071
|
-
function clampWarmHookTimeoutMs(timeoutMs) {
|
|
1072
|
-
if (!timeoutMs || Number.isNaN(timeoutMs) || timeoutMs <= 0) {
|
|
1073
|
-
return DEFAULT_WARM_HOOK_TIMEOUT_MS;
|
|
1074
|
-
}
|
|
1075
|
-
return Math.min(timeoutMs, MAX_WARM_HOOK_TIMEOUT_MS);
|
|
1076
|
-
}
|
|
1077
|
-
function buildHookOutputPreview(text, maxChars = DEFAULT_HOOK_OUTPUT_PREVIEW_CHARS) {
|
|
1078
|
-
if (text.length <= maxChars) {
|
|
1079
|
-
return { output: text, outputTruncated: false, outputTotalChars: text.length };
|
|
1080
|
-
}
|
|
1081
|
-
return {
|
|
1082
|
-
output: `${text.slice(0, maxChars)}
|
|
1083
|
-
...[truncated \u2014 download the full log to see the rest]`,
|
|
1084
|
-
outputTruncated: true,
|
|
1085
|
-
outputTotalChars: text.length
|
|
1086
|
-
};
|
|
1087
|
-
}
|
|
1088
|
-
function parseWarmHookConfig(value, filename = "replicas.json") {
|
|
1089
|
-
if (typeof value === "string") {
|
|
1090
|
-
return value;
|
|
1091
|
-
}
|
|
1092
|
-
if (!isRecord2(value)) {
|
|
1093
|
-
throw new Error(`Invalid ${filename}: "warmHook" must be a string or object`);
|
|
1094
|
-
}
|
|
1095
|
-
if (!Array.isArray(value.commands) || !value.commands.every((entry) => typeof entry === "string")) {
|
|
1096
|
-
throw new Error(`Invalid ${filename}: "warmHook.commands" must be an array of shell commands`);
|
|
1097
|
-
}
|
|
1098
|
-
if (value.timeout !== void 0 && (typeof value.timeout !== "number" || value.timeout <= 0)) {
|
|
1099
|
-
throw new Error(`Invalid ${filename}: "warmHook.timeout" must be a positive number`);
|
|
1100
|
-
}
|
|
1101
|
-
return {
|
|
1102
|
-
commands: value.commands,
|
|
1103
|
-
timeout: value.timeout
|
|
1104
|
-
};
|
|
1105
|
-
}
|
|
1106
|
-
function resolveWarmHookConfig(value) {
|
|
1107
|
-
if (value === void 0) {
|
|
1108
|
-
return null;
|
|
1109
|
-
}
|
|
1110
|
-
const parsed = parseWarmHookConfig(value);
|
|
1111
|
-
const commands = typeof parsed === "string" ? [parsed.trim()].filter(Boolean) : parsed.commands.map((command) => command.trim()).filter(Boolean);
|
|
1112
|
-
if (commands.length === 0) {
|
|
1113
|
-
return null;
|
|
1114
|
-
}
|
|
1115
|
-
return {
|
|
1116
|
-
commands,
|
|
1117
|
-
timeoutMs: typeof parsed === "string" ? void 0 : parsed.timeout
|
|
1118
|
-
};
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
// ../shared/src/replicas-config.ts
|
|
1122
|
-
var REPLICAS_CONFIG_FILENAMES = ["replicas.json", "replicas.yaml", "replicas.yml"];
|
|
1123
|
-
function isRecord3(value) {
|
|
1124
|
-
return typeof value === "object" && value !== null;
|
|
1125
|
-
}
|
|
1126
|
-
function parseReplicasConfig(value, filename = "replicas.json") {
|
|
1127
|
-
if (!isRecord3(value)) {
|
|
1128
|
-
throw new Error(`Invalid ${filename}: expected an object`);
|
|
1110
|
+
if (!remoteRef) {
|
|
1111
|
+
return { status: "not_found" };
|
|
1112
|
+
}
|
|
1113
|
+
} catch {
|
|
1114
|
+
return { status: "error" };
|
|
1115
|
+
}
|
|
1116
|
+
try {
|
|
1117
|
+
const prInfo = execFileSync2("gh", ["pr", "view", branch, "--json", "url", "--jq", ".url"], {
|
|
1118
|
+
cwd: repoPath,
|
|
1119
|
+
encoding: "utf-8",
|
|
1120
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1121
|
+
}).trim();
|
|
1122
|
+
return prInfo ? { status: "found", url: prInfo } : { status: "not_found" };
|
|
1123
|
+
} catch (error) {
|
|
1124
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1125
|
+
console.warn(`[GitService] gh pr view ${branch} failed for ${repoName}: ${message}`);
|
|
1126
|
+
return { status: "error" };
|
|
1127
|
+
}
|
|
1129
1128
|
}
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
if (
|
|
1133
|
-
|
|
1129
|
+
resolveDefaultBranch(repoPath) {
|
|
1130
|
+
const cached = this.defaultBranchCache.get(repoPath);
|
|
1131
|
+
if (cached) {
|
|
1132
|
+
return cached;
|
|
1134
1133
|
}
|
|
1135
|
-
|
|
1134
|
+
const fromSymbolicRef = this.resolveDefaultBranchFromSymbolicRef(repoPath);
|
|
1135
|
+
if (fromSymbolicRef) {
|
|
1136
|
+
this.defaultBranchCache.set(repoPath, fromSymbolicRef);
|
|
1137
|
+
return fromSymbolicRef;
|
|
1138
|
+
}
|
|
1139
|
+
const fallback = "main";
|
|
1140
|
+
this.defaultBranchCache.set(repoPath, fallback);
|
|
1141
|
+
return fallback;
|
|
1136
1142
|
}
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1143
|
+
resolveDefaultBranchFromSymbolicRef(repoPath) {
|
|
1144
|
+
try {
|
|
1145
|
+
const output = runGitCommand(["symbolic-ref", "--short", "refs/remotes/origin/HEAD"], repoPath);
|
|
1146
|
+
const match = output.match(/^origin\/(.+)$/);
|
|
1147
|
+
return match ? match[1] : null;
|
|
1148
|
+
} catch {
|
|
1149
|
+
return null;
|
|
1140
1150
|
}
|
|
1141
|
-
config.systemPrompt = value.systemPrompt;
|
|
1142
1151
|
}
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1152
|
+
findAvailableBranchName(baseName, cwd) {
|
|
1153
|
+
const sanitizedBaseName = this.sanitizeBranchName(baseName);
|
|
1154
|
+
if (!branchExists(sanitizedBaseName, cwd)) {
|
|
1155
|
+
return sanitizedBaseName;
|
|
1146
1156
|
}
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1157
|
+
return `${sanitizedBaseName}-${Date.now()}`;
|
|
1158
|
+
}
|
|
1159
|
+
sanitizeBranchName(name) {
|
|
1160
|
+
const normalized = name.toLowerCase().replace(/[^a-z0-9._/-]+/g, "-").replace(/\/{2,}/g, "/").replace(/^-+|-+$/g, "");
|
|
1161
|
+
return normalized || "replicas";
|
|
1162
|
+
}
|
|
1163
|
+
async refreshRepoMetadata(repo, currentBranch, startHooksCompleted, persistedState, observedBranches) {
|
|
1164
|
+
const prResult = await this.getPullRequestUrl(repo.name, repo.path, currentBranch, persistedState);
|
|
1165
|
+
let prUrls = persistedState?.prUrls ?? [];
|
|
1166
|
+
if (prResult.status === "found") {
|
|
1167
|
+
prUrls = appendUniqueUrl(prUrls, prResult.url);
|
|
1150
1168
|
}
|
|
1151
|
-
if (
|
|
1152
|
-
|
|
1169
|
+
if (observedBranches) {
|
|
1170
|
+
for (const branch of observedBranches) {
|
|
1171
|
+
if (branch === currentBranch) continue;
|
|
1172
|
+
const branchResult = this.lookupPrOnRemote(repo.name, repo.path, branch);
|
|
1173
|
+
if (branchResult.status === "found") {
|
|
1174
|
+
prUrls = appendUniqueUrl(prUrls, branchResult.url);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1153
1177
|
}
|
|
1154
|
-
|
|
1178
|
+
const state = {
|
|
1179
|
+
name: repo.name,
|
|
1180
|
+
path: repo.path,
|
|
1181
|
+
defaultBranch: repo.defaultBranch,
|
|
1182
|
+
currentBranch,
|
|
1183
|
+
prUrls,
|
|
1184
|
+
gitDiff: this.getGitDiffStats(repo.path, repo.defaultBranch),
|
|
1185
|
+
startHooksCompleted
|
|
1186
|
+
};
|
|
1187
|
+
await saveRepoState(repo.name, state, state);
|
|
1188
|
+
return state;
|
|
1155
1189
|
}
|
|
1156
|
-
|
|
1157
|
-
|
|
1190
|
+
pathExists(path4) {
|
|
1191
|
+
return existsSync2(path4);
|
|
1158
1192
|
}
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
parsed = parseYaml(content);
|
|
1166
|
-
} else {
|
|
1167
|
-
parsed = JSON.parse(content);
|
|
1193
|
+
async safeStat(path4) {
|
|
1194
|
+
try {
|
|
1195
|
+
return await stat(path4);
|
|
1196
|
+
} catch {
|
|
1197
|
+
return null;
|
|
1198
|
+
}
|
|
1168
1199
|
}
|
|
1169
|
-
return parseReplicasConfig(parsed, filename);
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
|
-
// ../shared/src/engine/environment.ts
|
|
1173
|
-
var DAYTONA_SNAPSHOT_ID = "06-05-2026-royal-york-v3";
|
|
1174
|
-
|
|
1175
|
-
// ../shared/src/engine/types.ts
|
|
1176
|
-
var DEFAULT_CHAT_TITLES = {
|
|
1177
|
-
claude: "Claude Code",
|
|
1178
|
-
codex: "Codex",
|
|
1179
|
-
relay: "Relay"
|
|
1180
1200
|
};
|
|
1181
|
-
var
|
|
1182
|
-
|
|
1183
|
-
// ../shared/src/routes/workspaces.ts
|
|
1184
|
-
var WORKSPACE_FILE_UPLOAD_MAX_SIZE_BYTES = 20 * 1024 * 1024;
|
|
1185
|
-
var WORKSPACE_FILE_CONTENT_MAX_SIZE_BYTES = 1 * 1024 * 1024;
|
|
1201
|
+
var gitService = new GitService();
|
|
1186
1202
|
|
|
1187
|
-
//
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1203
|
+
// src/utils/logger.ts
|
|
1204
|
+
import { appendFile, mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
1205
|
+
import { homedir as homedir3 } from "os";
|
|
1206
|
+
import { join as join5 } from "path";
|
|
1207
|
+
import { format } from "util";
|
|
1208
|
+
import { randomBytes } from "crypto";
|
|
1209
|
+
var LOG_DIR = join5(homedir3(), ".replicas", "logs");
|
|
1210
|
+
var EngineLogger = class {
|
|
1211
|
+
_sessionId = null;
|
|
1212
|
+
filePath = null;
|
|
1213
|
+
writeChain = Promise.resolve();
|
|
1214
|
+
patched = false;
|
|
1215
|
+
get sessionId() {
|
|
1216
|
+
return this._sessionId;
|
|
1217
|
+
}
|
|
1218
|
+
async initialize() {
|
|
1219
|
+
await mkdir2(LOG_DIR, { recursive: true });
|
|
1220
|
+
this._sessionId = this.createSessionId();
|
|
1221
|
+
this.filePath = join5(LOG_DIR, `${this._sessionId}.log`);
|
|
1222
|
+
await writeFile2(this.filePath, `=== Replicas Engine Session ${this._sessionId} ===
|
|
1223
|
+
`, "utf-8");
|
|
1224
|
+
this.patchConsole();
|
|
1225
|
+
this.log("INFO", `Engine logging initialized at ${this.filePath}`);
|
|
1226
|
+
}
|
|
1227
|
+
patchConsole() {
|
|
1228
|
+
if (this.patched) return;
|
|
1229
|
+
this.patched = true;
|
|
1230
|
+
console.log = (...args) => {
|
|
1231
|
+
this.log("INFO", format(...args));
|
|
1232
|
+
};
|
|
1233
|
+
console.info = (...args) => {
|
|
1234
|
+
this.log("INFO", format(...args));
|
|
1235
|
+
};
|
|
1236
|
+
console.warn = (...args) => {
|
|
1237
|
+
this.log("WARN", format(...args));
|
|
1238
|
+
};
|
|
1239
|
+
console.error = (...args) => {
|
|
1240
|
+
this.log("ERROR", format(...args));
|
|
1241
|
+
};
|
|
1242
|
+
console.debug = (...args) => {
|
|
1243
|
+
this.log("DEBUG", format(...args));
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
log(level, message) {
|
|
1247
|
+
if (!this.filePath) return;
|
|
1248
|
+
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [${level}] ${message}
|
|
1249
|
+
`;
|
|
1250
|
+
this.writeChain = this.writeChain.then(() => appendFile(this.filePath, line, "utf-8")).catch((error) => {
|
|
1251
|
+
try {
|
|
1252
|
+
const err = error instanceof Error ? error.message : "Unknown error";
|
|
1253
|
+
process.stderr.write(`[EngineLogger] Failed to append log line: ${err}
|
|
1254
|
+
`);
|
|
1255
|
+
} catch {
|
|
1256
|
+
}
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
/** Wait for all queued log writes to complete. */
|
|
1260
|
+
flush() {
|
|
1261
|
+
return this.writeChain;
|
|
1262
|
+
}
|
|
1263
|
+
createSessionId() {
|
|
1264
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-");
|
|
1265
|
+
const suffix = randomBytes(3).toString("hex");
|
|
1266
|
+
return `${timestamp}-${suffix}`;
|
|
1267
|
+
}
|
|
1194
1268
|
};
|
|
1195
|
-
var
|
|
1196
|
-
|
|
1197
|
-
// ../shared/src/routes/environment.ts
|
|
1198
|
-
var RESERVED_MCP_NAME_PREFIXES = ["relay-", "replicas-"];
|
|
1199
|
-
function isReservedMcpName(name) {
|
|
1200
|
-
return RESERVED_MCP_NAME_PREFIXES.some((prefix) => name.startsWith(prefix));
|
|
1201
|
-
}
|
|
1269
|
+
var engineLogger = new EngineLogger();
|
|
1202
1270
|
|
|
1203
|
-
//
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
};
|
|
1209
|
-
|
|
1271
|
+
// src/services/replicas-config-service.ts
|
|
1272
|
+
import { readFile as readFile4, appendFile as appendFile2, writeFile as writeFile5, mkdir as mkdir5 } from "fs/promises";
|
|
1273
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1274
|
+
import { join as join8 } from "path";
|
|
1275
|
+
import { homedir as homedir5 } from "os";
|
|
1276
|
+
import { exec } from "child_process";
|
|
1277
|
+
import { promisify as promisify2 } from "util";
|
|
1210
1278
|
|
|
1211
1279
|
// src/services/environment-details-service.ts
|
|
1212
1280
|
import { mkdir as mkdir3, readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
|
|
@@ -1795,10 +1863,10 @@ import { homedir as homedir7 } from "os";
|
|
|
1795
1863
|
// src/utils/jsonl-reader.ts
|
|
1796
1864
|
import { readFile as readFile6 } from "fs/promises";
|
|
1797
1865
|
function isJsonlEvent(value) {
|
|
1798
|
-
if (!
|
|
1866
|
+
if (!isRecord3(value)) {
|
|
1799
1867
|
return false;
|
|
1800
1868
|
}
|
|
1801
|
-
return typeof value.timestamp === "string" && typeof value.type === "string" &&
|
|
1869
|
+
return typeof value.timestamp === "string" && typeof value.type === "string" && isRecord3(value.payload);
|
|
1802
1870
|
}
|
|
1803
1871
|
function parseJsonlEvents(lines) {
|
|
1804
1872
|
const events = [];
|
|
@@ -2591,15 +2659,6 @@ var PromptStream = class {
|
|
|
2591
2659
|
function isLinearThoughtEvent(event) {
|
|
2592
2660
|
return event.content.type === "thought";
|
|
2593
2661
|
}
|
|
2594
|
-
function appendMcpAwareness(instructions, mcpServers) {
|
|
2595
|
-
const names = Object.keys(mcpServers ?? {});
|
|
2596
|
-
if (names.length === 0) return instructions;
|
|
2597
|
-
const note = `MCP servers configured for this session: ${names.join(", ")}.
|
|
2598
|
-
Their tools are exposed in your tool list under the \`mcp__<server>__*\` prefix (e.g. \`mcp__notion__search\`). When the user asks about MCPs, available integrations, or wants to use one of these tools, look at your existing tool list directly \u2014 do NOT run \`claude mcp list\` or any other shell command to enumerate them. That command checks Claude Code's local CLI config and will never see MCPs attached to this SDK session.`;
|
|
2599
|
-
return instructions ? `${instructions}
|
|
2600
|
-
|
|
2601
|
-
${note}` : note;
|
|
2602
|
-
}
|
|
2603
2662
|
var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
|
|
2604
2663
|
historyFile;
|
|
2605
2664
|
sessionId = null;
|
|
@@ -2697,8 +2756,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
|
|
|
2697
2756
|
const promptStream = new PromptStream();
|
|
2698
2757
|
promptStream.push(userMessage);
|
|
2699
2758
|
this.activePromptStream = promptStream;
|
|
2700
|
-
const
|
|
2701
|
-
const combinedInstructions = appendMcpAwareness(baseInstructions, this.mcpServersConfig);
|
|
2759
|
+
const combinedInstructions = this.buildCombinedInstructions(customInstructions);
|
|
2702
2760
|
const systemPrompt = this.systemPromptOverride ? this.systemPromptOverride(combinedInstructions) : {
|
|
2703
2761
|
type: "preset",
|
|
2704
2762
|
preset: "claude_code",
|
|
@@ -2861,20 +2919,20 @@ function isLinearThoughtEvent2(event) {
|
|
|
2861
2919
|
return event.content.type === "thought";
|
|
2862
2920
|
}
|
|
2863
2921
|
function isJsonlEvent2(value) {
|
|
2864
|
-
if (!
|
|
2922
|
+
if (!isRecord3(value)) {
|
|
2865
2923
|
return false;
|
|
2866
2924
|
}
|
|
2867
|
-
return typeof value.timestamp === "string" && typeof value.type === "string" &&
|
|
2925
|
+
return typeof value.timestamp === "string" && typeof value.type === "string" && isRecord3(value.payload);
|
|
2868
2926
|
}
|
|
2869
2927
|
function sleep(ms) {
|
|
2870
2928
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
2871
2929
|
}
|
|
2872
2930
|
function extractRateLimitsSnapshot(parsed) {
|
|
2873
|
-
if (!
|
|
2874
|
-
const payload =
|
|
2875
|
-
const rateLimits =
|
|
2931
|
+
if (!isRecord3(parsed)) return null;
|
|
2932
|
+
const payload = isRecord3(parsed.payload) ? parsed.payload : parsed;
|
|
2933
|
+
const rateLimits = isRecord3(payload.rate_limits) ? payload.rate_limits : null;
|
|
2876
2934
|
if (!rateLimits) return null;
|
|
2877
|
-
const credits =
|
|
2935
|
+
const credits = isRecord3(rateLimits.credits) ? rateLimits.credits : null;
|
|
2878
2936
|
const hasCredits = credits && typeof credits.has_credits === "boolean" ? credits.has_credits : null;
|
|
2879
2937
|
const unlimited = credits && typeof credits.unlimited === "boolean" ? credits.unlimited : null;
|
|
2880
2938
|
const balance = credits && typeof credits.balance === "string" ? credits.balance : null;
|
|
@@ -3004,7 +3062,7 @@ var CodexManager = class extends CodingAgentManager {
|
|
|
3004
3062
|
try {
|
|
3005
3063
|
const existingContent = await readFile7(CODEX_CONFIG_PATH, "utf-8");
|
|
3006
3064
|
const parsed = parseToml(existingContent);
|
|
3007
|
-
if (
|
|
3065
|
+
if (isRecord3(parsed)) {
|
|
3008
3066
|
config = parsed;
|
|
3009
3067
|
}
|
|
3010
3068
|
} catch (parseError) {
|
|
@@ -3894,92 +3952,6 @@ var KeepAliveService = class _KeepAliveService {
|
|
|
3894
3952
|
};
|
|
3895
3953
|
var keepAliveService = new KeepAliveService();
|
|
3896
3954
|
|
|
3897
|
-
// src/services/mcp-store.ts
|
|
3898
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
3899
|
-
var STDIO_INHERITED_ENV_KEYS = ["PATH", "HOME", "USER", "SHELL", "LANG", "LC_ALL"];
|
|
3900
|
-
var cache = {};
|
|
3901
|
-
var initialized = false;
|
|
3902
|
-
function buildStdioEnv(configEnv) {
|
|
3903
|
-
const merged = {};
|
|
3904
|
-
for (const key of STDIO_INHERITED_ENV_KEYS) {
|
|
3905
|
-
const value = process.env[key];
|
|
3906
|
-
if (value !== void 0) {
|
|
3907
|
-
merged[key] = value;
|
|
3908
|
-
}
|
|
3909
|
-
}
|
|
3910
|
-
Object.assign(merged, configEnv);
|
|
3911
|
-
return merged;
|
|
3912
|
-
}
|
|
3913
|
-
function transformEntry(entry) {
|
|
3914
|
-
if (entry.transport === "stdio" && "command" in entry.config) {
|
|
3915
|
-
return {
|
|
3916
|
-
type: "stdio",
|
|
3917
|
-
command: entry.config.command,
|
|
3918
|
-
args: entry.config.args,
|
|
3919
|
-
env: buildStdioEnv(entry.config.env ?? {})
|
|
3920
|
-
};
|
|
3921
|
-
}
|
|
3922
|
-
if (entry.transport === "http" && "url" in entry.config) {
|
|
3923
|
-
return {
|
|
3924
|
-
type: "http",
|
|
3925
|
-
url: entry.config.url,
|
|
3926
|
-
headers: entry.config.headers
|
|
3927
|
-
};
|
|
3928
|
-
}
|
|
3929
|
-
if (entry.transport === "sse" && "url" in entry.config) {
|
|
3930
|
-
return {
|
|
3931
|
-
type: "sse",
|
|
3932
|
-
url: entry.config.url,
|
|
3933
|
-
headers: entry.config.headers
|
|
3934
|
-
};
|
|
3935
|
-
}
|
|
3936
|
-
return null;
|
|
3937
|
-
}
|
|
3938
|
-
function initializeMcpStore(filePath = SANDBOX_PATHS.REPLICAS_MCPS_FILE) {
|
|
3939
|
-
if (initialized) return;
|
|
3940
|
-
initialized = true;
|
|
3941
|
-
let raw;
|
|
3942
|
-
try {
|
|
3943
|
-
raw = readFileSync2(filePath, "utf-8");
|
|
3944
|
-
} catch (error) {
|
|
3945
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
3946
|
-
if (error.code === "ENOENT") {
|
|
3947
|
-
console.log(`[mcp_loaded] count=0 names=[] reason=no_file`);
|
|
3948
|
-
} else {
|
|
3949
|
-
console.warn(`[mcp_store_load_skipped] reason=${reason}`);
|
|
3950
|
-
}
|
|
3951
|
-
return;
|
|
3952
|
-
}
|
|
3953
|
-
let parsed;
|
|
3954
|
-
try {
|
|
3955
|
-
parsed = JSON.parse(raw);
|
|
3956
|
-
} catch (error) {
|
|
3957
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
3958
|
-
console.warn(`[mcp_store_load_failed] reason=parse_error detail=${reason}`);
|
|
3959
|
-
return;
|
|
3960
|
-
}
|
|
3961
|
-
const entries = parsed.mcpServers ?? {};
|
|
3962
|
-
const result = {};
|
|
3963
|
-
for (const [name, entry] of Object.entries(entries)) {
|
|
3964
|
-
if (isReservedMcpName(name)) {
|
|
3965
|
-
console.warn(`[mcp_name_reserved] name=${name} dropped`);
|
|
3966
|
-
continue;
|
|
3967
|
-
}
|
|
3968
|
-
const transformed = transformEntry(entry);
|
|
3969
|
-
if (!transformed) {
|
|
3970
|
-
console.warn(`[mcp_transport_unknown] name=${name} transport=${entry.transport ?? "undefined"}`);
|
|
3971
|
-
continue;
|
|
3972
|
-
}
|
|
3973
|
-
result[name] = transformed;
|
|
3974
|
-
}
|
|
3975
|
-
cache = result;
|
|
3976
|
-
const names = Object.keys(cache);
|
|
3977
|
-
console.log(`[mcp_loaded] count=${names.length} names=[${names.join(",")}]`);
|
|
3978
|
-
}
|
|
3979
|
-
function getMcpServers() {
|
|
3980
|
-
return cache;
|
|
3981
|
-
}
|
|
3982
|
-
|
|
3983
3955
|
// src/services/chat/errors.ts
|
|
3984
3956
|
var ChatNotFoundError = class extends Error {
|
|
3985
3957
|
constructor(chatId) {
|
|
@@ -4016,7 +3988,7 @@ function isCodexAvailable() {
|
|
|
4016
3988
|
return existsSync7(CODEX_AUTH_PATH2) || Boolean(ENGINE_ENV.OPENAI_API_KEY);
|
|
4017
3989
|
}
|
|
4018
3990
|
function isPersistedChat(value) {
|
|
4019
|
-
if (!
|
|
3991
|
+
if (!isRecord3(value)) {
|
|
4020
3992
|
return false;
|
|
4021
3993
|
}
|
|
4022
3994
|
const candidate = value;
|
|
@@ -4216,8 +4188,7 @@ var ChatService = class {
|
|
|
4216
4188
|
initialSessionId: persisted.providerSessionId,
|
|
4217
4189
|
onSaveSessionId: saveSession,
|
|
4218
4190
|
onTurnComplete: onProviderTurnComplete,
|
|
4219
|
-
onEvent: onProviderEvent
|
|
4220
|
-
mcpServers: getMcpServers()
|
|
4191
|
+
onEvent: onProviderEvent
|
|
4221
4192
|
});
|
|
4222
4193
|
} else if (persisted.provider === "relay") {
|
|
4223
4194
|
provider = new RelayManager({
|
|
@@ -5189,7 +5160,6 @@ process.on("unhandledRejection", (reason) => {
|
|
|
5189
5160
|
engineLogger.flush().finally(() => process.exit(1));
|
|
5190
5161
|
});
|
|
5191
5162
|
await eventService.initialize();
|
|
5192
|
-
initializeMcpStore();
|
|
5193
5163
|
var READY_MESSAGE = "========= REPLICAS WORKSPACE READY ==========";
|
|
5194
5164
|
var COMPLETION_MESSAGE = "========= REPLICAS WORKSPACE INITIALIZATION COMPLETE ==========";
|
|
5195
5165
|
function checkActiveSSHSessions() {
|