replicas-engine 0.1.230 → 0.1.231
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 +278 -86
- package/package.json +1 -1
package/dist/src/index.js
CHANGED
|
@@ -1652,6 +1652,22 @@ function parseGoalCommand(message) {
|
|
|
1652
1652
|
// ../shared/src/replicas-config.ts
|
|
1653
1653
|
import { parse as parseYaml } from "yaml";
|
|
1654
1654
|
|
|
1655
|
+
// ../shared/src/hook-config.ts
|
|
1656
|
+
function resolveHookConfig(value) {
|
|
1657
|
+
if (value === void 0) {
|
|
1658
|
+
return null;
|
|
1659
|
+
}
|
|
1660
|
+
const commands = typeof value === "string" ? [value.trim()].filter(Boolean) : value.commands.map((command) => command.trim()).filter(Boolean);
|
|
1661
|
+
if (commands.length === 0) {
|
|
1662
|
+
return null;
|
|
1663
|
+
}
|
|
1664
|
+
return {
|
|
1665
|
+
commands,
|
|
1666
|
+
timeoutMs: typeof value === "string" ? void 0 : value.timeout,
|
|
1667
|
+
separate: typeof value === "string" ? void 0 : value.separate
|
|
1668
|
+
};
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1655
1671
|
// ../shared/src/warm-hooks.ts
|
|
1656
1672
|
var DEFAULT_WARM_HOOK_TIMEOUT_MS = 60 * 60 * 1e3;
|
|
1657
1673
|
var MAX_WARM_HOOK_TIMEOUT_MS = 120 * 60 * 1e3;
|
|
@@ -1704,16 +1720,7 @@ function resolveWarmHookConfig(value) {
|
|
|
1704
1720
|
if (value === void 0) {
|
|
1705
1721
|
return null;
|
|
1706
1722
|
}
|
|
1707
|
-
|
|
1708
|
-
const commands = typeof parsed === "string" ? [parsed.trim()].filter(Boolean) : parsed.commands.map((command) => command.trim()).filter(Boolean);
|
|
1709
|
-
if (commands.length === 0) {
|
|
1710
|
-
return null;
|
|
1711
|
-
}
|
|
1712
|
-
return {
|
|
1713
|
-
commands,
|
|
1714
|
-
timeoutMs: typeof parsed === "string" ? void 0 : parsed.timeout,
|
|
1715
|
-
separate: typeof parsed === "string" ? void 0 : parsed.separate
|
|
1716
|
-
};
|
|
1723
|
+
return resolveHookConfig(parseWarmHookConfig(value));
|
|
1717
1724
|
}
|
|
1718
1725
|
|
|
1719
1726
|
// ../shared/src/replicas-config.ts
|
|
@@ -1777,7 +1784,7 @@ function isClaudeAuthErrorText(text) {
|
|
|
1777
1784
|
}
|
|
1778
1785
|
|
|
1779
1786
|
// ../shared/src/engine/environment.ts
|
|
1780
|
-
var DAYTONA_SNAPSHOT_ID = "29-05-2026-royal-york-
|
|
1787
|
+
var DAYTONA_SNAPSHOT_ID = "29-05-2026-royal-york-v2";
|
|
1781
1788
|
|
|
1782
1789
|
// ../shared/src/engine/types.ts
|
|
1783
1790
|
var DEFAULT_CHAT_TITLES = {
|
|
@@ -2023,7 +2030,9 @@ function loadEngineEnv() {
|
|
|
2023
2030
|
AWS_REGION: readEnv("AWS_REGION"),
|
|
2024
2031
|
REPLICAS_CLAUDE_AUTH_METHOD: parseClaudeAuthMethod(readEnv("REPLICAS_CLAUDE_AUTH_METHOD")),
|
|
2025
2032
|
REPLICAS_CODEX_AUTH_METHOD: parseCodexAuthMethod(readEnv("REPLICAS_CODEX_AUTH_METHOD")),
|
|
2026
|
-
REPLICAS_ENV_SYSTEM_PROMPT: readEnv("REPLICAS_ENV_SYSTEM_PROMPT")
|
|
2033
|
+
REPLICAS_ENV_SYSTEM_PROMPT: readEnv("REPLICAS_ENV_SYSTEM_PROMPT"),
|
|
2034
|
+
REPLICAS_ENV_START_HOOK: readEnv("REPLICAS_ENV_START_HOOK"),
|
|
2035
|
+
REPLICAS_DISABLE_AUTO_START_HOOKS: readEnv("REPLICAS_DISABLE_AUTO_START_HOOKS")?.toLowerCase() === "true"
|
|
2027
2036
|
};
|
|
2028
2037
|
if (!IS_WARMING_MODE && !env.WORKSPACE_ID) {
|
|
2029
2038
|
console.error("WORKSPACE_ID is not set \u2014 this is required in normal (non-warming) mode");
|
|
@@ -3054,8 +3063,7 @@ import { readFile as readFile4, appendFile as appendFile2, writeFile as writeFil
|
|
|
3054
3063
|
import { existsSync as existsSync4 } from "fs";
|
|
3055
3064
|
import { join as join9 } from "path";
|
|
3056
3065
|
import { homedir as homedir7 } from "os";
|
|
3057
|
-
import {
|
|
3058
|
-
import { promisify as promisify2 } from "util";
|
|
3066
|
+
import { spawn } from "child_process";
|
|
3059
3067
|
|
|
3060
3068
|
// src/services/environment-details-service.ts
|
|
3061
3069
|
import { mkdir as mkdir3, readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
|
|
@@ -3115,6 +3123,7 @@ function createDefaultDetails() {
|
|
|
3115
3123
|
engineVersion: DAYTONA_SNAPSHOT_ID,
|
|
3116
3124
|
globalWarmHookCompleted: { status: "n/a", details: null },
|
|
3117
3125
|
environmentWarmHookCompleted: { status: "n/a", details: null },
|
|
3126
|
+
environmentStartHookCompleted: { status: "n/a", details: null },
|
|
3118
3127
|
repositories: [],
|
|
3119
3128
|
filesUploaded: [],
|
|
3120
3129
|
envVarsSet: [],
|
|
@@ -3200,6 +3209,12 @@ var EnvironmentDetailsService = class {
|
|
|
3200
3209
|
current.lastUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3201
3210
|
await writeDetails(current);
|
|
3202
3211
|
}
|
|
3212
|
+
async setEnvironmentStartHook(status, details) {
|
|
3213
|
+
const current = await readDetails();
|
|
3214
|
+
current.environmentStartHookCompleted = { status, details: details ?? null };
|
|
3215
|
+
current.lastUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3216
|
+
await writeDetails(current);
|
|
3217
|
+
}
|
|
3203
3218
|
async setRepositoryWarmHook(repositoryName, status) {
|
|
3204
3219
|
const current = await readDetails();
|
|
3205
3220
|
current.repositories = upsertRepositoryStatus(current.repositories, [{
|
|
@@ -3295,7 +3310,6 @@ var StartHookLogsService = class {
|
|
|
3295
3310
|
var startHookLogsService = new StartHookLogsService();
|
|
3296
3311
|
|
|
3297
3312
|
// src/services/replicas-config-service.ts
|
|
3298
|
-
var execAsync = promisify2(exec);
|
|
3299
3313
|
var START_HOOKS_LOG = join9(homedir7(), ".replicas", "startHooks.log");
|
|
3300
3314
|
var START_HOOKS_RUNNING_PROMPT = `IMPORTANT - Start Hooks Running:
|
|
3301
3315
|
Start hooks are shell commands/scripts set by repository owners that run on workspace startup.
|
|
@@ -3327,6 +3341,12 @@ var ReplicasConfigService = class {
|
|
|
3327
3341
|
*/
|
|
3328
3342
|
async initialize() {
|
|
3329
3343
|
await this.loadConfigs();
|
|
3344
|
+
if (IS_WARMING_MODE || ENGINE_ENV.REPLICAS_DISABLE_AUTO_START_HOOKS) {
|
|
3345
|
+
this.hooksRunning = false;
|
|
3346
|
+
this.hooksCompleted = false;
|
|
3347
|
+
this.hooksFailed = false;
|
|
3348
|
+
return;
|
|
3349
|
+
}
|
|
3330
3350
|
void this.executeStartHooks().catch((error) => {
|
|
3331
3351
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3332
3352
|
this.hooksFailed = true;
|
|
@@ -3377,71 +3397,156 @@ var ReplicasConfigService = class {
|
|
|
3377
3397
|
console.error("Failed to write to start hooks log:", error);
|
|
3378
3398
|
}
|
|
3379
3399
|
}
|
|
3400
|
+
async executeEnvironmentStartHook(params) {
|
|
3401
|
+
await this.logToFile(`[environment] --- Running env-level start hook ---`);
|
|
3402
|
+
const result = await this.execStartHookCommand({
|
|
3403
|
+
command: params.content,
|
|
3404
|
+
label: "env start hook",
|
|
3405
|
+
repoName: "environment",
|
|
3406
|
+
cwd: homedir7(),
|
|
3407
|
+
timeout: params.timeoutMs ?? DEFAULT_START_HOOK_TIMEOUT_MS,
|
|
3408
|
+
onOutputChunk: params.onOutputChunk
|
|
3409
|
+
});
|
|
3410
|
+
if (result.exitCode !== 0) {
|
|
3411
|
+
this.hooksFailed = true;
|
|
3412
|
+
await environmentDetailsService.setEnvironmentStartHook("no");
|
|
3413
|
+
} else {
|
|
3414
|
+
await environmentDetailsService.setEnvironmentStartHook("yes");
|
|
3415
|
+
}
|
|
3416
|
+
return result;
|
|
3417
|
+
}
|
|
3380
3418
|
async execStartHookCommand(params) {
|
|
3381
3419
|
const output = [];
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3420
|
+
let outputBytes = 0;
|
|
3421
|
+
let bufferExceeded = false;
|
|
3422
|
+
const emit = (chunk) => {
|
|
3423
|
+
output.push(chunk);
|
|
3424
|
+
params.onOutputChunk?.(chunk);
|
|
3425
|
+
};
|
|
3426
|
+
await this.logToFile(`[${params.repoName}] --- Running: ${params.label} ---`);
|
|
3427
|
+
emit(`$ ${params.label}
|
|
3428
|
+
`);
|
|
3429
|
+
return new Promise((resolve3) => {
|
|
3430
|
+
const proc = spawn("bash", ["-lc", params.command], {
|
|
3385
3431
|
cwd: params.cwd,
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
env: process.env
|
|
3432
|
+
env: process.env,
|
|
3433
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3389
3434
|
});
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3435
|
+
let timedOut = false;
|
|
3436
|
+
let settled = false;
|
|
3437
|
+
const finish = (result) => {
|
|
3438
|
+
if (settled) return;
|
|
3439
|
+
settled = true;
|
|
3440
|
+
resolve3(result);
|
|
3441
|
+
};
|
|
3442
|
+
const timer = setTimeout(() => {
|
|
3443
|
+
timedOut = true;
|
|
3444
|
+
proc.kill("SIGTERM");
|
|
3445
|
+
}, params.timeout);
|
|
3446
|
+
const handleData = (data, streamName) => {
|
|
3447
|
+
outputBytes += data.length;
|
|
3448
|
+
if (bufferExceeded) return;
|
|
3449
|
+
if (outputBytes > HOOK_EXEC_MAX_BUFFER_BYTES) {
|
|
3450
|
+
bufferExceeded = true;
|
|
3451
|
+
const message = `
|
|
3452
|
+
[output truncated \u2014 exceeded ${HOOK_EXEC_MAX_BUFFER_BYTES} bytes]
|
|
3453
|
+
`;
|
|
3454
|
+
emit(message);
|
|
3455
|
+
void this.logToFile(`[${params.repoName}] [ERROR] ${message.trim()}`);
|
|
3456
|
+
proc.kill("SIGTERM");
|
|
3457
|
+
return;
|
|
3458
|
+
}
|
|
3459
|
+
const text = data.toString();
|
|
3460
|
+
emit(text);
|
|
3461
|
+
void this.logToFile(`[${params.repoName}] [${streamName}] ${text}`);
|
|
3462
|
+
};
|
|
3463
|
+
proc.stdout.on("data", (data) => handleData(data, "stdout"));
|
|
3464
|
+
proc.stderr.on("data", (data) => handleData(data, "stderr"));
|
|
3465
|
+
proc.on("close", (code) => {
|
|
3466
|
+
clearTimeout(timer);
|
|
3467
|
+
const exitCode = bufferExceeded ? 1 : code ?? 1;
|
|
3468
|
+
if (exitCode !== 0) {
|
|
3469
|
+
const reason = timedOut ? "timed out" : `exited with code ${exitCode}`;
|
|
3470
|
+
const message = `[ERROR] ${params.label} failed: ${reason}
|
|
3471
|
+
`;
|
|
3472
|
+
emit(message);
|
|
3473
|
+
void this.logToFile(`[${params.repoName}] ${message.trim()}`);
|
|
3474
|
+
} else {
|
|
3475
|
+
void this.logToFile(`[${params.repoName}] --- Completed: ${params.label} ---`);
|
|
3476
|
+
}
|
|
3477
|
+
finish({ exitCode, timedOut: timedOut && !bufferExceeded, output });
|
|
3478
|
+
});
|
|
3479
|
+
proc.on("error", (error) => {
|
|
3480
|
+
clearTimeout(timer);
|
|
3481
|
+
const message = `[ERROR] ${params.label} failed: ${error.message}
|
|
3482
|
+
`;
|
|
3483
|
+
emit(message);
|
|
3484
|
+
void this.logToFile(`[${params.repoName}] ${message.trim()}`);
|
|
3485
|
+
finish({ exitCode: 1, timedOut: false, output });
|
|
3486
|
+
});
|
|
3487
|
+
proc.stdin.end();
|
|
3488
|
+
});
|
|
3489
|
+
}
|
|
3490
|
+
async executeStartHooks(params = {}) {
|
|
3491
|
+
const onEvent = params.onEvent ?? (() => {
|
|
3492
|
+
});
|
|
3493
|
+
const envHookContent = (params.environmentStartHook ?? ENGINE_ENV.REPLICAS_ENV_START_HOOK)?.trim() ?? "";
|
|
3494
|
+
const hookEntries = this.configs.filter((entry) => entry.config.startHook && entry.config.startHook.commands.length > 0);
|
|
3495
|
+
const outputBlocks = [];
|
|
3496
|
+
let overallExitCode = 0;
|
|
3497
|
+
let overallTimedOut = false;
|
|
3498
|
+
const recordResult = (result) => {
|
|
3499
|
+
const output = result.output.join("");
|
|
3500
|
+
if (output) {
|
|
3501
|
+
outputBlocks.push(output);
|
|
3397
3502
|
}
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
} catch (error) {
|
|
3401
|
-
const execError = error;
|
|
3402
|
-
if (execError.stdout) {
|
|
3403
|
-
output.push(execError.stdout);
|
|
3404
|
-
await this.logToFile(`[${params.repoName}] [stdout] ${execError.stdout}`);
|
|
3503
|
+
if (result.exitCode !== 0 && overallExitCode === 0) {
|
|
3504
|
+
overallExitCode = result.exitCode;
|
|
3405
3505
|
}
|
|
3406
|
-
if (
|
|
3407
|
-
|
|
3408
|
-
await this.logToFile(`[${params.repoName}] [stderr] ${execError.stderr}`);
|
|
3506
|
+
if (result.timedOut) {
|
|
3507
|
+
overallTimedOut = true;
|
|
3409
3508
|
}
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
output.push(`[ERROR] ${params.label} failed: ${errorMessage}`);
|
|
3413
|
-
await this.logToFile(`[${params.repoName}] [ERROR] ${params.label} failed: ${errorMessage}`);
|
|
3414
|
-
return { exitCode: execError.code ?? 1, timedOut: execError.killed === true, output };
|
|
3415
|
-
}
|
|
3416
|
-
}
|
|
3417
|
-
/**
|
|
3418
|
-
* Execute start hooks from all repositories sequentially.
|
|
3419
|
-
*/
|
|
3420
|
-
async executeStartHooks() {
|
|
3421
|
-
const hookEntries = this.configs.filter((entry) => entry.config.startHook && entry.config.startHook.commands.length > 0);
|
|
3422
|
-
if (hookEntries.length === 0) {
|
|
3509
|
+
};
|
|
3510
|
+
if (!envHookContent && hookEntries.length === 0) {
|
|
3423
3511
|
this.hooksRunning = false;
|
|
3424
3512
|
this.hooksCompleted = true;
|
|
3425
3513
|
this.hooksFailed = false;
|
|
3514
|
+
await environmentDetailsService.setEnvironmentStartHook("n/a");
|
|
3426
3515
|
const repos = await gitService.listRepositories();
|
|
3427
3516
|
for (const repo of repos) {
|
|
3428
3517
|
await environmentDetailsService.setRepositoryStartHook(repo.name, "n/a");
|
|
3429
3518
|
}
|
|
3430
|
-
|
|
3519
|
+
const result = { exitCode: 0, output: "No start hooks configured.", timedOut: false };
|
|
3520
|
+
onEvent({ type: "output", data: `${result.output}
|
|
3521
|
+
`, label: "start-hooks" });
|
|
3522
|
+
onEvent({ type: "complete", exitCode: result.exitCode, timedOut: result.timedOut });
|
|
3523
|
+
return result;
|
|
3431
3524
|
}
|
|
3432
3525
|
this.hooksRunning = true;
|
|
3433
3526
|
this.hooksCompleted = false;
|
|
3527
|
+
this.hooksFailed = false;
|
|
3434
3528
|
try {
|
|
3435
3529
|
await mkdir5(join9(homedir7(), ".replicas"), { recursive: true });
|
|
3436
3530
|
await writeFile5(
|
|
3437
3531
|
START_HOOKS_LOG,
|
|
3438
3532
|
`=== Start Hooks Execution Log ===
|
|
3439
3533
|
Started: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
3534
|
+
Environment hook: ${envHookContent ? "yes" : "no"}
|
|
3440
3535
|
Repositories: ${hookEntries.length}
|
|
3441
3536
|
|
|
3442
3537
|
`,
|
|
3443
3538
|
"utf-8"
|
|
3444
3539
|
);
|
|
3540
|
+
if (envHookContent) {
|
|
3541
|
+
const envResult = await this.executeEnvironmentStartHook({
|
|
3542
|
+
content: envHookContent,
|
|
3543
|
+
timeoutMs: params.timeoutMs,
|
|
3544
|
+
onOutputChunk: (chunk) => onEvent({ type: "output", data: chunk, label: "environment" })
|
|
3545
|
+
});
|
|
3546
|
+
recordResult(envResult);
|
|
3547
|
+
} else {
|
|
3548
|
+
await environmentDetailsService.setEnvironmentStartHook("n/a");
|
|
3549
|
+
}
|
|
3445
3550
|
const repos = await gitService.listRepositories();
|
|
3446
3551
|
const reposWithHooks = new Set(hookEntries.map((entry) => entry.repoName));
|
|
3447
3552
|
for (const repo of repos) {
|
|
@@ -3455,48 +3560,53 @@ Repositories: ${hookEntries.length}
|
|
|
3455
3560
|
continue;
|
|
3456
3561
|
}
|
|
3457
3562
|
const persistedRepoState = await loadRepoState(entry.repoName);
|
|
3458
|
-
if (persistedRepoState?.startHooksCompleted) {
|
|
3563
|
+
if (!params.ignorePersistedState && persistedRepoState?.startHooksCompleted) {
|
|
3459
3564
|
await this.logToFile(`[${entry.repoName}] Start hooks already completed in this workspace lifecycle, skipping`);
|
|
3460
3565
|
await environmentDetailsService.setRepositoryStartHook(entry.repoName, "yes");
|
|
3461
3566
|
continue;
|
|
3462
3567
|
}
|
|
3463
|
-
const timeout = startHookConfig.timeout ?? DEFAULT_START_HOOK_TIMEOUT_MS;
|
|
3568
|
+
const timeout = params.timeoutMs ?? startHookConfig.timeout ?? DEFAULT_START_HOOK_TIMEOUT_MS;
|
|
3464
3569
|
let repoFailed = false;
|
|
3465
3570
|
let lastExitCode = 0;
|
|
3466
3571
|
let repoTimedOut = false;
|
|
3467
3572
|
const repoOutput = [];
|
|
3573
|
+
const onRepoOutput = (chunk) => onEvent({ type: "output", data: chunk, label: `repo:${entry.repoName}` });
|
|
3468
3574
|
await this.logToFile(`[${entry.repoName}] Executing ${startHookConfig.commands.length} hook(s) with timeout ${timeout}ms`);
|
|
3469
3575
|
if (startHookConfig.separate === false) {
|
|
3470
3576
|
const combinedScript = `set -e
|
|
3471
3577
|
${startHookConfig.commands.join("\n")}`;
|
|
3472
|
-
const
|
|
3578
|
+
const result2 = await this.execStartHookCommand({
|
|
3473
3579
|
command: combinedScript,
|
|
3474
3580
|
label: "combined script",
|
|
3475
3581
|
repoName: entry.repoName,
|
|
3476
3582
|
cwd: entry.workingDirectory,
|
|
3477
|
-
timeout
|
|
3583
|
+
timeout,
|
|
3584
|
+
onOutputChunk: onRepoOutput
|
|
3478
3585
|
});
|
|
3479
|
-
repoOutput.push(...
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3586
|
+
repoOutput.push(...result2.output);
|
|
3587
|
+
recordResult(result2);
|
|
3588
|
+
if (result2.exitCode !== 0) {
|
|
3589
|
+
lastExitCode = result2.exitCode;
|
|
3590
|
+
repoTimedOut = result2.timedOut;
|
|
3483
3591
|
this.hooksFailed = true;
|
|
3484
3592
|
repoFailed = true;
|
|
3485
3593
|
await environmentDetailsService.setRepositoryStartHook(entry.repoName, "no");
|
|
3486
3594
|
}
|
|
3487
3595
|
} else {
|
|
3488
3596
|
for (const hook of startHookConfig.commands) {
|
|
3489
|
-
const
|
|
3597
|
+
const result2 = await this.execStartHookCommand({
|
|
3490
3598
|
command: hook,
|
|
3491
3599
|
label: hook,
|
|
3492
3600
|
repoName: entry.repoName,
|
|
3493
3601
|
cwd: entry.workingDirectory,
|
|
3494
|
-
timeout
|
|
3602
|
+
timeout,
|
|
3603
|
+
onOutputChunk: onRepoOutput
|
|
3495
3604
|
});
|
|
3496
|
-
repoOutput.push(...
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3605
|
+
repoOutput.push(...result2.output);
|
|
3606
|
+
recordResult(result2);
|
|
3607
|
+
if (result2.exitCode !== 0) {
|
|
3608
|
+
lastExitCode = result2.exitCode;
|
|
3609
|
+
repoTimedOut = result2.timedOut;
|
|
3500
3610
|
this.hooksFailed = true;
|
|
3501
3611
|
repoFailed = true;
|
|
3502
3612
|
await environmentDetailsService.setRepositoryStartHook(entry.repoName, "no");
|
|
@@ -3511,20 +3621,30 @@ ${startHookConfig.commands.join("\n")}`;
|
|
|
3511
3621
|
timedOut: repoTimedOut,
|
|
3512
3622
|
executedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3513
3623
|
});
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3624
|
+
if ((params.markRepoCompleted ?? true) && !repoFailed) {
|
|
3625
|
+
const fallbackRepoState = persistedRepoState ?? {
|
|
3626
|
+
name: entry.repoName,
|
|
3627
|
+
path: entry.workingDirectory,
|
|
3628
|
+
defaultBranch: entry.defaultBranch,
|
|
3629
|
+
currentBranch: entry.defaultBranch,
|
|
3630
|
+
prUrls: [],
|
|
3631
|
+
gitDiff: null,
|
|
3632
|
+
startHooksCompleted: false
|
|
3633
|
+
};
|
|
3634
|
+
await saveRepoState(entry.repoName, { startHooksCompleted: true }, fallbackRepoState);
|
|
3635
|
+
}
|
|
3524
3636
|
await environmentDetailsService.setRepositoryStartHook(entry.repoName, repoFailed ? "no" : "yes");
|
|
3525
3637
|
}
|
|
3526
3638
|
this.hooksCompleted = true;
|
|
3639
|
+
this.hooksFailed = overallExitCode !== 0;
|
|
3527
3640
|
await this.logToFile(`=== All start hooks completed at ${(/* @__PURE__ */ new Date()).toISOString()} ===`);
|
|
3641
|
+
const result = {
|
|
3642
|
+
exitCode: overallExitCode,
|
|
3643
|
+
output: outputBlocks.join("\n\n"),
|
|
3644
|
+
timedOut: overallTimedOut
|
|
3645
|
+
};
|
|
3646
|
+
onEvent({ type: "complete", exitCode: result.exitCode, timedOut: result.timedOut });
|
|
3647
|
+
return result;
|
|
3528
3648
|
} catch (error) {
|
|
3529
3649
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3530
3650
|
this.hooksFailed = true;
|
|
@@ -3536,6 +3656,16 @@ ${startHookConfig.commands.join("\n")}`;
|
|
|
3536
3656
|
this.hooksRunning = false;
|
|
3537
3657
|
}
|
|
3538
3658
|
}
|
|
3659
|
+
async runStartHooksStreaming(params) {
|
|
3660
|
+
await this.loadConfigs();
|
|
3661
|
+
return this.executeStartHooks({
|
|
3662
|
+
environmentStartHook: params.environmentStartHook,
|
|
3663
|
+
timeoutMs: params.timeoutMs,
|
|
3664
|
+
ignorePersistedState: true,
|
|
3665
|
+
markRepoCompleted: false,
|
|
3666
|
+
onEvent: params.onEvent
|
|
3667
|
+
});
|
|
3668
|
+
}
|
|
3539
3669
|
/**
|
|
3540
3670
|
* Check if start hooks are currently running.
|
|
3541
3671
|
*/
|
|
@@ -3548,14 +3678,18 @@ ${startHookConfig.commands.join("\n")}`;
|
|
|
3548
3678
|
didHooksFail() {
|
|
3549
3679
|
return this.hooksFailed;
|
|
3550
3680
|
}
|
|
3551
|
-
/**
|
|
3552
|
-
* Get aggregated start hook metadata.
|
|
3553
|
-
*/
|
|
3554
3681
|
getStartHookConfig() {
|
|
3555
|
-
const commands =
|
|
3682
|
+
const commands = [];
|
|
3683
|
+
const envHookContent = ENGINE_ENV.REPLICAS_ENV_START_HOOK?.trim();
|
|
3684
|
+
if (envHookContent) {
|
|
3685
|
+
commands.push(`[environment] ${envHookContent}`);
|
|
3686
|
+
}
|
|
3687
|
+
for (const entry of this.configs) {
|
|
3556
3688
|
const repoCommands = entry.config.startHook?.commands ?? [];
|
|
3557
|
-
|
|
3558
|
-
|
|
3689
|
+
for (const command of repoCommands) {
|
|
3690
|
+
commands.push(`[${entry.repoName}] ${command}`);
|
|
3691
|
+
}
|
|
3692
|
+
}
|
|
3559
3693
|
if (commands.length === 0) {
|
|
3560
3694
|
return void 0;
|
|
3561
3695
|
}
|
|
@@ -5359,7 +5493,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
|
|
|
5359
5493
|
};
|
|
5360
5494
|
|
|
5361
5495
|
// src/managers/codex-asp/app-server-process.ts
|
|
5362
|
-
import { spawn } from "child_process";
|
|
5496
|
+
import { spawn as spawn2 } from "child_process";
|
|
5363
5497
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
5364
5498
|
|
|
5365
5499
|
// src/managers/codex-asp/asp-client.ts
|
|
@@ -5581,7 +5715,7 @@ var AppServerProcess = class {
|
|
|
5581
5715
|
return { client: this.client };
|
|
5582
5716
|
}
|
|
5583
5717
|
this.shuttingDown = false;
|
|
5584
|
-
const child =
|
|
5718
|
+
const child = spawn2(this.binary, this.args, {
|
|
5585
5719
|
cwd: this.cwd,
|
|
5586
5720
|
env: this.env,
|
|
5587
5721
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9011,7 +9145,7 @@ var PlanService = class {
|
|
|
9011
9145
|
var planService = new PlanService();
|
|
9012
9146
|
|
|
9013
9147
|
// src/services/warm-hooks-service.ts
|
|
9014
|
-
import { spawn as
|
|
9148
|
+
import { spawn as spawn3 } from "child_process";
|
|
9015
9149
|
import { readFile as readFile12 } from "fs/promises";
|
|
9016
9150
|
import { existsSync as existsSync8 } from "fs";
|
|
9017
9151
|
import { join as join19 } from "path";
|
|
@@ -9200,7 +9334,7 @@ async function executeHookScriptStreaming(params) {
|
|
|
9200
9334
|
params.onChunk(`$ ${params.label}
|
|
9201
9335
|
`);
|
|
9202
9336
|
return new Promise((resolve3) => {
|
|
9203
|
-
const proc =
|
|
9337
|
+
const proc = spawn3("bash", ["-lc", params.content], {
|
|
9204
9338
|
cwd: params.cwd,
|
|
9205
9339
|
env: process.env,
|
|
9206
9340
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9855,6 +9989,64 @@ function createV1Routes(deps) {
|
|
|
9855
9989
|
);
|
|
9856
9990
|
}
|
|
9857
9991
|
});
|
|
9992
|
+
app2.post("/start-hooks/run/stream", async (c) => {
|
|
9993
|
+
try {
|
|
9994
|
+
const body = await c.req.json();
|
|
9995
|
+
const encoder = new TextEncoder();
|
|
9996
|
+
const stream = new ReadableStream({
|
|
9997
|
+
start: (controller) => {
|
|
9998
|
+
let closed = false;
|
|
9999
|
+
const safeEnqueue = (chunk) => {
|
|
10000
|
+
if (closed) return false;
|
|
10001
|
+
try {
|
|
10002
|
+
controller.enqueue(encoder.encode(chunk));
|
|
10003
|
+
return true;
|
|
10004
|
+
} catch {
|
|
10005
|
+
closed = true;
|
|
10006
|
+
return false;
|
|
10007
|
+
}
|
|
10008
|
+
};
|
|
10009
|
+
const heartbeat = setInterval(() => {
|
|
10010
|
+
if (!safeEnqueue(": ping\n\n")) clearInterval(heartbeat);
|
|
10011
|
+
}, 15e3);
|
|
10012
|
+
safeEnqueue(": connected\n\n");
|
|
10013
|
+
replicasConfigService.runStartHooksStreaming({
|
|
10014
|
+
environmentStartHook: body.environmentStartHook,
|
|
10015
|
+
timeoutMs: body.timeoutMs,
|
|
10016
|
+
onEvent: (event) => {
|
|
10017
|
+
safeEnqueue(`data: ${JSON.stringify(event)}
|
|
10018
|
+
|
|
10019
|
+
`);
|
|
10020
|
+
}
|
|
10021
|
+
}).then(() => {
|
|
10022
|
+
clearInterval(heartbeat);
|
|
10023
|
+
if (!closed) {
|
|
10024
|
+
closed = true;
|
|
10025
|
+
controller.close();
|
|
10026
|
+
}
|
|
10027
|
+
}).catch((err) => {
|
|
10028
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
10029
|
+
safeEnqueue(`data: ${JSON.stringify({ type: "error", data: message })}
|
|
10030
|
+
|
|
10031
|
+
`);
|
|
10032
|
+
clearInterval(heartbeat);
|
|
10033
|
+
if (!closed) {
|
|
10034
|
+
closed = true;
|
|
10035
|
+
controller.close();
|
|
10036
|
+
}
|
|
10037
|
+
});
|
|
10038
|
+
}
|
|
10039
|
+
});
|
|
10040
|
+
return new Response(stream, {
|
|
10041
|
+
headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" }
|
|
10042
|
+
});
|
|
10043
|
+
} catch (error) {
|
|
10044
|
+
return c.json(
|
|
10045
|
+
jsonError("Failed to run start hooks", error instanceof Error ? error.message : "Unknown error"),
|
|
10046
|
+
500
|
|
10047
|
+
);
|
|
10048
|
+
}
|
|
10049
|
+
});
|
|
9858
10050
|
app2.get("/warm-hooks/logs", async (c) => {
|
|
9859
10051
|
try {
|
|
9860
10052
|
const logs = await warmHookLogsService.getAllLogs();
|