@gemslibe/rbo 0.2.0 → 0.4.0
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/README.md +11 -0
- package/config/agent.json +34 -0
- package/config/controller.json +20 -0
- package/dist/rbo-mcp-stdio.js +71 -16
- package/dist/rbo.js +965 -306
- package/package.json +8 -3
- package/scripts/stop-running-rbo.mjs +306 -0
package/dist/rbo.js
CHANGED
|
@@ -14,9 +14,9 @@ var RBO_CONTROLLER_VERSION, RBO_AGENT_VERSION, RBO_STDIO_ADAPTER_VERSION, RBO_WI
|
|
|
14
14
|
var init_versions = __esm({
|
|
15
15
|
"../../packages/shared/dist/versions.js"() {
|
|
16
16
|
"use strict";
|
|
17
|
-
RBO_CONTROLLER_VERSION = "0.
|
|
18
|
-
RBO_AGENT_VERSION = "0.
|
|
19
|
-
RBO_STDIO_ADAPTER_VERSION = "0.
|
|
17
|
+
RBO_CONTROLLER_VERSION = "0.4.0";
|
|
18
|
+
RBO_AGENT_VERSION = "0.4.0";
|
|
19
|
+
RBO_STDIO_ADAPTER_VERSION = "0.4.0";
|
|
20
20
|
RBO_WIRE_PROTOCOL_MIN_VERSION = 1;
|
|
21
21
|
RBO_WIRE_PROTOCOL_MAX_VERSION = 1;
|
|
22
22
|
RBO_CONTROLLER_SCHEMA_VERSION = 3;
|
|
@@ -204,11 +204,11 @@ async function resolveContainedCwd(projectPath, cwd) {
|
|
|
204
204
|
if (!isPathContained(projectPath, joined)) {
|
|
205
205
|
throw new Error(`cwd escapes project root: ${cwd}`);
|
|
206
206
|
}
|
|
207
|
-
const { access: access4, mkdir:
|
|
207
|
+
const { access: access4, mkdir: mkdir15 } = await import("node:fs/promises");
|
|
208
208
|
try {
|
|
209
209
|
await access4(joined);
|
|
210
210
|
} catch {
|
|
211
|
-
await
|
|
211
|
+
await mkdir15(joined, { recursive: true });
|
|
212
212
|
}
|
|
213
213
|
await assertRealPathContained(projectPath, joined);
|
|
214
214
|
return resolveRealPath(joined);
|
|
@@ -1636,7 +1636,7 @@ import { z as z4 } from "zod";
|
|
|
1636
1636
|
function getMcpToolDef(name) {
|
|
1637
1637
|
return MCP_TOOL_DEFS.find((def) => def.name === name);
|
|
1638
1638
|
}
|
|
1639
|
-
var JOB_SUBMIT_INPUT, JOB_CONFIRM_INPUT, AGENTS_LIST_INPUT, JOB_GET_INPUT, JOB_WAIT_INPUT, JOB_LOGS_INPUT, JOB_CANCEL_INPUT, JOB_ARTIFACTS_INPUT, ARTIFACT_MATERIALIZE_INPUT, AGENT_PROBE_INPUT, MCP_TOOL_DEFS;
|
|
1639
|
+
var JOB_SUBMIT_INPUT, JOB_CONFIRM_INPUT, AGENTS_LIST_INPUT, JOB_GET_INPUT, JOB_WAIT_INPUT, JOB_LOGS_INPUT, JOB_CANCEL_INPUT, JOB_ARTIFACTS_INPUT, ARTIFACT_MATERIALIZE_INPUT, AGENT_PROBE_INPUT, JOB_RUN_INPUT, MCP_TOOL_DEFS;
|
|
1640
1640
|
var init_mcp_tools = __esm({
|
|
1641
1641
|
"../../packages/protocol/dist/mcp-tools.js"() {
|
|
1642
1642
|
"use strict";
|
|
@@ -1680,6 +1680,21 @@ var init_mcp_tools = __esm({
|
|
|
1680
1680
|
AGENT_PROBE_INPUT = {
|
|
1681
1681
|
agent_id: z4.string().min(1)
|
|
1682
1682
|
};
|
|
1683
|
+
JOB_RUN_INPUT = {
|
|
1684
|
+
command: z4.string().min(1).optional(),
|
|
1685
|
+
project_root: z4.string().min(1).optional(),
|
|
1686
|
+
job_id: z4.string().min(1).optional(),
|
|
1687
|
+
cwd: z4.string().default("."),
|
|
1688
|
+
timeout_seconds: z4.number().positive().max(3600).default(3600),
|
|
1689
|
+
wait_seconds: z4.number().int().min(0).max(3600).optional(),
|
|
1690
|
+
/** Max seconds this MCP response blocks; default keeps typical 60s clients safe. */
|
|
1691
|
+
mcp_wait_slice_seconds: z4.number().int().min(1).max(55).default(50),
|
|
1692
|
+
artifacts: JobRequestSchema.shape.artifacts,
|
|
1693
|
+
risk_level: JobRequestSchema.shape.risk_level,
|
|
1694
|
+
client_request_id: z4.string().min(1).optional(),
|
|
1695
|
+
name: z4.string().optional(),
|
|
1696
|
+
include_log_tail_lines: z4.number().int().min(0).max(1e3).default(80)
|
|
1697
|
+
};
|
|
1683
1698
|
MCP_TOOL_DEFS = [
|
|
1684
1699
|
{
|
|
1685
1700
|
name: "agents_list",
|
|
@@ -1691,6 +1706,11 @@ var init_mcp_tools = __esm({
|
|
|
1691
1706
|
description: "Submit a build/test job and capture an immutable workspace snapshot.",
|
|
1692
1707
|
inputShape: JOB_SUBMIT_INPUT
|
|
1693
1708
|
},
|
|
1709
|
+
{
|
|
1710
|
+
name: "job_run",
|
|
1711
|
+
description: "Run a command remotely (snapshot + execute). Waits up to mcp_wait_slice_seconds (default 50). If still running, returns resume:true \u2014 call again with the same job_id. Preferred for interactive AI clients.",
|
|
1712
|
+
inputShape: JOB_RUN_INPUT
|
|
1713
|
+
},
|
|
1694
1714
|
{
|
|
1695
1715
|
name: "job_confirm",
|
|
1696
1716
|
description: "Confirm a destructive or hardware-risk job after snapshot capture.",
|
|
@@ -2028,7 +2048,7 @@ var init_runtime_env = __esm({
|
|
|
2028
2048
|
});
|
|
2029
2049
|
|
|
2030
2050
|
// ../../packages/executor/dist/windows-executor-path.js
|
|
2031
|
-
import { existsSync as
|
|
2051
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
2032
2052
|
import { createRequire } from "node:module";
|
|
2033
2053
|
import { dirname as dirname3, join as join11 } from "node:path";
|
|
2034
2054
|
import { fileURLToPath } from "node:url";
|
|
@@ -2048,7 +2068,7 @@ function describeWindowsExecutorResolution(options = {}) {
|
|
|
2048
2068
|
const env = options.env ?? process.env;
|
|
2049
2069
|
const platform3 = options.platform ?? process.platform;
|
|
2050
2070
|
const arch2 = options.arch ?? process.arch;
|
|
2051
|
-
const exists = options.existsSyncFn ??
|
|
2071
|
+
const exists = options.existsSyncFn ?? existsSync3;
|
|
2052
2072
|
const moduleDir = options.moduleDir ?? dirname3(fileURLToPath(import.meta.url));
|
|
2053
2073
|
const fromEnv = env.RBO_WINDOWS_EXECUTOR;
|
|
2054
2074
|
if (fromEnv && exists(fromEnv)) {
|
|
@@ -2067,11 +2087,11 @@ function describeWindowsExecutorResolution(options = {}) {
|
|
|
2067
2087
|
}
|
|
2068
2088
|
candidates.push(join11(moduleDir, WINDOWS_EXECUTOR_BINARY_NAME), join11(moduleDir, "..", WINDOWS_EXECUTOR_BINARY_NAME), join11(moduleDir, "..", "bin", WINDOWS_EXECUTOR_BINARY_NAME), join11(moduleDir, "../..", "bin", WINDOWS_EXECUTOR_BINARY_NAME));
|
|
2069
2089
|
candidates.push(
|
|
2070
|
-
join11(moduleDir, "../../../native/windows-executor/target/debug", WINDOWS_EXECUTOR_BINARY_NAME),
|
|
2071
2090
|
join11(moduleDir, "../../../native/windows-executor/target/release", WINDOWS_EXECUTOR_BINARY_NAME),
|
|
2091
|
+
join11(moduleDir, "../../../native/windows-executor/target/debug", WINDOWS_EXECUTOR_BINARY_NAME),
|
|
2072
2092
|
// When bundled into apps/cli/dist/rbo.js, climb to repo root.
|
|
2073
|
-
join11(moduleDir, "../../native/windows-executor/target/
|
|
2074
|
-
join11(moduleDir, "../../native/windows-executor/target/
|
|
2093
|
+
join11(moduleDir, "../../native/windows-executor/target/release", WINDOWS_EXECUTOR_BINARY_NAME),
|
|
2094
|
+
join11(moduleDir, "../../native/windows-executor/target/debug", WINDOWS_EXECUTOR_BINARY_NAME)
|
|
2075
2095
|
);
|
|
2076
2096
|
if (options.extraCandidates) {
|
|
2077
2097
|
candidates.push(...options.extraCandidates);
|
|
@@ -2178,8 +2198,11 @@ async function writeJobScript(controlDir, execution, scriptName) {
|
|
|
2178
2198
|
const scriptPath = join12(controlDir, fileName);
|
|
2179
2199
|
const isCleanup = scriptName === "cleanup.ps1" || scriptName === "cleanup.sh" || scriptName === "cleanup.cmd";
|
|
2180
2200
|
const scriptBody = isCleanup ? execution.cleanup_script ?? "" : execution.script;
|
|
2181
|
-
|
|
2201
|
+
let body = isDirect && process.platform !== "win32" && !scriptBody.startsWith("#!") ? `#!/usr/bin/env bash
|
|
2182
2202
|
${scriptBody}` : scriptBody;
|
|
2203
|
+
if (isPowerShell) {
|
|
2204
|
+
body = `${POWERSHELL_JOB_PRELUDE}${body}`;
|
|
2205
|
+
}
|
|
2183
2206
|
await writeFile6(scriptPath, body, "utf8");
|
|
2184
2207
|
if (process.platform !== "win32") {
|
|
2185
2208
|
await chmod(scriptPath, 493);
|
|
@@ -2525,7 +2548,7 @@ async function runCleanupScript(input) {
|
|
|
2525
2548
|
}
|
|
2526
2549
|
return { exitCode: result.exitCode, timedOut: false };
|
|
2527
2550
|
}
|
|
2528
|
-
var execFileAsync4, ManagedChildProcess, PHASE3_UNSUPPORTED_SHELLS;
|
|
2551
|
+
var execFileAsync4, ManagedChildProcess, PHASE3_UNSUPPORTED_SHELLS, POWERSHELL_JOB_PRELUDE;
|
|
2529
2552
|
var init_script = __esm({
|
|
2530
2553
|
"../../packages/executor/dist/script.js"() {
|
|
2531
2554
|
"use strict";
|
|
@@ -2555,6 +2578,13 @@ var init_script = __esm({
|
|
|
2555
2578
|
}
|
|
2556
2579
|
};
|
|
2557
2580
|
PHASE3_UNSUPPORTED_SHELLS = ["sh", "zsh", "cmd", "pwsh"];
|
|
2581
|
+
POWERSHELL_JOB_PRELUDE = [
|
|
2582
|
+
"# RBO PowerShell runtime prelude (injected; do not remove)",
|
|
2583
|
+
"$global:LASTEXITCODE = 0",
|
|
2584
|
+
"# --- end RBO prelude ---",
|
|
2585
|
+
"",
|
|
2586
|
+
""
|
|
2587
|
+
].join("\n");
|
|
2558
2588
|
}
|
|
2559
2589
|
});
|
|
2560
2590
|
|
|
@@ -4238,7 +4268,7 @@ var init_materialize = __esm({
|
|
|
4238
4268
|
});
|
|
4239
4269
|
|
|
4240
4270
|
// ../../packages/snapshot/dist/index.js
|
|
4241
|
-
import { z as
|
|
4271
|
+
import { z as z7 } from "zod";
|
|
4242
4272
|
var Sha256HexSchema, ContentIdSchema, SafeRelativePathSchema, SnapshotFileEntrySchema, SnapshotWorkspaceSchema, SnapshotAdditionalRootSchema, SnapshotRepoSchema, payloadBase, FullSnapshotManifestSchema, GitOverlaySnapshotManifestSchema, SnapshotManifestSchema, SnapshotInstanceSchema;
|
|
4243
4273
|
var init_dist3 = __esm({
|
|
4244
4274
|
"../../packages/snapshot/dist/index.js"() {
|
|
@@ -4252,88 +4282,88 @@ var init_dist3 = __esm({
|
|
|
4252
4282
|
init_capture();
|
|
4253
4283
|
init_overlay();
|
|
4254
4284
|
init_materialize();
|
|
4255
|
-
Sha256HexSchema =
|
|
4256
|
-
ContentIdSchema =
|
|
4257
|
-
SafeRelativePathSchema =
|
|
4285
|
+
Sha256HexSchema = z7.string().regex(/^[0-9a-f]{64}$/i);
|
|
4286
|
+
ContentIdSchema = z7.string().regex(/^sha256:[0-9a-f]{64}$/i);
|
|
4287
|
+
SafeRelativePathSchema = z7.string().min(1).refine((value) => isSafeRelativePath(value), {
|
|
4258
4288
|
message: "must be a relative path without '..', absolute, or UNC segments"
|
|
4259
4289
|
});
|
|
4260
|
-
SnapshotFileEntrySchema =
|
|
4261
|
-
|
|
4262
|
-
path:
|
|
4263
|
-
type:
|
|
4264
|
-
mode:
|
|
4265
|
-
size:
|
|
4290
|
+
SnapshotFileEntrySchema = z7.discriminatedUnion("type", [
|
|
4291
|
+
z7.object({
|
|
4292
|
+
path: z7.string().min(1),
|
|
4293
|
+
type: z7.literal("file"),
|
|
4294
|
+
mode: z7.enum(["100644", "100755"]),
|
|
4295
|
+
size: z7.number().int().nonnegative(),
|
|
4266
4296
|
sha256: Sha256HexSchema
|
|
4267
4297
|
}),
|
|
4268
|
-
|
|
4269
|
-
path:
|
|
4270
|
-
type:
|
|
4271
|
-
mode:
|
|
4272
|
-
target:
|
|
4298
|
+
z7.object({
|
|
4299
|
+
path: z7.string().min(1),
|
|
4300
|
+
type: z7.literal("symlink"),
|
|
4301
|
+
mode: z7.literal("120000"),
|
|
4302
|
+
target: z7.string().min(1)
|
|
4273
4303
|
})
|
|
4274
4304
|
]);
|
|
4275
|
-
SnapshotWorkspaceSchema =
|
|
4305
|
+
SnapshotWorkspaceSchema = z7.object({
|
|
4276
4306
|
main_mount: SafeRelativePathSchema,
|
|
4277
|
-
cwd:
|
|
4307
|
+
cwd: z7.string().min(1).refine((value) => isSafeRelativePath(value, { allowDot: true }), {
|
|
4278
4308
|
message: "must be a relative path without '..', absolute, or UNC segments"
|
|
4279
4309
|
})
|
|
4280
4310
|
});
|
|
4281
|
-
SnapshotAdditionalRootSchema =
|
|
4282
|
-
id:
|
|
4311
|
+
SnapshotAdditionalRootSchema = z7.object({
|
|
4312
|
+
id: z7.string().min(1),
|
|
4283
4313
|
mount: SafeRelativePathSchema,
|
|
4284
|
-
file_count:
|
|
4285
|
-
total_size:
|
|
4314
|
+
file_count: z7.number().int().nonnegative(),
|
|
4315
|
+
total_size: z7.number().int().nonnegative(),
|
|
4286
4316
|
tree_sha256: Sha256HexSchema,
|
|
4287
|
-
mode:
|
|
4288
|
-
});
|
|
4289
|
-
SnapshotRepoSchema =
|
|
4290
|
-
canonical_id:
|
|
4291
|
-
url:
|
|
4292
|
-
branch:
|
|
4293
|
-
base_commit:
|
|
4294
|
-
head_is_pushed:
|
|
4317
|
+
mode: z7.enum(["read_only", "read_write"]).default("read_only")
|
|
4318
|
+
});
|
|
4319
|
+
SnapshotRepoSchema = z7.object({
|
|
4320
|
+
canonical_id: z7.string().min(1),
|
|
4321
|
+
url: z7.string().min(1),
|
|
4322
|
+
branch: z7.string().nullable(),
|
|
4323
|
+
base_commit: z7.string().min(1),
|
|
4324
|
+
head_is_pushed: z7.boolean(),
|
|
4295
4325
|
/** Optional; Controller derives from branch when absent (§10.6). */
|
|
4296
|
-
fetch_refs:
|
|
4326
|
+
fetch_refs: z7.array(z7.string().min(1)).optional()
|
|
4297
4327
|
});
|
|
4298
4328
|
payloadBase = {
|
|
4299
|
-
format:
|
|
4300
|
-
compression:
|
|
4329
|
+
format: z7.literal("tar"),
|
|
4330
|
+
compression: z7.literal("zstd"),
|
|
4301
4331
|
sha256: Sha256HexSchema,
|
|
4302
|
-
size:
|
|
4332
|
+
size: z7.number().int().nonnegative()
|
|
4303
4333
|
};
|
|
4304
|
-
FullSnapshotManifestSchema =
|
|
4305
|
-
schema_version:
|
|
4334
|
+
FullSnapshotManifestSchema = z7.object({
|
|
4335
|
+
schema_version: z7.literal(1),
|
|
4306
4336
|
content_id: ContentIdSchema,
|
|
4307
4337
|
repo: SnapshotRepoSchema.partial({ base_commit: true }).optional(),
|
|
4308
4338
|
workspace: SnapshotWorkspaceSchema,
|
|
4309
|
-
source:
|
|
4310
|
-
files:
|
|
4311
|
-
empty_directories:
|
|
4339
|
+
source: z7.object({
|
|
4340
|
+
files: z7.array(SnapshotFileEntrySchema),
|
|
4341
|
+
empty_directories: z7.array(z7.string()).default([])
|
|
4312
4342
|
}),
|
|
4313
|
-
additional_roots:
|
|
4314
|
-
payload:
|
|
4343
|
+
additional_roots: z7.array(SnapshotAdditionalRootSchema).default([]),
|
|
4344
|
+
payload: z7.object({ mode: z7.literal("full"), ...payloadBase })
|
|
4315
4345
|
}).strict();
|
|
4316
|
-
GitOverlaySnapshotManifestSchema =
|
|
4317
|
-
schema_version:
|
|
4346
|
+
GitOverlaySnapshotManifestSchema = z7.object({
|
|
4347
|
+
schema_version: z7.literal(1),
|
|
4318
4348
|
content_id: ContentIdSchema,
|
|
4319
4349
|
repo: SnapshotRepoSchema,
|
|
4320
4350
|
workspace: SnapshotWorkspaceSchema,
|
|
4321
|
-
overlay:
|
|
4322
|
-
files:
|
|
4323
|
-
deletions:
|
|
4324
|
-
empty_directories:
|
|
4351
|
+
overlay: z7.object({
|
|
4352
|
+
files: z7.array(SnapshotFileEntrySchema),
|
|
4353
|
+
deletions: z7.array(z7.string()),
|
|
4354
|
+
empty_directories: z7.array(z7.string()).default([])
|
|
4325
4355
|
}),
|
|
4326
|
-
additional_roots:
|
|
4327
|
-
payload:
|
|
4356
|
+
additional_roots: z7.array(SnapshotAdditionalRootSchema).default([]),
|
|
4357
|
+
payload: z7.object({ mode: z7.literal("git_overlay"), ...payloadBase })
|
|
4328
4358
|
}).strict();
|
|
4329
|
-
SnapshotManifestSchema =
|
|
4359
|
+
SnapshotManifestSchema = z7.union([
|
|
4330
4360
|
FullSnapshotManifestSchema,
|
|
4331
4361
|
GitOverlaySnapshotManifestSchema
|
|
4332
4362
|
]);
|
|
4333
|
-
SnapshotInstanceSchema =
|
|
4334
|
-
snapshot_id:
|
|
4363
|
+
SnapshotInstanceSchema = z7.object({
|
|
4364
|
+
snapshot_id: z7.string().min(1),
|
|
4335
4365
|
content_id: ContentIdSchema,
|
|
4336
|
-
captured_at:
|
|
4366
|
+
captured_at: z7.string().min(1)
|
|
4337
4367
|
});
|
|
4338
4368
|
}
|
|
4339
4369
|
});
|
|
@@ -4723,21 +4753,48 @@ var init_dist4 = __esm({
|
|
|
4723
4753
|
});
|
|
4724
4754
|
|
|
4725
4755
|
// ../controller/dist/config.js
|
|
4726
|
-
import { mkdirSync as mkdirSync5 } from "node:fs";
|
|
4727
|
-
import { join as join25 } from "node:path";
|
|
4756
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "node:fs";
|
|
4757
|
+
import { isAbsolute as isAbsolute2, join as join25 } from "node:path";
|
|
4758
|
+
import { z as z8 } from "zod";
|
|
4728
4759
|
function parseCsv2(raw) {
|
|
4729
4760
|
if (!raw?.trim()) {
|
|
4730
4761
|
return [];
|
|
4731
4762
|
}
|
|
4732
4763
|
return raw.split(",").map((part) => part.trim()).filter(Boolean);
|
|
4733
4764
|
}
|
|
4734
|
-
function
|
|
4735
|
-
|
|
4736
|
-
|
|
4765
|
+
function envSet2(key, env = process.env) {
|
|
4766
|
+
return env[key] !== void 0;
|
|
4767
|
+
}
|
|
4768
|
+
function parseEnvNumber2(key, raw) {
|
|
4769
|
+
const n = Number(raw);
|
|
4770
|
+
if (!Number.isFinite(n)) {
|
|
4771
|
+
throw new Error(`Invalid ${key}=${JSON.stringify(raw)}: expected a finite number`);
|
|
4737
4772
|
}
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4773
|
+
return n;
|
|
4774
|
+
}
|
|
4775
|
+
function parseEnvInt2(key, raw) {
|
|
4776
|
+
const n = parseEnvNumber2(key, raw);
|
|
4777
|
+
if (!Number.isInteger(n)) {
|
|
4778
|
+
throw new Error(`Invalid ${key}=${JSON.stringify(raw)}: expected an integer`);
|
|
4779
|
+
}
|
|
4780
|
+
return n;
|
|
4781
|
+
}
|
|
4782
|
+
function assertAbsoluteAllowlistPaths(paths, label) {
|
|
4783
|
+
return paths.map((path, index) => {
|
|
4784
|
+
const parsed = AbsoluteAllowlistPathSchema.safeParse(path);
|
|
4785
|
+
if (!parsed.success) {
|
|
4786
|
+
throw new Error(`Invalid ${label}[${index}]=${JSON.stringify(path)}: must be a non-empty absolute path`);
|
|
4787
|
+
}
|
|
4788
|
+
return parsed.data;
|
|
4789
|
+
});
|
|
4790
|
+
}
|
|
4791
|
+
function parseGitAllowlistFromEnv2(env = process.env) {
|
|
4792
|
+
if (!envSet2("RBO_GIT_ALLOWLIST_SCHEMES", env) && !envSet2("RBO_GIT_ALLOWLIST_HOSTS", env) && !envSet2("RBO_GIT_ALLOWLIST_PREFIXES", env)) {
|
|
4793
|
+
return void 0;
|
|
4794
|
+
}
|
|
4795
|
+
const schemes = parseCsv2(env.RBO_GIT_ALLOWLIST_SCHEMES);
|
|
4796
|
+
const hosts = parseCsv2(env.RBO_GIT_ALLOWLIST_HOSTS);
|
|
4797
|
+
const prefixesRaw = env.RBO_GIT_ALLOWLIST_PREFIXES?.trim();
|
|
4741
4798
|
let repository_prefixes;
|
|
4742
4799
|
if (prefixesRaw) {
|
|
4743
4800
|
try {
|
|
@@ -4756,34 +4813,124 @@ function parseGitAllowlist2(overrides) {
|
|
|
4756
4813
|
...repository_prefixes ? { repository_prefixes } : {}
|
|
4757
4814
|
};
|
|
4758
4815
|
}
|
|
4816
|
+
function mergeGitAllowlist2(file, fromEnv, override) {
|
|
4817
|
+
if (override) {
|
|
4818
|
+
return override;
|
|
4819
|
+
}
|
|
4820
|
+
if (fromEnv) {
|
|
4821
|
+
return fromEnv;
|
|
4822
|
+
}
|
|
4823
|
+
if (file) {
|
|
4824
|
+
return {
|
|
4825
|
+
schemes: file.schemes && file.schemes.length > 0 ? file.schemes : [...DEFAULT_GIT_ALLOWLIST_SCHEMES2],
|
|
4826
|
+
hosts: file.hosts && file.hosts.length > 0 ? file.hosts : [...DEFAULT_GIT_ALLOWLIST_HOSTS2],
|
|
4827
|
+
...file.repository_prefixes ? { repository_prefixes: file.repository_prefixes } : {}
|
|
4828
|
+
};
|
|
4829
|
+
}
|
|
4830
|
+
return {
|
|
4831
|
+
schemes: [...DEFAULT_GIT_ALLOWLIST_SCHEMES2],
|
|
4832
|
+
hosts: [...DEFAULT_GIT_ALLOWLIST_HOSTS2]
|
|
4833
|
+
};
|
|
4834
|
+
}
|
|
4835
|
+
function defaultControllerConfigFile() {
|
|
4836
|
+
return {
|
|
4837
|
+
mcp_host: "127.0.0.1",
|
|
4838
|
+
mcp_port: 7410,
|
|
4839
|
+
agent_plane_port: 7411,
|
|
4840
|
+
controller_public_host: "127.0.0.1",
|
|
4841
|
+
data_plane_base_url: null,
|
|
4842
|
+
allowed_project_roots: [],
|
|
4843
|
+
allowed_artifact_destinations: [],
|
|
4844
|
+
git_allowlist: {
|
|
4845
|
+
schemes: [...DEFAULT_GIT_ALLOWLIST_SCHEMES2],
|
|
4846
|
+
hosts: [...DEFAULT_GIT_ALLOWLIST_HOSTS2]
|
|
4847
|
+
},
|
|
4848
|
+
local_max_concurrent_jobs: 1,
|
|
4849
|
+
disconnect_grace_seconds: 60,
|
|
4850
|
+
orphan_timeout_seconds: 300,
|
|
4851
|
+
reconcile_deadline_seconds: 120,
|
|
4852
|
+
allow_local_fallback: true,
|
|
4853
|
+
max_git_bundle_bytes: DEFAULT_MAX_GIT_BUNDLE_BYTES,
|
|
4854
|
+
local_fallback_max_host_cpu_percent: 80
|
|
4855
|
+
};
|
|
4856
|
+
}
|
|
4857
|
+
function resolveControllerConfigPath(dataDir) {
|
|
4858
|
+
return join25(dataDir, CONTROLLER_CONFIG_FILENAME);
|
|
4859
|
+
}
|
|
4860
|
+
function readControllerConfigFile(configPath) {
|
|
4861
|
+
if (!existsSync8(configPath)) {
|
|
4862
|
+
return void 0;
|
|
4863
|
+
}
|
|
4864
|
+
let raw;
|
|
4865
|
+
try {
|
|
4866
|
+
raw = JSON.parse(readFileSync7(configPath, "utf8"));
|
|
4867
|
+
} catch (error) {
|
|
4868
|
+
throw new Error(`Invalid controller config JSON at ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
4869
|
+
}
|
|
4870
|
+
const parsed = ControllerConfigFileSchema.safeParse(raw);
|
|
4871
|
+
if (!parsed.success) {
|
|
4872
|
+
throw new Error(`Invalid controller config at ${configPath}: ${parsed.error.message}`);
|
|
4873
|
+
}
|
|
4874
|
+
return parsed.data;
|
|
4875
|
+
}
|
|
4876
|
+
function writeDefaultControllerConfigFile(dataDir, options = {}) {
|
|
4877
|
+
mkdirSync5(dataDir, { recursive: true });
|
|
4878
|
+
const path = resolveControllerConfigPath(dataDir);
|
|
4879
|
+
if (existsSync8(path) && !options.force) {
|
|
4880
|
+
return { path, written: false };
|
|
4881
|
+
}
|
|
4882
|
+
const body = `${JSON.stringify(defaultControllerConfigFile(), null, 2)}
|
|
4883
|
+
`;
|
|
4884
|
+
writeFileSync5(path, body, "utf8");
|
|
4885
|
+
return { path, written: true };
|
|
4886
|
+
}
|
|
4759
4887
|
function loadControllerConfig(overrides = {}) {
|
|
4760
|
-
const
|
|
4888
|
+
const { configPath: configPathOption, ...fieldOverrides } = overrides;
|
|
4889
|
+
const dataDir = fieldOverrides.dataDir ?? resolveControllerDataDir();
|
|
4890
|
+
const configPath = configPathOption === void 0 ? resolveControllerConfigPath(dataDir) : configPathOption;
|
|
4891
|
+
const file = configPath === null ? void 0 : readControllerConfigFile(configPath);
|
|
4892
|
+
const mcpHost = fieldOverrides.mcpHost ?? (envSet2("RBO_MCP_HOST") ? process.env.RBO_MCP_HOST : void 0) ?? file?.mcp_host ?? "127.0.0.1";
|
|
4893
|
+
const mcpPort = fieldOverrides.mcpPort ?? (envSet2("RBO_MCP_PORT") ? parseEnvInt2("RBO_MCP_PORT", process.env.RBO_MCP_PORT) : void 0) ?? file?.mcp_port ?? 7410;
|
|
4894
|
+
const agentPlanePort = fieldOverrides.agentPlanePort ?? (envSet2("RBO_AGENT_PORT") ? parseEnvInt2("RBO_AGENT_PORT", process.env.RBO_AGENT_PORT) : void 0) ?? file?.agent_plane_port ?? 7411;
|
|
4895
|
+
const controllerPublicHost = fieldOverrides.controllerPublicHost ?? (envSet2("RBO_CONTROLLER_PUBLIC_HOST") ? process.env.RBO_CONTROLLER_PUBLIC_HOST : void 0) ?? file?.controller_public_host ?? "127.0.0.1";
|
|
4896
|
+
let dataPlaneBaseUrl = fieldOverrides.dataPlaneBaseUrl;
|
|
4897
|
+
if (dataPlaneBaseUrl === void 0) {
|
|
4898
|
+
if (envSet2("RBO_DATA_PLANE_BASE_URL")) {
|
|
4899
|
+
dataPlaneBaseUrl = process.env.RBO_DATA_PLANE_BASE_URL;
|
|
4900
|
+
} else if (file?.data_plane_base_url !== void 0) {
|
|
4901
|
+
dataPlaneBaseUrl = file.data_plane_base_url ?? void 0;
|
|
4902
|
+
}
|
|
4903
|
+
}
|
|
4904
|
+
const allowedProjectRoots = assertAbsoluteAllowlistPaths(fieldOverrides.allowedProjectRoots ?? (envSet2("RBO_ALLOWED_PROJECT_ROOTS") ? parseCsv2(process.env.RBO_ALLOWED_PROJECT_ROOTS) : void 0) ?? file?.allowed_project_roots ?? [], "allowed_project_roots");
|
|
4905
|
+
const allowedArtifactDestinations = assertAbsoluteAllowlistPaths(fieldOverrides.allowedArtifactDestinations ?? (envSet2("RBO_ALLOWED_ARTIFACT_DESTINATIONS") ? parseCsv2(process.env.RBO_ALLOWED_ARTIFACT_DESTINATIONS) : void 0) ?? file?.allowed_artifact_destinations ?? [], "allowed_artifact_destinations");
|
|
4906
|
+
const allowLocalFallback = fieldOverrides.allowLocalFallback ?? (envSet2("RBO_ALLOW_LOCAL_FALLBACK") ? process.env.RBO_ALLOW_LOCAL_FALLBACK !== "false" : void 0) ?? file?.allow_local_fallback ?? true;
|
|
4907
|
+
const maxHostCpuBusyFraction = fieldOverrides.maxHostCpuBusyFraction ?? (envSet2("RBO_LOCAL_FALLBACK_MAX_HOST_CPU_PERCENT") ? parseEnvNumber2("RBO_LOCAL_FALLBACK_MAX_HOST_CPU_PERCENT", process.env.RBO_LOCAL_FALLBACK_MAX_HOST_CPU_PERCENT) / 100 : void 0) ?? (file?.local_fallback_max_host_cpu_percent !== void 0 ? file.local_fallback_max_host_cpu_percent / 100 : void 0) ?? 0.8;
|
|
4761
4908
|
return {
|
|
4762
|
-
mcpHost
|
|
4763
|
-
mcpPort
|
|
4764
|
-
agentPlanePort
|
|
4765
|
-
controllerPublicHost
|
|
4766
|
-
dataPlaneBaseUrl
|
|
4909
|
+
mcpHost,
|
|
4910
|
+
mcpPort,
|
|
4911
|
+
agentPlanePort,
|
|
4912
|
+
controllerPublicHost,
|
|
4913
|
+
dataPlaneBaseUrl,
|
|
4767
4914
|
dataDir,
|
|
4768
|
-
databasePath:
|
|
4769
|
-
allowedProjectRoots
|
|
4770
|
-
allowedArtifactDestinations
|
|
4771
|
-
gitAllowlist:
|
|
4915
|
+
databasePath: fieldOverrides.databasePath ?? join25(dataDir, "controller.db"),
|
|
4916
|
+
allowedProjectRoots,
|
|
4917
|
+
allowedArtifactDestinations,
|
|
4918
|
+
gitAllowlist: mergeGitAllowlist2(file?.git_allowlist, parseGitAllowlistFromEnv2(), fieldOverrides.gitAllowlist),
|
|
4772
4919
|
localExecutor: {
|
|
4773
|
-
maxConcurrentJobs:
|
|
4920
|
+
maxConcurrentJobs: fieldOverrides.localExecutor?.maxConcurrentJobs ?? (envSet2("RBO_LOCAL_MAX_CONCURRENT_JOBS") ? parseEnvInt2("RBO_LOCAL_MAX_CONCURRENT_JOBS", process.env.RBO_LOCAL_MAX_CONCURRENT_JOBS) : void 0) ?? file?.local_max_concurrent_jobs ?? 1
|
|
4774
4921
|
},
|
|
4775
|
-
disconnectGraceSeconds:
|
|
4776
|
-
orphanTimeoutSeconds:
|
|
4777
|
-
reconcileDeadlineSeconds:
|
|
4778
|
-
allowLocalFallback
|
|
4779
|
-
maxGitBundleBytes:
|
|
4780
|
-
maxHostCpuBusyFraction
|
|
4922
|
+
disconnectGraceSeconds: fieldOverrides.disconnectGraceSeconds ?? (envSet2("RBO_DISCONNECT_GRACE_SECONDS") ? parseEnvInt2("RBO_DISCONNECT_GRACE_SECONDS", process.env.RBO_DISCONNECT_GRACE_SECONDS) : void 0) ?? file?.disconnect_grace_seconds ?? 60,
|
|
4923
|
+
orphanTimeoutSeconds: fieldOverrides.orphanTimeoutSeconds ?? (envSet2("RBO_ORPHAN_TIMEOUT_SECONDS") ? parseEnvInt2("RBO_ORPHAN_TIMEOUT_SECONDS", process.env.RBO_ORPHAN_TIMEOUT_SECONDS) : void 0) ?? file?.orphan_timeout_seconds ?? 300,
|
|
4924
|
+
reconcileDeadlineSeconds: fieldOverrides.reconcileDeadlineSeconds ?? (envSet2("RBO_RECONCILE_DEADLINE_SECONDS") ? parseEnvInt2("RBO_RECONCILE_DEADLINE_SECONDS", process.env.RBO_RECONCILE_DEADLINE_SECONDS) : void 0) ?? file?.reconcile_deadline_seconds ?? 120,
|
|
4925
|
+
allowLocalFallback,
|
|
4926
|
+
maxGitBundleBytes: fieldOverrides.maxGitBundleBytes ?? (envSet2("RBO_MAX_GIT_BUNDLE_BYTES") ? parseEnvInt2("RBO_MAX_GIT_BUNDLE_BYTES", process.env.RBO_MAX_GIT_BUNDLE_BYTES) : void 0) ?? file?.max_git_bundle_bytes ?? DEFAULT_MAX_GIT_BUNDLE_BYTES,
|
|
4927
|
+
maxHostCpuBusyFraction
|
|
4781
4928
|
};
|
|
4782
4929
|
}
|
|
4783
4930
|
function ensureDataDir(config) {
|
|
4784
4931
|
mkdirSync5(config.dataDir, { recursive: true });
|
|
4785
4932
|
}
|
|
4786
|
-
var DEFAULT_MAX_GIT_BUNDLE_BYTES, DEFAULT_GIT_ALLOWLIST_SCHEMES2, DEFAULT_GIT_ALLOWLIST_HOSTS2;
|
|
4933
|
+
var DEFAULT_MAX_GIT_BUNDLE_BYTES, DEFAULT_GIT_ALLOWLIST_SCHEMES2, DEFAULT_GIT_ALLOWLIST_HOSTS2, CONTROLLER_CONFIG_FILENAME, GitAllowlistFileSchema2, AbsoluteAllowlistPathSchema, ControllerConfigFileSchema;
|
|
4787
4934
|
var init_config = __esm({
|
|
4788
4935
|
"../controller/dist/config.js"() {
|
|
4789
4936
|
"use strict";
|
|
@@ -4791,6 +4938,32 @@ var init_config = __esm({
|
|
|
4791
4938
|
DEFAULT_MAX_GIT_BUNDLE_BYTES = 512 * 1024 * 1024;
|
|
4792
4939
|
DEFAULT_GIT_ALLOWLIST_SCHEMES2 = ["https", "ssh"];
|
|
4793
4940
|
DEFAULT_GIT_ALLOWLIST_HOSTS2 = ["github.com"];
|
|
4941
|
+
CONTROLLER_CONFIG_FILENAME = "controller.json";
|
|
4942
|
+
GitAllowlistFileSchema2 = z8.object({
|
|
4943
|
+
schemes: z8.array(z8.string().min(1)).optional(),
|
|
4944
|
+
hosts: z8.array(z8.string().min(1)).optional(),
|
|
4945
|
+
repository_prefixes: z8.array(z8.string().min(1)).optional()
|
|
4946
|
+
});
|
|
4947
|
+
AbsoluteAllowlistPathSchema = z8.string().refine((value) => value.length > 0 && value === value.trim() && isAbsolute2(value), {
|
|
4948
|
+
message: 'must be a non-empty absolute path (relative paths, ".", and whitespace are rejected)'
|
|
4949
|
+
});
|
|
4950
|
+
ControllerConfigFileSchema = z8.object({
|
|
4951
|
+
mcp_host: z8.string().min(1).optional(),
|
|
4952
|
+
mcp_port: z8.number().int().positive().optional(),
|
|
4953
|
+
agent_plane_port: z8.number().int().positive().optional(),
|
|
4954
|
+
controller_public_host: z8.string().min(1).optional(),
|
|
4955
|
+
data_plane_base_url: z8.string().min(1).nullable().optional(),
|
|
4956
|
+
allowed_project_roots: z8.array(AbsoluteAllowlistPathSchema).optional(),
|
|
4957
|
+
allowed_artifact_destinations: z8.array(AbsoluteAllowlistPathSchema).optional(),
|
|
4958
|
+
git_allowlist: GitAllowlistFileSchema2.optional(),
|
|
4959
|
+
local_max_concurrent_jobs: z8.number().int().positive().optional(),
|
|
4960
|
+
disconnect_grace_seconds: z8.number().int().nonnegative().optional(),
|
|
4961
|
+
orphan_timeout_seconds: z8.number().int().nonnegative().optional(),
|
|
4962
|
+
reconcile_deadline_seconds: z8.number().int().nonnegative().optional(),
|
|
4963
|
+
allow_local_fallback: z8.boolean().optional(),
|
|
4964
|
+
max_git_bundle_bytes: z8.number().int().positive().optional(),
|
|
4965
|
+
local_fallback_max_host_cpu_percent: z8.number().min(0).max(100).optional()
|
|
4966
|
+
}).strict();
|
|
4794
4967
|
}
|
|
4795
4968
|
});
|
|
4796
4969
|
|
|
@@ -5028,7 +5201,7 @@ var init_database = __esm({
|
|
|
5028
5201
|
});
|
|
5029
5202
|
|
|
5030
5203
|
// ../controller/dist/execution/artifacts.js
|
|
5031
|
-
import { appendFile as appendFile3, mkdir as
|
|
5204
|
+
import { appendFile as appendFile3, mkdir as mkdir11, writeFile as writeFile11 } from "node:fs/promises";
|
|
5032
5205
|
import { join as join26 } from "node:path";
|
|
5033
5206
|
async function persistCollectedArtifacts(input) {
|
|
5034
5207
|
const results = [];
|
|
@@ -5073,7 +5246,7 @@ async function materializeArtifactToDestination(input) {
|
|
|
5073
5246
|
if (!allowed) {
|
|
5074
5247
|
throw new Error("Destination path is outside allowed artifact destinations");
|
|
5075
5248
|
}
|
|
5076
|
-
const { access: access4, readFile:
|
|
5249
|
+
const { access: access4, readFile: readFile19, rename: rename6 } = await import("node:fs/promises");
|
|
5077
5250
|
try {
|
|
5078
5251
|
await access4(input.destinationPath);
|
|
5079
5252
|
if (!input.overwrite) {
|
|
@@ -5084,20 +5257,20 @@ async function materializeArtifactToDestination(input) {
|
|
|
5084
5257
|
throw error;
|
|
5085
5258
|
}
|
|
5086
5259
|
}
|
|
5087
|
-
const content = await
|
|
5260
|
+
const content = await readFile19(row.path);
|
|
5088
5261
|
if (sha256(content) !== row.sha256) {
|
|
5089
5262
|
throw new Error("Stored artifact hash mismatch");
|
|
5090
5263
|
}
|
|
5091
5264
|
const tempPath = `${input.destinationPath}.rbo.tmp`;
|
|
5092
|
-
await
|
|
5093
|
-
const writtenHash = sha256(await
|
|
5265
|
+
await writeFile11(tempPath, content);
|
|
5266
|
+
const writtenHash = sha256(await readFile19(tempPath));
|
|
5094
5267
|
if (writtenHash !== row.sha256) {
|
|
5095
5268
|
throw new Error("Temporary artifact hash mismatch");
|
|
5096
5269
|
}
|
|
5097
5270
|
await rename6(tempPath, input.destinationPath);
|
|
5098
5271
|
if (input.dataDir) {
|
|
5099
5272
|
const auditDir = join26(input.dataDir, "audit");
|
|
5100
|
-
await
|
|
5273
|
+
await mkdir11(auditDir, { recursive: true });
|
|
5101
5274
|
const auditLine = JSON.stringify({
|
|
5102
5275
|
type: "artifact_materialize",
|
|
5103
5276
|
created_at: nowIso(),
|
|
@@ -5284,7 +5457,7 @@ var init_lifecycle = __esm({
|
|
|
5284
5457
|
});
|
|
5285
5458
|
|
|
5286
5459
|
// ../controller/dist/execution/runner.js
|
|
5287
|
-
import { mkdir as
|
|
5460
|
+
import { mkdir as mkdir12, readFile as readFile14, rm as rm10, writeFile as writeFile12 } from "node:fs/promises";
|
|
5288
5461
|
import { dirname as dirname7, join as join27 } from "node:path";
|
|
5289
5462
|
function getLocalRunningJobsCount() {
|
|
5290
5463
|
return admissionActive;
|
|
@@ -5410,9 +5583,9 @@ async function runLocalJob(ctx, jobId) {
|
|
|
5410
5583
|
const persistentLogs = attemptLogDir(ctx.dataDir, attempt.id);
|
|
5411
5584
|
const controlDir = attemptControlDir(ctx.dataDir, attempt.id);
|
|
5412
5585
|
const artifactsDir = attemptArtifactsDir(ctx.dataDir, attempt.id);
|
|
5413
|
-
await
|
|
5414
|
-
await
|
|
5415
|
-
await
|
|
5586
|
+
await mkdir12(artifactsDir, { recursive: true });
|
|
5587
|
+
await mkdir12(persistentLogs, { recursive: true });
|
|
5588
|
+
await mkdir12(controlDir, { recursive: true });
|
|
5416
5589
|
let timedOut = false;
|
|
5417
5590
|
let cancelled = false;
|
|
5418
5591
|
let durationComplete = false;
|
|
@@ -5433,7 +5606,7 @@ async function runLocalJob(ctx, jobId) {
|
|
|
5433
5606
|
if (!snapshotRow?.payload_path) {
|
|
5434
5607
|
throw new RboError("internal", "Job is missing snapshot payload");
|
|
5435
5608
|
}
|
|
5436
|
-
const manifest = JSON.parse(await
|
|
5609
|
+
const manifest = JSON.parse(await readFile14(snapshotRow.manifest_path, "utf8"));
|
|
5437
5610
|
const materialized = await materializeFullSnapshot({
|
|
5438
5611
|
manifest,
|
|
5439
5612
|
archivePath: snapshotRow.payload_path,
|
|
@@ -5451,7 +5624,7 @@ async function runLocalJob(ctx, jobId) {
|
|
|
5451
5624
|
});
|
|
5452
5625
|
const warningsPath = join27(dirname7(snapshotRow.manifest_path), "secret-warnings.json");
|
|
5453
5626
|
try {
|
|
5454
|
-
const warnings = JSON.parse(await
|
|
5627
|
+
const warnings = JSON.parse(await readFile14(warningsPath, "utf8"));
|
|
5455
5628
|
for (const warning of warnings) {
|
|
5456
5629
|
await emitJobEvent(ctx, logs, {
|
|
5457
5630
|
type: "secret_warning",
|
|
@@ -5698,9 +5871,9 @@ async function captureAndPersistSnapshot(ctx, jobId, request) {
|
|
|
5698
5871
|
contentStorageDir: storageDir
|
|
5699
5872
|
});
|
|
5700
5873
|
const manifestPath = join27(storageDir, "manifest.json");
|
|
5701
|
-
await
|
|
5702
|
-
await
|
|
5703
|
-
await
|
|
5874
|
+
await writeFile12(manifestPath, JSON.stringify(captured.manifest, null, 2));
|
|
5875
|
+
await writeFile12(join27(storageDir, "secret-warnings.json"), JSON.stringify(captured.secretWarnings));
|
|
5876
|
+
await writeFile12(join27(storageDir, "git-source-requirements.json"), JSON.stringify(captured.gitSourceRequirements));
|
|
5704
5877
|
persistSnapshot(ctx.db, {
|
|
5705
5878
|
snapshotId: captured.instance.snapshot_id,
|
|
5706
5879
|
contentId: captured.manifest.content_id,
|
|
@@ -5746,7 +5919,7 @@ function mergeGitSourceToolRequirements(request, gitSourceRequirements) {
|
|
|
5746
5919
|
}
|
|
5747
5920
|
async function readGitSourceRequirements(dataDir, jobId) {
|
|
5748
5921
|
try {
|
|
5749
|
-
const raw = await
|
|
5922
|
+
const raw = await readFile14(join27(dataDir, "snapshots", jobId, "git-source-requirements.json"), "utf8");
|
|
5750
5923
|
const parsed = JSON.parse(raw);
|
|
5751
5924
|
return {
|
|
5752
5925
|
submodules: parsed.submodules === true,
|
|
@@ -5838,7 +6011,7 @@ var init_data_tokens = __esm({
|
|
|
5838
6011
|
// ../controller/dist/http/data-plane.js
|
|
5839
6012
|
import { createHash as createHash7 } from "node:crypto";
|
|
5840
6013
|
import { createReadStream as createReadStream2, createWriteStream as createWriteStream2 } from "node:fs";
|
|
5841
|
-
import { access as access3, mkdir as
|
|
6014
|
+
import { access as access3, mkdir as mkdir13, readFile as readFile15, rename as rename5, rm as rm11, stat as stat8 } from "node:fs/promises";
|
|
5842
6015
|
import { dirname as dirname8, join as join28 } from "node:path";
|
|
5843
6016
|
function artifactUploadStreamLimit(declaredSizeBytes, globalCapBytes = DEFAULT_ARTIFACT_CAP_BYTES) {
|
|
5844
6017
|
return Math.min(globalCapBytes, declaredSizeBytes);
|
|
@@ -5868,7 +6041,7 @@ async function filterMissingArtifacts(dataDir, attemptId, artifacts) {
|
|
|
5868
6041
|
for (const art of artifacts) {
|
|
5869
6042
|
const destPath = join28(artifactsDir, art.logical_name);
|
|
5870
6043
|
try {
|
|
5871
|
-
const buf = await
|
|
6044
|
+
const buf = await readFile15(destPath);
|
|
5872
6045
|
const hash = createHash7("sha256").update(buf).digest("hex");
|
|
5873
6046
|
if (hash === art.sha256 && buf.length === art.size_bytes) {
|
|
5874
6047
|
continue;
|
|
@@ -5988,7 +6161,7 @@ async function handleDataPlaneRequest(req, res, options) {
|
|
|
5988
6161
|
const transferStats = await stat8(transferSnapshot);
|
|
5989
6162
|
payloadPath = transferSnapshot;
|
|
5990
6163
|
sizeBytes = transferStats.size;
|
|
5991
|
-
const transferData = await
|
|
6164
|
+
const transferData = await readFile15(transferSnapshot);
|
|
5992
6165
|
sha2562 = createHash7("sha256").update(transferData).digest("hex");
|
|
5993
6166
|
} catch {
|
|
5994
6167
|
}
|
|
@@ -6087,8 +6260,8 @@ async function handleDataPlaneRequest(req, res, options) {
|
|
|
6087
6260
|
return true;
|
|
6088
6261
|
}
|
|
6089
6262
|
const partPath = join28(artifactsDir, `.upload-${createHash7("sha256").update(logicalName).digest("hex").slice(0, 16)}.part`);
|
|
6090
|
-
await
|
|
6091
|
-
await
|
|
6263
|
+
await mkdir13(dirname8(destPath), { recursive: true });
|
|
6264
|
+
await mkdir13(artifactsDir, { recursive: true });
|
|
6092
6265
|
const hasher = createHash7("sha256");
|
|
6093
6266
|
const writeStream = createWriteStream2(partPath);
|
|
6094
6267
|
let sizeCounter = 0;
|
|
@@ -6297,7 +6470,7 @@ async function handleDataPlaneRequest(req, res, options) {
|
|
|
6297
6470
|
}
|
|
6298
6471
|
const bundlePath = join28(attemptTransferDir(options.dataDir, attemptId), "bundle.gitbundle");
|
|
6299
6472
|
try {
|
|
6300
|
-
const data = await
|
|
6473
|
+
const data = await readFile15(bundlePath);
|
|
6301
6474
|
const sha2562 = createHash7("sha256").update(data).digest("hex");
|
|
6302
6475
|
res.writeHead(200, {
|
|
6303
6476
|
"content-type": "application/octet-stream",
|
|
@@ -6696,8 +6869,8 @@ var init_coordinator = __esm({
|
|
|
6696
6869
|
// ../controller/dist/execution/remote-execution.js
|
|
6697
6870
|
import { execFile as execFile8 } from "node:child_process";
|
|
6698
6871
|
import { createHash as createHash8 } from "node:crypto";
|
|
6699
|
-
import { readFileSync as
|
|
6700
|
-
import { mkdir as
|
|
6872
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
6873
|
+
import { mkdir as mkdir14, readFile as readFile16, rm as rm12, stat as stat9, writeFile as writeFile13 } from "node:fs/promises";
|
|
6701
6874
|
import { dirname as dirname9, join as join29 } from "node:path";
|
|
6702
6875
|
import { promisify as promisify8 } from "node:util";
|
|
6703
6876
|
function snapshotManifestMode(manifest) {
|
|
@@ -6804,7 +6977,7 @@ function failAttemptPrepare(opts, attemptId, jobId, category, message) {
|
|
|
6804
6977
|
});
|
|
6805
6978
|
}
|
|
6806
6979
|
async function createGitBundle(projectRoot, baseCommit, bundlePath, maxBytes = DEFAULT_MAX_GIT_BUNDLE_BYTES) {
|
|
6807
|
-
await
|
|
6980
|
+
await mkdir14(dirname9(bundlePath), { recursive: true });
|
|
6808
6981
|
const { stdout: head } = await execFileAsync8("git", ["rev-parse", "HEAD"], {
|
|
6809
6982
|
cwd: projectRoot,
|
|
6810
6983
|
windowsHide: true
|
|
@@ -6833,7 +7006,7 @@ async function createGitBundle(projectRoot, baseCommit, bundlePath, maxBytes = D
|
|
|
6833
7006
|
await rm12(bundlePath, { force: true });
|
|
6834
7007
|
throw new GitBundleSizeExceededError(sizeBytes, maxBytes);
|
|
6835
7008
|
}
|
|
6836
|
-
const data = await
|
|
7009
|
+
const data = await readFile16(bundlePath);
|
|
6837
7010
|
return {
|
|
6838
7011
|
sizeBytes: data.length,
|
|
6839
7012
|
sha256: createHash8("sha256").update(data).digest("hex")
|
|
@@ -6849,9 +7022,9 @@ async function ensureFullFallbackArchive(opts, attemptId, jobId) {
|
|
|
6849
7022
|
const manifestPath = join29(transferDir, "snapshot.manifest.json");
|
|
6850
7023
|
try {
|
|
6851
7024
|
const existing = await stat9(archivePath);
|
|
6852
|
-
const data = await
|
|
7025
|
+
const data = await readFile16(archivePath);
|
|
6853
7026
|
const sha2562 = createHash8("sha256").update(data).digest("hex");
|
|
6854
|
-
const manifestRaw = await
|
|
7027
|
+
const manifestRaw = await readFile16(manifestPath, "utf8");
|
|
6855
7028
|
const manifest = JSON.parse(manifestRaw);
|
|
6856
7029
|
if (manifest.payload?.sha256 === sha2562 && manifest.payload?.size === existing.size) {
|
|
6857
7030
|
return {
|
|
@@ -6863,7 +7036,7 @@ async function ensureFullFallbackArchive(opts, attemptId, jobId) {
|
|
|
6863
7036
|
}
|
|
6864
7037
|
} catch {
|
|
6865
7038
|
}
|
|
6866
|
-
await
|
|
7039
|
+
await mkdir14(transferDir, { recursive: true });
|
|
6867
7040
|
const captured = await captureFullSnapshot({
|
|
6868
7041
|
projectRoot: request.source.project_root,
|
|
6869
7042
|
allowedProjectRoots: opts.allowedProjectRoots ?? [request.source.project_root],
|
|
@@ -6876,8 +7049,8 @@ async function ensureFullFallbackArchive(opts, attemptId, jobId) {
|
|
|
6876
7049
|
additionalRoots: request.source.additional_roots,
|
|
6877
7050
|
contentStorageDir: transferDir
|
|
6878
7051
|
});
|
|
6879
|
-
await
|
|
6880
|
-
await
|
|
7052
|
+
await writeFile13(archivePath, await readFile16(captured.archivePath));
|
|
7053
|
+
await writeFile13(manifestPath, JSON.stringify(captured.manifest, null, 2));
|
|
6881
7054
|
return {
|
|
6882
7055
|
archivePath,
|
|
6883
7056
|
sizeBytes: captured.manifest.payload.size,
|
|
@@ -7023,7 +7196,7 @@ function handleRemoteLeaseAccept(opts, agentId, payload) {
|
|
|
7023
7196
|
let manifest;
|
|
7024
7197
|
if (snapshotRow.manifest_path) {
|
|
7025
7198
|
try {
|
|
7026
|
-
manifest = JSON.parse(
|
|
7199
|
+
manifest = JSON.parse(readFileSync8(snapshotRow.manifest_path, "utf8"));
|
|
7027
7200
|
} catch {
|
|
7028
7201
|
}
|
|
7029
7202
|
}
|
|
@@ -7050,7 +7223,7 @@ async function handleRemoteSourceNeed(opts, agentId, payload) {
|
|
|
7050
7223
|
}
|
|
7051
7224
|
let manifest;
|
|
7052
7225
|
try {
|
|
7053
|
-
manifest = JSON.parse(
|
|
7226
|
+
manifest = JSON.parse(readFileSync8(snapshotRow.manifest_path, "utf8"));
|
|
7054
7227
|
} catch {
|
|
7055
7228
|
failAttemptPrepare(opts, attempt.id, attempt.job_id, "materialization", "Cannot create bundle: manifest unreadable");
|
|
7056
7229
|
return;
|
|
@@ -7097,7 +7270,7 @@ async function handleRemoteSourceNeed(opts, agentId, payload) {
|
|
|
7097
7270
|
}
|
|
7098
7271
|
let manifest;
|
|
7099
7272
|
try {
|
|
7100
|
-
manifest = JSON.parse(
|
|
7273
|
+
manifest = JSON.parse(readFileSync8(snapshotRow.manifest_path, "utf8"));
|
|
7101
7274
|
} catch {
|
|
7102
7275
|
manifest = void 0;
|
|
7103
7276
|
}
|
|
@@ -7240,6 +7413,18 @@ async function handleRemoteLogChunk(opts, agentId, payload) {
|
|
|
7240
7413
|
updateAttempt(opts.db, attempt.id, { log_acked_sequence: payload.sequence });
|
|
7241
7414
|
sendAck();
|
|
7242
7415
|
}
|
|
7416
|
+
function enqueueRemoteLogChunk(opts, agentId, payload) {
|
|
7417
|
+
const key = payload.attempt_id;
|
|
7418
|
+
const prev = logChunkChains.get(key) ?? Promise.resolve();
|
|
7419
|
+
const next = prev.catch(() => void 0).then(() => handleRemoteLogChunk(opts, agentId, payload));
|
|
7420
|
+
logChunkChains.set(key, next);
|
|
7421
|
+
void next.finally(() => {
|
|
7422
|
+
if (logChunkChains.get(key) === next) {
|
|
7423
|
+
logChunkChains.delete(key);
|
|
7424
|
+
}
|
|
7425
|
+
});
|
|
7426
|
+
return next;
|
|
7427
|
+
}
|
|
7243
7428
|
function handleRemoteJobStarted(opts, agentId, payload) {
|
|
7244
7429
|
const attempt = loadAttemptByLease(opts.db, payload.attempt_id, payload.lease_id, payload.lease_epoch);
|
|
7245
7430
|
if (!rejectStale("job_started", attempt, agentId, ["running"])) {
|
|
@@ -7353,7 +7538,7 @@ function handleRemoteCleanupComplete(opts, agentId, payload) {
|
|
|
7353
7538
|
result_json: JSON.stringify({ outcome, exit_code: exitCode })
|
|
7354
7539
|
});
|
|
7355
7540
|
}
|
|
7356
|
-
var execFileAsync8, logger11, DEFAULT_LEASE_TTL_SECONDS, GitBundleSizeExceededError;
|
|
7541
|
+
var execFileAsync8, logger11, DEFAULT_LEASE_TTL_SECONDS, GitBundleSizeExceededError, logChunkChains;
|
|
7357
7542
|
var init_remote_execution = __esm({
|
|
7358
7543
|
"../controller/dist/execution/remote-execution.js"() {
|
|
7359
7544
|
"use strict";
|
|
@@ -7380,6 +7565,7 @@ var init_remote_execution = __esm({
|
|
|
7380
7565
|
this.name = "GitBundleSizeExceededError";
|
|
7381
7566
|
}
|
|
7382
7567
|
};
|
|
7568
|
+
logChunkChains = /* @__PURE__ */ new Map();
|
|
7383
7569
|
}
|
|
7384
7570
|
});
|
|
7385
7571
|
|
|
@@ -7714,7 +7900,7 @@ function selectAgentForJob(agents, request, options = {}) {
|
|
|
7714
7900
|
const busyAgentRunningJobs = [];
|
|
7715
7901
|
for (const candidate of agents) {
|
|
7716
7902
|
const caps = candidate.capabilities;
|
|
7717
|
-
const effectiveCapacity =
|
|
7903
|
+
const effectiveCapacity = caps.execution.max_jobs;
|
|
7718
7904
|
if (effectiveCapacity <= 0 || candidate.activeJobsCount >= effectiveCapacity) {
|
|
7719
7905
|
if (effectiveCapacity > 0) {
|
|
7720
7906
|
busyAgentRunningJobs.push(candidate.activeJobsCount);
|
|
@@ -8186,7 +8372,7 @@ async function handleJobConfirm(ctx, args) {
|
|
|
8186
8372
|
});
|
|
8187
8373
|
return { job_id: job.id, state: "queued" };
|
|
8188
8374
|
}
|
|
8189
|
-
async function waitForJob(ctx, jobId, waitSeconds, includeLogTailLines) {
|
|
8375
|
+
async function waitForJob(ctx, jobId, waitSeconds, includeLogTailLines, options) {
|
|
8190
8376
|
const deadline = Date.now() + waitSeconds * 1e3;
|
|
8191
8377
|
let job = getJob(ctx.db, jobId);
|
|
8192
8378
|
if (!job) {
|
|
@@ -8195,6 +8381,9 @@ async function waitForJob(ctx, jobId, waitSeconds, includeLogTailLines) {
|
|
|
8195
8381
|
};
|
|
8196
8382
|
}
|
|
8197
8383
|
while (job && !isTerminalJobState(job.state) && Date.now() < deadline) {
|
|
8384
|
+
if (options?.onTick) {
|
|
8385
|
+
await options.onTick(job);
|
|
8386
|
+
}
|
|
8198
8387
|
await new Promise((resolvePromise) => setTimeout(resolvePromise, 200));
|
|
8199
8388
|
job = getJob(ctx.db, jobId);
|
|
8200
8389
|
}
|
|
@@ -8303,22 +8492,18 @@ var init_submit = __esm({
|
|
|
8303
8492
|
|
|
8304
8493
|
// src/main.ts
|
|
8305
8494
|
init_dist();
|
|
8306
|
-
import { readFile as
|
|
8495
|
+
import { readFile as readFile18 } from "node:fs/promises";
|
|
8307
8496
|
|
|
8308
8497
|
// src/commands/agent.ts
|
|
8309
|
-
import { existsSync as
|
|
8310
|
-
import { mkdir as mkdir11, readFile as readFile14, writeFile as writeFile11 } from "node:fs/promises";
|
|
8498
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
8311
8499
|
import { join as join24 } from "node:path";
|
|
8312
8500
|
|
|
8313
|
-
// ../agent/dist/
|
|
8501
|
+
// ../agent/dist/config.js
|
|
8502
|
+
init_dist2();
|
|
8314
8503
|
init_dist();
|
|
8315
|
-
|
|
8316
|
-
|
|
8317
|
-
import {
|
|
8318
|
-
import { readFile as readFile5, readdir as readdir5, statfs } from "node:fs/promises";
|
|
8319
|
-
import { arch, cpus as cpus2, freemem, hostname, loadavg, platform as platform2, release, totalmem } from "node:os";
|
|
8320
|
-
import { join as join8 } from "node:path";
|
|
8321
|
-
import { promisify as promisify2 } from "node:util";
|
|
8504
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
8505
|
+
import { dirname as dirname2, join as join7 } from "node:path";
|
|
8506
|
+
import { z as z6 } from "zod";
|
|
8322
8507
|
|
|
8323
8508
|
// ../agent/dist/build-cache/index.js
|
|
8324
8509
|
import { access, readdir as readdir3 } from "node:fs/promises";
|
|
@@ -9021,12 +9206,6 @@ function resolveBuildCachesDir(stateDir) {
|
|
|
9021
9206
|
return join5(stateDir, "build-caches");
|
|
9022
9207
|
}
|
|
9023
9208
|
|
|
9024
|
-
// ../agent/dist/config.js
|
|
9025
|
-
init_dist2();
|
|
9026
|
-
init_dist();
|
|
9027
|
-
import { mkdirSync as mkdirSync2 } from "node:fs";
|
|
9028
|
-
import { dirname as dirname2, join as join7 } from "node:path";
|
|
9029
|
-
|
|
9030
9209
|
// ../agent/dist/repos/mirror.js
|
|
9031
9210
|
init_dist();
|
|
9032
9211
|
import { execFile } from "node:child_process";
|
|
@@ -9383,19 +9562,77 @@ var DEFAULT_LOG_SPOOL_MAX_BYTES = 536870912;
|
|
|
9383
9562
|
var DEFAULT_LOG_SEND_QUEUE_MAX = 64;
|
|
9384
9563
|
var DEFAULT_GIT_ALLOWLIST_SCHEMES = ["https", "ssh"];
|
|
9385
9564
|
var DEFAULT_GIT_ALLOWLIST_HOSTS = ["github.com"];
|
|
9565
|
+
var AGENT_CONFIG_FILENAME = "agent.json";
|
|
9566
|
+
var AGENT_CONFIG_SCHEMA_VERSION = 1;
|
|
9567
|
+
var RiskLevelSchema2 = z6.enum(["safe", "normal", "destructive", "hardware"]);
|
|
9568
|
+
var GitAllowlistFileSchema = z6.object({
|
|
9569
|
+
schemes: z6.array(z6.string().min(1)).optional(),
|
|
9570
|
+
hosts: z6.array(z6.string().min(1)).optional(),
|
|
9571
|
+
repository_prefixes: z6.array(z6.string().min(1)).optional()
|
|
9572
|
+
});
|
|
9573
|
+
var RepoCacheFileSchema = z6.object({
|
|
9574
|
+
max_size_gb: z6.number().positive().optional(),
|
|
9575
|
+
min_free_disk_gb: z6.number().nonnegative().optional(),
|
|
9576
|
+
retention_days: z6.number().nonnegative().optional()
|
|
9577
|
+
});
|
|
9578
|
+
var BuildCacheFileSchema = z6.object({
|
|
9579
|
+
enabled_kinds: z6.array(BuildCacheKindSchema).optional(),
|
|
9580
|
+
max_size_gb: z6.number().positive().optional(),
|
|
9581
|
+
min_free_disk_gb: z6.number().nonnegative().optional(),
|
|
9582
|
+
retention_days: z6.number().nonnegative().optional(),
|
|
9583
|
+
allow_read_risk_levels: z6.array(RiskLevelSchema2).optional(),
|
|
9584
|
+
allow_write_risk_levels: z6.array(RiskLevelSchema2).optional()
|
|
9585
|
+
});
|
|
9586
|
+
var AgentConfigFileSchema = z6.object({
|
|
9587
|
+
schema_version: z6.number().int().positive().optional(),
|
|
9588
|
+
initialized_at: z6.string().min(1).optional(),
|
|
9589
|
+
controller_url: z6.string().optional(),
|
|
9590
|
+
controller_fingerprint: z6.string().optional(),
|
|
9591
|
+
display_name: z6.string().min(1).optional(),
|
|
9592
|
+
max_jobs: z6.number().int().positive().optional(),
|
|
9593
|
+
repo_cache_dir: z6.string().min(1).nullable().optional(),
|
|
9594
|
+
secret_map: z6.record(z6.string()).nullable().optional(),
|
|
9595
|
+
git_allowlist: GitAllowlistFileSchema.optional(),
|
|
9596
|
+
repo_cache: RepoCacheFileSchema.optional(),
|
|
9597
|
+
build_cache: BuildCacheFileSchema.optional(),
|
|
9598
|
+
log_spool_max_bytes: z6.number().int().positive().optional(),
|
|
9599
|
+
log_send_queue_max: z6.number().int().positive().optional(),
|
|
9600
|
+
disconnect_grace_seconds: z6.number().int().nonnegative().optional(),
|
|
9601
|
+
orphan_timeout_seconds: z6.number().int().nonnegative().optional(),
|
|
9602
|
+
reconcile_deadline_seconds: z6.number().int().nonnegative().optional(),
|
|
9603
|
+
disk_min_free_bytes: z6.number().int().nonnegative().nullable().optional(),
|
|
9604
|
+
configured_priority: z6.number().nullable().optional()
|
|
9605
|
+
}).strict();
|
|
9386
9606
|
function parseCsv(raw) {
|
|
9387
9607
|
if (!raw?.trim()) {
|
|
9388
9608
|
return [];
|
|
9389
9609
|
}
|
|
9390
9610
|
return raw.split(",").map((part) => part.trim()).filter(Boolean);
|
|
9391
9611
|
}
|
|
9392
|
-
function
|
|
9393
|
-
|
|
9394
|
-
|
|
9612
|
+
function envSet(key, env = process.env) {
|
|
9613
|
+
return env[key] !== void 0;
|
|
9614
|
+
}
|
|
9615
|
+
function parseEnvNumber(key, raw) {
|
|
9616
|
+
const n = Number(raw);
|
|
9617
|
+
if (!Number.isFinite(n)) {
|
|
9618
|
+
throw new Error(`Invalid ${key}=${JSON.stringify(raw)}: expected a finite number`);
|
|
9619
|
+
}
|
|
9620
|
+
return n;
|
|
9621
|
+
}
|
|
9622
|
+
function parseEnvInt(key, raw) {
|
|
9623
|
+
const n = parseEnvNumber(key, raw);
|
|
9624
|
+
if (!Number.isInteger(n)) {
|
|
9625
|
+
throw new Error(`Invalid ${key}=${JSON.stringify(raw)}: expected an integer`);
|
|
9395
9626
|
}
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
|
|
9627
|
+
return n;
|
|
9628
|
+
}
|
|
9629
|
+
function parseGitAllowlistFromEnv(env = process.env) {
|
|
9630
|
+
if (!envSet("RBO_GIT_ALLOWLIST_SCHEMES", env) && !envSet("RBO_GIT_ALLOWLIST_HOSTS", env) && !envSet("RBO_GIT_ALLOWLIST_PREFIXES", env)) {
|
|
9631
|
+
return void 0;
|
|
9632
|
+
}
|
|
9633
|
+
const schemes = parseCsv(env.RBO_GIT_ALLOWLIST_SCHEMES);
|
|
9634
|
+
const hosts = parseCsv(env.RBO_GIT_ALLOWLIST_HOSTS);
|
|
9635
|
+
const prefixesRaw = env.RBO_GIT_ALLOWLIST_PREFIXES?.trim();
|
|
9399
9636
|
let repository_prefixes;
|
|
9400
9637
|
if (prefixesRaw) {
|
|
9401
9638
|
try {
|
|
@@ -9414,17 +9651,23 @@ function parseGitAllowlist(overrides) {
|
|
|
9414
9651
|
...repository_prefixes ? { repository_prefixes } : {}
|
|
9415
9652
|
};
|
|
9416
9653
|
}
|
|
9417
|
-
function
|
|
9418
|
-
if (
|
|
9419
|
-
return
|
|
9654
|
+
function mergeGitAllowlist(file, fromEnv, override) {
|
|
9655
|
+
if (override) {
|
|
9656
|
+
return override;
|
|
9657
|
+
}
|
|
9658
|
+
if (fromEnv) {
|
|
9659
|
+
return fromEnv;
|
|
9660
|
+
}
|
|
9661
|
+
if (file) {
|
|
9662
|
+
return {
|
|
9663
|
+
schemes: file.schemes && file.schemes.length > 0 ? file.schemes : [...DEFAULT_GIT_ALLOWLIST_SCHEMES],
|
|
9664
|
+
hosts: file.hosts && file.hosts.length > 0 ? file.hosts : [...DEFAULT_GIT_ALLOWLIST_HOSTS],
|
|
9665
|
+
...file.repository_prefixes ? { repository_prefixes: file.repository_prefixes } : {}
|
|
9666
|
+
};
|
|
9420
9667
|
}
|
|
9421
|
-
const maxSize = Number(process.env.RBO_REPO_CACHE_MAX_SIZE_GB ?? DEFAULT_REPO_CACHE_CONFIG.max_size_gb);
|
|
9422
|
-
const minFree = Number(process.env.RBO_REPO_CACHE_MIN_FREE_DISK_GB ?? DEFAULT_REPO_CACHE_CONFIG.min_free_disk_gb);
|
|
9423
|
-
const retention = Number(process.env.RBO_REPO_CACHE_RETENTION_DAYS ?? DEFAULT_REPO_CACHE_CONFIG.retention_days);
|
|
9424
9668
|
return {
|
|
9425
|
-
|
|
9426
|
-
|
|
9427
|
-
retention_days: retention
|
|
9669
|
+
schemes: [...DEFAULT_GIT_ALLOWLIST_SCHEMES],
|
|
9670
|
+
hosts: [...DEFAULT_GIT_ALLOWLIST_HOSTS]
|
|
9428
9671
|
};
|
|
9429
9672
|
}
|
|
9430
9673
|
function parseBuildCacheKinds(raw) {
|
|
@@ -9455,23 +9698,30 @@ function parseRiskLevels(raw) {
|
|
|
9455
9698
|
}
|
|
9456
9699
|
return levels;
|
|
9457
9700
|
}
|
|
9458
|
-
function
|
|
9459
|
-
if (
|
|
9460
|
-
return
|
|
9701
|
+
function mergeRepoCache(file, override) {
|
|
9702
|
+
if (override) {
|
|
9703
|
+
return override;
|
|
9461
9704
|
}
|
|
9705
|
+
const maxSize = envSet("RBO_REPO_CACHE_MAX_SIZE_GB") ? parseEnvNumber("RBO_REPO_CACHE_MAX_SIZE_GB", process.env.RBO_REPO_CACHE_MAX_SIZE_GB) : file?.max_size_gb ?? DEFAULT_REPO_CACHE_CONFIG.max_size_gb;
|
|
9706
|
+
const minFree = envSet("RBO_REPO_CACHE_MIN_FREE_DISK_GB") ? parseEnvNumber("RBO_REPO_CACHE_MIN_FREE_DISK_GB", process.env.RBO_REPO_CACHE_MIN_FREE_DISK_GB) : file?.min_free_disk_gb ?? DEFAULT_REPO_CACHE_CONFIG.min_free_disk_gb;
|
|
9707
|
+
const retention = envSet("RBO_REPO_CACHE_RETENTION_DAYS") ? parseEnvNumber("RBO_REPO_CACHE_RETENTION_DAYS", process.env.RBO_REPO_CACHE_RETENTION_DAYS) : file?.retention_days ?? DEFAULT_REPO_CACHE_CONFIG.retention_days;
|
|
9462
9708
|
return {
|
|
9463
|
-
|
|
9464
|
-
|
|
9465
|
-
|
|
9466
|
-
|
|
9467
|
-
|
|
9468
|
-
|
|
9469
|
-
|
|
9470
|
-
|
|
9471
|
-
|
|
9472
|
-
|
|
9473
|
-
|
|
9474
|
-
|
|
9709
|
+
max_size_gb: maxSize,
|
|
9710
|
+
min_free_disk_gb: minFree,
|
|
9711
|
+
retention_days: retention
|
|
9712
|
+
};
|
|
9713
|
+
}
|
|
9714
|
+
function mergeBuildCache(file, override) {
|
|
9715
|
+
if (override) {
|
|
9716
|
+
return override;
|
|
9717
|
+
}
|
|
9718
|
+
return {
|
|
9719
|
+
enabledKinds: (envSet("RBO_BUILD_CACHE_ENABLED_KINDS") ? parseBuildCacheKinds(process.env.RBO_BUILD_CACHE_ENABLED_KINDS) : void 0) ?? file?.enabled_kinds ?? [...DEFAULT_BUILD_CACHE_CONFIG.enabledKinds],
|
|
9720
|
+
maxSizeGb: envSet("RBO_BUILD_CACHE_MAX_SIZE_GB") ? parseEnvNumber("RBO_BUILD_CACHE_MAX_SIZE_GB", process.env.RBO_BUILD_CACHE_MAX_SIZE_GB) : file?.max_size_gb ?? DEFAULT_BUILD_CACHE_CONFIG.maxSizeGb,
|
|
9721
|
+
minFreeDiskGb: envSet("RBO_BUILD_CACHE_MIN_FREE_DISK_GB") ? parseEnvNumber("RBO_BUILD_CACHE_MIN_FREE_DISK_GB", process.env.RBO_BUILD_CACHE_MIN_FREE_DISK_GB) : file?.min_free_disk_gb ?? DEFAULT_BUILD_CACHE_CONFIG.minFreeDiskGb,
|
|
9722
|
+
retentionDays: envSet("RBO_BUILD_CACHE_RETENTION_DAYS") ? parseEnvNumber("RBO_BUILD_CACHE_RETENTION_DAYS", process.env.RBO_BUILD_CACHE_RETENTION_DAYS) : file?.retention_days ?? DEFAULT_BUILD_CACHE_CONFIG.retentionDays,
|
|
9723
|
+
allowReadRiskLevels: (envSet("RBO_BUILD_CACHE_ALLOW_READ_RISKS") ? parseRiskLevels(process.env.RBO_BUILD_CACHE_ALLOW_READ_RISKS) : void 0) ?? file?.allow_read_risk_levels ?? [...DEFAULT_BUILD_CACHE_CONFIG.allowReadRiskLevels],
|
|
9724
|
+
allowWriteRiskLevels: (envSet("RBO_BUILD_CACHE_ALLOW_WRITE_RISKS") ? parseRiskLevels(process.env.RBO_BUILD_CACHE_ALLOW_WRITE_RISKS) : void 0) ?? file?.allow_write_risk_levels ?? [...DEFAULT_BUILD_CACHE_CONFIG.allowWriteRiskLevels]
|
|
9475
9725
|
};
|
|
9476
9726
|
}
|
|
9477
9727
|
function parseSecretMap(raw) {
|
|
@@ -9495,50 +9745,157 @@ function parseSecretMap(raw) {
|
|
|
9495
9745
|
throw new Error(`Invalid RBO_SECRET_MAP: ${error instanceof Error ? error.message : String(error)}`);
|
|
9496
9746
|
}
|
|
9497
9747
|
}
|
|
9748
|
+
function defaultAgentConfigFile(options = {}) {
|
|
9749
|
+
return {
|
|
9750
|
+
schema_version: AGENT_CONFIG_SCHEMA_VERSION,
|
|
9751
|
+
initialized_at: options.initializedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
9752
|
+
controller_url: "",
|
|
9753
|
+
controller_fingerprint: "",
|
|
9754
|
+
display_name: "rbo-agent",
|
|
9755
|
+
max_jobs: 1,
|
|
9756
|
+
repo_cache_dir: null,
|
|
9757
|
+
secret_map: null,
|
|
9758
|
+
git_allowlist: {
|
|
9759
|
+
schemes: [...DEFAULT_GIT_ALLOWLIST_SCHEMES],
|
|
9760
|
+
hosts: [...DEFAULT_GIT_ALLOWLIST_HOSTS]
|
|
9761
|
+
},
|
|
9762
|
+
repo_cache: {
|
|
9763
|
+
max_size_gb: DEFAULT_REPO_CACHE_CONFIG.max_size_gb,
|
|
9764
|
+
min_free_disk_gb: DEFAULT_REPO_CACHE_CONFIG.min_free_disk_gb,
|
|
9765
|
+
retention_days: DEFAULT_REPO_CACHE_CONFIG.retention_days
|
|
9766
|
+
},
|
|
9767
|
+
build_cache: {
|
|
9768
|
+
enabled_kinds: [...DEFAULT_BUILD_CACHE_CONFIG.enabledKinds],
|
|
9769
|
+
max_size_gb: DEFAULT_BUILD_CACHE_CONFIG.maxSizeGb,
|
|
9770
|
+
min_free_disk_gb: DEFAULT_BUILD_CACHE_CONFIG.minFreeDiskGb,
|
|
9771
|
+
retention_days: DEFAULT_BUILD_CACHE_CONFIG.retentionDays,
|
|
9772
|
+
allow_read_risk_levels: [...DEFAULT_BUILD_CACHE_CONFIG.allowReadRiskLevels],
|
|
9773
|
+
allow_write_risk_levels: [...DEFAULT_BUILD_CACHE_CONFIG.allowWriteRiskLevels]
|
|
9774
|
+
},
|
|
9775
|
+
log_spool_max_bytes: DEFAULT_LOG_SPOOL_MAX_BYTES,
|
|
9776
|
+
log_send_queue_max: DEFAULT_LOG_SEND_QUEUE_MAX,
|
|
9777
|
+
disconnect_grace_seconds: 60,
|
|
9778
|
+
orphan_timeout_seconds: 300,
|
|
9779
|
+
reconcile_deadline_seconds: 120,
|
|
9780
|
+
disk_min_free_bytes: null,
|
|
9781
|
+
configured_priority: null
|
|
9782
|
+
};
|
|
9783
|
+
}
|
|
9784
|
+
function resolveAgentConfigPath(stateDir) {
|
|
9785
|
+
return join7(stateDir, AGENT_CONFIG_FILENAME);
|
|
9786
|
+
}
|
|
9787
|
+
function readAgentConfigFile(configPath) {
|
|
9788
|
+
if (!existsSync2(configPath)) {
|
|
9789
|
+
return void 0;
|
|
9790
|
+
}
|
|
9791
|
+
let raw;
|
|
9792
|
+
try {
|
|
9793
|
+
raw = JSON.parse(readFileSync3(configPath, "utf8"));
|
|
9794
|
+
} catch (error) {
|
|
9795
|
+
throw new Error(`Invalid agent config JSON at ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
9796
|
+
}
|
|
9797
|
+
const parsed = AgentConfigFileSchema.safeParse(raw);
|
|
9798
|
+
if (!parsed.success) {
|
|
9799
|
+
throw new Error(`Invalid agent config at ${configPath}: ${parsed.error.message}`);
|
|
9800
|
+
}
|
|
9801
|
+
return parsed.data;
|
|
9802
|
+
}
|
|
9803
|
+
function writeDefaultAgentConfigFile(stateDir, options = {}) {
|
|
9804
|
+
mkdirSync2(stateDir, { recursive: true });
|
|
9805
|
+
const path = resolveAgentConfigPath(stateDir);
|
|
9806
|
+
if (existsSync2(path) && !options.force) {
|
|
9807
|
+
let initialized_at2 = (/* @__PURE__ */ new Date(0)).toISOString();
|
|
9808
|
+
let schema_version = AGENT_CONFIG_SCHEMA_VERSION;
|
|
9809
|
+
try {
|
|
9810
|
+
const existing = readAgentConfigFile(path);
|
|
9811
|
+
initialized_at2 = existing?.initialized_at ?? initialized_at2;
|
|
9812
|
+
schema_version = existing?.schema_version ?? schema_version;
|
|
9813
|
+
} catch {
|
|
9814
|
+
}
|
|
9815
|
+
return {
|
|
9816
|
+
path,
|
|
9817
|
+
written: false,
|
|
9818
|
+
initialized_at: initialized_at2,
|
|
9819
|
+
schema_version
|
|
9820
|
+
};
|
|
9821
|
+
}
|
|
9822
|
+
const initialized_at = options.initializedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
9823
|
+
const body = `${JSON.stringify(defaultAgentConfigFile({ initializedAt: initialized_at }), null, 2)}
|
|
9824
|
+
`;
|
|
9825
|
+
writeFileSync2(path, body, "utf8");
|
|
9826
|
+
return {
|
|
9827
|
+
path,
|
|
9828
|
+
written: true,
|
|
9829
|
+
initialized_at,
|
|
9830
|
+
schema_version: AGENT_CONFIG_SCHEMA_VERSION
|
|
9831
|
+
};
|
|
9832
|
+
}
|
|
9498
9833
|
function loadAgentConfig(overrides = {}) {
|
|
9499
|
-
const
|
|
9500
|
-
const
|
|
9834
|
+
const { configPath: configPathOption, ...fieldOverrides } = overrides;
|
|
9835
|
+
const stateDir = fieldOverrides.stateDir ?? resolveAgentStateDir();
|
|
9836
|
+
const configPath = configPathOption === void 0 ? resolveAgentConfigPath(stateDir) : configPathOption;
|
|
9837
|
+
const file = configPath === null ? void 0 : readAgentConfigFile(configPath);
|
|
9838
|
+
const controllerUrl = fieldOverrides.controllerUrl ?? (envSet("RBO_CONTROLLER_URL") ? process.env.RBO_CONTROLLER_URL : void 0) ?? (file?.controller_url?.trim() ? file.controller_url.trim() : void 0);
|
|
9839
|
+
const controllerFingerprint = fieldOverrides.controllerFingerprint ?? (envSet("RBO_CONTROLLER_FINGERPRINT") ? process.env.RBO_CONTROLLER_FINGERPRINT : void 0) ?? (file?.controller_fingerprint?.trim() ? file.controller_fingerprint.trim() : void 0);
|
|
9501
9840
|
if (!controllerUrl) {
|
|
9502
|
-
throw new Error("
|
|
9841
|
+
throw new Error("controller_url is required \u2014 set it in agent.json (or RBO_CONTROLLER_URL=wss://<host>:7411/agent)");
|
|
9503
9842
|
}
|
|
9504
9843
|
if (!controllerFingerprint) {
|
|
9505
|
-
throw new Error("
|
|
9844
|
+
throw new Error("controller_fingerprint is required \u2014 set it in agent.json (or RBO_CONTROLLER_FINGERPRINT from `rbo controller fingerprint`)");
|
|
9845
|
+
}
|
|
9846
|
+
const repoCache = mergeRepoCache(file?.repo_cache, fieldOverrides.repoCache);
|
|
9847
|
+
let secretMap = fieldOverrides.secretMap;
|
|
9848
|
+
if (secretMap === void 0) {
|
|
9849
|
+
if (envSet("RBO_SECRET_MAP")) {
|
|
9850
|
+
secretMap = parseSecretMap(process.env.RBO_SECRET_MAP);
|
|
9851
|
+
} else if (file?.secret_map !== void 0) {
|
|
9852
|
+
secretMap = file.secret_map ?? void 0;
|
|
9853
|
+
}
|
|
9854
|
+
}
|
|
9855
|
+
let repoCacheDir = fieldOverrides.repoCacheDir;
|
|
9856
|
+
if (repoCacheDir === void 0) {
|
|
9857
|
+
if (envSet("RBO_REPO_CACHE_DIR") && process.env.RBO_REPO_CACHE_DIR?.trim()) {
|
|
9858
|
+
repoCacheDir = process.env.RBO_REPO_CACHE_DIR.trim();
|
|
9859
|
+
} else if (file?.repo_cache_dir) {
|
|
9860
|
+
repoCacheDir = file.repo_cache_dir;
|
|
9861
|
+
}
|
|
9862
|
+
}
|
|
9863
|
+
let configuredPriority = fieldOverrides.configuredPriority;
|
|
9864
|
+
if (configuredPriority === void 0) {
|
|
9865
|
+
if (envSet("RBO_CONFIGURED_PRIORITY")) {
|
|
9866
|
+
configuredPriority = parseEnvNumber("RBO_CONFIGURED_PRIORITY", process.env.RBO_CONFIGURED_PRIORITY);
|
|
9867
|
+
} else if (file?.configured_priority !== void 0 && file.configured_priority !== null) {
|
|
9868
|
+
configuredPriority = file.configured_priority;
|
|
9869
|
+
}
|
|
9870
|
+
}
|
|
9871
|
+
let diskMinFreeBytes = fieldOverrides.diskMinFreeBytes;
|
|
9872
|
+
if (diskMinFreeBytes === void 0) {
|
|
9873
|
+
if (envSet("RBO_DISK_MIN_FREE_BYTES")) {
|
|
9874
|
+
diskMinFreeBytes = parseEnvInt("RBO_DISK_MIN_FREE_BYTES", process.env.RBO_DISK_MIN_FREE_BYTES);
|
|
9875
|
+
} else if (file?.disk_min_free_bytes !== void 0 && file.disk_min_free_bytes !== null) {
|
|
9876
|
+
diskMinFreeBytes = file.disk_min_free_bytes;
|
|
9877
|
+
} else {
|
|
9878
|
+
diskMinFreeBytes = Math.max(DEFAULT_DISK_MIN_FREE_BYTES, Math.floor(repoCache.min_free_disk_gb * 1024 ** 3));
|
|
9879
|
+
}
|
|
9506
9880
|
}
|
|
9507
9881
|
return {
|
|
9508
9882
|
controllerUrl,
|
|
9509
9883
|
controllerFingerprint,
|
|
9510
|
-
displayName:
|
|
9511
|
-
maxJobs:
|
|
9512
|
-
stateDir
|
|
9513
|
-
repoCacheDir
|
|
9514
|
-
secretMap
|
|
9515
|
-
gitAllowlist:
|
|
9516
|
-
repoCache
|
|
9517
|
-
buildCache:
|
|
9518
|
-
logSpoolMaxBytes:
|
|
9519
|
-
logSendQueueMax:
|
|
9520
|
-
disconnectGraceSeconds:
|
|
9521
|
-
orphanTimeoutSeconds:
|
|
9522
|
-
reconcileDeadlineSeconds:
|
|
9523
|
-
diskMinFreeBytes
|
|
9524
|
-
|
|
9525
|
-
return overrides.diskMinFreeBytes;
|
|
9526
|
-
}
|
|
9527
|
-
if (process.env.RBO_DISK_MIN_FREE_BYTES) {
|
|
9528
|
-
return Number(process.env.RBO_DISK_MIN_FREE_BYTES);
|
|
9529
|
-
}
|
|
9530
|
-
const repoCache = parseRepoCache(overrides.repoCache);
|
|
9531
|
-
return Math.max(DEFAULT_DISK_MIN_FREE_BYTES, Math.floor(repoCache.min_free_disk_gb * 1024 ** 3));
|
|
9532
|
-
})(),
|
|
9533
|
-
configuredPriority: (() => {
|
|
9534
|
-
if (overrides.configuredPriority !== void 0) {
|
|
9535
|
-
return overrides.configuredPriority;
|
|
9536
|
-
}
|
|
9537
|
-
if (process.env.RBO_CONFIGURED_PRIORITY) {
|
|
9538
|
-
return Number(process.env.RBO_CONFIGURED_PRIORITY);
|
|
9539
|
-
}
|
|
9540
|
-
return void 0;
|
|
9541
|
-
})()
|
|
9884
|
+
displayName: fieldOverrides.displayName ?? (envSet("RBO_AGENT_NAME") ? process.env.RBO_AGENT_NAME : void 0) ?? file?.display_name ?? "rbo-agent",
|
|
9885
|
+
maxJobs: fieldOverrides.maxJobs ?? (envSet("RBO_MAX_JOBS") ? parseEnvInt("RBO_MAX_JOBS", process.env.RBO_MAX_JOBS) : void 0) ?? file?.max_jobs ?? 1,
|
|
9886
|
+
stateDir,
|
|
9887
|
+
repoCacheDir,
|
|
9888
|
+
secretMap,
|
|
9889
|
+
gitAllowlist: mergeGitAllowlist(file?.git_allowlist, parseGitAllowlistFromEnv(), fieldOverrides.gitAllowlist),
|
|
9890
|
+
repoCache,
|
|
9891
|
+
buildCache: mergeBuildCache(file?.build_cache, fieldOverrides.buildCache),
|
|
9892
|
+
logSpoolMaxBytes: fieldOverrides.logSpoolMaxBytes ?? (envSet("RBO_LOG_SPOOL_MAX_BYTES") ? parseEnvInt("RBO_LOG_SPOOL_MAX_BYTES", process.env.RBO_LOG_SPOOL_MAX_BYTES) : void 0) ?? file?.log_spool_max_bytes ?? DEFAULT_LOG_SPOOL_MAX_BYTES,
|
|
9893
|
+
logSendQueueMax: fieldOverrides.logSendQueueMax ?? (envSet("RBO_LOG_SEND_QUEUE_MAX") ? parseEnvInt("RBO_LOG_SEND_QUEUE_MAX", process.env.RBO_LOG_SEND_QUEUE_MAX) : void 0) ?? file?.log_send_queue_max ?? DEFAULT_LOG_SEND_QUEUE_MAX,
|
|
9894
|
+
disconnectGraceSeconds: fieldOverrides.disconnectGraceSeconds ?? (envSet("RBO_DISCONNECT_GRACE_SECONDS") ? parseEnvInt("RBO_DISCONNECT_GRACE_SECONDS", process.env.RBO_DISCONNECT_GRACE_SECONDS) : void 0) ?? file?.disconnect_grace_seconds ?? 60,
|
|
9895
|
+
orphanTimeoutSeconds: fieldOverrides.orphanTimeoutSeconds ?? (envSet("RBO_ORPHAN_TIMEOUT_SECONDS") ? parseEnvInt("RBO_ORPHAN_TIMEOUT_SECONDS", process.env.RBO_ORPHAN_TIMEOUT_SECONDS) : void 0) ?? file?.orphan_timeout_seconds ?? 300,
|
|
9896
|
+
reconcileDeadlineSeconds: fieldOverrides.reconcileDeadlineSeconds ?? (envSet("RBO_RECONCILE_DEADLINE_SECONDS") ? parseEnvInt("RBO_RECONCILE_DEADLINE_SECONDS", process.env.RBO_RECONCILE_DEADLINE_SECONDS) : void 0) ?? file?.reconcile_deadline_seconds ?? 120,
|
|
9897
|
+
diskMinFreeBytes,
|
|
9898
|
+
configuredPriority
|
|
9542
9899
|
};
|
|
9543
9900
|
}
|
|
9544
9901
|
function resolveRepoCacheRoot(config) {
|
|
@@ -9554,7 +9911,15 @@ function ensureStateDir(config) {
|
|
|
9554
9911
|
mkdirSync2(config.stateDir, { recursive: true });
|
|
9555
9912
|
}
|
|
9556
9913
|
|
|
9914
|
+
// ../agent/dist/run.js
|
|
9915
|
+
init_dist();
|
|
9916
|
+
|
|
9557
9917
|
// ../agent/dist/capabilities/probe.js
|
|
9918
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
9919
|
+
import { readFile as readFile5, readdir as readdir5, statfs } from "node:fs/promises";
|
|
9920
|
+
import { arch, cpus as cpus2, freemem, hostname, loadavg, platform as platform2, release, totalmem } from "node:os";
|
|
9921
|
+
import { join as join8 } from "node:path";
|
|
9922
|
+
import { promisify as promisify2 } from "node:util";
|
|
9558
9923
|
var execFileAsync2 = promisify2(execFile2);
|
|
9559
9924
|
function osFamily() {
|
|
9560
9925
|
const p = platform2();
|
|
@@ -9704,7 +10069,7 @@ async function probeCapabilities(input) {
|
|
|
9704
10069
|
// ../agent/dist/connection/client.js
|
|
9705
10070
|
init_dist2();
|
|
9706
10071
|
init_dist();
|
|
9707
|
-
import { existsSync as
|
|
10072
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
9708
10073
|
import { hostname as hostname2 } from "node:os";
|
|
9709
10074
|
import { join as join22 } from "node:path";
|
|
9710
10075
|
import WebSocket from "ws";
|
|
@@ -9808,7 +10173,7 @@ init_dist4();
|
|
|
9808
10173
|
init_dist();
|
|
9809
10174
|
init_dist3();
|
|
9810
10175
|
import { createHash as createHash6 } from "node:crypto";
|
|
9811
|
-
import { createReadStream, existsSync as
|
|
10176
|
+
import { createReadStream, existsSync as existsSync4 } from "node:fs";
|
|
9812
10177
|
import { copyFile, mkdir as mkdir9, readFile as readFile13, rename as rename4, rm as rm9, stat as stat7 } from "node:fs/promises";
|
|
9813
10178
|
import { request as httpsRequest } from "node:https";
|
|
9814
10179
|
import { join as join21 } from "node:path";
|
|
@@ -9911,7 +10276,7 @@ var SpoolSender = class {
|
|
|
9911
10276
|
|
|
9912
10277
|
// ../agent/dist/recovery/attempt-metadata.js
|
|
9913
10278
|
init_dist();
|
|
9914
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
10279
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync4, renameSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
9915
10280
|
import { readdir as readdir10, rm as rm6 } from "node:fs/promises";
|
|
9916
10281
|
import { dirname as dirname5, join as join18 } from "node:path";
|
|
9917
10282
|
function attemptMetadataDir(stateDir, attemptId) {
|
|
@@ -9929,14 +10294,14 @@ function writeAttemptMetadata(stateDir, meta) {
|
|
|
9929
10294
|
...meta,
|
|
9930
10295
|
updated_at: meta.updated_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
9931
10296
|
};
|
|
9932
|
-
|
|
10297
|
+
writeFileSync3(tmp, `${JSON.stringify(payload, null, 2)}
|
|
9933
10298
|
`, { encoding: "utf8", mode: 384 });
|
|
9934
10299
|
renameSync(tmp, target);
|
|
9935
10300
|
}
|
|
9936
10301
|
function readAttemptMetadata(stateDir, attemptId) {
|
|
9937
10302
|
const path = attemptMetadataPath(stateDir, attemptId);
|
|
9938
10303
|
try {
|
|
9939
|
-
const raw =
|
|
10304
|
+
const raw = readFileSync4(path, "utf8");
|
|
9940
10305
|
return JSON.parse(raw);
|
|
9941
10306
|
} catch {
|
|
9942
10307
|
return null;
|
|
@@ -10084,8 +10449,8 @@ async function applyDiskPressureCleanup(options) {
|
|
|
10084
10449
|
for (const name of entries) {
|
|
10085
10450
|
const metaPath = join19(reposDir, name, "metadata.json");
|
|
10086
10451
|
try {
|
|
10087
|
-
const { readFile:
|
|
10088
|
-
const raw = await
|
|
10452
|
+
const { readFile: readFile19 } = await import("node:fs/promises");
|
|
10453
|
+
const raw = await readFile19(metaPath, "utf8");
|
|
10089
10454
|
const meta = JSON.parse(raw);
|
|
10090
10455
|
if ((meta.active_worktree_count ?? 0) > 0) {
|
|
10091
10456
|
continue;
|
|
@@ -10299,6 +10664,49 @@ async function streamDownloadWithLimits(source, destPath, expectedSize, expected
|
|
|
10299
10664
|
}
|
|
10300
10665
|
}
|
|
10301
10666
|
|
|
10667
|
+
// ../agent/dist/executor/tls-pin.js
|
|
10668
|
+
init_dist();
|
|
10669
|
+
import { Agent as HttpsAgent } from "node:https";
|
|
10670
|
+
var controllerDataPlaneAgent = new HttpsAgent({
|
|
10671
|
+
keepAlive: false,
|
|
10672
|
+
maxCachedSessions: 100
|
|
10673
|
+
});
|
|
10674
|
+
function pinnedTlsRequestOptions(expectedFingerprint) {
|
|
10675
|
+
return {
|
|
10676
|
+
rejectUnauthorized: false,
|
|
10677
|
+
agent: controllerDataPlaneAgent,
|
|
10678
|
+
checkServerIdentity: (_host, cert) => {
|
|
10679
|
+
if (!cert?.raw) {
|
|
10680
|
+
return void 0;
|
|
10681
|
+
}
|
|
10682
|
+
const actual = certificateFingerprint(cert.raw);
|
|
10683
|
+
if (actual !== expectedFingerprint) {
|
|
10684
|
+
return new Error(`Controller certificate fingerprint mismatch: expected ${expectedFingerprint}, got ${actual}`);
|
|
10685
|
+
}
|
|
10686
|
+
return void 0;
|
|
10687
|
+
}
|
|
10688
|
+
};
|
|
10689
|
+
}
|
|
10690
|
+
function assertPinnedPeerCert(res, expectedFingerprint) {
|
|
10691
|
+
const socket = res.socket;
|
|
10692
|
+
if (!socket) {
|
|
10693
|
+
return new Error("Controller did not present a TLS certificate");
|
|
10694
|
+
}
|
|
10695
|
+
if (socket.isSessionReused?.()) {
|
|
10696
|
+
return void 0;
|
|
10697
|
+
}
|
|
10698
|
+
const cert = socket.getPeerCertificate?.(true);
|
|
10699
|
+
const raw = cert?.raw ?? socket.getX509Certificate?.()?.raw;
|
|
10700
|
+
if (!raw) {
|
|
10701
|
+
return new Error("Controller did not present a TLS certificate");
|
|
10702
|
+
}
|
|
10703
|
+
const actual = certificateFingerprint(raw);
|
|
10704
|
+
if (actual !== expectedFingerprint) {
|
|
10705
|
+
return new Error(`Controller certificate fingerprint mismatch: expected ${expectedFingerprint}, got ${actual}`);
|
|
10706
|
+
}
|
|
10707
|
+
return void 0;
|
|
10708
|
+
}
|
|
10709
|
+
|
|
10302
10710
|
// ../agent/dist/executor/index.js
|
|
10303
10711
|
var logger4 = createLogger("agent.executor");
|
|
10304
10712
|
var ARTIFACT_TOKEN_TIMEOUT_MS = 6e4;
|
|
@@ -10320,18 +10728,6 @@ function resolveProjectIdentity(offer, prepare) {
|
|
|
10320
10728
|
}
|
|
10321
10729
|
return `local:${offer.snapshot_metadata.content_id}`;
|
|
10322
10730
|
}
|
|
10323
|
-
function assertPinnedPeerCert(res, expectedFingerprint) {
|
|
10324
|
-
const socket = res.socket;
|
|
10325
|
-
const cert = socket.getPeerCertificate?.(true);
|
|
10326
|
-
if (!cert?.raw) {
|
|
10327
|
-
return new Error("Controller did not present a TLS certificate");
|
|
10328
|
-
}
|
|
10329
|
-
const actual = certificateFingerprint(cert.raw);
|
|
10330
|
-
if (actual !== expectedFingerprint) {
|
|
10331
|
-
return new Error(`Controller certificate fingerprint mismatch: expected ${expectedFingerprint}, got ${actual}`);
|
|
10332
|
-
}
|
|
10333
|
-
return void 0;
|
|
10334
|
-
}
|
|
10335
10731
|
var AgentJobExecutor = class {
|
|
10336
10732
|
socket;
|
|
10337
10733
|
config;
|
|
@@ -11067,7 +11463,7 @@ var AgentJobExecutor = class {
|
|
|
11067
11463
|
const currentProfiles = this.config.toolchainProfiles ?? [];
|
|
11068
11464
|
for (const profile of offer.selected_toolchain_profiles ?? []) {
|
|
11069
11465
|
const activationPath = profile.activation.path;
|
|
11070
|
-
if (activationPath && !
|
|
11466
|
+
if (activationPath && !existsSync4(activationPath)) {
|
|
11071
11467
|
this.failTerminal(run.attempt_id, run.lease_id, run.lease_epoch, "toolchain_changed", `Selected toolchain path missing: ${activationPath}`);
|
|
11072
11468
|
await rm9(attemptDir, { recursive: true, force: true }).catch(() => void 0);
|
|
11073
11469
|
this.clearAttempt();
|
|
@@ -11494,22 +11890,6 @@ var AgentJobExecutor = class {
|
|
|
11494
11890
|
this.sendFrame("artifact_manifest", attemptId, leaseId, leaseEpoch, manifest);
|
|
11495
11891
|
});
|
|
11496
11892
|
}
|
|
11497
|
-
pinnedTlsOptions() {
|
|
11498
|
-
const expected = this.config.controllerFingerprint;
|
|
11499
|
-
return {
|
|
11500
|
-
rejectUnauthorized: false,
|
|
11501
|
-
checkServerIdentity: (_host, cert) => {
|
|
11502
|
-
if (!cert.raw) {
|
|
11503
|
-
return new Error("Controller did not present a TLS certificate");
|
|
11504
|
-
}
|
|
11505
|
-
const actual = certificateFingerprint(cert.raw);
|
|
11506
|
-
if (actual !== expected) {
|
|
11507
|
-
return new Error(`Controller certificate fingerprint mismatch: expected ${expected}, got ${actual}`);
|
|
11508
|
-
}
|
|
11509
|
-
return void 0;
|
|
11510
|
-
}
|
|
11511
|
-
};
|
|
11512
|
-
}
|
|
11513
11893
|
downloadSnapshotFile(downloadUrl, dataToken, destPath, expectedSize, expectedSha256) {
|
|
11514
11894
|
return new Promise((resolvePromise, rejectPromise) => {
|
|
11515
11895
|
const req = httpsRequest(downloadUrl, {
|
|
@@ -11517,7 +11897,7 @@ var AgentJobExecutor = class {
|
|
|
11517
11897
|
headers: {
|
|
11518
11898
|
Authorization: `Bearer ${dataToken}`
|
|
11519
11899
|
},
|
|
11520
|
-
...this.
|
|
11900
|
+
...pinnedTlsRequestOptions(this.config.controllerFingerprint)
|
|
11521
11901
|
}, (res) => {
|
|
11522
11902
|
const pinError = assertPinnedPeerCert(res, this.config.controllerFingerprint);
|
|
11523
11903
|
if (pinError) {
|
|
@@ -11546,7 +11926,7 @@ var AgentJobExecutor = class {
|
|
|
11546
11926
|
"X-RBO-SHA256": sha2562,
|
|
11547
11927
|
"X-RBO-Artifact-Name": logicalName
|
|
11548
11928
|
},
|
|
11549
|
-
...this.
|
|
11929
|
+
...pinnedTlsRequestOptions(this.config.controllerFingerprint)
|
|
11550
11930
|
}, (res) => {
|
|
11551
11931
|
const pinError = assertPinnedPeerCert(res, this.config.controllerFingerprint);
|
|
11552
11932
|
if (pinError) {
|
|
@@ -11839,8 +12219,8 @@ var AgentConnection = class {
|
|
|
11839
12219
|
return join22(this.options.stateDir, "agent-state.json");
|
|
11840
12220
|
}
|
|
11841
12221
|
loadState() {
|
|
11842
|
-
if (
|
|
11843
|
-
return JSON.parse(
|
|
12222
|
+
if (existsSync5(this.statePath())) {
|
|
12223
|
+
return JSON.parse(readFileSync5(this.statePath(), "utf8"));
|
|
11844
12224
|
}
|
|
11845
12225
|
const pair = generateDeviceKeyPair();
|
|
11846
12226
|
const state = {
|
|
@@ -11852,7 +12232,7 @@ var AgentConnection = class {
|
|
|
11852
12232
|
}
|
|
11853
12233
|
saveState(state) {
|
|
11854
12234
|
mkdirSync4(this.options.stateDir, { recursive: true });
|
|
11855
|
-
|
|
12235
|
+
writeFileSync4(this.statePath(), JSON.stringify(state), { mode: 384 });
|
|
11856
12236
|
}
|
|
11857
12237
|
hasStoredCredential() {
|
|
11858
12238
|
const state = this.loadState();
|
|
@@ -12329,7 +12709,7 @@ init_dist();
|
|
|
12329
12709
|
|
|
12330
12710
|
// src/commands/daemon.ts
|
|
12331
12711
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
12332
|
-
import { existsSync as
|
|
12712
|
+
import { existsSync as existsSync6, openSync, readFileSync as readFileSync6 } from "node:fs";
|
|
12333
12713
|
import { mkdir as mkdir10, writeFile as writeFile10 } from "node:fs/promises";
|
|
12334
12714
|
import { dirname as dirname6, join as join23 } from "node:path";
|
|
12335
12715
|
function stripDaemonFlag(args) {
|
|
@@ -12360,10 +12740,10 @@ function isProcessAlive(pid) {
|
|
|
12360
12740
|
}
|
|
12361
12741
|
}
|
|
12362
12742
|
function assertNoLivePid(pidFile, label) {
|
|
12363
|
-
if (!
|
|
12743
|
+
if (!existsSync6(pidFile)) {
|
|
12364
12744
|
return;
|
|
12365
12745
|
}
|
|
12366
|
-
const raw =
|
|
12746
|
+
const raw = readFileSync6(pidFile, "utf8").trim();
|
|
12367
12747
|
const pid = Number.parseInt(raw, 10);
|
|
12368
12748
|
if (isProcessAlive(pid)) {
|
|
12369
12749
|
throw new Error(
|
|
@@ -12407,36 +12787,21 @@ async function spawnDetachedDaemon(options) {
|
|
|
12407
12787
|
}
|
|
12408
12788
|
|
|
12409
12789
|
// src/commands/agent.ts
|
|
12410
|
-
var AGENT_MARKER = "agent.json";
|
|
12411
|
-
var AGENT_SCHEMA_VERSION = 1;
|
|
12412
12790
|
function isAgentInitialized(stateDir) {
|
|
12413
|
-
return
|
|
12791
|
+
return existsSync7(join24(stateDir, AGENT_CONFIG_FILENAME));
|
|
12414
12792
|
}
|
|
12415
|
-
async function
|
|
12416
|
-
const
|
|
12417
|
-
|
|
12418
|
-
return void 0;
|
|
12419
|
-
}
|
|
12420
|
-
const marker = JSON.parse(await readFile14(markerPath, "utf8"));
|
|
12793
|
+
async function runAgentInit(options = {}) {
|
|
12794
|
+
const stateDir = options.stateDir ?? resolveAgentStateDir();
|
|
12795
|
+
const result = writeDefaultAgentConfigFile(stateDir, { force: options.force });
|
|
12421
12796
|
return {
|
|
12422
12797
|
stateDir,
|
|
12423
|
-
initialized_at:
|
|
12424
|
-
schema_version:
|
|
12798
|
+
initialized_at: result.initialized_at,
|
|
12799
|
+
schema_version: result.schema_version,
|
|
12800
|
+
configPath: result.path,
|
|
12801
|
+
configWritten: result.written,
|
|
12802
|
+
...result.written ? {} : { hint: "agent.json already exists; pass --force to rewrite defaults" }
|
|
12425
12803
|
};
|
|
12426
12804
|
}
|
|
12427
|
-
async function runAgentInit(options = {}) {
|
|
12428
|
-
const stateDir = options.stateDir ?? resolveAgentStateDir();
|
|
12429
|
-
await mkdir11(stateDir, { recursive: true });
|
|
12430
|
-
const existing = await readAgentMarker(stateDir);
|
|
12431
|
-
if (existing) {
|
|
12432
|
-
return existing;
|
|
12433
|
-
}
|
|
12434
|
-
const initialized_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
12435
|
-
const marker = { initialized_at, schema_version: AGENT_SCHEMA_VERSION };
|
|
12436
|
-
await writeFile11(join24(stateDir, AGENT_MARKER), `${JSON.stringify(marker, null, 2)}
|
|
12437
|
-
`, "utf8");
|
|
12438
|
-
return { stateDir, ...marker };
|
|
12439
|
-
}
|
|
12440
12805
|
function assertAgentInitialized(stateDir) {
|
|
12441
12806
|
if (!isAgentInitialized(stateDir)) {
|
|
12442
12807
|
throw new Error("Agent is not initialized. Run `rbo agent init` first.");
|
|
@@ -12476,8 +12841,24 @@ async function postAdmin(baseUrl, action, body) {
|
|
|
12476
12841
|
}
|
|
12477
12842
|
return json;
|
|
12478
12843
|
}
|
|
12479
|
-
function listAgentsRemote(baseUrl) {
|
|
12480
|
-
|
|
12844
|
+
async function listAgentsRemote(baseUrl) {
|
|
12845
|
+
const [agentsResult, pairingResult] = await Promise.all([
|
|
12846
|
+
postAdmin(baseUrl, "agents/list", {}),
|
|
12847
|
+
postAdmin(baseUrl, "pairing/list", {
|
|
12848
|
+
state: "pending"
|
|
12849
|
+
})
|
|
12850
|
+
]);
|
|
12851
|
+
return {
|
|
12852
|
+
agents: agentsResult.agents,
|
|
12853
|
+
pending_pairings: pairingResult.requests.map((row) => ({
|
|
12854
|
+
id: row.id,
|
|
12855
|
+
display_name: row.display_name,
|
|
12856
|
+
hostname: row.hostname,
|
|
12857
|
+
state: row.state,
|
|
12858
|
+
one_time_code: row.one_time_code,
|
|
12859
|
+
expires_at: row.expires_at
|
|
12860
|
+
}))
|
|
12861
|
+
};
|
|
12481
12862
|
}
|
|
12482
12863
|
function approveAgentRemote(baseUrl, pairingRequestId) {
|
|
12483
12864
|
return postAdmin(baseUrl, "pairing/approve", { pairing_request_id: pairingRequestId });
|
|
@@ -12490,8 +12871,9 @@ function probeAgentRemote(baseUrl, agentId) {
|
|
|
12490
12871
|
}
|
|
12491
12872
|
|
|
12492
12873
|
// src/commands/controller.ts
|
|
12493
|
-
|
|
12494
|
-
import {
|
|
12874
|
+
init_config();
|
|
12875
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
12876
|
+
import { readFile as readFile17 } from "node:fs/promises";
|
|
12495
12877
|
import { join as join32 } from "node:path";
|
|
12496
12878
|
|
|
12497
12879
|
// ../controller/dist/run.js
|
|
@@ -12509,7 +12891,7 @@ init_dist4();
|
|
|
12509
12891
|
init_dist2();
|
|
12510
12892
|
init_dist();
|
|
12511
12893
|
import { join as join31 } from "node:path";
|
|
12512
|
-
import { z as
|
|
12894
|
+
import { z as z9 } from "zod";
|
|
12513
12895
|
|
|
12514
12896
|
// ../controller/dist/agents/probe.js
|
|
12515
12897
|
function requestAgentProbe(connectedAgents, agentId) {
|
|
@@ -12563,6 +12945,189 @@ function listAgents(db, includeOffline) {
|
|
|
12563
12945
|
// ../controller/dist/mcp/handlers.js
|
|
12564
12946
|
init_artifacts2();
|
|
12565
12947
|
init_runner();
|
|
12948
|
+
|
|
12949
|
+
// ../controller/dist/jobs/job-run.js
|
|
12950
|
+
init_dist2();
|
|
12951
|
+
init_dist();
|
|
12952
|
+
init_lifecycle();
|
|
12953
|
+
init_submit();
|
|
12954
|
+
var DEFAULT_MCP_WAIT_SLICE_SECONDS = 50;
|
|
12955
|
+
function wrapCommandAsExecution(command, timeoutSeconds, platform3 = process.platform) {
|
|
12956
|
+
if (platform3 === "win32") {
|
|
12957
|
+
return {
|
|
12958
|
+
shell: "powershell",
|
|
12959
|
+
script: [
|
|
12960
|
+
"$ErrorActionPreference = 'Stop'",
|
|
12961
|
+
command,
|
|
12962
|
+
"if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }"
|
|
12963
|
+
].join("\n"),
|
|
12964
|
+
timeout_seconds: timeoutSeconds
|
|
12965
|
+
};
|
|
12966
|
+
}
|
|
12967
|
+
return {
|
|
12968
|
+
shell: "bash",
|
|
12969
|
+
script: `set -euo pipefail
|
|
12970
|
+
${command}
|
|
12971
|
+
`,
|
|
12972
|
+
timeout_seconds: timeoutSeconds
|
|
12973
|
+
};
|
|
12974
|
+
}
|
|
12975
|
+
function deriveJobName(command) {
|
|
12976
|
+
const compact = command.replace(/\s+/g, " ").trim();
|
|
12977
|
+
if (compact.length <= 72) {
|
|
12978
|
+
return compact;
|
|
12979
|
+
}
|
|
12980
|
+
return `${compact.slice(0, 69)}...`;
|
|
12981
|
+
}
|
|
12982
|
+
function buildJobRunRequest(input, platform3 = process.platform) {
|
|
12983
|
+
if (!input.command || !input.project_root) {
|
|
12984
|
+
throw RboError.validation("job_run requires command and project_root unless job_id is set");
|
|
12985
|
+
}
|
|
12986
|
+
const timeoutSeconds = input.timeout_seconds ?? 3600;
|
|
12987
|
+
return JobRequestSchema.parse({
|
|
12988
|
+
client_request_id: input.client_request_id ?? generateId("req"),
|
|
12989
|
+
name: input.name ?? deriveJobName(input.command),
|
|
12990
|
+
source: {
|
|
12991
|
+
project_root: input.project_root,
|
|
12992
|
+
cwd: input.cwd ?? "."
|
|
12993
|
+
},
|
|
12994
|
+
execution: wrapCommandAsExecution(input.command, timeoutSeconds, platform3),
|
|
12995
|
+
risk_level: input.risk_level ?? "normal",
|
|
12996
|
+
artifacts: input.artifacts ?? []
|
|
12997
|
+
});
|
|
12998
|
+
}
|
|
12999
|
+
function summarizeJob(job) {
|
|
13000
|
+
if (!job) {
|
|
13001
|
+
return {
|
|
13002
|
+
state: null,
|
|
13003
|
+
outcome: null,
|
|
13004
|
+
exit_code: null,
|
|
13005
|
+
failure_category: null,
|
|
13006
|
+
failure_message: null
|
|
13007
|
+
};
|
|
13008
|
+
}
|
|
13009
|
+
return {
|
|
13010
|
+
state: job.state,
|
|
13011
|
+
outcome: job.outcome,
|
|
13012
|
+
exit_code: job.exit_code,
|
|
13013
|
+
failure_category: job.failure_category,
|
|
13014
|
+
failure_message: job.failure_message
|
|
13015
|
+
};
|
|
13016
|
+
}
|
|
13017
|
+
function resolveSliceSeconds(rawInput) {
|
|
13018
|
+
const slice = rawInput.mcp_wait_slice_seconds ?? DEFAULT_MCP_WAIT_SLICE_SECONDS;
|
|
13019
|
+
return Math.min(55, Math.max(1, slice));
|
|
13020
|
+
}
|
|
13021
|
+
function resolveWaitBudget(rawInput) {
|
|
13022
|
+
const timeoutSeconds = rawInput.timeout_seconds ?? 3600;
|
|
13023
|
+
const requested = rawInput.wait_seconds ?? timeoutSeconds;
|
|
13024
|
+
return Math.min(requested, resolveSliceSeconds(rawInput));
|
|
13025
|
+
}
|
|
13026
|
+
async function finishJobRunResponse(ctx, jobId, waitResult) {
|
|
13027
|
+
if ("error" in waitResult && waitResult.error) {
|
|
13028
|
+
return waitResult;
|
|
13029
|
+
}
|
|
13030
|
+
const job = waitResult.job;
|
|
13031
|
+
const terminal = job ? isTerminalJobState(job.state) : false;
|
|
13032
|
+
const artifacts = terminal && job ? handleJobArtifacts(ctx.db, jobId).artifacts ?? [] : [];
|
|
13033
|
+
return {
|
|
13034
|
+
job_id: jobId,
|
|
13035
|
+
...summarizeJob(job),
|
|
13036
|
+
resume: !terminal,
|
|
13037
|
+
log_tail: waitResult.log_tail ?? null,
|
|
13038
|
+
artifacts
|
|
13039
|
+
};
|
|
13040
|
+
}
|
|
13041
|
+
async function handleJobRun(ctx, rawInput, options = {}) {
|
|
13042
|
+
const includeLogTailLines = rawInput.include_log_tail_lines ?? 80;
|
|
13043
|
+
const waitBudget = resolveWaitBudget(rawInput);
|
|
13044
|
+
let progressCounter = 0;
|
|
13045
|
+
const startedAt = Date.now();
|
|
13046
|
+
let lastProgressAt = 0;
|
|
13047
|
+
const onTick = options.onProgress ? async (job) => {
|
|
13048
|
+
const now = Date.now();
|
|
13049
|
+
if (now - lastProgressAt < 5e3 && progressCounter > 0) {
|
|
13050
|
+
return;
|
|
13051
|
+
}
|
|
13052
|
+
lastProgressAt = now;
|
|
13053
|
+
progressCounter += 1;
|
|
13054
|
+
const elapsedSec = Math.round((now - startedAt) / 1e3);
|
|
13055
|
+
await options.onProgress?.({
|
|
13056
|
+
progress: progressCounter,
|
|
13057
|
+
message: `job_run state=${job.state} elapsed=${elapsedSec}s`
|
|
13058
|
+
});
|
|
13059
|
+
} : void 0;
|
|
13060
|
+
let jobId = rawInput.job_id?.trim() || "";
|
|
13061
|
+
if (jobId) {
|
|
13062
|
+
const existing = getJob(ctx.db, jobId);
|
|
13063
|
+
if (!existing) {
|
|
13064
|
+
return {
|
|
13065
|
+
error: {
|
|
13066
|
+
category: "validation",
|
|
13067
|
+
message: `Unknown job_id '${jobId}'`,
|
|
13068
|
+
retryable: false
|
|
13069
|
+
}
|
|
13070
|
+
};
|
|
13071
|
+
}
|
|
13072
|
+
if (existing.state === "awaiting_confirmation") {
|
|
13073
|
+
return {
|
|
13074
|
+
job_id: jobId,
|
|
13075
|
+
...summarizeJob(existing),
|
|
13076
|
+
resume: false,
|
|
13077
|
+
confirmation_required: true,
|
|
13078
|
+
log_tail: null,
|
|
13079
|
+
artifacts: []
|
|
13080
|
+
};
|
|
13081
|
+
}
|
|
13082
|
+
if (isTerminalJobState(existing.state)) {
|
|
13083
|
+
const artifactsResult = handleJobArtifacts(ctx.db, jobId);
|
|
13084
|
+
return {
|
|
13085
|
+
job_id: jobId,
|
|
13086
|
+
...summarizeJob(existing),
|
|
13087
|
+
resume: false,
|
|
13088
|
+
log_tail: null,
|
|
13089
|
+
artifacts: Array.isArray(artifactsResult.artifacts) ? artifactsResult.artifacts : []
|
|
13090
|
+
};
|
|
13091
|
+
}
|
|
13092
|
+
} else {
|
|
13093
|
+
if (!rawInput.command || !rawInput.project_root) {
|
|
13094
|
+
return {
|
|
13095
|
+
error: {
|
|
13096
|
+
category: "validation",
|
|
13097
|
+
message: "job_run requires command and project_root unless job_id is set",
|
|
13098
|
+
retryable: false
|
|
13099
|
+
}
|
|
13100
|
+
};
|
|
13101
|
+
}
|
|
13102
|
+
const request = buildJobRunRequest(rawInput);
|
|
13103
|
+
const submit = await handleJobSubmit(ctx, request);
|
|
13104
|
+
if ("error" in submit && submit.error) {
|
|
13105
|
+
return submit;
|
|
13106
|
+
}
|
|
13107
|
+
jobId = String(submit.job_id);
|
|
13108
|
+
if (submit.state === "awaiting_confirmation") {
|
|
13109
|
+
return {
|
|
13110
|
+
job_id: jobId,
|
|
13111
|
+
state: "awaiting_confirmation",
|
|
13112
|
+
confirmation_token: submit.confirmation_token,
|
|
13113
|
+
snapshot_id: submit.snapshot_id,
|
|
13114
|
+
content_id: submit.content_id,
|
|
13115
|
+
secret_warnings: submit.secret_warnings,
|
|
13116
|
+
outcome: null,
|
|
13117
|
+
exit_code: null,
|
|
13118
|
+
failure_category: null,
|
|
13119
|
+
failure_message: null,
|
|
13120
|
+
resume: false,
|
|
13121
|
+
log_tail: null,
|
|
13122
|
+
artifacts: []
|
|
13123
|
+
};
|
|
13124
|
+
}
|
|
13125
|
+
}
|
|
13126
|
+
const waitResult = await waitForJob(ctx, jobId, waitBudget, includeLogTailLines, { onTick });
|
|
13127
|
+
return finishJobRunResponse(ctx, jobId, waitResult);
|
|
13128
|
+
}
|
|
13129
|
+
|
|
13130
|
+
// ../controller/dist/mcp/handlers.js
|
|
12566
13131
|
init_lifecycle();
|
|
12567
13132
|
init_submit();
|
|
12568
13133
|
function runnerContext(ctx) {
|
|
@@ -12597,7 +13162,7 @@ function validateToolInput(name, args) {
|
|
|
12597
13162
|
if (!def) {
|
|
12598
13163
|
throw RboError.validation(`Unknown tool '${name}'`);
|
|
12599
13164
|
}
|
|
12600
|
-
const parsed =
|
|
13165
|
+
const parsed = z9.object(def.inputShape).safeParse(args ?? {});
|
|
12601
13166
|
if (!parsed.success) {
|
|
12602
13167
|
throw RboError.validation(`Invalid input for tool '${name}'`, {
|
|
12603
13168
|
issues: parsed.error.issues.map((issue) => ({
|
|
@@ -12615,6 +13180,21 @@ async function handleToolCall(ctx, name, rawArgs) {
|
|
|
12615
13180
|
return { agents: listAgents(ctx.db, args.include_offline === true) };
|
|
12616
13181
|
case "job_submit":
|
|
12617
13182
|
return handleJobSubmit(submitContext(ctx), args);
|
|
13183
|
+
case "job_run":
|
|
13184
|
+
return handleJobRun(submitContext(ctx), {
|
|
13185
|
+
command: args.command,
|
|
13186
|
+
project_root: args.project_root,
|
|
13187
|
+
job_id: args.job_id,
|
|
13188
|
+
cwd: args.cwd,
|
|
13189
|
+
timeout_seconds: args.timeout_seconds,
|
|
13190
|
+
wait_seconds: args.wait_seconds,
|
|
13191
|
+
mcp_wait_slice_seconds: args.mcp_wait_slice_seconds,
|
|
13192
|
+
artifacts: args.artifacts,
|
|
13193
|
+
risk_level: args.risk_level,
|
|
13194
|
+
client_request_id: args.client_request_id,
|
|
13195
|
+
name: args.name,
|
|
13196
|
+
include_log_tail_lines: args.include_log_tail_lines
|
|
13197
|
+
}, ctx.jobRunOptions);
|
|
12618
13198
|
case "job_confirm":
|
|
12619
13199
|
return handleJobConfirm(submitContext(ctx), {
|
|
12620
13200
|
job_id: args.job_id,
|
|
@@ -12722,8 +13302,27 @@ function buildMcpServer(ctx) {
|
|
|
12722
13302
|
version: RBO_CONTROLLER_VERSION
|
|
12723
13303
|
});
|
|
12724
13304
|
for (const def of MCP_TOOL_DEFS) {
|
|
12725
|
-
server.registerTool(def.name, { description: def.description, inputSchema: def.inputShape }, async (args) => {
|
|
12726
|
-
const
|
|
13305
|
+
server.registerTool(def.name, { description: def.description, inputSchema: def.inputShape }, async (args, extra) => {
|
|
13306
|
+
const toolCtx = def.name === "job_run" ? {
|
|
13307
|
+
...ctx,
|
|
13308
|
+
jobRunOptions: {
|
|
13309
|
+
onProgress: async (update) => {
|
|
13310
|
+
const progressToken = extra._meta?.progressToken;
|
|
13311
|
+
if (progressToken === void 0) {
|
|
13312
|
+
return;
|
|
13313
|
+
}
|
|
13314
|
+
await extra.sendNotification({
|
|
13315
|
+
method: "notifications/progress",
|
|
13316
|
+
params: {
|
|
13317
|
+
progressToken,
|
|
13318
|
+
progress: update.progress,
|
|
13319
|
+
message: update.message
|
|
13320
|
+
}
|
|
13321
|
+
});
|
|
13322
|
+
}
|
|
13323
|
+
}
|
|
13324
|
+
} : ctx;
|
|
13325
|
+
const result = await handleToolCall(toolCtx, def.name, args);
|
|
12727
13326
|
return {
|
|
12728
13327
|
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
12729
13328
|
};
|
|
@@ -12745,7 +13344,7 @@ function revokeAgent(db, agentId) {
|
|
|
12745
13344
|
}
|
|
12746
13345
|
}
|
|
12747
13346
|
function updateAgentCapabilities(db, agentId, report) {
|
|
12748
|
-
db.prepare("UPDATE agents SET capabilities_json = ?, hostname = ?, last_seen_at = ? WHERE id = ?").run(JSON.stringify(report), report.hostname, nowIso(), agentId);
|
|
13347
|
+
db.prepare("UPDATE agents SET capabilities_json = ?, hostname = ?, max_jobs = ?, last_seen_at = ? WHERE id = ?").run(JSON.stringify(report), report.hostname, report.execution.max_jobs, nowIso(), agentId);
|
|
12749
13348
|
}
|
|
12750
13349
|
function patchAgentCpuLoad(db, agentId, cpuLoad) {
|
|
12751
13350
|
const row = db.prepare("SELECT capabilities_json FROM agents WHERE id = ?").get(agentId);
|
|
@@ -12829,11 +13428,20 @@ function getPairingRequest(db, id) {
|
|
|
12829
13428
|
const row = db.prepare("SELECT * FROM pairing_requests WHERE id = ?").get(id);
|
|
12830
13429
|
return row ?? null;
|
|
12831
13430
|
}
|
|
13431
|
+
function expireStalePairingRequests(db) {
|
|
13432
|
+
const now = nowIso();
|
|
13433
|
+
const result = db.prepare(`UPDATE pairing_requests
|
|
13434
|
+
SET state = 'expired', resolved_at = COALESCE(resolved_at, ?)
|
|
13435
|
+
WHERE state IN ('pending', 'approved') AND expires_at <= ?`).run(now, now);
|
|
13436
|
+
return result.changes;
|
|
13437
|
+
}
|
|
12832
13438
|
function listPairingRequests(db, state) {
|
|
13439
|
+
expireStalePairingRequests(db);
|
|
12833
13440
|
const rows = state ? db.prepare("SELECT * FROM pairing_requests WHERE state = ? ORDER BY created_at").all(state) : db.prepare("SELECT * FROM pairing_requests ORDER BY created_at").all();
|
|
12834
13441
|
return rows;
|
|
12835
13442
|
}
|
|
12836
13443
|
function createPairingRequest(db, input) {
|
|
13444
|
+
expireStalePairingRequests(db);
|
|
12837
13445
|
const thumbprint = publicKeyThumbprint(input.devicePublicKeyPem);
|
|
12838
13446
|
const existing = db.prepare("SELECT * FROM pairing_requests WHERE device_thumbprint = ? AND state IN ('pending', 'approved') AND expires_at > ?").get(thumbprint, nowIso());
|
|
12839
13447
|
if (existing) {
|
|
@@ -13445,7 +14053,7 @@ async function startAgentPlaneServer(options) {
|
|
|
13445
14053
|
const payload = parsePayload(LogChunkPayloadSchema, message.payload, message.type);
|
|
13446
14054
|
if (!payload)
|
|
13447
14055
|
return;
|
|
13448
|
-
void
|
|
14056
|
+
void enqueueRemoteLogChunk(remoteOpts(), authenticated.agentId, payload);
|
|
13449
14057
|
return;
|
|
13450
14058
|
}
|
|
13451
14059
|
case "job_exit": {
|
|
@@ -13609,7 +14217,13 @@ async function runController(overrides = {}) {
|
|
|
13609
14217
|
init_dist();
|
|
13610
14218
|
async function runControllerInit(options) {
|
|
13611
14219
|
const identity = await ensureControllerIdentity(options.dataDir);
|
|
13612
|
-
|
|
14220
|
+
const config = writeDefaultControllerConfigFile(options.dataDir, { force: options.force });
|
|
14221
|
+
return {
|
|
14222
|
+
controllerId: identity.controllerId,
|
|
14223
|
+
fingerprint: identity.fingerprint,
|
|
14224
|
+
configPath: config.path,
|
|
14225
|
+
configWritten: config.written
|
|
14226
|
+
};
|
|
13613
14227
|
}
|
|
13614
14228
|
async function runControllerFingerprint(options) {
|
|
13615
14229
|
const identity = await ensureControllerIdentity(options.dataDir);
|
|
@@ -13618,10 +14232,10 @@ async function runControllerFingerprint(options) {
|
|
|
13618
14232
|
async function readExistingControllerId(dataDir) {
|
|
13619
14233
|
const certPath = join32(dataDir, "security", "controller-cert.pem");
|
|
13620
14234
|
const metaPath = join32(dataDir, "security", "controller.json");
|
|
13621
|
-
if (!
|
|
14235
|
+
if (!existsSync9(certPath) || !existsSync9(metaPath)) {
|
|
13622
14236
|
return void 0;
|
|
13623
14237
|
}
|
|
13624
|
-
const meta = JSON.parse(await
|
|
14238
|
+
const meta = JSON.parse(await readFile17(metaPath, "utf8"));
|
|
13625
14239
|
return meta.controller_id;
|
|
13626
14240
|
}
|
|
13627
14241
|
async function isControllerInitialized(dataDir) {
|
|
@@ -13665,11 +14279,25 @@ async function runControllerRestore(options) {
|
|
|
13665
14279
|
// src/commands/doctor.ts
|
|
13666
14280
|
init_dist4();
|
|
13667
14281
|
import { execFile as execFile9 } from "node:child_process";
|
|
13668
|
-
import { mkdirSync as mkdirSync6, writeFileSync as
|
|
14282
|
+
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "node:fs";
|
|
13669
14283
|
import { join as join33 } from "node:path";
|
|
13670
|
-
import { promisify as promisify9 } from "node:util";
|
|
14284
|
+
import { promisify as promisify9, styleText } from "node:util";
|
|
13671
14285
|
var execFileAsync9 = promisify9(execFile9);
|
|
13672
14286
|
var REQUIRED_NODE_ENGINES = { major: 22, minor: 14 };
|
|
14287
|
+
function doctorStatusTag(check) {
|
|
14288
|
+
if (!check.ok) return "FAIL";
|
|
14289
|
+
if (check.warn) return "WARN";
|
|
14290
|
+
return "OK ";
|
|
14291
|
+
}
|
|
14292
|
+
function formatDoctorCheckLine(check, options = {}) {
|
|
14293
|
+
const tag = doctorStatusTag(check);
|
|
14294
|
+
const colorName = !check.ok ? "red" : check.warn ? "yellow" : "green";
|
|
14295
|
+
const styledTag = options.color === false ? tag : styleText(colorName, tag, {
|
|
14296
|
+
stream: options.stream ?? process.stdout,
|
|
14297
|
+
validateStream: options.color !== true
|
|
14298
|
+
});
|
|
14299
|
+
return `${styledTag} ${check.name}: ${check.detail}`;
|
|
14300
|
+
}
|
|
13673
14301
|
async function checkGit() {
|
|
13674
14302
|
try {
|
|
13675
14303
|
const { stdout } = await execFileAsync9("git", ["--version"]);
|
|
@@ -13682,7 +14310,7 @@ function checkDataDirWritable(dataDir) {
|
|
|
13682
14310
|
try {
|
|
13683
14311
|
mkdirSync6(dataDir, { recursive: true });
|
|
13684
14312
|
const probe = join33(dataDir, ".rbo-doctor-probe");
|
|
13685
|
-
|
|
14313
|
+
writeFileSync6(probe, "ok");
|
|
13686
14314
|
return { name: "data_dir_writable", ok: true, detail: dataDir };
|
|
13687
14315
|
} catch (error) {
|
|
13688
14316
|
return { name: "data_dir_writable", ok: false, detail: String(error) };
|
|
@@ -13777,6 +14405,18 @@ async function runDoctor(options) {
|
|
|
13777
14405
|
}
|
|
13778
14406
|
|
|
13779
14407
|
// src/commands/flags.ts
|
|
14408
|
+
function parseForceFlag(args) {
|
|
14409
|
+
const rest = [];
|
|
14410
|
+
let force = false;
|
|
14411
|
+
for (const arg of args) {
|
|
14412
|
+
if (arg === "--force") {
|
|
14413
|
+
force = true;
|
|
14414
|
+
continue;
|
|
14415
|
+
}
|
|
14416
|
+
rest.push(arg);
|
|
14417
|
+
}
|
|
14418
|
+
return { force, rest };
|
|
14419
|
+
}
|
|
13780
14420
|
function parseDataDirFlag(args) {
|
|
13781
14421
|
const rest = [];
|
|
13782
14422
|
let dataDir;
|
|
@@ -13849,6 +14489,7 @@ function cancelJobRemote(baseUrl, jobId, reason) {
|
|
|
13849
14489
|
}
|
|
13850
14490
|
|
|
13851
14491
|
// src/commands/service.ts
|
|
14492
|
+
init_dist();
|
|
13852
14493
|
import { execFile as execFile10 } from "node:child_process";
|
|
13853
14494
|
import { promisify as promisify10 } from "node:util";
|
|
13854
14495
|
var execFileAsync10 = promisify10(execFile10);
|
|
@@ -13881,14 +14522,21 @@ var defaultCommandRunner = {
|
|
|
13881
14522
|
var SERVICE_NAME = "RBOAgent";
|
|
13882
14523
|
var LAUNCHD_LABEL = "com.rbo.agent";
|
|
13883
14524
|
var SYSTEMD_UNIT = "rbo-agent";
|
|
13884
|
-
function
|
|
14525
|
+
function renderAgentServiceCommand(options = {}) {
|
|
14526
|
+
const nodePath = options.nodePath ?? process.execPath;
|
|
14527
|
+
const rboScriptPath = options.rboScriptPath ?? process.argv[1] ?? "rbo.js";
|
|
14528
|
+
const stateDir = options.stateDir ?? resolveAgentStateDir();
|
|
14529
|
+
return `"${nodePath}" "${rboScriptPath}" agent start --state-dir "${stateDir}"`;
|
|
14530
|
+
}
|
|
14531
|
+
function renderServiceInstallPlan(platform3, options = {}) {
|
|
14532
|
+
const binPath = renderAgentServiceCommand(options);
|
|
13885
14533
|
switch (platform3) {
|
|
13886
14534
|
case "win32":
|
|
13887
14535
|
return {
|
|
13888
14536
|
kind: "windows_service",
|
|
13889
14537
|
serviceName: SERVICE_NAME,
|
|
13890
14538
|
commands: [
|
|
13891
|
-
`sc.exe create ${SERVICE_NAME} binPath=
|
|
14539
|
+
`sc.exe create ${SERVICE_NAME} binPath= ${JSON.stringify(binPath)} start= auto`,
|
|
13892
14540
|
`sc.exe description ${SERVICE_NAME} "Remote Build Orchestrator Agent"`,
|
|
13893
14541
|
`sc.exe start ${SERVICE_NAME}`
|
|
13894
14542
|
]
|
|
@@ -13898,6 +14546,7 @@ function renderServiceInstallPlan(platform3, executablePath = "C:/Program Files/
|
|
|
13898
14546
|
kind: "launchd",
|
|
13899
14547
|
serviceName: LAUNCHD_LABEL,
|
|
13900
14548
|
commands: [
|
|
14549
|
+
`# Write /Library/LaunchDaemons/${LAUNCHD_LABEL}.plist with ProgramArguments: ${binPath}`,
|
|
13901
14550
|
`launchctl load -w /Library/LaunchDaemons/${LAUNCHD_LABEL}.plist`,
|
|
13902
14551
|
`launchctl start ${LAUNCHD_LABEL}`
|
|
13903
14552
|
]
|
|
@@ -13907,6 +14556,7 @@ function renderServiceInstallPlan(platform3, executablePath = "C:/Program Files/
|
|
|
13907
14556
|
kind: "systemd",
|
|
13908
14557
|
serviceName: SYSTEMD_UNIT,
|
|
13909
14558
|
commands: [
|
|
14559
|
+
`# Write /etc/systemd/system/${SYSTEMD_UNIT}.service with ExecStart=${binPath}`,
|
|
13910
14560
|
"systemctl daemon-reload",
|
|
13911
14561
|
`systemctl enable ${SYSTEMD_UNIT}`,
|
|
13912
14562
|
`systemctl start ${SYSTEMD_UNIT}`
|
|
@@ -14009,10 +14659,10 @@ function renderServiceStopPlan(platform3) {
|
|
|
14009
14659
|
};
|
|
14010
14660
|
}
|
|
14011
14661
|
}
|
|
14012
|
-
function renderServiceActionPlan(platform3, action,
|
|
14662
|
+
function renderServiceActionPlan(platform3, action, options) {
|
|
14013
14663
|
switch (action) {
|
|
14014
14664
|
case "install":
|
|
14015
|
-
return renderServiceInstallPlan(platform3,
|
|
14665
|
+
return renderServiceInstallPlan(platform3, options);
|
|
14016
14666
|
case "uninstall":
|
|
14017
14667
|
return renderServiceUninstallPlan(platform3);
|
|
14018
14668
|
case "status":
|
|
@@ -14027,7 +14677,11 @@ function hasExecuteFlag(args) {
|
|
|
14027
14677
|
return args.includes("--execute");
|
|
14028
14678
|
}
|
|
14029
14679
|
function formatDryRunPlan(action, plan) {
|
|
14030
|
-
const lines = [
|
|
14680
|
+
const lines = [
|
|
14681
|
+
`# ${action} (dry run \u2014 pass --execute to run these commands)`,
|
|
14682
|
+
"# Prefer `rbo agent start --daemon` for day-to-day use; OS service install is best-effort",
|
|
14683
|
+
"# and expects node + bundled rbo.js (not a Program Files rbo-agent.exe)."
|
|
14684
|
+
];
|
|
14031
14685
|
for (const command of plan.commands) {
|
|
14032
14686
|
lines.push(command);
|
|
14033
14687
|
}
|
|
@@ -14036,6 +14690,10 @@ function formatDryRunPlan(action, plan) {
|
|
|
14036
14690
|
async function executeServicePlan(plan, runner = defaultCommandRunner) {
|
|
14037
14691
|
const results = [];
|
|
14038
14692
|
for (const command of plan.commands) {
|
|
14693
|
+
if (command.trimStart().startsWith("#")) {
|
|
14694
|
+
results.push({ command, stdout: "", stderr: "skipped comment", code: 0 });
|
|
14695
|
+
continue;
|
|
14696
|
+
}
|
|
14039
14697
|
const { stdout, stderr, code } = await runner.run(command);
|
|
14040
14698
|
results.push({ command, stdout, stderr, code });
|
|
14041
14699
|
if (code !== 0) {
|
|
@@ -14067,7 +14725,8 @@ async function main() {
|
|
|
14067
14725
|
const dataDir = flagDataDir ?? resolveControllerDataDir();
|
|
14068
14726
|
const sub = controllerArgs[0];
|
|
14069
14727
|
if (sub === "init") {
|
|
14070
|
-
const
|
|
14728
|
+
const { force } = parseForceFlag(controllerArgs.slice(1));
|
|
14729
|
+
const result = await runControllerInit({ dataDir, force });
|
|
14071
14730
|
console.log(JSON.stringify(result, null, 2));
|
|
14072
14731
|
return;
|
|
14073
14732
|
}
|
|
@@ -14106,7 +14765,7 @@ async function main() {
|
|
|
14106
14765
|
}
|
|
14107
14766
|
case "agents": {
|
|
14108
14767
|
const result = await listAgentsRemote(controllerUrl);
|
|
14109
|
-
console.log(JSON.stringify(result
|
|
14768
|
+
console.log(JSON.stringify(result, null, 2));
|
|
14110
14769
|
return;
|
|
14111
14770
|
}
|
|
14112
14771
|
case "agent": {
|
|
@@ -14135,7 +14794,8 @@ async function main() {
|
|
|
14135
14794
|
return;
|
|
14136
14795
|
}
|
|
14137
14796
|
if (sub === "init") {
|
|
14138
|
-
const
|
|
14797
|
+
const { force } = parseForceFlag(agentArgs.slice(1));
|
|
14798
|
+
const result = await runAgentInit({ stateDir, force });
|
|
14139
14799
|
console.log(JSON.stringify(result, null, 2));
|
|
14140
14800
|
return;
|
|
14141
14801
|
}
|
|
@@ -14178,8 +14838,7 @@ async function main() {
|
|
|
14178
14838
|
const dataDir = flagDataDir ?? resolveControllerDataDir();
|
|
14179
14839
|
const report = await runDoctor({ dataDir, controllerUrl });
|
|
14180
14840
|
for (const check of report.checks) {
|
|
14181
|
-
|
|
14182
|
-
console.log(`${tag} ${check.name}: ${check.detail}`);
|
|
14841
|
+
console.log(formatDoctorCheckLine(check));
|
|
14183
14842
|
}
|
|
14184
14843
|
process.exitCode = report.ok ? 0 : 1;
|
|
14185
14844
|
return;
|
|
@@ -14189,7 +14848,7 @@ async function main() {
|
|
|
14189
14848
|
if (!requestPath) {
|
|
14190
14849
|
throw new Error("Usage: rbo submit <job-request.json>");
|
|
14191
14850
|
}
|
|
14192
|
-
const request = JSON.parse(await
|
|
14851
|
+
const request = JSON.parse(await readFile18(requestPath, "utf8"));
|
|
14193
14852
|
const result = await submitJobRemote(controllerUrl, request);
|
|
14194
14853
|
console.log(JSON.stringify(result, null, 2));
|
|
14195
14854
|
return;
|