replicas-engine 0.1.229 → 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 +281 -88
- package/package.json +1 -1
package/dist/src/index.js
CHANGED
|
@@ -1652,9 +1652,26 @@ 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
|
-
var DEFAULT_WARM_HOOK_TIMEOUT_MS =
|
|
1657
|
-
var MAX_WARM_HOOK_TIMEOUT_MS =
|
|
1672
|
+
var DEFAULT_WARM_HOOK_TIMEOUT_MS = 60 * 60 * 1e3;
|
|
1673
|
+
var MAX_WARM_HOOK_TIMEOUT_MS = 120 * 60 * 1e3;
|
|
1674
|
+
var DEFAULT_START_HOOK_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
1658
1675
|
var DEFAULT_HOOK_OUTPUT_PREVIEW_CHARS = 1e5;
|
|
1659
1676
|
var HOOK_EXEC_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
1660
1677
|
function isRecord2(value) {
|
|
@@ -1703,16 +1720,7 @@ function resolveWarmHookConfig(value) {
|
|
|
1703
1720
|
if (value === void 0) {
|
|
1704
1721
|
return null;
|
|
1705
1722
|
}
|
|
1706
|
-
|
|
1707
|
-
const commands = typeof parsed === "string" ? [parsed.trim()].filter(Boolean) : parsed.commands.map((command) => command.trim()).filter(Boolean);
|
|
1708
|
-
if (commands.length === 0) {
|
|
1709
|
-
return null;
|
|
1710
|
-
}
|
|
1711
|
-
return {
|
|
1712
|
-
commands,
|
|
1713
|
-
timeoutMs: typeof parsed === "string" ? void 0 : parsed.timeout,
|
|
1714
|
-
separate: typeof parsed === "string" ? void 0 : parsed.separate
|
|
1715
|
-
};
|
|
1723
|
+
return resolveHookConfig(parseWarmHookConfig(value));
|
|
1716
1724
|
}
|
|
1717
1725
|
|
|
1718
1726
|
// ../shared/src/replicas-config.ts
|
|
@@ -1776,7 +1784,7 @@ function isClaudeAuthErrorText(text) {
|
|
|
1776
1784
|
}
|
|
1777
1785
|
|
|
1778
1786
|
// ../shared/src/engine/environment.ts
|
|
1779
|
-
var DAYTONA_SNAPSHOT_ID = "
|
|
1787
|
+
var DAYTONA_SNAPSHOT_ID = "29-05-2026-royal-york-v2";
|
|
1780
1788
|
|
|
1781
1789
|
// ../shared/src/engine/types.ts
|
|
1782
1790
|
var DEFAULT_CHAT_TITLES = {
|
|
@@ -2022,7 +2030,9 @@ function loadEngineEnv() {
|
|
|
2022
2030
|
AWS_REGION: readEnv("AWS_REGION"),
|
|
2023
2031
|
REPLICAS_CLAUDE_AUTH_METHOD: parseClaudeAuthMethod(readEnv("REPLICAS_CLAUDE_AUTH_METHOD")),
|
|
2024
2032
|
REPLICAS_CODEX_AUTH_METHOD: parseCodexAuthMethod(readEnv("REPLICAS_CODEX_AUTH_METHOD")),
|
|
2025
|
-
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"
|
|
2026
2036
|
};
|
|
2027
2037
|
if (!IS_WARMING_MODE && !env.WORKSPACE_ID) {
|
|
2028
2038
|
console.error("WORKSPACE_ID is not set \u2014 this is required in normal (non-warming) mode");
|
|
@@ -3053,8 +3063,7 @@ import { readFile as readFile4, appendFile as appendFile2, writeFile as writeFil
|
|
|
3053
3063
|
import { existsSync as existsSync4 } from "fs";
|
|
3054
3064
|
import { join as join9 } from "path";
|
|
3055
3065
|
import { homedir as homedir7 } from "os";
|
|
3056
|
-
import {
|
|
3057
|
-
import { promisify as promisify2 } from "util";
|
|
3066
|
+
import { spawn } from "child_process";
|
|
3058
3067
|
|
|
3059
3068
|
// src/services/environment-details-service.ts
|
|
3060
3069
|
import { mkdir as mkdir3, readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
|
|
@@ -3114,6 +3123,7 @@ function createDefaultDetails() {
|
|
|
3114
3123
|
engineVersion: DAYTONA_SNAPSHOT_ID,
|
|
3115
3124
|
globalWarmHookCompleted: { status: "n/a", details: null },
|
|
3116
3125
|
environmentWarmHookCompleted: { status: "n/a", details: null },
|
|
3126
|
+
environmentStartHookCompleted: { status: "n/a", details: null },
|
|
3117
3127
|
repositories: [],
|
|
3118
3128
|
filesUploaded: [],
|
|
3119
3129
|
envVarsSet: [],
|
|
@@ -3199,6 +3209,12 @@ var EnvironmentDetailsService = class {
|
|
|
3199
3209
|
current.lastUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3200
3210
|
await writeDetails(current);
|
|
3201
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
|
+
}
|
|
3202
3218
|
async setRepositoryWarmHook(repositoryName, status) {
|
|
3203
3219
|
const current = await readDetails();
|
|
3204
3220
|
current.repositories = upsertRepositoryStatus(current.repositories, [{
|
|
@@ -3294,7 +3310,6 @@ var StartHookLogsService = class {
|
|
|
3294
3310
|
var startHookLogsService = new StartHookLogsService();
|
|
3295
3311
|
|
|
3296
3312
|
// src/services/replicas-config-service.ts
|
|
3297
|
-
var execAsync = promisify2(exec);
|
|
3298
3313
|
var START_HOOKS_LOG = join9(homedir7(), ".replicas", "startHooks.log");
|
|
3299
3314
|
var START_HOOKS_RUNNING_PROMPT = `IMPORTANT - Start Hooks Running:
|
|
3300
3315
|
Start hooks are shell commands/scripts set by repository owners that run on workspace startup.
|
|
@@ -3326,6 +3341,12 @@ var ReplicasConfigService = class {
|
|
|
3326
3341
|
*/
|
|
3327
3342
|
async initialize() {
|
|
3328
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
|
+
}
|
|
3329
3350
|
void this.executeStartHooks().catch((error) => {
|
|
3330
3351
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3331
3352
|
this.hooksFailed = true;
|
|
@@ -3376,71 +3397,156 @@ var ReplicasConfigService = class {
|
|
|
3376
3397
|
console.error("Failed to write to start hooks log:", error);
|
|
3377
3398
|
}
|
|
3378
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
|
+
}
|
|
3379
3418
|
async execStartHookCommand(params) {
|
|
3380
3419
|
const output = [];
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
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], {
|
|
3384
3431
|
cwd: params.cwd,
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
env: process.env
|
|
3432
|
+
env: process.env,
|
|
3433
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3388
3434
|
});
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
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);
|
|
3396
3502
|
}
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
} catch (error) {
|
|
3400
|
-
const execError = error;
|
|
3401
|
-
if (execError.stdout) {
|
|
3402
|
-
output.push(execError.stdout);
|
|
3403
|
-
await this.logToFile(`[${params.repoName}] [stdout] ${execError.stdout}`);
|
|
3503
|
+
if (result.exitCode !== 0 && overallExitCode === 0) {
|
|
3504
|
+
overallExitCode = result.exitCode;
|
|
3404
3505
|
}
|
|
3405
|
-
if (
|
|
3406
|
-
|
|
3407
|
-
await this.logToFile(`[${params.repoName}] [stderr] ${execError.stderr}`);
|
|
3506
|
+
if (result.timedOut) {
|
|
3507
|
+
overallTimedOut = true;
|
|
3408
3508
|
}
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
output.push(`[ERROR] ${params.label} failed: ${errorMessage}`);
|
|
3412
|
-
await this.logToFile(`[${params.repoName}] [ERROR] ${params.label} failed: ${errorMessage}`);
|
|
3413
|
-
return { exitCode: execError.code ?? 1, timedOut: execError.killed === true, output };
|
|
3414
|
-
}
|
|
3415
|
-
}
|
|
3416
|
-
/**
|
|
3417
|
-
* Execute start hooks from all repositories sequentially.
|
|
3418
|
-
*/
|
|
3419
|
-
async executeStartHooks() {
|
|
3420
|
-
const hookEntries = this.configs.filter((entry) => entry.config.startHook && entry.config.startHook.commands.length > 0);
|
|
3421
|
-
if (hookEntries.length === 0) {
|
|
3509
|
+
};
|
|
3510
|
+
if (!envHookContent && hookEntries.length === 0) {
|
|
3422
3511
|
this.hooksRunning = false;
|
|
3423
3512
|
this.hooksCompleted = true;
|
|
3424
3513
|
this.hooksFailed = false;
|
|
3514
|
+
await environmentDetailsService.setEnvironmentStartHook("n/a");
|
|
3425
3515
|
const repos = await gitService.listRepositories();
|
|
3426
3516
|
for (const repo of repos) {
|
|
3427
3517
|
await environmentDetailsService.setRepositoryStartHook(repo.name, "n/a");
|
|
3428
3518
|
}
|
|
3429
|
-
|
|
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;
|
|
3430
3524
|
}
|
|
3431
3525
|
this.hooksRunning = true;
|
|
3432
3526
|
this.hooksCompleted = false;
|
|
3527
|
+
this.hooksFailed = false;
|
|
3433
3528
|
try {
|
|
3434
3529
|
await mkdir5(join9(homedir7(), ".replicas"), { recursive: true });
|
|
3435
3530
|
await writeFile5(
|
|
3436
3531
|
START_HOOKS_LOG,
|
|
3437
3532
|
`=== Start Hooks Execution Log ===
|
|
3438
3533
|
Started: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
3534
|
+
Environment hook: ${envHookContent ? "yes" : "no"}
|
|
3439
3535
|
Repositories: ${hookEntries.length}
|
|
3440
3536
|
|
|
3441
3537
|
`,
|
|
3442
3538
|
"utf-8"
|
|
3443
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
|
+
}
|
|
3444
3550
|
const repos = await gitService.listRepositories();
|
|
3445
3551
|
const reposWithHooks = new Set(hookEntries.map((entry) => entry.repoName));
|
|
3446
3552
|
for (const repo of repos) {
|
|
@@ -3454,48 +3560,53 @@ Repositories: ${hookEntries.length}
|
|
|
3454
3560
|
continue;
|
|
3455
3561
|
}
|
|
3456
3562
|
const persistedRepoState = await loadRepoState(entry.repoName);
|
|
3457
|
-
if (persistedRepoState?.startHooksCompleted) {
|
|
3563
|
+
if (!params.ignorePersistedState && persistedRepoState?.startHooksCompleted) {
|
|
3458
3564
|
await this.logToFile(`[${entry.repoName}] Start hooks already completed in this workspace lifecycle, skipping`);
|
|
3459
3565
|
await environmentDetailsService.setRepositoryStartHook(entry.repoName, "yes");
|
|
3460
3566
|
continue;
|
|
3461
3567
|
}
|
|
3462
|
-
const timeout = startHookConfig.timeout ??
|
|
3568
|
+
const timeout = params.timeoutMs ?? startHookConfig.timeout ?? DEFAULT_START_HOOK_TIMEOUT_MS;
|
|
3463
3569
|
let repoFailed = false;
|
|
3464
3570
|
let lastExitCode = 0;
|
|
3465
3571
|
let repoTimedOut = false;
|
|
3466
3572
|
const repoOutput = [];
|
|
3573
|
+
const onRepoOutput = (chunk) => onEvent({ type: "output", data: chunk, label: `repo:${entry.repoName}` });
|
|
3467
3574
|
await this.logToFile(`[${entry.repoName}] Executing ${startHookConfig.commands.length} hook(s) with timeout ${timeout}ms`);
|
|
3468
3575
|
if (startHookConfig.separate === false) {
|
|
3469
3576
|
const combinedScript = `set -e
|
|
3470
3577
|
${startHookConfig.commands.join("\n")}`;
|
|
3471
|
-
const
|
|
3578
|
+
const result2 = await this.execStartHookCommand({
|
|
3472
3579
|
command: combinedScript,
|
|
3473
3580
|
label: "combined script",
|
|
3474
3581
|
repoName: entry.repoName,
|
|
3475
3582
|
cwd: entry.workingDirectory,
|
|
3476
|
-
timeout
|
|
3583
|
+
timeout,
|
|
3584
|
+
onOutputChunk: onRepoOutput
|
|
3477
3585
|
});
|
|
3478
|
-
repoOutput.push(...
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3586
|
+
repoOutput.push(...result2.output);
|
|
3587
|
+
recordResult(result2);
|
|
3588
|
+
if (result2.exitCode !== 0) {
|
|
3589
|
+
lastExitCode = result2.exitCode;
|
|
3590
|
+
repoTimedOut = result2.timedOut;
|
|
3482
3591
|
this.hooksFailed = true;
|
|
3483
3592
|
repoFailed = true;
|
|
3484
3593
|
await environmentDetailsService.setRepositoryStartHook(entry.repoName, "no");
|
|
3485
3594
|
}
|
|
3486
3595
|
} else {
|
|
3487
3596
|
for (const hook of startHookConfig.commands) {
|
|
3488
|
-
const
|
|
3597
|
+
const result2 = await this.execStartHookCommand({
|
|
3489
3598
|
command: hook,
|
|
3490
3599
|
label: hook,
|
|
3491
3600
|
repoName: entry.repoName,
|
|
3492
3601
|
cwd: entry.workingDirectory,
|
|
3493
|
-
timeout
|
|
3602
|
+
timeout,
|
|
3603
|
+
onOutputChunk: onRepoOutput
|
|
3494
3604
|
});
|
|
3495
|
-
repoOutput.push(...
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3605
|
+
repoOutput.push(...result2.output);
|
|
3606
|
+
recordResult(result2);
|
|
3607
|
+
if (result2.exitCode !== 0) {
|
|
3608
|
+
lastExitCode = result2.exitCode;
|
|
3609
|
+
repoTimedOut = result2.timedOut;
|
|
3499
3610
|
this.hooksFailed = true;
|
|
3500
3611
|
repoFailed = true;
|
|
3501
3612
|
await environmentDetailsService.setRepositoryStartHook(entry.repoName, "no");
|
|
@@ -3510,20 +3621,30 @@ ${startHookConfig.commands.join("\n")}`;
|
|
|
3510
3621
|
timedOut: repoTimedOut,
|
|
3511
3622
|
executedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3512
3623
|
});
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
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
|
+
}
|
|
3523
3636
|
await environmentDetailsService.setRepositoryStartHook(entry.repoName, repoFailed ? "no" : "yes");
|
|
3524
3637
|
}
|
|
3525
3638
|
this.hooksCompleted = true;
|
|
3639
|
+
this.hooksFailed = overallExitCode !== 0;
|
|
3526
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;
|
|
3527
3648
|
} catch (error) {
|
|
3528
3649
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3529
3650
|
this.hooksFailed = true;
|
|
@@ -3535,6 +3656,16 @@ ${startHookConfig.commands.join("\n")}`;
|
|
|
3535
3656
|
this.hooksRunning = false;
|
|
3536
3657
|
}
|
|
3537
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
|
+
}
|
|
3538
3669
|
/**
|
|
3539
3670
|
* Check if start hooks are currently running.
|
|
3540
3671
|
*/
|
|
@@ -3547,14 +3678,18 @@ ${startHookConfig.commands.join("\n")}`;
|
|
|
3547
3678
|
didHooksFail() {
|
|
3548
3679
|
return this.hooksFailed;
|
|
3549
3680
|
}
|
|
3550
|
-
/**
|
|
3551
|
-
* Get aggregated start hook metadata.
|
|
3552
|
-
*/
|
|
3553
3681
|
getStartHookConfig() {
|
|
3554
|
-
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) {
|
|
3555
3688
|
const repoCommands = entry.config.startHook?.commands ?? [];
|
|
3556
|
-
|
|
3557
|
-
|
|
3689
|
+
for (const command of repoCommands) {
|
|
3690
|
+
commands.push(`[${entry.repoName}] ${command}`);
|
|
3691
|
+
}
|
|
3692
|
+
}
|
|
3558
3693
|
if (commands.length === 0) {
|
|
3559
3694
|
return void 0;
|
|
3560
3695
|
}
|
|
@@ -5358,7 +5493,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
|
|
|
5358
5493
|
};
|
|
5359
5494
|
|
|
5360
5495
|
// src/managers/codex-asp/app-server-process.ts
|
|
5361
|
-
import { spawn } from "child_process";
|
|
5496
|
+
import { spawn as spawn2 } from "child_process";
|
|
5362
5497
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
5363
5498
|
|
|
5364
5499
|
// src/managers/codex-asp/asp-client.ts
|
|
@@ -5580,7 +5715,7 @@ var AppServerProcess = class {
|
|
|
5580
5715
|
return { client: this.client };
|
|
5581
5716
|
}
|
|
5582
5717
|
this.shuttingDown = false;
|
|
5583
|
-
const child =
|
|
5718
|
+
const child = spawn2(this.binary, this.args, {
|
|
5584
5719
|
cwd: this.cwd,
|
|
5585
5720
|
env: this.env,
|
|
5586
5721
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9010,7 +9145,7 @@ var PlanService = class {
|
|
|
9010
9145
|
var planService = new PlanService();
|
|
9011
9146
|
|
|
9012
9147
|
// src/services/warm-hooks-service.ts
|
|
9013
|
-
import { spawn as
|
|
9148
|
+
import { spawn as spawn3 } from "child_process";
|
|
9014
9149
|
import { readFile as readFile12 } from "fs/promises";
|
|
9015
9150
|
import { existsSync as existsSync8 } from "fs";
|
|
9016
9151
|
import { join as join19 } from "path";
|
|
@@ -9199,7 +9334,7 @@ async function executeHookScriptStreaming(params) {
|
|
|
9199
9334
|
params.onChunk(`$ ${params.label}
|
|
9200
9335
|
`);
|
|
9201
9336
|
return new Promise((resolve3) => {
|
|
9202
|
-
const proc =
|
|
9337
|
+
const proc = spawn3("bash", ["-lc", params.content], {
|
|
9203
9338
|
cwd: params.cwd,
|
|
9204
9339
|
env: process.env,
|
|
9205
9340
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9854,6 +9989,64 @@ function createV1Routes(deps) {
|
|
|
9854
9989
|
);
|
|
9855
9990
|
}
|
|
9856
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
|
+
});
|
|
9857
10050
|
app2.get("/warm-hooks/logs", async (c) => {
|
|
9858
10051
|
try {
|
|
9859
10052
|
const logs = await warmHookLogsService.getAllLogs();
|