@gemslibe/rbo 0.2.0 → 0.3.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/dist/rbo.js CHANGED
@@ -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: mkdir16 } = await import("node:fs/promises");
207
+ const { access: access4, mkdir: mkdir15 } = await import("node:fs/promises");
208
208
  try {
209
209
  await access4(joined);
210
210
  } catch {
211
- await mkdir16(joined, { recursive: true });
211
+ await mkdir15(joined, { recursive: true });
212
212
  }
213
213
  await assertRealPathContained(projectPath, joined);
214
214
  return resolveRealPath(joined);
@@ -2028,7 +2028,7 @@ var init_runtime_env = __esm({
2028
2028
  });
2029
2029
 
2030
2030
  // ../../packages/executor/dist/windows-executor-path.js
2031
- import { existsSync as existsSync2 } from "node:fs";
2031
+ import { existsSync as existsSync3 } from "node:fs";
2032
2032
  import { createRequire } from "node:module";
2033
2033
  import { dirname as dirname3, join as join11 } from "node:path";
2034
2034
  import { fileURLToPath } from "node:url";
@@ -2048,7 +2048,7 @@ function describeWindowsExecutorResolution(options = {}) {
2048
2048
  const env = options.env ?? process.env;
2049
2049
  const platform3 = options.platform ?? process.platform;
2050
2050
  const arch2 = options.arch ?? process.arch;
2051
- const exists = options.existsSyncFn ?? existsSync2;
2051
+ const exists = options.existsSyncFn ?? existsSync3;
2052
2052
  const moduleDir = options.moduleDir ?? dirname3(fileURLToPath(import.meta.url));
2053
2053
  const fromEnv = env.RBO_WINDOWS_EXECUTOR;
2054
2054
  if (fromEnv && exists(fromEnv)) {
@@ -4238,7 +4238,7 @@ var init_materialize = __esm({
4238
4238
  });
4239
4239
 
4240
4240
  // ../../packages/snapshot/dist/index.js
4241
- import { z as z6 } from "zod";
4241
+ import { z as z7 } from "zod";
4242
4242
  var Sha256HexSchema, ContentIdSchema, SafeRelativePathSchema, SnapshotFileEntrySchema, SnapshotWorkspaceSchema, SnapshotAdditionalRootSchema, SnapshotRepoSchema, payloadBase, FullSnapshotManifestSchema, GitOverlaySnapshotManifestSchema, SnapshotManifestSchema, SnapshotInstanceSchema;
4243
4243
  var init_dist3 = __esm({
4244
4244
  "../../packages/snapshot/dist/index.js"() {
@@ -4252,88 +4252,88 @@ var init_dist3 = __esm({
4252
4252
  init_capture();
4253
4253
  init_overlay();
4254
4254
  init_materialize();
4255
- Sha256HexSchema = z6.string().regex(/^[0-9a-f]{64}$/i);
4256
- ContentIdSchema = z6.string().regex(/^sha256:[0-9a-f]{64}$/i);
4257
- SafeRelativePathSchema = z6.string().min(1).refine((value) => isSafeRelativePath(value), {
4255
+ Sha256HexSchema = z7.string().regex(/^[0-9a-f]{64}$/i);
4256
+ ContentIdSchema = z7.string().regex(/^sha256:[0-9a-f]{64}$/i);
4257
+ SafeRelativePathSchema = z7.string().min(1).refine((value) => isSafeRelativePath(value), {
4258
4258
  message: "must be a relative path without '..', absolute, or UNC segments"
4259
4259
  });
4260
- SnapshotFileEntrySchema = z6.discriminatedUnion("type", [
4261
- z6.object({
4262
- path: z6.string().min(1),
4263
- type: z6.literal("file"),
4264
- mode: z6.enum(["100644", "100755"]),
4265
- size: z6.number().int().nonnegative(),
4260
+ SnapshotFileEntrySchema = z7.discriminatedUnion("type", [
4261
+ z7.object({
4262
+ path: z7.string().min(1),
4263
+ type: z7.literal("file"),
4264
+ mode: z7.enum(["100644", "100755"]),
4265
+ size: z7.number().int().nonnegative(),
4266
4266
  sha256: Sha256HexSchema
4267
4267
  }),
4268
- z6.object({
4269
- path: z6.string().min(1),
4270
- type: z6.literal("symlink"),
4271
- mode: z6.literal("120000"),
4272
- target: z6.string().min(1)
4268
+ z7.object({
4269
+ path: z7.string().min(1),
4270
+ type: z7.literal("symlink"),
4271
+ mode: z7.literal("120000"),
4272
+ target: z7.string().min(1)
4273
4273
  })
4274
4274
  ]);
4275
- SnapshotWorkspaceSchema = z6.object({
4275
+ SnapshotWorkspaceSchema = z7.object({
4276
4276
  main_mount: SafeRelativePathSchema,
4277
- cwd: z6.string().min(1).refine((value) => isSafeRelativePath(value, { allowDot: true }), {
4277
+ cwd: z7.string().min(1).refine((value) => isSafeRelativePath(value, { allowDot: true }), {
4278
4278
  message: "must be a relative path without '..', absolute, or UNC segments"
4279
4279
  })
4280
4280
  });
4281
- SnapshotAdditionalRootSchema = z6.object({
4282
- id: z6.string().min(1),
4281
+ SnapshotAdditionalRootSchema = z7.object({
4282
+ id: z7.string().min(1),
4283
4283
  mount: SafeRelativePathSchema,
4284
- file_count: z6.number().int().nonnegative(),
4285
- total_size: z6.number().int().nonnegative(),
4284
+ file_count: z7.number().int().nonnegative(),
4285
+ total_size: z7.number().int().nonnegative(),
4286
4286
  tree_sha256: Sha256HexSchema,
4287
- mode: z6.enum(["read_only", "read_write"]).default("read_only")
4288
- });
4289
- SnapshotRepoSchema = z6.object({
4290
- canonical_id: z6.string().min(1),
4291
- url: z6.string().min(1),
4292
- branch: z6.string().nullable(),
4293
- base_commit: z6.string().min(1),
4294
- head_is_pushed: z6.boolean(),
4287
+ mode: z7.enum(["read_only", "read_write"]).default("read_only")
4288
+ });
4289
+ SnapshotRepoSchema = z7.object({
4290
+ canonical_id: z7.string().min(1),
4291
+ url: z7.string().min(1),
4292
+ branch: z7.string().nullable(),
4293
+ base_commit: z7.string().min(1),
4294
+ head_is_pushed: z7.boolean(),
4295
4295
  /** Optional; Controller derives from branch when absent (§10.6). */
4296
- fetch_refs: z6.array(z6.string().min(1)).optional()
4296
+ fetch_refs: z7.array(z7.string().min(1)).optional()
4297
4297
  });
4298
4298
  payloadBase = {
4299
- format: z6.literal("tar"),
4300
- compression: z6.literal("zstd"),
4299
+ format: z7.literal("tar"),
4300
+ compression: z7.literal("zstd"),
4301
4301
  sha256: Sha256HexSchema,
4302
- size: z6.number().int().nonnegative()
4302
+ size: z7.number().int().nonnegative()
4303
4303
  };
4304
- FullSnapshotManifestSchema = z6.object({
4305
- schema_version: z6.literal(1),
4304
+ FullSnapshotManifestSchema = z7.object({
4305
+ schema_version: z7.literal(1),
4306
4306
  content_id: ContentIdSchema,
4307
4307
  repo: SnapshotRepoSchema.partial({ base_commit: true }).optional(),
4308
4308
  workspace: SnapshotWorkspaceSchema,
4309
- source: z6.object({
4310
- files: z6.array(SnapshotFileEntrySchema),
4311
- empty_directories: z6.array(z6.string()).default([])
4309
+ source: z7.object({
4310
+ files: z7.array(SnapshotFileEntrySchema),
4311
+ empty_directories: z7.array(z7.string()).default([])
4312
4312
  }),
4313
- additional_roots: z6.array(SnapshotAdditionalRootSchema).default([]),
4314
- payload: z6.object({ mode: z6.literal("full"), ...payloadBase })
4313
+ additional_roots: z7.array(SnapshotAdditionalRootSchema).default([]),
4314
+ payload: z7.object({ mode: z7.literal("full"), ...payloadBase })
4315
4315
  }).strict();
4316
- GitOverlaySnapshotManifestSchema = z6.object({
4317
- schema_version: z6.literal(1),
4316
+ GitOverlaySnapshotManifestSchema = z7.object({
4317
+ schema_version: z7.literal(1),
4318
4318
  content_id: ContentIdSchema,
4319
4319
  repo: SnapshotRepoSchema,
4320
4320
  workspace: SnapshotWorkspaceSchema,
4321
- overlay: z6.object({
4322
- files: z6.array(SnapshotFileEntrySchema),
4323
- deletions: z6.array(z6.string()),
4324
- empty_directories: z6.array(z6.string()).default([])
4321
+ overlay: z7.object({
4322
+ files: z7.array(SnapshotFileEntrySchema),
4323
+ deletions: z7.array(z7.string()),
4324
+ empty_directories: z7.array(z7.string()).default([])
4325
4325
  }),
4326
- additional_roots: z6.array(SnapshotAdditionalRootSchema).default([]),
4327
- payload: z6.object({ mode: z6.literal("git_overlay"), ...payloadBase })
4326
+ additional_roots: z7.array(SnapshotAdditionalRootSchema).default([]),
4327
+ payload: z7.object({ mode: z7.literal("git_overlay"), ...payloadBase })
4328
4328
  }).strict();
4329
- SnapshotManifestSchema = z6.union([
4329
+ SnapshotManifestSchema = z7.union([
4330
4330
  FullSnapshotManifestSchema,
4331
4331
  GitOverlaySnapshotManifestSchema
4332
4332
  ]);
4333
- SnapshotInstanceSchema = z6.object({
4334
- snapshot_id: z6.string().min(1),
4333
+ SnapshotInstanceSchema = z7.object({
4334
+ snapshot_id: z7.string().min(1),
4335
4335
  content_id: ContentIdSchema,
4336
- captured_at: z6.string().min(1)
4336
+ captured_at: z7.string().min(1)
4337
4337
  });
4338
4338
  }
4339
4339
  });
@@ -4723,21 +4723,48 @@ var init_dist4 = __esm({
4723
4723
  });
4724
4724
 
4725
4725
  // ../controller/dist/config.js
4726
- import { mkdirSync as mkdirSync5 } from "node:fs";
4727
- import { join as join25 } from "node:path";
4726
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "node:fs";
4727
+ import { isAbsolute as isAbsolute2, join as join25 } from "node:path";
4728
+ import { z as z8 } from "zod";
4728
4729
  function parseCsv2(raw) {
4729
4730
  if (!raw?.trim()) {
4730
4731
  return [];
4731
4732
  }
4732
4733
  return raw.split(",").map((part) => part.trim()).filter(Boolean);
4733
4734
  }
4734
- function parseGitAllowlist2(overrides) {
4735
- if (overrides) {
4736
- return overrides;
4735
+ function envSet2(key, env = process.env) {
4736
+ return env[key] !== void 0;
4737
+ }
4738
+ function parseEnvNumber2(key, raw) {
4739
+ const n = Number(raw);
4740
+ if (!Number.isFinite(n)) {
4741
+ throw new Error(`Invalid ${key}=${JSON.stringify(raw)}: expected a finite number`);
4742
+ }
4743
+ return n;
4744
+ }
4745
+ function parseEnvInt2(key, raw) {
4746
+ const n = parseEnvNumber2(key, raw);
4747
+ if (!Number.isInteger(n)) {
4748
+ throw new Error(`Invalid ${key}=${JSON.stringify(raw)}: expected an integer`);
4749
+ }
4750
+ return n;
4751
+ }
4752
+ function assertAbsoluteAllowlistPaths(paths, label) {
4753
+ return paths.map((path, index) => {
4754
+ const parsed = AbsoluteAllowlistPathSchema.safeParse(path);
4755
+ if (!parsed.success) {
4756
+ throw new Error(`Invalid ${label}[${index}]=${JSON.stringify(path)}: must be a non-empty absolute path`);
4757
+ }
4758
+ return parsed.data;
4759
+ });
4760
+ }
4761
+ function parseGitAllowlistFromEnv2(env = process.env) {
4762
+ if (!envSet2("RBO_GIT_ALLOWLIST_SCHEMES", env) && !envSet2("RBO_GIT_ALLOWLIST_HOSTS", env) && !envSet2("RBO_GIT_ALLOWLIST_PREFIXES", env)) {
4763
+ return void 0;
4737
4764
  }
4738
- const schemes = parseCsv2(process.env.RBO_GIT_ALLOWLIST_SCHEMES);
4739
- const hosts = parseCsv2(process.env.RBO_GIT_ALLOWLIST_HOSTS);
4740
- const prefixesRaw = process.env.RBO_GIT_ALLOWLIST_PREFIXES?.trim();
4765
+ const schemes = parseCsv2(env.RBO_GIT_ALLOWLIST_SCHEMES);
4766
+ const hosts = parseCsv2(env.RBO_GIT_ALLOWLIST_HOSTS);
4767
+ const prefixesRaw = env.RBO_GIT_ALLOWLIST_PREFIXES?.trim();
4741
4768
  let repository_prefixes;
4742
4769
  if (prefixesRaw) {
4743
4770
  try {
@@ -4756,34 +4783,124 @@ function parseGitAllowlist2(overrides) {
4756
4783
  ...repository_prefixes ? { repository_prefixes } : {}
4757
4784
  };
4758
4785
  }
4786
+ function mergeGitAllowlist2(file, fromEnv, override) {
4787
+ if (override) {
4788
+ return override;
4789
+ }
4790
+ if (fromEnv) {
4791
+ return fromEnv;
4792
+ }
4793
+ if (file) {
4794
+ return {
4795
+ schemes: file.schemes && file.schemes.length > 0 ? file.schemes : [...DEFAULT_GIT_ALLOWLIST_SCHEMES2],
4796
+ hosts: file.hosts && file.hosts.length > 0 ? file.hosts : [...DEFAULT_GIT_ALLOWLIST_HOSTS2],
4797
+ ...file.repository_prefixes ? { repository_prefixes: file.repository_prefixes } : {}
4798
+ };
4799
+ }
4800
+ return {
4801
+ schemes: [...DEFAULT_GIT_ALLOWLIST_SCHEMES2],
4802
+ hosts: [...DEFAULT_GIT_ALLOWLIST_HOSTS2]
4803
+ };
4804
+ }
4805
+ function defaultControllerConfigFile() {
4806
+ return {
4807
+ mcp_host: "127.0.0.1",
4808
+ mcp_port: 7410,
4809
+ agent_plane_port: 7411,
4810
+ controller_public_host: "127.0.0.1",
4811
+ data_plane_base_url: null,
4812
+ allowed_project_roots: [],
4813
+ allowed_artifact_destinations: [],
4814
+ git_allowlist: {
4815
+ schemes: [...DEFAULT_GIT_ALLOWLIST_SCHEMES2],
4816
+ hosts: [...DEFAULT_GIT_ALLOWLIST_HOSTS2]
4817
+ },
4818
+ local_max_concurrent_jobs: 1,
4819
+ disconnect_grace_seconds: 60,
4820
+ orphan_timeout_seconds: 300,
4821
+ reconcile_deadline_seconds: 120,
4822
+ allow_local_fallback: true,
4823
+ max_git_bundle_bytes: DEFAULT_MAX_GIT_BUNDLE_BYTES,
4824
+ local_fallback_max_host_cpu_percent: 80
4825
+ };
4826
+ }
4827
+ function resolveControllerConfigPath(dataDir) {
4828
+ return join25(dataDir, CONTROLLER_CONFIG_FILENAME);
4829
+ }
4830
+ function readControllerConfigFile(configPath) {
4831
+ if (!existsSync8(configPath)) {
4832
+ return void 0;
4833
+ }
4834
+ let raw;
4835
+ try {
4836
+ raw = JSON.parse(readFileSync7(configPath, "utf8"));
4837
+ } catch (error) {
4838
+ throw new Error(`Invalid controller config JSON at ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
4839
+ }
4840
+ const parsed = ControllerConfigFileSchema.safeParse(raw);
4841
+ if (!parsed.success) {
4842
+ throw new Error(`Invalid controller config at ${configPath}: ${parsed.error.message}`);
4843
+ }
4844
+ return parsed.data;
4845
+ }
4846
+ function writeDefaultControllerConfigFile(dataDir, options = {}) {
4847
+ mkdirSync5(dataDir, { recursive: true });
4848
+ const path = resolveControllerConfigPath(dataDir);
4849
+ if (existsSync8(path) && !options.force) {
4850
+ return { path, written: false };
4851
+ }
4852
+ const body = `${JSON.stringify(defaultControllerConfigFile(), null, 2)}
4853
+ `;
4854
+ writeFileSync5(path, body, "utf8");
4855
+ return { path, written: true };
4856
+ }
4759
4857
  function loadControllerConfig(overrides = {}) {
4760
- const dataDir = overrides.dataDir ?? resolveControllerDataDir();
4858
+ const { configPath: configPathOption, ...fieldOverrides } = overrides;
4859
+ const dataDir = fieldOverrides.dataDir ?? resolveControllerDataDir();
4860
+ const configPath = configPathOption === void 0 ? resolveControllerConfigPath(dataDir) : configPathOption;
4861
+ const file = configPath === null ? void 0 : readControllerConfigFile(configPath);
4862
+ const mcpHost = fieldOverrides.mcpHost ?? (envSet2("RBO_MCP_HOST") ? process.env.RBO_MCP_HOST : void 0) ?? file?.mcp_host ?? "127.0.0.1";
4863
+ const mcpPort = fieldOverrides.mcpPort ?? (envSet2("RBO_MCP_PORT") ? parseEnvInt2("RBO_MCP_PORT", process.env.RBO_MCP_PORT) : void 0) ?? file?.mcp_port ?? 7410;
4864
+ const agentPlanePort = fieldOverrides.agentPlanePort ?? (envSet2("RBO_AGENT_PORT") ? parseEnvInt2("RBO_AGENT_PORT", process.env.RBO_AGENT_PORT) : void 0) ?? file?.agent_plane_port ?? 7411;
4865
+ 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";
4866
+ let dataPlaneBaseUrl = fieldOverrides.dataPlaneBaseUrl;
4867
+ if (dataPlaneBaseUrl === void 0) {
4868
+ if (envSet2("RBO_DATA_PLANE_BASE_URL")) {
4869
+ dataPlaneBaseUrl = process.env.RBO_DATA_PLANE_BASE_URL;
4870
+ } else if (file?.data_plane_base_url !== void 0) {
4871
+ dataPlaneBaseUrl = file.data_plane_base_url ?? void 0;
4872
+ }
4873
+ }
4874
+ 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");
4875
+ 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");
4876
+ const allowLocalFallback = fieldOverrides.allowLocalFallback ?? (envSet2("RBO_ALLOW_LOCAL_FALLBACK") ? process.env.RBO_ALLOW_LOCAL_FALLBACK !== "false" : void 0) ?? file?.allow_local_fallback ?? true;
4877
+ 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
4878
  return {
4762
- mcpHost: overrides.mcpHost ?? process.env.RBO_MCP_HOST ?? "127.0.0.1",
4763
- mcpPort: overrides.mcpPort ?? Number(process.env.RBO_MCP_PORT ?? 7410),
4764
- agentPlanePort: overrides.agentPlanePort ?? Number(process.env.RBO_AGENT_PORT ?? 7411),
4765
- controllerPublicHost: overrides.controllerPublicHost ?? process.env.RBO_CONTROLLER_PUBLIC_HOST ?? "127.0.0.1",
4766
- dataPlaneBaseUrl: overrides.dataPlaneBaseUrl ?? process.env.RBO_DATA_PLANE_BASE_URL,
4879
+ mcpHost,
4880
+ mcpPort,
4881
+ agentPlanePort,
4882
+ controllerPublicHost,
4883
+ dataPlaneBaseUrl,
4767
4884
  dataDir,
4768
- databasePath: overrides.databasePath ?? join25(dataDir, "controller.db"),
4769
- allowedProjectRoots: overrides.allowedProjectRoots ?? parseCsv2(process.env.RBO_ALLOWED_PROJECT_ROOTS),
4770
- allowedArtifactDestinations: overrides.allowedArtifactDestinations ?? parseCsv2(process.env.RBO_ALLOWED_ARTIFACT_DESTINATIONS),
4771
- gitAllowlist: parseGitAllowlist2(overrides.gitAllowlist),
4885
+ databasePath: fieldOverrides.databasePath ?? join25(dataDir, "controller.db"),
4886
+ allowedProjectRoots,
4887
+ allowedArtifactDestinations,
4888
+ gitAllowlist: mergeGitAllowlist2(file?.git_allowlist, parseGitAllowlistFromEnv2(), fieldOverrides.gitAllowlist),
4772
4889
  localExecutor: {
4773
- maxConcurrentJobs: overrides.localExecutor?.maxConcurrentJobs ?? Number(process.env.RBO_LOCAL_MAX_CONCURRENT_JOBS ?? 1)
4890
+ 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
4891
  },
4775
- disconnectGraceSeconds: overrides.disconnectGraceSeconds ?? Number(process.env.RBO_DISCONNECT_GRACE_SECONDS ?? 60),
4776
- orphanTimeoutSeconds: overrides.orphanTimeoutSeconds ?? Number(process.env.RBO_ORPHAN_TIMEOUT_SECONDS ?? 300),
4777
- reconcileDeadlineSeconds: overrides.reconcileDeadlineSeconds ?? Number(process.env.RBO_RECONCILE_DEADLINE_SECONDS ?? 120),
4778
- allowLocalFallback: overrides.allowLocalFallback ?? process.env.RBO_ALLOW_LOCAL_FALLBACK !== "false",
4779
- maxGitBundleBytes: overrides.maxGitBundleBytes ?? Number(process.env.RBO_MAX_GIT_BUNDLE_BYTES ?? DEFAULT_MAX_GIT_BUNDLE_BYTES),
4780
- maxHostCpuBusyFraction: overrides.maxHostCpuBusyFraction ?? Number(process.env.RBO_LOCAL_FALLBACK_MAX_HOST_CPU_PERCENT ?? 80) / 100
4892
+ 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,
4893
+ 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,
4894
+ 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,
4895
+ allowLocalFallback,
4896
+ 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,
4897
+ maxHostCpuBusyFraction
4781
4898
  };
4782
4899
  }
4783
4900
  function ensureDataDir(config) {
4784
4901
  mkdirSync5(config.dataDir, { recursive: true });
4785
4902
  }
4786
- var DEFAULT_MAX_GIT_BUNDLE_BYTES, DEFAULT_GIT_ALLOWLIST_SCHEMES2, DEFAULT_GIT_ALLOWLIST_HOSTS2;
4903
+ var DEFAULT_MAX_GIT_BUNDLE_BYTES, DEFAULT_GIT_ALLOWLIST_SCHEMES2, DEFAULT_GIT_ALLOWLIST_HOSTS2, CONTROLLER_CONFIG_FILENAME, GitAllowlistFileSchema2, AbsoluteAllowlistPathSchema, ControllerConfigFileSchema;
4787
4904
  var init_config = __esm({
4788
4905
  "../controller/dist/config.js"() {
4789
4906
  "use strict";
@@ -4791,6 +4908,32 @@ var init_config = __esm({
4791
4908
  DEFAULT_MAX_GIT_BUNDLE_BYTES = 512 * 1024 * 1024;
4792
4909
  DEFAULT_GIT_ALLOWLIST_SCHEMES2 = ["https", "ssh"];
4793
4910
  DEFAULT_GIT_ALLOWLIST_HOSTS2 = ["github.com"];
4911
+ CONTROLLER_CONFIG_FILENAME = "controller.json";
4912
+ GitAllowlistFileSchema2 = z8.object({
4913
+ schemes: z8.array(z8.string().min(1)).optional(),
4914
+ hosts: z8.array(z8.string().min(1)).optional(),
4915
+ repository_prefixes: z8.array(z8.string().min(1)).optional()
4916
+ });
4917
+ AbsoluteAllowlistPathSchema = z8.string().refine((value) => value.length > 0 && value === value.trim() && isAbsolute2(value), {
4918
+ message: 'must be a non-empty absolute path (relative paths, ".", and whitespace are rejected)'
4919
+ });
4920
+ ControllerConfigFileSchema = z8.object({
4921
+ mcp_host: z8.string().min(1).optional(),
4922
+ mcp_port: z8.number().int().positive().optional(),
4923
+ agent_plane_port: z8.number().int().positive().optional(),
4924
+ controller_public_host: z8.string().min(1).optional(),
4925
+ data_plane_base_url: z8.string().min(1).nullable().optional(),
4926
+ allowed_project_roots: z8.array(AbsoluteAllowlistPathSchema).optional(),
4927
+ allowed_artifact_destinations: z8.array(AbsoluteAllowlistPathSchema).optional(),
4928
+ git_allowlist: GitAllowlistFileSchema2.optional(),
4929
+ local_max_concurrent_jobs: z8.number().int().positive().optional(),
4930
+ disconnect_grace_seconds: z8.number().int().nonnegative().optional(),
4931
+ orphan_timeout_seconds: z8.number().int().nonnegative().optional(),
4932
+ reconcile_deadline_seconds: z8.number().int().nonnegative().optional(),
4933
+ allow_local_fallback: z8.boolean().optional(),
4934
+ max_git_bundle_bytes: z8.number().int().positive().optional(),
4935
+ local_fallback_max_host_cpu_percent: z8.number().min(0).max(100).optional()
4936
+ }).strict();
4794
4937
  }
4795
4938
  });
4796
4939
 
@@ -5028,7 +5171,7 @@ var init_database = __esm({
5028
5171
  });
5029
5172
 
5030
5173
  // ../controller/dist/execution/artifacts.js
5031
- import { appendFile as appendFile3, mkdir as mkdir12, writeFile as writeFile12 } from "node:fs/promises";
5174
+ import { appendFile as appendFile3, mkdir as mkdir11, writeFile as writeFile11 } from "node:fs/promises";
5032
5175
  import { join as join26 } from "node:path";
5033
5176
  async function persistCollectedArtifacts(input) {
5034
5177
  const results = [];
@@ -5073,7 +5216,7 @@ async function materializeArtifactToDestination(input) {
5073
5216
  if (!allowed) {
5074
5217
  throw new Error("Destination path is outside allowed artifact destinations");
5075
5218
  }
5076
- const { access: access4, readFile: readFile20, rename: rename6 } = await import("node:fs/promises");
5219
+ const { access: access4, readFile: readFile19, rename: rename6 } = await import("node:fs/promises");
5077
5220
  try {
5078
5221
  await access4(input.destinationPath);
5079
5222
  if (!input.overwrite) {
@@ -5084,20 +5227,20 @@ async function materializeArtifactToDestination(input) {
5084
5227
  throw error;
5085
5228
  }
5086
5229
  }
5087
- const content = await readFile20(row.path);
5230
+ const content = await readFile19(row.path);
5088
5231
  if (sha256(content) !== row.sha256) {
5089
5232
  throw new Error("Stored artifact hash mismatch");
5090
5233
  }
5091
5234
  const tempPath = `${input.destinationPath}.rbo.tmp`;
5092
- await writeFile12(tempPath, content);
5093
- const writtenHash = sha256(await readFile20(tempPath));
5235
+ await writeFile11(tempPath, content);
5236
+ const writtenHash = sha256(await readFile19(tempPath));
5094
5237
  if (writtenHash !== row.sha256) {
5095
5238
  throw new Error("Temporary artifact hash mismatch");
5096
5239
  }
5097
5240
  await rename6(tempPath, input.destinationPath);
5098
5241
  if (input.dataDir) {
5099
5242
  const auditDir = join26(input.dataDir, "audit");
5100
- await mkdir12(auditDir, { recursive: true });
5243
+ await mkdir11(auditDir, { recursive: true });
5101
5244
  const auditLine = JSON.stringify({
5102
5245
  type: "artifact_materialize",
5103
5246
  created_at: nowIso(),
@@ -5284,7 +5427,7 @@ var init_lifecycle = __esm({
5284
5427
  });
5285
5428
 
5286
5429
  // ../controller/dist/execution/runner.js
5287
- import { mkdir as mkdir13, readFile as readFile15, rm as rm10, writeFile as writeFile13 } from "node:fs/promises";
5430
+ import { mkdir as mkdir12, readFile as readFile14, rm as rm10, writeFile as writeFile12 } from "node:fs/promises";
5288
5431
  import { dirname as dirname7, join as join27 } from "node:path";
5289
5432
  function getLocalRunningJobsCount() {
5290
5433
  return admissionActive;
@@ -5410,9 +5553,9 @@ async function runLocalJob(ctx, jobId) {
5410
5553
  const persistentLogs = attemptLogDir(ctx.dataDir, attempt.id);
5411
5554
  const controlDir = attemptControlDir(ctx.dataDir, attempt.id);
5412
5555
  const artifactsDir = attemptArtifactsDir(ctx.dataDir, attempt.id);
5413
- await mkdir13(artifactsDir, { recursive: true });
5414
- await mkdir13(persistentLogs, { recursive: true });
5415
- await mkdir13(controlDir, { recursive: true });
5556
+ await mkdir12(artifactsDir, { recursive: true });
5557
+ await mkdir12(persistentLogs, { recursive: true });
5558
+ await mkdir12(controlDir, { recursive: true });
5416
5559
  let timedOut = false;
5417
5560
  let cancelled = false;
5418
5561
  let durationComplete = false;
@@ -5433,7 +5576,7 @@ async function runLocalJob(ctx, jobId) {
5433
5576
  if (!snapshotRow?.payload_path) {
5434
5577
  throw new RboError("internal", "Job is missing snapshot payload");
5435
5578
  }
5436
- const manifest = JSON.parse(await readFile15(snapshotRow.manifest_path, "utf8"));
5579
+ const manifest = JSON.parse(await readFile14(snapshotRow.manifest_path, "utf8"));
5437
5580
  const materialized = await materializeFullSnapshot({
5438
5581
  manifest,
5439
5582
  archivePath: snapshotRow.payload_path,
@@ -5451,7 +5594,7 @@ async function runLocalJob(ctx, jobId) {
5451
5594
  });
5452
5595
  const warningsPath = join27(dirname7(snapshotRow.manifest_path), "secret-warnings.json");
5453
5596
  try {
5454
- const warnings = JSON.parse(await readFile15(warningsPath, "utf8"));
5597
+ const warnings = JSON.parse(await readFile14(warningsPath, "utf8"));
5455
5598
  for (const warning of warnings) {
5456
5599
  await emitJobEvent(ctx, logs, {
5457
5600
  type: "secret_warning",
@@ -5698,9 +5841,9 @@ async function captureAndPersistSnapshot(ctx, jobId, request) {
5698
5841
  contentStorageDir: storageDir
5699
5842
  });
5700
5843
  const manifestPath = join27(storageDir, "manifest.json");
5701
- await writeFile13(manifestPath, JSON.stringify(captured.manifest, null, 2));
5702
- await writeFile13(join27(storageDir, "secret-warnings.json"), JSON.stringify(captured.secretWarnings));
5703
- await writeFile13(join27(storageDir, "git-source-requirements.json"), JSON.stringify(captured.gitSourceRequirements));
5844
+ await writeFile12(manifestPath, JSON.stringify(captured.manifest, null, 2));
5845
+ await writeFile12(join27(storageDir, "secret-warnings.json"), JSON.stringify(captured.secretWarnings));
5846
+ await writeFile12(join27(storageDir, "git-source-requirements.json"), JSON.stringify(captured.gitSourceRequirements));
5704
5847
  persistSnapshot(ctx.db, {
5705
5848
  snapshotId: captured.instance.snapshot_id,
5706
5849
  contentId: captured.manifest.content_id,
@@ -5746,7 +5889,7 @@ function mergeGitSourceToolRequirements(request, gitSourceRequirements) {
5746
5889
  }
5747
5890
  async function readGitSourceRequirements(dataDir, jobId) {
5748
5891
  try {
5749
- const raw = await readFile15(join27(dataDir, "snapshots", jobId, "git-source-requirements.json"), "utf8");
5892
+ const raw = await readFile14(join27(dataDir, "snapshots", jobId, "git-source-requirements.json"), "utf8");
5750
5893
  const parsed = JSON.parse(raw);
5751
5894
  return {
5752
5895
  submodules: parsed.submodules === true,
@@ -5838,7 +5981,7 @@ var init_data_tokens = __esm({
5838
5981
  // ../controller/dist/http/data-plane.js
5839
5982
  import { createHash as createHash7 } from "node:crypto";
5840
5983
  import { createReadStream as createReadStream2, createWriteStream as createWriteStream2 } from "node:fs";
5841
- import { access as access3, mkdir as mkdir14, readFile as readFile16, rename as rename5, rm as rm11, stat as stat8 } from "node:fs/promises";
5984
+ import { access as access3, mkdir as mkdir13, readFile as readFile15, rename as rename5, rm as rm11, stat as stat8 } from "node:fs/promises";
5842
5985
  import { dirname as dirname8, join as join28 } from "node:path";
5843
5986
  function artifactUploadStreamLimit(declaredSizeBytes, globalCapBytes = DEFAULT_ARTIFACT_CAP_BYTES) {
5844
5987
  return Math.min(globalCapBytes, declaredSizeBytes);
@@ -5868,7 +6011,7 @@ async function filterMissingArtifacts(dataDir, attemptId, artifacts) {
5868
6011
  for (const art of artifacts) {
5869
6012
  const destPath = join28(artifactsDir, art.logical_name);
5870
6013
  try {
5871
- const buf = await readFile16(destPath);
6014
+ const buf = await readFile15(destPath);
5872
6015
  const hash = createHash7("sha256").update(buf).digest("hex");
5873
6016
  if (hash === art.sha256 && buf.length === art.size_bytes) {
5874
6017
  continue;
@@ -5988,7 +6131,7 @@ async function handleDataPlaneRequest(req, res, options) {
5988
6131
  const transferStats = await stat8(transferSnapshot);
5989
6132
  payloadPath = transferSnapshot;
5990
6133
  sizeBytes = transferStats.size;
5991
- const transferData = await readFile16(transferSnapshot);
6134
+ const transferData = await readFile15(transferSnapshot);
5992
6135
  sha2562 = createHash7("sha256").update(transferData).digest("hex");
5993
6136
  } catch {
5994
6137
  }
@@ -6087,8 +6230,8 @@ async function handleDataPlaneRequest(req, res, options) {
6087
6230
  return true;
6088
6231
  }
6089
6232
  const partPath = join28(artifactsDir, `.upload-${createHash7("sha256").update(logicalName).digest("hex").slice(0, 16)}.part`);
6090
- await mkdir14(dirname8(destPath), { recursive: true });
6091
- await mkdir14(artifactsDir, { recursive: true });
6233
+ await mkdir13(dirname8(destPath), { recursive: true });
6234
+ await mkdir13(artifactsDir, { recursive: true });
6092
6235
  const hasher = createHash7("sha256");
6093
6236
  const writeStream = createWriteStream2(partPath);
6094
6237
  let sizeCounter = 0;
@@ -6297,7 +6440,7 @@ async function handleDataPlaneRequest(req, res, options) {
6297
6440
  }
6298
6441
  const bundlePath = join28(attemptTransferDir(options.dataDir, attemptId), "bundle.gitbundle");
6299
6442
  try {
6300
- const data = await readFile16(bundlePath);
6443
+ const data = await readFile15(bundlePath);
6301
6444
  const sha2562 = createHash7("sha256").update(data).digest("hex");
6302
6445
  res.writeHead(200, {
6303
6446
  "content-type": "application/octet-stream",
@@ -6696,8 +6839,8 @@ var init_coordinator = __esm({
6696
6839
  // ../controller/dist/execution/remote-execution.js
6697
6840
  import { execFile as execFile8 } from "node:child_process";
6698
6841
  import { createHash as createHash8 } from "node:crypto";
6699
- import { readFileSync as readFileSync6 } from "node:fs";
6700
- import { mkdir as mkdir15, readFile as readFile17, rm as rm12, stat as stat9, writeFile as writeFile14 } from "node:fs/promises";
6842
+ import { readFileSync as readFileSync8 } from "node:fs";
6843
+ import { mkdir as mkdir14, readFile as readFile16, rm as rm12, stat as stat9, writeFile as writeFile13 } from "node:fs/promises";
6701
6844
  import { dirname as dirname9, join as join29 } from "node:path";
6702
6845
  import { promisify as promisify8 } from "node:util";
6703
6846
  function snapshotManifestMode(manifest) {
@@ -6804,7 +6947,7 @@ function failAttemptPrepare(opts, attemptId, jobId, category, message) {
6804
6947
  });
6805
6948
  }
6806
6949
  async function createGitBundle(projectRoot, baseCommit, bundlePath, maxBytes = DEFAULT_MAX_GIT_BUNDLE_BYTES) {
6807
- await mkdir15(dirname9(bundlePath), { recursive: true });
6950
+ await mkdir14(dirname9(bundlePath), { recursive: true });
6808
6951
  const { stdout: head } = await execFileAsync8("git", ["rev-parse", "HEAD"], {
6809
6952
  cwd: projectRoot,
6810
6953
  windowsHide: true
@@ -6833,7 +6976,7 @@ async function createGitBundle(projectRoot, baseCommit, bundlePath, maxBytes = D
6833
6976
  await rm12(bundlePath, { force: true });
6834
6977
  throw new GitBundleSizeExceededError(sizeBytes, maxBytes);
6835
6978
  }
6836
- const data = await readFile17(bundlePath);
6979
+ const data = await readFile16(bundlePath);
6837
6980
  return {
6838
6981
  sizeBytes: data.length,
6839
6982
  sha256: createHash8("sha256").update(data).digest("hex")
@@ -6849,9 +6992,9 @@ async function ensureFullFallbackArchive(opts, attemptId, jobId) {
6849
6992
  const manifestPath = join29(transferDir, "snapshot.manifest.json");
6850
6993
  try {
6851
6994
  const existing = await stat9(archivePath);
6852
- const data = await readFile17(archivePath);
6995
+ const data = await readFile16(archivePath);
6853
6996
  const sha2562 = createHash8("sha256").update(data).digest("hex");
6854
- const manifestRaw = await readFile17(manifestPath, "utf8");
6997
+ const manifestRaw = await readFile16(manifestPath, "utf8");
6855
6998
  const manifest = JSON.parse(manifestRaw);
6856
6999
  if (manifest.payload?.sha256 === sha2562 && manifest.payload?.size === existing.size) {
6857
7000
  return {
@@ -6863,7 +7006,7 @@ async function ensureFullFallbackArchive(opts, attemptId, jobId) {
6863
7006
  }
6864
7007
  } catch {
6865
7008
  }
6866
- await mkdir15(transferDir, { recursive: true });
7009
+ await mkdir14(transferDir, { recursive: true });
6867
7010
  const captured = await captureFullSnapshot({
6868
7011
  projectRoot: request.source.project_root,
6869
7012
  allowedProjectRoots: opts.allowedProjectRoots ?? [request.source.project_root],
@@ -6876,8 +7019,8 @@ async function ensureFullFallbackArchive(opts, attemptId, jobId) {
6876
7019
  additionalRoots: request.source.additional_roots,
6877
7020
  contentStorageDir: transferDir
6878
7021
  });
6879
- await writeFile14(archivePath, await readFile17(captured.archivePath));
6880
- await writeFile14(manifestPath, JSON.stringify(captured.manifest, null, 2));
7022
+ await writeFile13(archivePath, await readFile16(captured.archivePath));
7023
+ await writeFile13(manifestPath, JSON.stringify(captured.manifest, null, 2));
6881
7024
  return {
6882
7025
  archivePath,
6883
7026
  sizeBytes: captured.manifest.payload.size,
@@ -7023,7 +7166,7 @@ function handleRemoteLeaseAccept(opts, agentId, payload) {
7023
7166
  let manifest;
7024
7167
  if (snapshotRow.manifest_path) {
7025
7168
  try {
7026
- manifest = JSON.parse(readFileSync6(snapshotRow.manifest_path, "utf8"));
7169
+ manifest = JSON.parse(readFileSync8(snapshotRow.manifest_path, "utf8"));
7027
7170
  } catch {
7028
7171
  }
7029
7172
  }
@@ -7050,7 +7193,7 @@ async function handleRemoteSourceNeed(opts, agentId, payload) {
7050
7193
  }
7051
7194
  let manifest;
7052
7195
  try {
7053
- manifest = JSON.parse(readFileSync6(snapshotRow.manifest_path, "utf8"));
7196
+ manifest = JSON.parse(readFileSync8(snapshotRow.manifest_path, "utf8"));
7054
7197
  } catch {
7055
7198
  failAttemptPrepare(opts, attempt.id, attempt.job_id, "materialization", "Cannot create bundle: manifest unreadable");
7056
7199
  return;
@@ -7097,7 +7240,7 @@ async function handleRemoteSourceNeed(opts, agentId, payload) {
7097
7240
  }
7098
7241
  let manifest;
7099
7242
  try {
7100
- manifest = JSON.parse(readFileSync6(snapshotRow.manifest_path, "utf8"));
7243
+ manifest = JSON.parse(readFileSync8(snapshotRow.manifest_path, "utf8"));
7101
7244
  } catch {
7102
7245
  manifest = void 0;
7103
7246
  }
@@ -7714,7 +7857,7 @@ function selectAgentForJob(agents, request, options = {}) {
7714
7857
  const busyAgentRunningJobs = [];
7715
7858
  for (const candidate of agents) {
7716
7859
  const caps = candidate.capabilities;
7717
- const effectiveCapacity = Math.min(caps.execution.max_jobs, 1);
7860
+ const effectiveCapacity = caps.execution.max_jobs;
7718
7861
  if (effectiveCapacity <= 0 || candidate.activeJobsCount >= effectiveCapacity) {
7719
7862
  if (effectiveCapacity > 0) {
7720
7863
  busyAgentRunningJobs.push(candidate.activeJobsCount);
@@ -8303,22 +8446,18 @@ var init_submit = __esm({
8303
8446
 
8304
8447
  // src/main.ts
8305
8448
  init_dist();
8306
- import { readFile as readFile19 } from "node:fs/promises";
8449
+ import { readFile as readFile18 } from "node:fs/promises";
8307
8450
 
8308
8451
  // src/commands/agent.ts
8309
- import { existsSync as existsSync6 } from "node:fs";
8310
- import { mkdir as mkdir11, readFile as readFile14, writeFile as writeFile11 } from "node:fs/promises";
8452
+ import { existsSync as existsSync7 } from "node:fs";
8311
8453
  import { join as join24 } from "node:path";
8312
8454
 
8313
- // ../agent/dist/run.js
8455
+ // ../agent/dist/config.js
8456
+ init_dist2();
8314
8457
  init_dist();
8315
-
8316
- // ../agent/dist/capabilities/probe.js
8317
- import { execFile as execFile2 } from "node:child_process";
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";
8458
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
8459
+ import { dirname as dirname2, join as join7 } from "node:path";
8460
+ import { z as z6 } from "zod";
8322
8461
 
8323
8462
  // ../agent/dist/build-cache/index.js
8324
8463
  import { access, readdir as readdir3 } from "node:fs/promises";
@@ -9021,12 +9160,6 @@ function resolveBuildCachesDir(stateDir) {
9021
9160
  return join5(stateDir, "build-caches");
9022
9161
  }
9023
9162
 
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
9163
  // ../agent/dist/repos/mirror.js
9031
9164
  init_dist();
9032
9165
  import { execFile } from "node:child_process";
@@ -9383,19 +9516,77 @@ var DEFAULT_LOG_SPOOL_MAX_BYTES = 536870912;
9383
9516
  var DEFAULT_LOG_SEND_QUEUE_MAX = 64;
9384
9517
  var DEFAULT_GIT_ALLOWLIST_SCHEMES = ["https", "ssh"];
9385
9518
  var DEFAULT_GIT_ALLOWLIST_HOSTS = ["github.com"];
9519
+ var AGENT_CONFIG_FILENAME = "agent.json";
9520
+ var AGENT_CONFIG_SCHEMA_VERSION = 1;
9521
+ var RiskLevelSchema2 = z6.enum(["safe", "normal", "destructive", "hardware"]);
9522
+ var GitAllowlistFileSchema = z6.object({
9523
+ schemes: z6.array(z6.string().min(1)).optional(),
9524
+ hosts: z6.array(z6.string().min(1)).optional(),
9525
+ repository_prefixes: z6.array(z6.string().min(1)).optional()
9526
+ });
9527
+ var RepoCacheFileSchema = z6.object({
9528
+ max_size_gb: z6.number().positive().optional(),
9529
+ min_free_disk_gb: z6.number().nonnegative().optional(),
9530
+ retention_days: z6.number().nonnegative().optional()
9531
+ });
9532
+ var BuildCacheFileSchema = z6.object({
9533
+ enabled_kinds: z6.array(BuildCacheKindSchema).optional(),
9534
+ max_size_gb: z6.number().positive().optional(),
9535
+ min_free_disk_gb: z6.number().nonnegative().optional(),
9536
+ retention_days: z6.number().nonnegative().optional(),
9537
+ allow_read_risk_levels: z6.array(RiskLevelSchema2).optional(),
9538
+ allow_write_risk_levels: z6.array(RiskLevelSchema2).optional()
9539
+ });
9540
+ var AgentConfigFileSchema = z6.object({
9541
+ schema_version: z6.number().int().positive().optional(),
9542
+ initialized_at: z6.string().min(1).optional(),
9543
+ controller_url: z6.string().optional(),
9544
+ controller_fingerprint: z6.string().optional(),
9545
+ display_name: z6.string().min(1).optional(),
9546
+ max_jobs: z6.number().int().positive().optional(),
9547
+ repo_cache_dir: z6.string().min(1).nullable().optional(),
9548
+ secret_map: z6.record(z6.string()).nullable().optional(),
9549
+ git_allowlist: GitAllowlistFileSchema.optional(),
9550
+ repo_cache: RepoCacheFileSchema.optional(),
9551
+ build_cache: BuildCacheFileSchema.optional(),
9552
+ log_spool_max_bytes: z6.number().int().positive().optional(),
9553
+ log_send_queue_max: z6.number().int().positive().optional(),
9554
+ disconnect_grace_seconds: z6.number().int().nonnegative().optional(),
9555
+ orphan_timeout_seconds: z6.number().int().nonnegative().optional(),
9556
+ reconcile_deadline_seconds: z6.number().int().nonnegative().optional(),
9557
+ disk_min_free_bytes: z6.number().int().nonnegative().nullable().optional(),
9558
+ configured_priority: z6.number().nullable().optional()
9559
+ }).strict();
9386
9560
  function parseCsv(raw) {
9387
9561
  if (!raw?.trim()) {
9388
9562
  return [];
9389
9563
  }
9390
9564
  return raw.split(",").map((part) => part.trim()).filter(Boolean);
9391
9565
  }
9392
- function parseGitAllowlist(overrides) {
9393
- if (overrides) {
9394
- return overrides;
9566
+ function envSet(key, env = process.env) {
9567
+ return env[key] !== void 0;
9568
+ }
9569
+ function parseEnvNumber(key, raw) {
9570
+ const n = Number(raw);
9571
+ if (!Number.isFinite(n)) {
9572
+ throw new Error(`Invalid ${key}=${JSON.stringify(raw)}: expected a finite number`);
9573
+ }
9574
+ return n;
9575
+ }
9576
+ function parseEnvInt(key, raw) {
9577
+ const n = parseEnvNumber(key, raw);
9578
+ if (!Number.isInteger(n)) {
9579
+ throw new Error(`Invalid ${key}=${JSON.stringify(raw)}: expected an integer`);
9580
+ }
9581
+ return n;
9582
+ }
9583
+ function parseGitAllowlistFromEnv(env = process.env) {
9584
+ if (!envSet("RBO_GIT_ALLOWLIST_SCHEMES", env) && !envSet("RBO_GIT_ALLOWLIST_HOSTS", env) && !envSet("RBO_GIT_ALLOWLIST_PREFIXES", env)) {
9585
+ return void 0;
9395
9586
  }
9396
- const schemes = parseCsv(process.env.RBO_GIT_ALLOWLIST_SCHEMES);
9397
- const hosts = parseCsv(process.env.RBO_GIT_ALLOWLIST_HOSTS);
9398
- const prefixesRaw = process.env.RBO_GIT_ALLOWLIST_PREFIXES?.trim();
9587
+ const schemes = parseCsv(env.RBO_GIT_ALLOWLIST_SCHEMES);
9588
+ const hosts = parseCsv(env.RBO_GIT_ALLOWLIST_HOSTS);
9589
+ const prefixesRaw = env.RBO_GIT_ALLOWLIST_PREFIXES?.trim();
9399
9590
  let repository_prefixes;
9400
9591
  if (prefixesRaw) {
9401
9592
  try {
@@ -9414,17 +9605,23 @@ function parseGitAllowlist(overrides) {
9414
9605
  ...repository_prefixes ? { repository_prefixes } : {}
9415
9606
  };
9416
9607
  }
9417
- function parseRepoCache(overrides) {
9418
- if (overrides) {
9419
- return overrides;
9608
+ function mergeGitAllowlist(file, fromEnv, override) {
9609
+ if (override) {
9610
+ return override;
9611
+ }
9612
+ if (fromEnv) {
9613
+ return fromEnv;
9614
+ }
9615
+ if (file) {
9616
+ return {
9617
+ schemes: file.schemes && file.schemes.length > 0 ? file.schemes : [...DEFAULT_GIT_ALLOWLIST_SCHEMES],
9618
+ hosts: file.hosts && file.hosts.length > 0 ? file.hosts : [...DEFAULT_GIT_ALLOWLIST_HOSTS],
9619
+ ...file.repository_prefixes ? { repository_prefixes: file.repository_prefixes } : {}
9620
+ };
9420
9621
  }
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
9622
  return {
9425
- max_size_gb: maxSize,
9426
- min_free_disk_gb: minFree,
9427
- retention_days: retention
9623
+ schemes: [...DEFAULT_GIT_ALLOWLIST_SCHEMES],
9624
+ hosts: [...DEFAULT_GIT_ALLOWLIST_HOSTS]
9428
9625
  };
9429
9626
  }
9430
9627
  function parseBuildCacheKinds(raw) {
@@ -9455,23 +9652,30 @@ function parseRiskLevels(raw) {
9455
9652
  }
9456
9653
  return levels;
9457
9654
  }
9458
- function parseBuildCache(overrides) {
9459
- if (overrides) {
9460
- return overrides;
9655
+ function mergeRepoCache(file, override) {
9656
+ if (override) {
9657
+ return override;
9658
+ }
9659
+ 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;
9660
+ 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;
9661
+ 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;
9662
+ return {
9663
+ max_size_gb: maxSize,
9664
+ min_free_disk_gb: minFree,
9665
+ retention_days: retention
9666
+ };
9667
+ }
9668
+ function mergeBuildCache(file, override) {
9669
+ if (override) {
9670
+ return override;
9461
9671
  }
9462
9672
  return {
9463
- enabledKinds: parseBuildCacheKinds(process.env.RBO_BUILD_CACHE_ENABLED_KINDS) ?? [
9464
- ...DEFAULT_BUILD_CACHE_CONFIG.enabledKinds
9465
- ],
9466
- maxSizeGb: Number(process.env.RBO_BUILD_CACHE_MAX_SIZE_GB ?? DEFAULT_BUILD_CACHE_CONFIG.maxSizeGb),
9467
- minFreeDiskGb: Number(process.env.RBO_BUILD_CACHE_MIN_FREE_DISK_GB ?? DEFAULT_BUILD_CACHE_CONFIG.minFreeDiskGb),
9468
- retentionDays: Number(process.env.RBO_BUILD_CACHE_RETENTION_DAYS ?? DEFAULT_BUILD_CACHE_CONFIG.retentionDays),
9469
- allowReadRiskLevels: parseRiskLevels(process.env.RBO_BUILD_CACHE_ALLOW_READ_RISKS) ?? [
9470
- ...DEFAULT_BUILD_CACHE_CONFIG.allowReadRiskLevels
9471
- ],
9472
- allowWriteRiskLevels: parseRiskLevels(process.env.RBO_BUILD_CACHE_ALLOW_WRITE_RISKS) ?? [
9473
- ...DEFAULT_BUILD_CACHE_CONFIG.allowWriteRiskLevels
9474
- ]
9673
+ 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],
9674
+ 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,
9675
+ 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,
9676
+ 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,
9677
+ 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],
9678
+ 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
9679
  };
9476
9680
  }
9477
9681
  function parseSecretMap(raw) {
@@ -9495,50 +9699,157 @@ function parseSecretMap(raw) {
9495
9699
  throw new Error(`Invalid RBO_SECRET_MAP: ${error instanceof Error ? error.message : String(error)}`);
9496
9700
  }
9497
9701
  }
9702
+ function defaultAgentConfigFile(options = {}) {
9703
+ return {
9704
+ schema_version: AGENT_CONFIG_SCHEMA_VERSION,
9705
+ initialized_at: options.initializedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
9706
+ controller_url: "",
9707
+ controller_fingerprint: "",
9708
+ display_name: "rbo-agent",
9709
+ max_jobs: 1,
9710
+ repo_cache_dir: null,
9711
+ secret_map: null,
9712
+ git_allowlist: {
9713
+ schemes: [...DEFAULT_GIT_ALLOWLIST_SCHEMES],
9714
+ hosts: [...DEFAULT_GIT_ALLOWLIST_HOSTS]
9715
+ },
9716
+ repo_cache: {
9717
+ max_size_gb: DEFAULT_REPO_CACHE_CONFIG.max_size_gb,
9718
+ min_free_disk_gb: DEFAULT_REPO_CACHE_CONFIG.min_free_disk_gb,
9719
+ retention_days: DEFAULT_REPO_CACHE_CONFIG.retention_days
9720
+ },
9721
+ build_cache: {
9722
+ enabled_kinds: [...DEFAULT_BUILD_CACHE_CONFIG.enabledKinds],
9723
+ max_size_gb: DEFAULT_BUILD_CACHE_CONFIG.maxSizeGb,
9724
+ min_free_disk_gb: DEFAULT_BUILD_CACHE_CONFIG.minFreeDiskGb,
9725
+ retention_days: DEFAULT_BUILD_CACHE_CONFIG.retentionDays,
9726
+ allow_read_risk_levels: [...DEFAULT_BUILD_CACHE_CONFIG.allowReadRiskLevels],
9727
+ allow_write_risk_levels: [...DEFAULT_BUILD_CACHE_CONFIG.allowWriteRiskLevels]
9728
+ },
9729
+ log_spool_max_bytes: DEFAULT_LOG_SPOOL_MAX_BYTES,
9730
+ log_send_queue_max: DEFAULT_LOG_SEND_QUEUE_MAX,
9731
+ disconnect_grace_seconds: 60,
9732
+ orphan_timeout_seconds: 300,
9733
+ reconcile_deadline_seconds: 120,
9734
+ disk_min_free_bytes: null,
9735
+ configured_priority: null
9736
+ };
9737
+ }
9738
+ function resolveAgentConfigPath(stateDir) {
9739
+ return join7(stateDir, AGENT_CONFIG_FILENAME);
9740
+ }
9741
+ function readAgentConfigFile(configPath) {
9742
+ if (!existsSync2(configPath)) {
9743
+ return void 0;
9744
+ }
9745
+ let raw;
9746
+ try {
9747
+ raw = JSON.parse(readFileSync3(configPath, "utf8"));
9748
+ } catch (error) {
9749
+ throw new Error(`Invalid agent config JSON at ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
9750
+ }
9751
+ const parsed = AgentConfigFileSchema.safeParse(raw);
9752
+ if (!parsed.success) {
9753
+ throw new Error(`Invalid agent config at ${configPath}: ${parsed.error.message}`);
9754
+ }
9755
+ return parsed.data;
9756
+ }
9757
+ function writeDefaultAgentConfigFile(stateDir, options = {}) {
9758
+ mkdirSync2(stateDir, { recursive: true });
9759
+ const path = resolveAgentConfigPath(stateDir);
9760
+ if (existsSync2(path) && !options.force) {
9761
+ let initialized_at2 = (/* @__PURE__ */ new Date(0)).toISOString();
9762
+ let schema_version = AGENT_CONFIG_SCHEMA_VERSION;
9763
+ try {
9764
+ const existing = readAgentConfigFile(path);
9765
+ initialized_at2 = existing?.initialized_at ?? initialized_at2;
9766
+ schema_version = existing?.schema_version ?? schema_version;
9767
+ } catch {
9768
+ }
9769
+ return {
9770
+ path,
9771
+ written: false,
9772
+ initialized_at: initialized_at2,
9773
+ schema_version
9774
+ };
9775
+ }
9776
+ const initialized_at = options.initializedAt ?? (/* @__PURE__ */ new Date()).toISOString();
9777
+ const body = `${JSON.stringify(defaultAgentConfigFile({ initializedAt: initialized_at }), null, 2)}
9778
+ `;
9779
+ writeFileSync2(path, body, "utf8");
9780
+ return {
9781
+ path,
9782
+ written: true,
9783
+ initialized_at,
9784
+ schema_version: AGENT_CONFIG_SCHEMA_VERSION
9785
+ };
9786
+ }
9498
9787
  function loadAgentConfig(overrides = {}) {
9499
- const controllerUrl = overrides.controllerUrl ?? process.env.RBO_CONTROLLER_URL;
9500
- const controllerFingerprint = overrides.controllerFingerprint ?? process.env.RBO_CONTROLLER_FINGERPRINT;
9788
+ const { configPath: configPathOption, ...fieldOverrides } = overrides;
9789
+ const stateDir = fieldOverrides.stateDir ?? resolveAgentStateDir();
9790
+ const configPath = configPathOption === void 0 ? resolveAgentConfigPath(stateDir) : configPathOption;
9791
+ const file = configPath === null ? void 0 : readAgentConfigFile(configPath);
9792
+ 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);
9793
+ 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
9794
  if (!controllerUrl) {
9502
- throw new Error("RBO_CONTROLLER_URL is required (wss://<host>:7411/agent)");
9795
+ throw new Error("controller_url is required \u2014 set it in agent.json (or RBO_CONTROLLER_URL=wss://<host>:7411/agent)");
9503
9796
  }
9504
9797
  if (!controllerFingerprint) {
9505
- throw new Error("RBO_CONTROLLER_FINGERPRINT is required \u2014 run `rbo controller fingerprint` on the Controller and copy the value here");
9798
+ throw new Error("controller_fingerprint is required \u2014 set it in agent.json (or RBO_CONTROLLER_FINGERPRINT from `rbo controller fingerprint`)");
9799
+ }
9800
+ const repoCache = mergeRepoCache(file?.repo_cache, fieldOverrides.repoCache);
9801
+ let secretMap = fieldOverrides.secretMap;
9802
+ if (secretMap === void 0) {
9803
+ if (envSet("RBO_SECRET_MAP")) {
9804
+ secretMap = parseSecretMap(process.env.RBO_SECRET_MAP);
9805
+ } else if (file?.secret_map !== void 0) {
9806
+ secretMap = file.secret_map ?? void 0;
9807
+ }
9808
+ }
9809
+ let repoCacheDir = fieldOverrides.repoCacheDir;
9810
+ if (repoCacheDir === void 0) {
9811
+ if (envSet("RBO_REPO_CACHE_DIR") && process.env.RBO_REPO_CACHE_DIR?.trim()) {
9812
+ repoCacheDir = process.env.RBO_REPO_CACHE_DIR.trim();
9813
+ } else if (file?.repo_cache_dir) {
9814
+ repoCacheDir = file.repo_cache_dir;
9815
+ }
9816
+ }
9817
+ let configuredPriority = fieldOverrides.configuredPriority;
9818
+ if (configuredPriority === void 0) {
9819
+ if (envSet("RBO_CONFIGURED_PRIORITY")) {
9820
+ configuredPriority = parseEnvNumber("RBO_CONFIGURED_PRIORITY", process.env.RBO_CONFIGURED_PRIORITY);
9821
+ } else if (file?.configured_priority !== void 0 && file.configured_priority !== null) {
9822
+ configuredPriority = file.configured_priority;
9823
+ }
9824
+ }
9825
+ let diskMinFreeBytes = fieldOverrides.diskMinFreeBytes;
9826
+ if (diskMinFreeBytes === void 0) {
9827
+ if (envSet("RBO_DISK_MIN_FREE_BYTES")) {
9828
+ diskMinFreeBytes = parseEnvInt("RBO_DISK_MIN_FREE_BYTES", process.env.RBO_DISK_MIN_FREE_BYTES);
9829
+ } else if (file?.disk_min_free_bytes !== void 0 && file.disk_min_free_bytes !== null) {
9830
+ diskMinFreeBytes = file.disk_min_free_bytes;
9831
+ } else {
9832
+ diskMinFreeBytes = Math.max(DEFAULT_DISK_MIN_FREE_BYTES, Math.floor(repoCache.min_free_disk_gb * 1024 ** 3));
9833
+ }
9506
9834
  }
9507
9835
  return {
9508
9836
  controllerUrl,
9509
9837
  controllerFingerprint,
9510
- displayName: overrides.displayName ?? process.env.RBO_AGENT_NAME ?? "rbo-agent",
9511
- maxJobs: overrides.maxJobs ?? Number(process.env.RBO_MAX_JOBS ?? 1),
9512
- stateDir: overrides.stateDir ?? resolveAgentStateDir(),
9513
- repoCacheDir: overrides.repoCacheDir ?? (process.env.RBO_REPO_CACHE_DIR?.trim() ? process.env.RBO_REPO_CACHE_DIR.trim() : void 0),
9514
- secretMap: overrides.secretMap ?? parseSecretMap(process.env.RBO_SECRET_MAP),
9515
- gitAllowlist: parseGitAllowlist(overrides.gitAllowlist),
9516
- repoCache: parseRepoCache(overrides.repoCache),
9517
- buildCache: parseBuildCache(overrides.buildCache),
9518
- logSpoolMaxBytes: overrides.logSpoolMaxBytes ?? Number(process.env.RBO_LOG_SPOOL_MAX_BYTES ?? DEFAULT_LOG_SPOOL_MAX_BYTES),
9519
- logSendQueueMax: overrides.logSendQueueMax ?? Number(process.env.RBO_LOG_SEND_QUEUE_MAX ?? DEFAULT_LOG_SEND_QUEUE_MAX),
9520
- disconnectGraceSeconds: overrides.disconnectGraceSeconds ?? Number(process.env.RBO_DISCONNECT_GRACE_SECONDS ?? 60),
9521
- orphanTimeoutSeconds: overrides.orphanTimeoutSeconds ?? Number(process.env.RBO_ORPHAN_TIMEOUT_SECONDS ?? 300),
9522
- reconcileDeadlineSeconds: overrides.reconcileDeadlineSeconds ?? Number(process.env.RBO_RECONCILE_DEADLINE_SECONDS ?? 120),
9523
- diskMinFreeBytes: (() => {
9524
- if (overrides.diskMinFreeBytes !== void 0) {
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
- })()
9838
+ displayName: fieldOverrides.displayName ?? (envSet("RBO_AGENT_NAME") ? process.env.RBO_AGENT_NAME : void 0) ?? file?.display_name ?? "rbo-agent",
9839
+ maxJobs: fieldOverrides.maxJobs ?? (envSet("RBO_MAX_JOBS") ? parseEnvInt("RBO_MAX_JOBS", process.env.RBO_MAX_JOBS) : void 0) ?? file?.max_jobs ?? 1,
9840
+ stateDir,
9841
+ repoCacheDir,
9842
+ secretMap,
9843
+ gitAllowlist: mergeGitAllowlist(file?.git_allowlist, parseGitAllowlistFromEnv(), fieldOverrides.gitAllowlist),
9844
+ repoCache,
9845
+ buildCache: mergeBuildCache(file?.build_cache, fieldOverrides.buildCache),
9846
+ 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,
9847
+ 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,
9848
+ 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,
9849
+ 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,
9850
+ 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,
9851
+ diskMinFreeBytes,
9852
+ configuredPriority
9542
9853
  };
9543
9854
  }
9544
9855
  function resolveRepoCacheRoot(config) {
@@ -9554,7 +9865,15 @@ function ensureStateDir(config) {
9554
9865
  mkdirSync2(config.stateDir, { recursive: true });
9555
9866
  }
9556
9867
 
9868
+ // ../agent/dist/run.js
9869
+ init_dist();
9870
+
9557
9871
  // ../agent/dist/capabilities/probe.js
9872
+ import { execFile as execFile2 } from "node:child_process";
9873
+ import { readFile as readFile5, readdir as readdir5, statfs } from "node:fs/promises";
9874
+ import { arch, cpus as cpus2, freemem, hostname, loadavg, platform as platform2, release, totalmem } from "node:os";
9875
+ import { join as join8 } from "node:path";
9876
+ import { promisify as promisify2 } from "node:util";
9558
9877
  var execFileAsync2 = promisify2(execFile2);
9559
9878
  function osFamily() {
9560
9879
  const p = platform2();
@@ -9704,7 +10023,7 @@ async function probeCapabilities(input) {
9704
10023
  // ../agent/dist/connection/client.js
9705
10024
  init_dist2();
9706
10025
  init_dist();
9707
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
10026
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
9708
10027
  import { hostname as hostname2 } from "node:os";
9709
10028
  import { join as join22 } from "node:path";
9710
10029
  import WebSocket from "ws";
@@ -9808,7 +10127,7 @@ init_dist4();
9808
10127
  init_dist();
9809
10128
  init_dist3();
9810
10129
  import { createHash as createHash6 } from "node:crypto";
9811
- import { createReadStream, existsSync as existsSync3 } from "node:fs";
10130
+ import { createReadStream, existsSync as existsSync4 } from "node:fs";
9812
10131
  import { copyFile, mkdir as mkdir9, readFile as readFile13, rename as rename4, rm as rm9, stat as stat7 } from "node:fs/promises";
9813
10132
  import { request as httpsRequest } from "node:https";
9814
10133
  import { join as join21 } from "node:path";
@@ -9911,7 +10230,7 @@ var SpoolSender = class {
9911
10230
 
9912
10231
  // ../agent/dist/recovery/attempt-metadata.js
9913
10232
  init_dist();
9914
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync, writeFileSync as writeFileSync2 } from "node:fs";
10233
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync4, renameSync, writeFileSync as writeFileSync3 } from "node:fs";
9915
10234
  import { readdir as readdir10, rm as rm6 } from "node:fs/promises";
9916
10235
  import { dirname as dirname5, join as join18 } from "node:path";
9917
10236
  function attemptMetadataDir(stateDir, attemptId) {
@@ -9929,14 +10248,14 @@ function writeAttemptMetadata(stateDir, meta) {
9929
10248
  ...meta,
9930
10249
  updated_at: meta.updated_at || (/* @__PURE__ */ new Date()).toISOString()
9931
10250
  };
9932
- writeFileSync2(tmp, `${JSON.stringify(payload, null, 2)}
10251
+ writeFileSync3(tmp, `${JSON.stringify(payload, null, 2)}
9933
10252
  `, { encoding: "utf8", mode: 384 });
9934
10253
  renameSync(tmp, target);
9935
10254
  }
9936
10255
  function readAttemptMetadata(stateDir, attemptId) {
9937
10256
  const path = attemptMetadataPath(stateDir, attemptId);
9938
10257
  try {
9939
- const raw = readFileSync3(path, "utf8");
10258
+ const raw = readFileSync4(path, "utf8");
9940
10259
  return JSON.parse(raw);
9941
10260
  } catch {
9942
10261
  return null;
@@ -10084,8 +10403,8 @@ async function applyDiskPressureCleanup(options) {
10084
10403
  for (const name of entries) {
10085
10404
  const metaPath = join19(reposDir, name, "metadata.json");
10086
10405
  try {
10087
- const { readFile: readFile20 } = await import("node:fs/promises");
10088
- const raw = await readFile20(metaPath, "utf8");
10406
+ const { readFile: readFile19 } = await import("node:fs/promises");
10407
+ const raw = await readFile19(metaPath, "utf8");
10089
10408
  const meta = JSON.parse(raw);
10090
10409
  if ((meta.active_worktree_count ?? 0) > 0) {
10091
10410
  continue;
@@ -11067,7 +11386,7 @@ var AgentJobExecutor = class {
11067
11386
  const currentProfiles = this.config.toolchainProfiles ?? [];
11068
11387
  for (const profile of offer.selected_toolchain_profiles ?? []) {
11069
11388
  const activationPath = profile.activation.path;
11070
- if (activationPath && !existsSync3(activationPath)) {
11389
+ if (activationPath && !existsSync4(activationPath)) {
11071
11390
  this.failTerminal(run.attempt_id, run.lease_id, run.lease_epoch, "toolchain_changed", `Selected toolchain path missing: ${activationPath}`);
11072
11391
  await rm9(attemptDir, { recursive: true, force: true }).catch(() => void 0);
11073
11392
  this.clearAttempt();
@@ -11839,8 +12158,8 @@ var AgentConnection = class {
11839
12158
  return join22(this.options.stateDir, "agent-state.json");
11840
12159
  }
11841
12160
  loadState() {
11842
- if (existsSync4(this.statePath())) {
11843
- return JSON.parse(readFileSync4(this.statePath(), "utf8"));
12161
+ if (existsSync5(this.statePath())) {
12162
+ return JSON.parse(readFileSync5(this.statePath(), "utf8"));
11844
12163
  }
11845
12164
  const pair = generateDeviceKeyPair();
11846
12165
  const state = {
@@ -11852,7 +12171,7 @@ var AgentConnection = class {
11852
12171
  }
11853
12172
  saveState(state) {
11854
12173
  mkdirSync4(this.options.stateDir, { recursive: true });
11855
- writeFileSync3(this.statePath(), JSON.stringify(state), { mode: 384 });
12174
+ writeFileSync4(this.statePath(), JSON.stringify(state), { mode: 384 });
11856
12175
  }
11857
12176
  hasStoredCredential() {
11858
12177
  const state = this.loadState();
@@ -12329,7 +12648,7 @@ init_dist();
12329
12648
 
12330
12649
  // src/commands/daemon.ts
12331
12650
  import { spawn as nodeSpawn } from "node:child_process";
12332
- import { existsSync as existsSync5, openSync, readFileSync as readFileSync5 } from "node:fs";
12651
+ import { existsSync as existsSync6, openSync, readFileSync as readFileSync6 } from "node:fs";
12333
12652
  import { mkdir as mkdir10, writeFile as writeFile10 } from "node:fs/promises";
12334
12653
  import { dirname as dirname6, join as join23 } from "node:path";
12335
12654
  function stripDaemonFlag(args) {
@@ -12360,10 +12679,10 @@ function isProcessAlive(pid) {
12360
12679
  }
12361
12680
  }
12362
12681
  function assertNoLivePid(pidFile, label) {
12363
- if (!existsSync5(pidFile)) {
12682
+ if (!existsSync6(pidFile)) {
12364
12683
  return;
12365
12684
  }
12366
- const raw = readFileSync5(pidFile, "utf8").trim();
12685
+ const raw = readFileSync6(pidFile, "utf8").trim();
12367
12686
  const pid = Number.parseInt(raw, 10);
12368
12687
  if (isProcessAlive(pid)) {
12369
12688
  throw new Error(
@@ -12407,36 +12726,21 @@ async function spawnDetachedDaemon(options) {
12407
12726
  }
12408
12727
 
12409
12728
  // src/commands/agent.ts
12410
- var AGENT_MARKER = "agent.json";
12411
- var AGENT_SCHEMA_VERSION = 1;
12412
12729
  function isAgentInitialized(stateDir) {
12413
- return existsSync6(join24(stateDir, AGENT_MARKER));
12730
+ return existsSync7(join24(stateDir, AGENT_CONFIG_FILENAME));
12414
12731
  }
12415
- async function readAgentMarker(stateDir) {
12416
- const markerPath = join24(stateDir, AGENT_MARKER);
12417
- if (!existsSync6(markerPath)) {
12418
- return void 0;
12419
- }
12420
- const marker = JSON.parse(await readFile14(markerPath, "utf8"));
12732
+ async function runAgentInit(options = {}) {
12733
+ const stateDir = options.stateDir ?? resolveAgentStateDir();
12734
+ const result = writeDefaultAgentConfigFile(stateDir, { force: options.force });
12421
12735
  return {
12422
12736
  stateDir,
12423
- initialized_at: marker.initialized_at,
12424
- schema_version: marker.schema_version
12737
+ initialized_at: result.initialized_at,
12738
+ schema_version: result.schema_version,
12739
+ configPath: result.path,
12740
+ configWritten: result.written,
12741
+ ...result.written ? {} : { hint: "agent.json already exists; pass --force to rewrite defaults" }
12425
12742
  };
12426
12743
  }
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
12744
  function assertAgentInitialized(stateDir) {
12441
12745
  if (!isAgentInitialized(stateDir)) {
12442
12746
  throw new Error("Agent is not initialized. Run `rbo agent init` first.");
@@ -12476,8 +12780,24 @@ async function postAdmin(baseUrl, action, body) {
12476
12780
  }
12477
12781
  return json;
12478
12782
  }
12479
- function listAgentsRemote(baseUrl) {
12480
- return postAdmin(baseUrl, "agents/list", {});
12783
+ async function listAgentsRemote(baseUrl) {
12784
+ const [agentsResult, pairingResult] = await Promise.all([
12785
+ postAdmin(baseUrl, "agents/list", {}),
12786
+ postAdmin(baseUrl, "pairing/list", {
12787
+ state: "pending"
12788
+ })
12789
+ ]);
12790
+ return {
12791
+ agents: agentsResult.agents,
12792
+ pending_pairings: pairingResult.requests.map((row) => ({
12793
+ id: row.id,
12794
+ display_name: row.display_name,
12795
+ hostname: row.hostname,
12796
+ state: row.state,
12797
+ one_time_code: row.one_time_code,
12798
+ expires_at: row.expires_at
12799
+ }))
12800
+ };
12481
12801
  }
12482
12802
  function approveAgentRemote(baseUrl, pairingRequestId) {
12483
12803
  return postAdmin(baseUrl, "pairing/approve", { pairing_request_id: pairingRequestId });
@@ -12490,8 +12810,9 @@ function probeAgentRemote(baseUrl, agentId) {
12490
12810
  }
12491
12811
 
12492
12812
  // src/commands/controller.ts
12493
- import { existsSync as existsSync7 } from "node:fs";
12494
- import { readFile as readFile18 } from "node:fs/promises";
12813
+ init_config();
12814
+ import { existsSync as existsSync9 } from "node:fs";
12815
+ import { readFile as readFile17 } from "node:fs/promises";
12495
12816
  import { join as join32 } from "node:path";
12496
12817
 
12497
12818
  // ../controller/dist/run.js
@@ -12509,7 +12830,7 @@ init_dist4();
12509
12830
  init_dist2();
12510
12831
  init_dist();
12511
12832
  import { join as join31 } from "node:path";
12512
- import { z as z7 } from "zod";
12833
+ import { z as z9 } from "zod";
12513
12834
 
12514
12835
  // ../controller/dist/agents/probe.js
12515
12836
  function requestAgentProbe(connectedAgents, agentId) {
@@ -12597,7 +12918,7 @@ function validateToolInput(name, args) {
12597
12918
  if (!def) {
12598
12919
  throw RboError.validation(`Unknown tool '${name}'`);
12599
12920
  }
12600
- const parsed = z7.object(def.inputShape).safeParse(args ?? {});
12921
+ const parsed = z9.object(def.inputShape).safeParse(args ?? {});
12601
12922
  if (!parsed.success) {
12602
12923
  throw RboError.validation(`Invalid input for tool '${name}'`, {
12603
12924
  issues: parsed.error.issues.map((issue) => ({
@@ -12745,7 +13066,7 @@ function revokeAgent(db, agentId) {
12745
13066
  }
12746
13067
  }
12747
13068
  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);
13069
+ 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
13070
  }
12750
13071
  function patchAgentCpuLoad(db, agentId, cpuLoad) {
12751
13072
  const row = db.prepare("SELECT capabilities_json FROM agents WHERE id = ?").get(agentId);
@@ -12829,11 +13150,20 @@ function getPairingRequest(db, id) {
12829
13150
  const row = db.prepare("SELECT * FROM pairing_requests WHERE id = ?").get(id);
12830
13151
  return row ?? null;
12831
13152
  }
13153
+ function expireStalePairingRequests(db) {
13154
+ const now = nowIso();
13155
+ const result = db.prepare(`UPDATE pairing_requests
13156
+ SET state = 'expired', resolved_at = COALESCE(resolved_at, ?)
13157
+ WHERE state IN ('pending', 'approved') AND expires_at <= ?`).run(now, now);
13158
+ return result.changes;
13159
+ }
12832
13160
  function listPairingRequests(db, state) {
13161
+ expireStalePairingRequests(db);
12833
13162
  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
13163
  return rows;
12835
13164
  }
12836
13165
  function createPairingRequest(db, input) {
13166
+ expireStalePairingRequests(db);
12837
13167
  const thumbprint = publicKeyThumbprint(input.devicePublicKeyPem);
12838
13168
  const existing = db.prepare("SELECT * FROM pairing_requests WHERE device_thumbprint = ? AND state IN ('pending', 'approved') AND expires_at > ?").get(thumbprint, nowIso());
12839
13169
  if (existing) {
@@ -13609,7 +13939,13 @@ async function runController(overrides = {}) {
13609
13939
  init_dist();
13610
13940
  async function runControllerInit(options) {
13611
13941
  const identity = await ensureControllerIdentity(options.dataDir);
13612
- return { controllerId: identity.controllerId, fingerprint: identity.fingerprint };
13942
+ const config = writeDefaultControllerConfigFile(options.dataDir, { force: options.force });
13943
+ return {
13944
+ controllerId: identity.controllerId,
13945
+ fingerprint: identity.fingerprint,
13946
+ configPath: config.path,
13947
+ configWritten: config.written
13948
+ };
13613
13949
  }
13614
13950
  async function runControllerFingerprint(options) {
13615
13951
  const identity = await ensureControllerIdentity(options.dataDir);
@@ -13618,10 +13954,10 @@ async function runControllerFingerprint(options) {
13618
13954
  async function readExistingControllerId(dataDir) {
13619
13955
  const certPath = join32(dataDir, "security", "controller-cert.pem");
13620
13956
  const metaPath = join32(dataDir, "security", "controller.json");
13621
- if (!existsSync7(certPath) || !existsSync7(metaPath)) {
13957
+ if (!existsSync9(certPath) || !existsSync9(metaPath)) {
13622
13958
  return void 0;
13623
13959
  }
13624
- const meta = JSON.parse(await readFile18(metaPath, "utf8"));
13960
+ const meta = JSON.parse(await readFile17(metaPath, "utf8"));
13625
13961
  return meta.controller_id;
13626
13962
  }
13627
13963
  async function isControllerInitialized(dataDir) {
@@ -13665,11 +14001,25 @@ async function runControllerRestore(options) {
13665
14001
  // src/commands/doctor.ts
13666
14002
  init_dist4();
13667
14003
  import { execFile as execFile9 } from "node:child_process";
13668
- import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync4 } from "node:fs";
14004
+ import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "node:fs";
13669
14005
  import { join as join33 } from "node:path";
13670
- import { promisify as promisify9 } from "node:util";
14006
+ import { promisify as promisify9, styleText } from "node:util";
13671
14007
  var execFileAsync9 = promisify9(execFile9);
13672
14008
  var REQUIRED_NODE_ENGINES = { major: 22, minor: 14 };
14009
+ function doctorStatusTag(check) {
14010
+ if (!check.ok) return "FAIL";
14011
+ if (check.warn) return "WARN";
14012
+ return "OK ";
14013
+ }
14014
+ function formatDoctorCheckLine(check, options = {}) {
14015
+ const tag = doctorStatusTag(check);
14016
+ const colorName = !check.ok ? "red" : check.warn ? "yellow" : "green";
14017
+ const styledTag = options.color === false ? tag : styleText(colorName, tag, {
14018
+ stream: options.stream ?? process.stdout,
14019
+ validateStream: options.color !== true
14020
+ });
14021
+ return `${styledTag} ${check.name}: ${check.detail}`;
14022
+ }
13673
14023
  async function checkGit() {
13674
14024
  try {
13675
14025
  const { stdout } = await execFileAsync9("git", ["--version"]);
@@ -13682,7 +14032,7 @@ function checkDataDirWritable(dataDir) {
13682
14032
  try {
13683
14033
  mkdirSync6(dataDir, { recursive: true });
13684
14034
  const probe = join33(dataDir, ".rbo-doctor-probe");
13685
- writeFileSync4(probe, "ok");
14035
+ writeFileSync6(probe, "ok");
13686
14036
  return { name: "data_dir_writable", ok: true, detail: dataDir };
13687
14037
  } catch (error) {
13688
14038
  return { name: "data_dir_writable", ok: false, detail: String(error) };
@@ -13777,6 +14127,18 @@ async function runDoctor(options) {
13777
14127
  }
13778
14128
 
13779
14129
  // src/commands/flags.ts
14130
+ function parseForceFlag(args) {
14131
+ const rest = [];
14132
+ let force = false;
14133
+ for (const arg of args) {
14134
+ if (arg === "--force") {
14135
+ force = true;
14136
+ continue;
14137
+ }
14138
+ rest.push(arg);
14139
+ }
14140
+ return { force, rest };
14141
+ }
13780
14142
  function parseDataDirFlag(args) {
13781
14143
  const rest = [];
13782
14144
  let dataDir;
@@ -13849,6 +14211,7 @@ function cancelJobRemote(baseUrl, jobId, reason) {
13849
14211
  }
13850
14212
 
13851
14213
  // src/commands/service.ts
14214
+ init_dist();
13852
14215
  import { execFile as execFile10 } from "node:child_process";
13853
14216
  import { promisify as promisify10 } from "node:util";
13854
14217
  var execFileAsync10 = promisify10(execFile10);
@@ -13881,14 +14244,21 @@ var defaultCommandRunner = {
13881
14244
  var SERVICE_NAME = "RBOAgent";
13882
14245
  var LAUNCHD_LABEL = "com.rbo.agent";
13883
14246
  var SYSTEMD_UNIT = "rbo-agent";
13884
- function renderServiceInstallPlan(platform3, executablePath = "C:/Program Files/RBO/rbo-agent.exe") {
14247
+ function renderAgentServiceCommand(options = {}) {
14248
+ const nodePath = options.nodePath ?? process.execPath;
14249
+ const rboScriptPath = options.rboScriptPath ?? process.argv[1] ?? "rbo.js";
14250
+ const stateDir = options.stateDir ?? resolveAgentStateDir();
14251
+ return `"${nodePath}" "${rboScriptPath}" agent start --state-dir "${stateDir}"`;
14252
+ }
14253
+ function renderServiceInstallPlan(platform3, options = {}) {
14254
+ const binPath = renderAgentServiceCommand(options);
13885
14255
  switch (platform3) {
13886
14256
  case "win32":
13887
14257
  return {
13888
14258
  kind: "windows_service",
13889
14259
  serviceName: SERVICE_NAME,
13890
14260
  commands: [
13891
- `sc.exe create ${SERVICE_NAME} binPath= "${executablePath}" start= auto`,
14261
+ `sc.exe create ${SERVICE_NAME} binPath= ${JSON.stringify(binPath)} start= auto`,
13892
14262
  `sc.exe description ${SERVICE_NAME} "Remote Build Orchestrator Agent"`,
13893
14263
  `sc.exe start ${SERVICE_NAME}`
13894
14264
  ]
@@ -13898,6 +14268,7 @@ function renderServiceInstallPlan(platform3, executablePath = "C:/Program Files/
13898
14268
  kind: "launchd",
13899
14269
  serviceName: LAUNCHD_LABEL,
13900
14270
  commands: [
14271
+ `# Write /Library/LaunchDaemons/${LAUNCHD_LABEL}.plist with ProgramArguments: ${binPath}`,
13901
14272
  `launchctl load -w /Library/LaunchDaemons/${LAUNCHD_LABEL}.plist`,
13902
14273
  `launchctl start ${LAUNCHD_LABEL}`
13903
14274
  ]
@@ -13907,6 +14278,7 @@ function renderServiceInstallPlan(platform3, executablePath = "C:/Program Files/
13907
14278
  kind: "systemd",
13908
14279
  serviceName: SYSTEMD_UNIT,
13909
14280
  commands: [
14281
+ `# Write /etc/systemd/system/${SYSTEMD_UNIT}.service with ExecStart=${binPath}`,
13910
14282
  "systemctl daemon-reload",
13911
14283
  `systemctl enable ${SYSTEMD_UNIT}`,
13912
14284
  `systemctl start ${SYSTEMD_UNIT}`
@@ -14009,10 +14381,10 @@ function renderServiceStopPlan(platform3) {
14009
14381
  };
14010
14382
  }
14011
14383
  }
14012
- function renderServiceActionPlan(platform3, action, executablePath) {
14384
+ function renderServiceActionPlan(platform3, action, options) {
14013
14385
  switch (action) {
14014
14386
  case "install":
14015
- return renderServiceInstallPlan(platform3, executablePath);
14387
+ return renderServiceInstallPlan(platform3, options);
14016
14388
  case "uninstall":
14017
14389
  return renderServiceUninstallPlan(platform3);
14018
14390
  case "status":
@@ -14027,7 +14399,11 @@ function hasExecuteFlag(args) {
14027
14399
  return args.includes("--execute");
14028
14400
  }
14029
14401
  function formatDryRunPlan(action, plan) {
14030
- const lines = [`# ${action} (dry run \u2014 pass --execute to run these commands)`];
14402
+ const lines = [
14403
+ `# ${action} (dry run \u2014 pass --execute to run these commands)`,
14404
+ "# Prefer `rbo agent start --daemon` for day-to-day use; OS service install is best-effort",
14405
+ "# and expects node + bundled rbo.js (not a Program Files rbo-agent.exe)."
14406
+ ];
14031
14407
  for (const command of plan.commands) {
14032
14408
  lines.push(command);
14033
14409
  }
@@ -14036,6 +14412,10 @@ function formatDryRunPlan(action, plan) {
14036
14412
  async function executeServicePlan(plan, runner = defaultCommandRunner) {
14037
14413
  const results = [];
14038
14414
  for (const command of plan.commands) {
14415
+ if (command.trimStart().startsWith("#")) {
14416
+ results.push({ command, stdout: "", stderr: "skipped comment", code: 0 });
14417
+ continue;
14418
+ }
14039
14419
  const { stdout, stderr, code } = await runner.run(command);
14040
14420
  results.push({ command, stdout, stderr, code });
14041
14421
  if (code !== 0) {
@@ -14067,7 +14447,8 @@ async function main() {
14067
14447
  const dataDir = flagDataDir ?? resolveControllerDataDir();
14068
14448
  const sub = controllerArgs[0];
14069
14449
  if (sub === "init") {
14070
- const result = await runControllerInit({ dataDir });
14450
+ const { force } = parseForceFlag(controllerArgs.slice(1));
14451
+ const result = await runControllerInit({ dataDir, force });
14071
14452
  console.log(JSON.stringify(result, null, 2));
14072
14453
  return;
14073
14454
  }
@@ -14106,7 +14487,7 @@ async function main() {
14106
14487
  }
14107
14488
  case "agents": {
14108
14489
  const result = await listAgentsRemote(controllerUrl);
14109
- console.log(JSON.stringify(result.agents, null, 2));
14490
+ console.log(JSON.stringify(result, null, 2));
14110
14491
  return;
14111
14492
  }
14112
14493
  case "agent": {
@@ -14135,7 +14516,8 @@ async function main() {
14135
14516
  return;
14136
14517
  }
14137
14518
  if (sub === "init") {
14138
- const result = await runAgentInit({ stateDir });
14519
+ const { force } = parseForceFlag(agentArgs.slice(1));
14520
+ const result = await runAgentInit({ stateDir, force });
14139
14521
  console.log(JSON.stringify(result, null, 2));
14140
14522
  return;
14141
14523
  }
@@ -14178,8 +14560,7 @@ async function main() {
14178
14560
  const dataDir = flagDataDir ?? resolveControllerDataDir();
14179
14561
  const report = await runDoctor({ dataDir, controllerUrl });
14180
14562
  for (const check of report.checks) {
14181
- const tag = !check.ok ? "FAIL" : check.warn ? "WARN" : "OK ";
14182
- console.log(`${tag} ${check.name}: ${check.detail}`);
14563
+ console.log(formatDoctorCheckLine(check));
14183
14564
  }
14184
14565
  process.exitCode = report.ok ? 0 : 1;
14185
14566
  return;
@@ -14189,7 +14570,7 @@ async function main() {
14189
14570
  if (!requestPath) {
14190
14571
  throw new Error("Usage: rbo submit <job-request.json>");
14191
14572
  }
14192
- const request = JSON.parse(await readFile19(requestPath, "utf8"));
14573
+ const request = JSON.parse(await readFile18(requestPath, "utf8"));
14193
14574
  const result = await submitJobRemote(controllerUrl, request);
14194
14575
  console.log(JSON.stringify(result, null, 2));
14195
14576
  return;