@pleri/olam-cli 0.1.134 → 0.1.136

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/index.js CHANGED
@@ -4420,7 +4420,7 @@ function loadRepoManifest(repoDir) {
4420
4420
  }
4421
4421
  return { ...body, source };
4422
4422
  }
4423
- var runtimeSchema, appSchema, serviceSchema, deploySchema, BootstrapKindSchema, BootstrapStepSchema, RepoManifestSchema, KNOWN_TOP_LEVEL_KEYS, FORBIDDEN_KEYS;
4423
+ var runtimeSchema, appSchema, serviceSchema, deploySchema, planChatSchema, BootstrapKindSchema, BootstrapStepSchema, RepoManifestSchema, KNOWN_TOP_LEVEL_KEYS, FORBIDDEN_KEYS;
4424
4424
  var init_repo_manifest = __esm({
4425
4425
  "../core/dist/world/repo-manifest.js"() {
4426
4426
  "use strict";
@@ -4526,6 +4526,9 @@ var init_repo_manifest = __esm({
4526
4526
  deploySchema = external_exports.object({
4527
4527
  tags: external_exports.array(external_exports.string()).optional()
4528
4528
  }).passthrough();
4529
+ planChatSchema = external_exports.object({
4530
+ enabled: external_exports.boolean()
4531
+ }).strict();
4529
4532
  BootstrapKindSchema = external_exports.enum(["gems", "node", "pg"]);
4530
4533
  BootstrapStepSchema = external_exports.union([
4531
4534
  external_exports.string(),
@@ -4548,7 +4551,8 @@ var init_repo_manifest = __esm({
4548
4551
  secrets: external_exports.string().optional(),
4549
4552
  bootstrap: external_exports.array(BootstrapStepSchema).optional(),
4550
4553
  start: external_exports.string().optional(),
4551
- deploy: deploySchema.optional()
4554
+ deploy: deploySchema.optional(),
4555
+ plan_chat: planChatSchema.optional()
4552
4556
  // TODO(phase-C): runtime field consumed by stack-install.ts (T16).
4553
4557
  // TODO(phase-D): app.fixed (sticky port) consumed by port-allocation.
4554
4558
  // TODO(phase-D): idempotent_check on bootstrap steps in bootstrap-runner.
@@ -4556,11 +4560,11 @@ var init_repo_manifest = __esm({
4556
4560
  }).passthrough().superRefine((val, ctx) => {
4557
4561
  refineForbiddenKeys(val, [], ctx, true);
4558
4562
  }).superRefine((val, ctx) => {
4559
- const hasContent = val.version !== void 0 || val.runtime !== void 0 || val.app !== void 0 || val.services !== void 0 || val.env !== void 0 || val.secrets !== void 0 || val.bootstrap !== void 0 || val.start !== void 0 || val.deploy !== void 0;
4563
+ const hasContent = val.version !== void 0 || val.runtime !== void 0 || val.app !== void 0 || val.services !== void 0 || val.env !== void 0 || val.secrets !== void 0 || val.bootstrap !== void 0 || val.start !== void 0 || val.deploy !== void 0 || val.plan_chat !== void 0;
4560
4564
  if (!hasContent) {
4561
4565
  ctx.addIssue({
4562
4566
  code: external_exports.ZodIssueCode.custom,
4563
- message: "Manifest must declare at least one of: version, runtime, app, services, env, secrets, bootstrap, start, deploy"
4567
+ message: "Manifest must declare at least one of: version, runtime, app, services, env, secrets, bootstrap, start, deploy, plan_chat"
4564
4568
  });
4565
4569
  }
4566
4570
  });
@@ -4574,6 +4578,10 @@ var init_repo_manifest = __esm({
4574
4578
  "bootstrap",
4575
4579
  "start",
4576
4580
  "deploy",
4581
+ // olam-plan-chat-chunks-substrate Phase A task A4: opt-in for the chunks
4582
+ // thought substrate. Read by `resolveThoughtSubstrate` in
4583
+ // `packages/core/src/thought/substrate-router.ts`.
4584
+ "plan_chat",
4577
4585
  // F-7 (olam-hybrid-shared-postgres dogfood finding): native inheritance —
4578
4586
  // `.olam.yaml` may declare `inherits: .adb.yaml` to deep-merge an adb-shaped
4579
4587
  // base manifest into itself. Retires the atlas-one `bin/olam-create-wrap.sh`
@@ -5025,7 +5033,24 @@ var init_schema2 = __esm({
5025
5033
  * (B1/C4) reads this file and forwards the value as
5026
5034
  * `AGENTMEMORY_SECRET` into worlds.
5027
5035
  */
5028
- secret_ref: external_exports.string().min(1).optional().default("~/.olam/cloud-memory-secret")
5036
+ secret_ref: external_exports.string().min(1).optional().default("~/.olam/cloud-memory-secret"),
5037
+ /**
5038
+ * DO SQLite write-through bridge toggle. Defaults to `true` (bridge
5039
+ * active — every state-mutating call captures the engine's export to
5040
+ * a DO snapshot, restored on cold-start).
5041
+ *
5042
+ * Operators set `bridge: false` to bisect bridge vs engine bugs:
5043
+ * the CLI threads this into the Worker as `OLAM_BRIDGE_DISABLED=1`,
5044
+ * which makes `AgentMemoryContainer.fetch` a plain pass-through
5045
+ * (no capture, no restore). The next deploy with `bridge: true`
5046
+ * resumes captures automatically.
5047
+ *
5048
+ * See `docs/design/olam-agent-memory-do-bridge-schema.md` for the
5049
+ * full storage contract; the bridge is OFF only as a diagnosis aid.
5050
+ *
5051
+ * Plan reference: docs/plans/olam-agent-memory-do-sqlite-bridge/phase-c-tasks.md C3
5052
+ */
5053
+ bridge: external_exports.boolean().optional().default(true)
5029
5054
  });
5030
5055
  MemorySchema = external_exports.object({
5031
5056
  mode: external_exports.enum(["local", "cloud"]).optional().default("local"),
@@ -5370,7 +5395,7 @@ async function safeText(res) {
5370
5395
  }
5371
5396
  }
5372
5397
  function sleep(ms) {
5373
- return new Promise((resolve14) => setTimeout(resolve14, ms));
5398
+ return new Promise((resolve15) => setTimeout(resolve15, ms));
5374
5399
  }
5375
5400
  var DEFAULT_BASE_URL, DEFAULT_TIMEOUT_MS, RETRY_COUNT, RETRY_BACKOFF_MS, AuthClient;
5376
5401
  var init_client = __esm({
@@ -5522,7 +5547,7 @@ function resolveAuthServicePath() {
5522
5547
  return path9.join(pkgsDir, "auth-service");
5523
5548
  }
5524
5549
  function sleep2(ms) {
5525
- return new Promise((resolve14) => setTimeout(resolve14, ms));
5550
+ return new Promise((resolve15) => setTimeout(resolve15, ms));
5526
5551
  }
5527
5552
  var DEFAULT_PORT, DEFAULT_VOLUME, DEFAULT_CONTAINER, DEFAULT_IMAGE, AuthContainerController;
5528
5553
  var init_container = __esm({
@@ -5820,7 +5845,7 @@ var init_network = __esm({
5820
5845
  // ../adapters/dist/docker/pull.js
5821
5846
  import { spawn } from "node:child_process";
5822
5847
  function spawnAsync(cmd, args, opts = {}) {
5823
- return new Promise((resolve14) => {
5848
+ return new Promise((resolve15) => {
5824
5849
  const child = spawn(cmd, [...args], {
5825
5850
  stdio: ["ignore", "pipe", "pipe"],
5826
5851
  signal: opts.signal
@@ -5834,10 +5859,10 @@ function spawnAsync(cmd, args, opts = {}) {
5834
5859
  stderr += chunk.toString();
5835
5860
  });
5836
5861
  child.on("error", (err) => {
5837
- resolve14({ exitCode: -1, stdout, stderr: stderr + err.message });
5862
+ resolve15({ exitCode: -1, stdout, stderr: stderr + err.message });
5838
5863
  });
5839
5864
  child.on("close", (code) => {
5840
- resolve14({ exitCode: code ?? -1, stdout, stderr });
5865
+ resolve15({ exitCode: code ?? -1, stdout, stderr });
5841
5866
  });
5842
5867
  });
5843
5868
  }
@@ -6272,6 +6297,10 @@ var init_container2 = __esm({
6272
6297
  opts.push(`size=${m.size}`);
6273
6298
  if (m.mode !== void 0)
6274
6299
  opts.push(`mode=${m.mode.toString(8).padStart(4, "0")}`);
6300
+ if (m.uid !== void 0)
6301
+ opts.push(`uid=${m.uid}`);
6302
+ if (m.gid !== void 0)
6303
+ opts.push(`gid=${m.gid}`);
6275
6304
  acc[m.target] = opts.join(",");
6276
6305
  return acc;
6277
6306
  }, {})
@@ -6307,7 +6336,7 @@ var demuxStream, execInContainer;
6307
6336
  var init_exec = __esm({
6308
6337
  "../adapters/dist/docker/exec.js"() {
6309
6338
  "use strict";
6310
- demuxStream = (stream) => new Promise((resolve14, reject) => {
6339
+ demuxStream = (stream) => new Promise((resolve15, reject) => {
6311
6340
  const stdoutChunks = [];
6312
6341
  const stderrChunks = [];
6313
6342
  const stdout = new PassThrough();
@@ -6321,7 +6350,7 @@ var init_exec = __esm({
6321
6350
  stream.pipe(stdout);
6322
6351
  }
6323
6352
  stream.on("end", () => {
6324
- resolve14({
6353
+ resolve15({
6325
6354
  stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
6326
6355
  stderr: Buffer.concat(stderrChunks).toString("utf-8")
6327
6356
  });
@@ -6781,7 +6810,7 @@ var init_connection = __esm({
6781
6810
  // -----------------------------------------------------------------------
6782
6811
  async exec(host, command) {
6783
6812
  const client = await this.getConnection(host);
6784
- return new Promise((resolve14, reject) => {
6813
+ return new Promise((resolve15, reject) => {
6785
6814
  client.exec(command, (err, stream) => {
6786
6815
  if (err) {
6787
6816
  reject(new Error(`SSH exec failed on ${host}: ${err.message}`));
@@ -6796,7 +6825,7 @@ var init_connection = __esm({
6796
6825
  stderr += data.toString();
6797
6826
  });
6798
6827
  stream.on("close", (code) => {
6799
- resolve14({
6828
+ resolve15({
6800
6829
  exitCode: code ?? 0,
6801
6830
  stdout: stdout.trimEnd(),
6802
6831
  stderr: stderr.trimEnd()
@@ -6827,10 +6856,10 @@ var init_connection = __esm({
6827
6856
  throw new Error(`No SSH configuration found for host: ${host}`);
6828
6857
  }
6829
6858
  const client = new SSHClient();
6830
- return new Promise((resolve14, reject) => {
6859
+ return new Promise((resolve15, reject) => {
6831
6860
  client.on("ready", () => {
6832
6861
  this.connections.set(host, client);
6833
- resolve14(client);
6862
+ resolve15(client);
6834
6863
  }).on("error", (err) => {
6835
6864
  this.connections.delete(host);
6836
6865
  reject(new Error(`SSH connection to ${host} failed: ${err.message}`));
@@ -8461,8 +8490,8 @@ function copyMatchingFiles(sourcePath, destPath, pattern) {
8461
8490
  try {
8462
8491
  const matches2 = globSync(fullPattern);
8463
8492
  for (const match2 of matches2) {
8464
- const relative3 = path14.relative(sourcePath, match2);
8465
- const dest = path14.join(destPath, relative3);
8493
+ const relative4 = path14.relative(sourcePath, match2);
8494
+ const dest = path14.join(destPath, relative4);
8466
8495
  fs13.mkdirSync(path14.dirname(dest), { recursive: true });
8467
8496
  fs13.copyFileSync(match2, dest);
8468
8497
  }
@@ -8742,8 +8771,8 @@ import { execFileSync as execFileSync4 } from "node:child_process";
8742
8771
  import * as fs15 from "node:fs";
8743
8772
  import * as os9 from "node:os";
8744
8773
  import * as path16 from "node:path";
8745
- function expandHome2(p, homedir35) {
8746
- return p.replace(/^~(?=$|\/|\\)/, homedir35());
8774
+ function expandHome2(p, homedir36) {
8775
+ return p.replace(/^~(?=$|\/|\\)/, homedir36());
8747
8776
  }
8748
8777
  function sanitizeRepoFilename(name) {
8749
8778
  const sanitized = name.replace(/[^A-Za-z0-9._-]/g, "_");
@@ -8766,7 +8795,7 @@ ${stderr}`;
8766
8795
  }
8767
8796
  function snapshotBaselineDiff(repos, workspacePath, deps = {}) {
8768
8797
  const exec = deps.exec ?? ((cmd, args, opts) => execFileSync4(cmd, args, opts));
8769
- const homedir35 = deps.homedir ?? (() => os9.homedir());
8798
+ const homedir36 = deps.homedir ?? (() => os9.homedir());
8770
8799
  const baselineDir = path16.join(workspacePath, ".olam", "baseline");
8771
8800
  try {
8772
8801
  fs15.mkdirSync(baselineDir, { recursive: true });
@@ -8782,7 +8811,7 @@ function snapshotBaselineDiff(repos, workspacePath, deps = {}) {
8782
8811
  continue;
8783
8812
  const filename = `${sanitizeRepoFilename(repo.name)}.diff`;
8784
8813
  const outPath = path16.join(baselineDir, filename);
8785
- const repoPath = expandHome2(repo.path, homedir35);
8814
+ const repoPath = expandHome2(repo.path, homedir36);
8786
8815
  if (!fs15.existsSync(repoPath)) {
8787
8816
  writeBaselineFile(outPath, `# repo: ${repo.name}
8788
8817
  # (skipped: path ${repoPath} does not exist)
@@ -8902,17 +8931,17 @@ function extractStderr(err) {
8902
8931
  }
8903
8932
  function carryUncommittedEdits(repos, workspacePath, deps = {}) {
8904
8933
  const exec = deps.exec ?? ((cmd, args, opts) => execFileSync4(cmd, args, opts));
8905
- const homedir35 = deps.homedir ?? (() => os9.homedir());
8934
+ const homedir36 = deps.homedir ?? (() => os9.homedir());
8906
8935
  const existsSync64 = deps.existsSync ?? ((p) => fs15.existsSync(p));
8907
8936
  const copyFileSync9 = deps.copyFileSync ?? ((src, dest) => fs15.copyFileSync(src, dest));
8908
- const mkdirSync35 = deps.mkdirSync ?? ((dirPath, opts) => {
8937
+ const mkdirSync36 = deps.mkdirSync ?? ((dirPath, opts) => {
8909
8938
  fs15.mkdirSync(dirPath, opts);
8910
8939
  });
8911
8940
  const plans = [];
8912
8941
  for (const repo of repos) {
8913
8942
  if (!repo.path)
8914
8943
  continue;
8915
- const repoPath = expandHome2(repo.path, homedir35);
8944
+ const repoPath = expandHome2(repo.path, homedir36);
8916
8945
  const worktreePath = path16.join(workspacePath, repo.name);
8917
8946
  if (!existsSync64(repoPath))
8918
8947
  continue;
@@ -8979,7 +9008,7 @@ function carryUncommittedEdits(repos, workspacePath, deps = {}) {
8979
9008
  if (!existsSync64(src))
8980
9009
  continue;
8981
9010
  try {
8982
- mkdirSync35(path16.dirname(dest), { recursive: true });
9011
+ mkdirSync36(path16.dirname(dest), { recursive: true });
8983
9012
  copyFileSync9(src, dest);
8984
9013
  } catch (err) {
8985
9014
  const msg = err instanceof Error ? err.message : String(err);
@@ -9422,18 +9451,18 @@ function computeFingerprint(runtimes) {
9422
9451
  const joined = parts.join("_");
9423
9452
  return sanitizeTag(joined);
9424
9453
  }
9425
- function computeImageTag(runtimes) {
9426
- const baseDigest = getBaseImageDigest();
9454
+ function computeImageTag(runtimes, baseImageRef = `${BASE_IMAGE}:latest`) {
9455
+ const baseDigest = getImageDigest(baseImageRef);
9427
9456
  const prefix = baseDigest.slice(0, 8);
9428
9457
  const fingerprint = computeFingerprint(runtimes);
9429
9458
  const tag = `${prefix}_${fingerprint}`;
9430
9459
  return sanitizeTag(tag);
9431
9460
  }
9432
- function computeImageName(runtimes) {
9433
- return `${BASE_IMAGE}:${computeImageTag(runtimes)}`;
9461
+ function computeImageName(runtimes, baseImageRef = `${BASE_IMAGE}:latest`) {
9462
+ return `${BASE_IMAGE}:${computeImageTag(runtimes, baseImageRef)}`;
9434
9463
  }
9435
- function lookupCachedImage(runtimes) {
9436
- const tag = computeImageTag(runtimes);
9464
+ function lookupCachedImage(runtimes, baseImageRef = `${BASE_IMAGE}:latest`) {
9465
+ const tag = computeImageTag(runtimes, baseImageRef);
9437
9466
  const imageName = `${BASE_IMAGE}:${tag}`;
9438
9467
  try {
9439
9468
  execSync2(`docker image inspect ${imageName} > /dev/null 2>&1`, {
@@ -9450,18 +9479,24 @@ function commitAsImage(containerName, imageName) {
9450
9479
  const now = (/* @__PURE__ */ new Date()).toISOString();
9451
9480
  execSync2(`docker commit --change 'LABEL ${LABEL_PREFIX}=true' --change 'LABEL ${LABEL_PREFIX}.created-at=${now}' --change 'LABEL ${LABEL_PREFIX}.base-digest=${baseDigest}' ${containerName} ${imageName}`, { stdio: "pipe", timeout: 12e4 });
9452
9481
  }
9453
- function getBaseImageDigest() {
9454
- if (cachedBaseDigest)
9455
- return cachedBaseDigest;
9482
+ function getImageDigest(imageRef) {
9483
+ const memo = cachedImageDigests.get(imageRef);
9484
+ if (memo)
9485
+ return memo;
9456
9486
  try {
9457
- const digest = execSync2(`docker inspect ${BASE_IMAGE}:latest --format '{{.Id}}'`, { encoding: "utf-8", timeout: 5e3 }).trim();
9458
- cachedBaseDigest = digest.replace("sha256:", "").slice(0, 16);
9459
- return cachedBaseDigest;
9487
+ const digest = execSync2(`docker inspect ${imageRef} --format '{{.Id}}'`, { encoding: "utf-8", timeout: 5e3 }).trim();
9488
+ const short = digest.replace("sha256:", "").slice(0, 16);
9489
+ cachedImageDigests.set(imageRef, short);
9490
+ return short;
9460
9491
  } catch {
9461
- cachedBaseDigest = crypto3.createHash("sha256").update("unknown").digest("hex").slice(0, 16);
9462
- return cachedBaseDigest;
9492
+ const fallback = crypto3.createHash("sha256").update(imageRef).digest("hex").slice(0, 16);
9493
+ cachedImageDigests.set(imageRef, fallback);
9494
+ return fallback;
9463
9495
  }
9464
9496
  }
9497
+ function getBaseImageDigest() {
9498
+ return getImageDigest(`${BASE_IMAGE}:latest`);
9499
+ }
9465
9500
  function sanitizeTag(raw) {
9466
9501
  let tag = raw.toLowerCase().replace(/[^a-z0-9._-]/g, "-");
9467
9502
  if (tag.length > MAX_TAG_LENGTH) {
@@ -9470,13 +9505,14 @@ function sanitizeTag(raw) {
9470
9505
  }
9471
9506
  return tag;
9472
9507
  }
9473
- var BASE_IMAGE, LABEL_PREFIX, MAX_TAG_LENGTH, cachedBaseDigest;
9508
+ var BASE_IMAGE, LABEL_PREFIX, MAX_TAG_LENGTH, cachedImageDigests;
9474
9509
  var init_stack_image = __esm({
9475
9510
  "../core/dist/world/stack-image.js"() {
9476
9511
  "use strict";
9477
9512
  BASE_IMAGE = "olam-devbox";
9478
9513
  LABEL_PREFIX = "olam.stack-image";
9479
9514
  MAX_TAG_LENGTH = 128;
9515
+ cachedImageDigests = /* @__PURE__ */ new Map();
9480
9516
  }
9481
9517
  });
9482
9518
 
@@ -10485,6 +10521,81 @@ var init_loader2 = __esm({
10485
10521
  }
10486
10522
  });
10487
10523
 
10524
+ // ../core/dist/world/auto-dispatch-task.js
10525
+ async function probeHealth(containerName, dockerExec, budgetMs, sleep6) {
10526
+ const deadline = Date.now() + budgetMs;
10527
+ const cadenceMs = 100;
10528
+ let lastErr = "";
10529
+ while (Date.now() < deadline) {
10530
+ try {
10531
+ const out = dockerExec(containerName, `curl -s -o /dev/null -w '%{http_code}' http://localhost:8080/health`).trim();
10532
+ if (out === "200")
10533
+ return;
10534
+ lastErr = `HTTP ${out || "(no response)"}`;
10535
+ } catch (err) {
10536
+ lastErr = err instanceof Error ? err.message.slice(0, 200) : String(err).slice(0, 200);
10537
+ }
10538
+ await sleep6(cadenceMs);
10539
+ }
10540
+ throw new TaskDispatchError(`container-cp /health did not return 200 within ${budgetMs}ms`, "health-probe", lastErr || "no response");
10541
+ }
10542
+ function startAgent(containerName, dockerExec) {
10543
+ const out = dockerExec(containerName, `curl -s -o /dev/null -w '%{http_code}' -X POST http://localhost:8080/session/start-agent`).trim();
10544
+ if (out !== "200" && out !== "202") {
10545
+ throw new TaskDispatchError(`/session/start-agent returned HTTP ${out || "(no response)"}`, "start-agent", `out=${out}`);
10546
+ }
10547
+ }
10548
+ function dispatch(containerName, prompt, dockerExec) {
10549
+ const body = JSON.stringify({ prompt });
10550
+ const b64 = Buffer.from(body, "utf8").toString("base64");
10551
+ const tmpFile = "/tmp/olam-auto-dispatch.json";
10552
+ dockerExec(containerName, `echo '${b64}' | base64 -d > ${tmpFile}`);
10553
+ const out = dockerExec(containerName, `curl -s -o /dev/null -w '%{http_code}' -X POST -H 'Content-Type: application/json' -d @${tmpFile} http://localhost:8080/dispatch`).trim();
10554
+ if (out !== "202" && out !== "200") {
10555
+ throw new TaskDispatchError(`/dispatch returned HTTP ${out || "(no response)"}`, "dispatch-post", `out=${out}`);
10556
+ }
10557
+ }
10558
+ function composeTaskPrompt(task, policies, formatPoliciesBrief2) {
10559
+ if (policies.length === 0 || !formatPoliciesBrief2)
10560
+ return task;
10561
+ const seen = /* @__PURE__ */ new Set();
10562
+ const unique = policies.filter((p) => {
10563
+ if (seen.has(p.id))
10564
+ return false;
10565
+ seen.add(p.id);
10566
+ return true;
10567
+ });
10568
+ if (unique.length === 0)
10569
+ return task;
10570
+ return `${formatPoliciesBrief2(unique)}## Your task
10571
+ ${task}`;
10572
+ }
10573
+ async function autoDispatchTask(opts) {
10574
+ const { containerName, task, policies, dockerExec, healthBudgetMs = 15e3, sleep: sleep6 = DEFAULT_SLEEP, logger = console, formatPoliciesBrief: formatPoliciesBrief2 } = opts;
10575
+ const finalPrompt = composeTaskPrompt(task, policies, formatPoliciesBrief2);
10576
+ await probeHealth(containerName, dockerExec, healthBudgetMs, sleep6);
10577
+ startAgent(containerName, dockerExec);
10578
+ dispatch(containerName, finalPrompt, dockerExec);
10579
+ logger.log(`[world] Task auto-dispatched (${finalPrompt.length} chars)`);
10580
+ }
10581
+ var TaskDispatchError, DEFAULT_SLEEP;
10582
+ var init_auto_dispatch_task = __esm({
10583
+ "../core/dist/world/auto-dispatch-task.js"() {
10584
+ "use strict";
10585
+ TaskDispatchError = class extends Error {
10586
+ kind;
10587
+ detail;
10588
+ constructor(message, kind, detail) {
10589
+ super(message);
10590
+ this.kind = kind;
10591
+ this.detail = detail;
10592
+ this.name = "TaskDispatchError";
10593
+ }
10594
+ };
10595
+ DEFAULT_SLEEP = (ms) => new Promise((resolve15) => setTimeout(resolve15, ms));
10596
+ }
10597
+ });
10598
+
10488
10599
  // ../core/dist/global-config/schema.js
10489
10600
  function isAbsoluteOrTilde(p) {
10490
10601
  return p.startsWith("/") || p === "~" || p.startsWith("~/");
@@ -11058,6 +11169,7 @@ var manager_exports = {};
11058
11169
  __export(manager_exports, {
11059
11170
  AuthPreflightError: () => AuthPreflightError,
11060
11171
  BotIdentityError: () => BotIdentityError,
11172
+ TaskDispatchError: () => TaskDispatchError,
11061
11173
  WorkspaceNotFoundError: () => WorkspaceNotFoundError,
11062
11174
  WorldManager: () => WorldManager,
11063
11175
  applyPostgresNetworkOverrides: () => applyPostgresNetworkOverrides,
@@ -11858,6 +11970,8 @@ var init_manager = __esm({
11858
11970
  init_secrets_fetcher();
11859
11971
  init_olam_yaml();
11860
11972
  init_loader2();
11973
+ init_auto_dispatch_task();
11974
+ init_auto_dispatch_task();
11861
11975
  init_runbooks();
11862
11976
  init_store2();
11863
11977
  init_bridge();
@@ -12161,9 +12275,32 @@ ${detail}`);
12161
12275
  }
12162
12276
  }
12163
12277
  const repoSecretUrls = enrichedRepos.filter((r) => typeof r.manifest?.secrets === "string" && r.manifest.secrets.length > 0).map((r) => ({ repoName: r.name, secretsUrl: r.manifest.secrets }));
12164
- let stackCacheHit = false;
12165
12278
  let selectedImage;
12279
+ let cacheArchOverride;
12280
+ const selected = selectDevboxImageForWorld({
12281
+ config: this.config,
12282
+ repos,
12283
+ worldspec: opts.worldspec
12284
+ });
12285
+ if (selected) {
12286
+ selectedImage = selected.image;
12287
+ cacheArchOverride = selected.cacheArch;
12288
+ if (selected.source === "worldspec") {
12289
+ console.log(`[WorldManager] worldspec override \u2014 using ${selected.image} (tag=${selected.tag})`);
12290
+ } else {
12291
+ console.log(`[WorldManager] image_selector matched \u2014 using ${selected.image} (tag=${selected.tag}${selected.cacheArch ? `, cache_arch=${selected.cacheArch}` : ""})`);
12292
+ }
12293
+ } else {
12294
+ const hasRailsRepo = repos.some((r) => r.type === "rails");
12295
+ if (hasRailsRepo) {
12296
+ selectedImage = resolveDevboxImage(this.config, "amd64");
12297
+ cacheArchOverride = "x64";
12298
+ console.log(`[WorldManager] Rails repo detected \u2014 using ${selectedImage} + x64 mise-cache (Rosetta path)`);
12299
+ }
12300
+ }
12301
+ let stackCacheHit = false;
12166
12302
  const preDetectedStacks = /* @__PURE__ */ new Map();
12303
+ const cacheBaseRef = selectedImage ?? "olam-devbox:latest";
12167
12304
  if (this.provider.capabilities.supportsCustomImages) {
12168
12305
  try {
12169
12306
  const hostExec = makeHostExecFn();
@@ -12178,7 +12315,7 @@ ${detail}`);
12178
12315
  }
12179
12316
  const runtimes = collectUniqueRuntimes(Array.from(preDetectedStacks.values()));
12180
12317
  if (runtimes.size > 0) {
12181
- const cacheResult = lookupCachedImage(runtimes);
12318
+ const cacheResult = lookupCachedImage(runtimes, cacheBaseRef);
12182
12319
  if (cacheResult.hit) {
12183
12320
  selectedImage = cacheResult.imageName;
12184
12321
  stackCacheHit = true;
@@ -12192,30 +12329,6 @@ ${detail}`);
12192
12329
  console.warn(`[WorldManager] host-side stack detection failed: ${msg}`);
12193
12330
  }
12194
12331
  }
12195
- let cacheArchOverride;
12196
- if (!stackCacheHit) {
12197
- const selected = selectDevboxImageForWorld({
12198
- config: this.config,
12199
- repos,
12200
- worldspec: opts.worldspec
12201
- });
12202
- if (selected) {
12203
- selectedImage = selected.image;
12204
- cacheArchOverride = selected.cacheArch;
12205
- if (selected.source === "worldspec") {
12206
- console.log(`[WorldManager] worldspec override \u2014 using ${selected.image} (tag=${selected.tag})`);
12207
- } else {
12208
- console.log(`[WorldManager] image_selector matched \u2014 using ${selected.image} (tag=${selected.tag}${selected.cacheArch ? `, cache_arch=${selected.cacheArch}` : ""})`);
12209
- }
12210
- } else {
12211
- const hasRailsRepo = repos.some((r) => r.type === "rails");
12212
- if (hasRailsRepo) {
12213
- selectedImage = resolveDevboxImage(this.config, "amd64");
12214
- cacheArchOverride = "x64";
12215
- console.log(`[WorldManager] Rails repo detected \u2014 using ${selectedImage} + x64 mise-cache (Rosetta path)`);
12216
- }
12217
- }
12218
- }
12219
12332
  const appPorts = [];
12220
12333
  for (const repo of enrichedRepos) {
12221
12334
  const manifestPort = repo.manifest?.app?.port;
@@ -12369,12 +12482,22 @@ ${detail}`);
12369
12482
  ...extraNetworks ? { extraNetworks } : {},
12370
12483
  // Closes SEC-002 residual: hybrid worlds get a tmpfs mount at
12371
12484
  // /run/olam to receive the per-world postgres credentials. Size
12372
- // 256K is generous for a few env files; mode 0700 means only
12373
- // root (the container's PID 1, which then drops to the app user
12374
- // via the entrypoint) can list the directory. Non-hybrid worlds
12375
- // skip this entirely no behavior change.
12485
+ // 256K is generous for a few env files; mode 0700 keeps the dir
12486
+ // owner-only. uid/gid 999 matches the `olam` user from the Phase
12487
+ // E E4 base image contract — without setting these, the tmpfs is
12488
+ // root-owned and `docker exec` (which runs as the image's USER,
12489
+ // i.e. olam) hits EACCES on credential writes. Non-hybrid worlds
12490
+ // skip the mount entirely — no behavior change.
12376
12491
  ...tmpfsPostgresCredContent ? {
12377
- tmpfsMounts: [{ target: "/run/olam", size: 256 * 1024, mode: 448 }],
12492
+ tmpfsMounts: [
12493
+ {
12494
+ target: "/run/olam",
12495
+ size: 256 * 1024,
12496
+ mode: 448,
12497
+ uid: 999,
12498
+ gid: 999
12499
+ }
12500
+ ],
12378
12501
  tmpfsCredentialWrites: [
12379
12502
  { path: "/run/olam/postgres.env", content: tmpfsPostgresCredContent }
12380
12503
  ]
@@ -12586,48 +12709,38 @@ ${detail}`);
12586
12709
  }
12587
12710
  }
12588
12711
  }
12589
- if (credentialsInjected.claude) {
12590
- try {
12591
- execSync5(`docker exec ${containerName} curl -sf -X POST http://localhost:8080/session/start-agent 2>/dev/null || true`, { stdio: "pipe", timeout: 45e3 });
12592
- } catch {
12593
- }
12594
- if (opts.task) {
12595
- let taskWithPolicies = opts.task;
12712
+ if (opts.task) {
12713
+ const allPolicies = repos.flatMap((repo) => {
12714
+ const repoWorktree = path24.join(workspacePath, repo.name);
12596
12715
  try {
12597
- const allPolicies = repos.flatMap((repo) => {
12598
- const repoWorktree = path24.join(workspacePath, repo.name);
12599
- return loadPolicies(repoWorktree);
12600
- });
12601
- const seen = /* @__PURE__ */ new Set();
12602
- const uniquePolicies = allPolicies.filter((p) => {
12603
- if (seen.has(p.id))
12604
- return false;
12605
- seen.add(p.id);
12606
- return true;
12607
- });
12608
- if (uniquePolicies.length > 0) {
12609
- const brief = formatPoliciesBrief(uniquePolicies);
12610
- taskWithPolicies = `${brief}## Your task
12611
- ${opts.task}`;
12612
- execSync5(`docker exec ${containerName} mkdir -p /home/olam/.olam/policies`, { stdio: "pipe", timeout: 1e4 });
12613
- for (const repo of repos) {
12614
- const policiesDir = path24.join(workspacePath, repo.name, ".olam", "policies");
12615
- if (fs23.existsSync(policiesDir)) {
12616
- execSync5(`docker cp "${policiesDir}/." "${containerName}:/home/olam/.olam/policies/"`, { stdio: "pipe", timeout: 15e3 });
12617
- }
12618
- }
12619
- }
12716
+ return loadPolicies(repoWorktree);
12620
12717
  } catch (err) {
12621
12718
  const msg = err instanceof Error ? err.message : String(err);
12622
- console.warn(`[world] policy injection failed (non-fatal): ${msg}`);
12719
+ console.warn(`[world] policy load failed for ${repo.name} (non-fatal): ${msg}`);
12720
+ return [];
12623
12721
  }
12722
+ });
12723
+ if (allPolicies.length > 0) {
12624
12724
  try {
12625
- const payload = JSON.stringify({ prompt: taskWithPolicies }).replace(/'/g, "'\\''");
12626
- execSync5(`docker exec ${containerName} curl -sf -X POST -H 'Content-Type: application/json' -d '${payload}' http://localhost:8080/dispatch 2>/dev/null || true`, { stdio: "pipe", timeout: 3e4 });
12627
- console.log("[world] Task auto-dispatched");
12628
- } catch {
12725
+ execSync5(`docker exec ${containerName} mkdir -p /home/olam/.olam/policies`, { stdio: "pipe", timeout: 1e4 });
12726
+ for (const repo of repos) {
12727
+ const policiesDir = path24.join(workspacePath, repo.name, ".olam", "policies");
12728
+ if (fs23.existsSync(policiesDir)) {
12729
+ execSync5(`docker cp "${policiesDir}/." "${containerName}:/home/olam/.olam/policies/"`, { stdio: "pipe", timeout: 15e3 });
12730
+ }
12731
+ }
12732
+ } catch (err) {
12733
+ const msg = err instanceof Error ? err.message : String(err);
12734
+ console.warn(`[world] policy copy failed (non-fatal): ${msg}`);
12629
12735
  }
12630
12736
  }
12737
+ await autoDispatchTask({
12738
+ containerName,
12739
+ task: opts.task,
12740
+ policies: allPolicies,
12741
+ dockerExec: this.dockerExec,
12742
+ formatPoliciesBrief
12743
+ });
12631
12744
  }
12632
12745
  const controlPlanePort = 19080 + portOffset;
12633
12746
  const dashboardPort = controlPlanePort;
@@ -14111,7 +14224,7 @@ function isCloudflaredAvailable() {
14111
14224
  }
14112
14225
  }
14113
14226
  function startTunnel(port2) {
14114
- return new Promise((resolve14, reject) => {
14227
+ return new Promise((resolve15, reject) => {
14115
14228
  const child = spawn3("cloudflared", ["tunnel", "--url", `http://localhost:${port2}`], {
14116
14229
  stdio: ["ignore", "pipe", "pipe"],
14117
14230
  detached: false
@@ -14133,7 +14246,7 @@ function startTunnel(port2) {
14133
14246
  if (match2) {
14134
14247
  resolved = true;
14135
14248
  clearTimeout(timeout);
14136
- resolve14(match2[0]);
14249
+ resolve15(match2[0]);
14137
14250
  }
14138
14251
  }
14139
14252
  child.stdout?.on("data", scan);
@@ -14220,8 +14333,8 @@ var init_dashboard = __esm({
14220
14333
  }
14221
14334
  throw err;
14222
14335
  }
14223
- await new Promise((resolve14, reject) => {
14224
- this.server.on("listening", resolve14);
14336
+ await new Promise((resolve15, reject) => {
14337
+ this.server.on("listening", resolve15);
14225
14338
  this.server.on("error", reject);
14226
14339
  });
14227
14340
  this.info = { localUrl: `http://localhost:${port2}` };
@@ -14267,8 +14380,8 @@ var init_dashboard = __esm({
14267
14380
  async stop() {
14268
14381
  stopTunnel();
14269
14382
  if (this.server) {
14270
- await new Promise((resolve14) => {
14271
- this.server.close(() => resolve14());
14383
+ await new Promise((resolve15) => {
14384
+ this.server.close(() => resolve15());
14272
14385
  });
14273
14386
  this.server = null;
14274
14387
  }
@@ -14668,7 +14781,7 @@ async function gatherProbeFailureDiagnostics() {
14668
14781
  }
14669
14782
  return { bootstrapStatus, containerStatus };
14670
14783
  }
14671
- async function probeHealth() {
14784
+ async function probeHealth2() {
14672
14785
  try {
14673
14786
  const res = await fetch(`http://127.0.0.1:${HOST_CP_PORT}/health`, {
14674
14787
  signal: AbortSignal.timeout(2e3)
@@ -14726,7 +14839,7 @@ async function startHostCp(opts) {
14726
14839
  async function handleStart(opts) {
14727
14840
  const existing = await findHostCpContainer();
14728
14841
  if (existing && existing.state === "running") {
14729
- const health2 = await probeHealth();
14842
+ const health2 = await probeHealth2();
14730
14843
  if (health2) {
14731
14844
  printSuccess(`Host CP already running at http://127.0.0.1:${HOST_CP_PORT}`);
14732
14845
  printInfo("Container", existing.id);
@@ -14805,7 +14918,7 @@ async function handleStart(opts) {
14805
14918
  const deadline = Date.now() + 1e4;
14806
14919
  let healthy = false;
14807
14920
  while (Date.now() < deadline) {
14808
- const h = await probeHealth();
14921
+ const h = await probeHealth2();
14809
14922
  if (h) {
14810
14923
  healthy = true;
14811
14924
  break;
@@ -14858,7 +14971,7 @@ async function handleStop() {
14858
14971
  }
14859
14972
  async function buildStatusReport() {
14860
14973
  const container = await findHostCpContainer();
14861
- const health2 = await probeHealth();
14974
+ const health2 = await probeHealth2();
14862
14975
  const tokenFile = tokenPath();
14863
14976
  const tokenPresent = fs26.existsSync(tokenFile);
14864
14977
  let tokenModeOk = false;
@@ -15354,7 +15467,7 @@ async function ensureOlamPostgresSingleton(options = {}) {
15354
15467
  };
15355
15468
  }
15356
15469
  function sleep3(ms) {
15357
- return new Promise((resolve14) => setTimeout(resolve14, ms));
15470
+ return new Promise((resolve15) => setTimeout(resolve15, ms));
15358
15471
  }
15359
15472
  var DEFAULT_POSTGRES_NETWORK, DEFAULT_POSTGRES_CONTAINER, DEFAULT_POSTGRES_HOST_PORT, DEFAULT_POSTGRES_IMAGE, DEFAULT_POSTGRES_USER, DEFAULT_POSTGRES_PASSWORD, DEFAULT_POSTGRES_DB, DEFAULT_POSTGRES_READY_TIMEOUT_MS;
15360
15473
  var init_postgres_init_helpers = __esm({
@@ -16627,10 +16740,10 @@ async function confirm(message) {
16627
16740
  if (!process.stdin.isTTY) return true;
16628
16741
  const { createInterface: createInterface7 } = await import("node:readline");
16629
16742
  const rl = createInterface7({ input: process.stdin, output: process.stdout });
16630
- return new Promise((resolve14) => {
16743
+ return new Promise((resolve15) => {
16631
16744
  rl.question(`${message} [y/N] `, (answer) => {
16632
16745
  rl.close();
16633
- resolve14(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
16746
+ resolve15(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
16634
16747
  });
16635
16748
  });
16636
16749
  }
@@ -17008,6 +17121,17 @@ var KgServiceContainerController = class {
17008
17121
  // Mount the operator's KG root (OLAM_HOME-aware) so /build can persist graphs.
17009
17122
  "--volume",
17010
17123
  `${kgData}:${KG_SERVICE_VOLUME_TARGET}`,
17124
+ // kg-service-container-v2 Phase F: read-only mount of $HOME so /build
17125
+ // can ingest any project under the operator's home as the source repo.
17126
+ // Read-only is sufficient: server-side scratch in /tmp/ holds the
17127
+ // working copy + receives graphify's output before persisting to
17128
+ // /kg-data. Operator's source tree is never written. Mount path
17129
+ // `/host-home` is referenced by kg-build.ts when translating cwd
17130
+ // → container-side path. Closes Samuel's multi-submodule cp-r bug
17131
+ // by construction: copy runs INSIDE the container as root, not on
17132
+ // the host with the operator's UID hitting locked .git/objects/pack/*.
17133
+ "--volume",
17134
+ `${process.env.HOME ?? "/root"}:/host-home:ro`,
17011
17135
  this.imageTag
17012
17136
  ],
17013
17137
  { stdio: "pipe" }
@@ -17070,7 +17194,7 @@ var KgServiceContainerController = class {
17070
17194
  }
17071
17195
  };
17072
17196
  function sleep4(ms) {
17073
- return new Promise((resolve14) => setTimeout(resolve14, ms));
17197
+ return new Promise((resolve15) => setTimeout(resolve15, ms));
17074
17198
  }
17075
17199
 
17076
17200
  // src/commands/services.ts
@@ -17158,7 +17282,7 @@ var McpAuthContainerController = class {
17158
17282
  }
17159
17283
  };
17160
17284
  function sleep5(ms) {
17161
- return new Promise((resolve14) => setTimeout(resolve14, ms));
17285
+ return new Promise((resolve15) => setTimeout(resolve15, ms));
17162
17286
  }
17163
17287
  function dumpContainerLogs(container, tail = 40) {
17164
17288
  try {
@@ -17854,10 +17978,10 @@ var HOST_CP_URL = "http://127.0.0.1:19000";
17854
17978
  async function readHostCpTokenForCreate() {
17855
17979
  try {
17856
17980
  const { default: fs56 } = await import("node:fs");
17857
- const { default: os31 } = await import("node:os");
17981
+ const { default: os32 } = await import("node:os");
17858
17982
  const { default: path60 } = await import("node:path");
17859
17983
  const tp = path60.join(
17860
- process.env.OLAM_HOME ?? path60.join(os31.homedir(), ".olam"),
17984
+ process.env.OLAM_HOME ?? path60.join(os32.homedir(), ".olam"),
17861
17985
  "host-cp.token"
17862
17986
  );
17863
17987
  if (!fs56.existsSync(tp)) return null;
@@ -18224,6 +18348,23 @@ ${pc10.cyan("Host CP UI:")} ${worldUrl}`);
18224
18348
  }
18225
18349
  }
18226
18350
  } catch (err) {
18351
+ if (err instanceof TaskDispatchError) {
18352
+ spinner.fail("Auto-dispatch failed (world exists but task was not sent)");
18353
+ printError(
18354
+ `Task auto-dispatch failed (${err.kind}): ${err.message}`
18355
+ );
18356
+ console.log(
18357
+ ` ${pc10.yellow("Remedy:")} ${pc10.cyan(`olam dispatch ${resolvedName} '<your task>'`)}`
18358
+ );
18359
+ console.log(
18360
+ ` ${pc10.dim(`(or use 'olam list' to find the world ID if the name is ambiguous)`)}`
18361
+ );
18362
+ if (err.detail) {
18363
+ console.log(` ${pc10.dim(`detail: ${err.detail}`)}`);
18364
+ }
18365
+ process.exitCode = 1;
18366
+ return;
18367
+ }
18227
18368
  spinner.fail("Failed to create world");
18228
18369
  if (err instanceof AuthPreflightError) {
18229
18370
  printError(err.message);
@@ -18253,9 +18394,9 @@ function defaultNameFromPrompt(prompt) {
18253
18394
  async function readHostCpToken3() {
18254
18395
  try {
18255
18396
  const { default: fs56 } = await import("node:fs");
18256
- const { default: os31 } = await import("node:os");
18397
+ const { default: os32 } = await import("node:os");
18257
18398
  const { default: path60 } = await import("node:path");
18258
- const tp = path60.join(os31.homedir(), ".olam", "host-cp.token");
18399
+ const tp = path60.join(os32.homedir(), ".olam", "host-cp.token");
18259
18400
  if (!fs56.existsSync(tp)) return null;
18260
18401
  const raw = fs56.readFileSync(tp, "utf-8").trim();
18261
18402
  return raw.length > 0 ? raw : null;
@@ -18925,14 +19066,14 @@ function printTable(entries) {
18925
19066
  async function confirmInteractive() {
18926
19067
  process.stdout.write(" Type `yes` to proceed: ");
18927
19068
  const buf = [];
18928
- return new Promise((resolve14) => {
19069
+ return new Promise((resolve15) => {
18929
19070
  const onData = (chunk) => {
18930
19071
  buf.push(chunk);
18931
19072
  if (Buffer.concat(buf).toString("utf-8").includes("\n")) {
18932
19073
  process.stdin.removeListener("data", onData);
18933
19074
  process.stdin.pause();
18934
19075
  const answer = Buffer.concat(buf).toString("utf-8").trim();
18935
- resolve14(answer.toLowerCase() === "yes");
19076
+ resolve15(answer.toLowerCase() === "yes");
18936
19077
  }
18937
19078
  };
18938
19079
  process.stdin.resume();
@@ -24647,10 +24788,10 @@ async function confirm2(message) {
24647
24788
  if (!process.stdin.isTTY) return true;
24648
24789
  const { createInterface: createInterface7 } = await import("node:readline");
24649
24790
  const rl = createInterface7({ input: process.stdin, output: process.stdout });
24650
- return new Promise((resolve14) => {
24791
+ return new Promise((resolve15) => {
24651
24792
  rl.question(`${message} [y/N] `, (answer) => {
24652
24793
  rl.close();
24653
- resolve14(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
24794
+ resolve15(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
24654
24795
  });
24655
24796
  });
24656
24797
  }
@@ -27153,9 +27294,9 @@ function appendIdempotent(opts) {
27153
27294
  }
27154
27295
  function resolveShellRc(home, shellEnv) {
27155
27296
  if (!shellEnv) return null;
27156
- const basename6 = path45.basename(shellEnv);
27157
- if (basename6 === "zsh") return path45.join(home, ".zshrc");
27158
- if (basename6 === "bash") return path45.join(home, ".bashrc");
27297
+ const basename7 = path45.basename(shellEnv);
27298
+ if (basename7 === "zsh") return path45.join(home, ".zshrc");
27299
+ if (basename7 === "bash") return path45.join(home, ".bashrc");
27159
27300
  return null;
27160
27301
  }
27161
27302
 
@@ -27167,10 +27308,10 @@ var NEXT_STEPS_DOCS = [
27167
27308
  "docs/architecture/manifest-spec.md \u2014 per-repo .adb.yaml schema",
27168
27309
  "docs/architecture/config-spec.md \u2014 workspace .olam/config.yaml schema"
27169
27310
  ];
27170
- var defaultSpawn = (cmd, args) => new Promise((resolve14) => {
27311
+ var defaultSpawn = (cmd, args) => new Promise((resolve15) => {
27171
27312
  const child = spawn5(cmd, [...args], { stdio: "inherit" });
27172
- child.on("exit", (code) => resolve14({ status: code }));
27173
- child.on("error", () => resolve14({ status: 1 }));
27313
+ child.on("exit", (code) => resolve15({ status: code }));
27314
+ child.on("error", () => resolve15({ status: 1 }));
27174
27315
  });
27175
27316
  var defaultPrompt = (question, defaultYes) => {
27176
27317
  if (!process.stdin.isTTY) {
@@ -27180,18 +27321,18 @@ var defaultPrompt = (question, defaultYes) => {
27180
27321
  );
27181
27322
  return Promise.resolve(defaultYes);
27182
27323
  }
27183
- return new Promise((resolve14) => {
27324
+ return new Promise((resolve15) => {
27184
27325
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
27185
27326
  const suffix = defaultYes ? " [Y/n]: " : " [y/N]: ";
27186
27327
  rl.question(`${question}${suffix}`, (answer) => {
27187
27328
  rl.close();
27188
27329
  const t = answer.trim().toLowerCase();
27189
- if (t === "") resolve14(defaultYes);
27190
- else if (t === "y" || t === "yes") resolve14(true);
27191
- else if (t === "n" || t === "no") resolve14(false);
27192
- else resolve14(defaultYes);
27330
+ if (t === "") resolve15(defaultYes);
27331
+ else if (t === "y" || t === "yes") resolve15(true);
27332
+ else if (t === "n" || t === "no") resolve15(false);
27333
+ else resolve15(defaultYes);
27193
27334
  });
27194
- rl.on("close", () => resolve14(defaultYes));
27335
+ rl.on("close", () => resolve15(defaultYes));
27195
27336
  });
27196
27337
  };
27197
27338
  async function phase1SystemCheck(deps) {
@@ -28415,7 +28556,7 @@ function registerMcpLogin(cmd) {
28415
28556
  init_output();
28416
28557
  import * as readline2 from "node:readline";
28417
28558
  async function readTokenSilent(prompt) {
28418
- return new Promise((resolve14, reject) => {
28559
+ return new Promise((resolve15, reject) => {
28419
28560
  const rl = readline2.createInterface({
28420
28561
  input: process.stdin,
28421
28562
  output: process.stdout,
@@ -28433,7 +28574,7 @@ async function readTokenSilent(prompt) {
28433
28574
  process.stdin.removeListener("data", onData);
28434
28575
  process.stdout.write("\n");
28435
28576
  rl.close();
28436
- resolve14(token);
28577
+ resolve15(token);
28437
28578
  } else if (char === "") {
28438
28579
  if (process.stdin.isTTY) process.stdin.setRawMode(false);
28439
28580
  process.stdin.removeListener("data", onData);
@@ -28708,7 +28849,7 @@ async function discoverMcpSources(repoPaths) {
28708
28849
  import { spawn as spawn7 } from "node:child_process";
28709
28850
  var VALIDATION_TIMEOUT_MS = 5e3;
28710
28851
  async function validateMcpEntry(entry) {
28711
- return new Promise((resolve14) => {
28852
+ return new Promise((resolve15) => {
28712
28853
  let stdout = "";
28713
28854
  let timedOut = false;
28714
28855
  let child;
@@ -28718,7 +28859,7 @@ async function validateMcpEntry(entry) {
28718
28859
  env: { ...process.env, ...entry.env ?? {} }
28719
28860
  });
28720
28861
  } catch (err) {
28721
- resolve14({
28862
+ resolve15({
28722
28863
  name: entry.name,
28723
28864
  validated: false,
28724
28865
  reason: err instanceof Error ? err.message : "spawn failed"
@@ -28735,11 +28876,11 @@ async function validateMcpEntry(entry) {
28735
28876
  child.on("close", (code) => {
28736
28877
  clearTimeout(timer);
28737
28878
  if (timedOut) {
28738
- resolve14({ name: entry.name, validated: false, reason: "timeout (5s)" });
28879
+ resolve15({ name: entry.name, validated: false, reason: "timeout (5s)" });
28739
28880
  return;
28740
28881
  }
28741
28882
  const validated = code === 0 && stdout.trim().length > 0;
28742
- resolve14({
28883
+ resolve15({
28743
28884
  name: entry.name,
28744
28885
  validated,
28745
28886
  reason: validated ? "ok" : `exit code ${code ?? "null"}`
@@ -28747,7 +28888,7 @@ async function validateMcpEntry(entry) {
28747
28888
  });
28748
28889
  child.on("error", (err) => {
28749
28890
  clearTimeout(timer);
28750
- resolve14({ name: entry.name, validated: false, reason: err.message });
28891
+ resolve15({ name: entry.name, validated: false, reason: err.message });
28751
28892
  });
28752
28893
  });
28753
28894
  }
@@ -28762,11 +28903,11 @@ async function multiSelectPicker(entries) {
28762
28903
  );
28763
28904
  });
28764
28905
  console.log("\n" + pc29.dim('Enter numbers to import (e.g. 1,2,3 or "all" or Enter to skip):'));
28765
- const answer = await new Promise((resolve14) => {
28906
+ const answer = await new Promise((resolve15) => {
28766
28907
  const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
28767
28908
  rl.question("> ", (ans) => {
28768
28909
  rl.close();
28769
- resolve14(ans.trim());
28910
+ resolve15(ans.trim());
28770
28911
  });
28771
28912
  });
28772
28913
  if (!answer || answer === "") return [];
@@ -29051,6 +29192,12 @@ async function runMemoryStart() {
29051
29192
  env: {
29052
29193
  ...process.env,
29053
29194
  AGENTMEMORY_SECRET: secret,
29195
+ // Bind 0.0.0.0 so worlds inside Docker can reach the host service
29196
+ // via `host.docker.internal:3111`. Defaults to 127.0.0.1 in the
29197
+ // upstream iii-config, which would make the service host-only.
29198
+ // The bearer-secret gate above prevents non-authed access from
29199
+ // any other LAN host that can route to this machine.
29200
+ AGENTMEMORY_HOST: "0.0.0.0",
29054
29201
  PATH: `${OLAM_BIN_DIR}:${process.env.PATH ?? ""}`
29055
29202
  },
29056
29203
  stdio: ["ignore", logFd, logFd],
@@ -29244,8 +29391,8 @@ async function runMemoryLogs(opts) {
29244
29391
  args.push(MEMORY_LOG_PATH);
29245
29392
  printHeader(`olam memory logs (${opts.follow ? "follow" : `tail -n ${tailN}`})`);
29246
29393
  const child = spawn9("tail", args, { stdio: "inherit" });
29247
- return new Promise((resolve14) => {
29248
- child.on("exit", (code) => resolve14(code ?? 0));
29394
+ return new Promise((resolve15) => {
29395
+ child.on("exit", (code) => resolve15(code ?? 0));
29249
29396
  });
29250
29397
  }
29251
29398
  function registerMemoryLogs(cmd) {
@@ -29611,10 +29758,88 @@ function registerMemory(program2) {
29611
29758
  // src/commands/kg-build.ts
29612
29759
  init_storage_paths();
29613
29760
  init_workspace_name();
29761
+ import * as fs53 from "node:fs";
29762
+ import * as os30 from "node:os";
29763
+ import * as path57 from "node:path";
29764
+
29765
+ // ../core/dist/kg/kg-service-client.js
29766
+ var KG_SERVICE_PORT_DEFAULT = 9997;
29767
+ function port() {
29768
+ const env = process.env.OLAM_KG_SERVICE_PORT;
29769
+ if (!env)
29770
+ return KG_SERVICE_PORT_DEFAULT;
29771
+ const n = Number.parseInt(env, 10);
29772
+ return Number.isFinite(n) && n > 0 ? n : KG_SERVICE_PORT_DEFAULT;
29773
+ }
29774
+ function url(path60) {
29775
+ return `http://127.0.0.1:${port()}${path60}`;
29776
+ }
29777
+ function kgServiceHealthUrl() {
29778
+ return url("/health");
29779
+ }
29780
+ function kgServiceClassifyUrl() {
29781
+ return url("/classify");
29782
+ }
29783
+ function kgServiceStatusUrl() {
29784
+ return url("/status");
29785
+ }
29786
+ function kgServiceBuildUrl() {
29787
+ return url("/build");
29788
+ }
29789
+ var KgServiceUnreachableError = class extends Error {
29790
+ cause;
29791
+ constructor(message, cause) {
29792
+ super(message);
29793
+ this.cause = cause;
29794
+ this.name = "KgServiceUnreachableError";
29795
+ }
29796
+ };
29797
+ var DEFAULT_TIMEOUT_MS3 = 5e3;
29798
+ async function postJson(endpointUrl, body, timeoutMs = DEFAULT_TIMEOUT_MS3) {
29799
+ let res;
29800
+ try {
29801
+ res = await fetch(endpointUrl, {
29802
+ method: "POST",
29803
+ headers: { "Content-Type": "application/json" },
29804
+ body: JSON.stringify(body),
29805
+ signal: AbortSignal.timeout(timeoutMs)
29806
+ });
29807
+ } catch (err) {
29808
+ throw new KgServiceUnreachableError(`kg-service not reachable at ${endpointUrl} \u2014 run \`olam services up\` to start it.`, err);
29809
+ }
29810
+ if (!res.ok) {
29811
+ const errBody = await res.text().catch(() => "");
29812
+ throw new Error(`${endpointUrl} returned ${res.status}: ${errBody.slice(0, 500)}`);
29813
+ }
29814
+ return await res.json();
29815
+ }
29816
+ async function getJson(endpointUrl, timeoutMs = DEFAULT_TIMEOUT_MS3) {
29817
+ let res;
29818
+ try {
29819
+ res = await fetch(endpointUrl, { signal: AbortSignal.timeout(timeoutMs) });
29820
+ } catch (err) {
29821
+ throw new KgServiceUnreachableError(`kg-service not reachable at ${endpointUrl} \u2014 run \`olam services up\` to start it.`, err);
29822
+ }
29823
+ if (!res.ok) {
29824
+ throw new Error(`${endpointUrl} returned ${res.status}`);
29825
+ }
29826
+ return await res.json();
29827
+ }
29828
+ async function classify(req, opts = {}) {
29829
+ return postJson(kgServiceClassifyUrl(), req, opts.timeoutMs);
29830
+ }
29831
+ async function health(opts = {}) {
29832
+ return getJson(kgServiceHealthUrl(), opts.timeoutMs ?? 2e3);
29833
+ }
29834
+ async function status(opts = {}) {
29835
+ return getJson(kgServiceStatusUrl(), opts.timeoutMs);
29836
+ }
29837
+ async function build(req, opts = {}) {
29838
+ return postJson(kgServiceBuildUrl(), req, opts.timeoutMs ?? 9e5);
29839
+ }
29840
+
29841
+ // src/commands/kg-build.ts
29614
29842
  init_output();
29615
- import { spawnSync as spawnSync20 } from "node:child_process";
29616
- import fs53 from "node:fs";
29617
- import path57 from "node:path";
29618
29843
 
29619
29844
  // src/commands/kg-status.ts
29620
29845
  init_storage_paths();
@@ -29978,16 +30203,16 @@ async function runKgWatch(workspaceArg, opts, deps = {}) {
29978
30203
  process.on("SIGINT", () => forward("SIGINT"));
29979
30204
  process.on("SIGTERM", () => forward("SIGTERM"));
29980
30205
  }
29981
- return new Promise((resolve14) => {
30206
+ return new Promise((resolve15) => {
29982
30207
  child.on("exit", (code, signal) => {
29983
30208
  removePidFile(name);
29984
30209
  const exitCode = typeof code === "number" ? code : signal === "SIGINT" || signal === "SIGTERM" ? 0 : 1;
29985
- resolve14({ exitCode, pidWritten: true });
30210
+ resolve15({ exitCode, pidWritten: true });
29986
30211
  });
29987
30212
  child.on("error", (err) => {
29988
30213
  removePidFile(name);
29989
30214
  printError(`graphify subprocess error: ${err.message}`);
29990
- resolve14({ exitCode: 1, pidWritten: true });
30215
+ resolve15({ exitCode: 1, pidWritten: true });
29991
30216
  });
29992
30217
  });
29993
30218
  }
@@ -30002,78 +30227,6 @@ function registerKgWatchCommand(kg) {
30002
30227
 
30003
30228
  // src/commands/kg-classify.ts
30004
30229
  import pc31 from "picocolors";
30005
-
30006
- // ../core/dist/kg/kg-service-client.js
30007
- var KG_SERVICE_PORT_DEFAULT = 9997;
30008
- function port() {
30009
- const env = process.env.OLAM_KG_SERVICE_PORT;
30010
- if (!env)
30011
- return KG_SERVICE_PORT_DEFAULT;
30012
- const n = Number.parseInt(env, 10);
30013
- return Number.isFinite(n) && n > 0 ? n : KG_SERVICE_PORT_DEFAULT;
30014
- }
30015
- function url(path60) {
30016
- return `http://127.0.0.1:${port()}${path60}`;
30017
- }
30018
- function kgServiceHealthUrl() {
30019
- return url("/health");
30020
- }
30021
- function kgServiceClassifyUrl() {
30022
- return url("/classify");
30023
- }
30024
- function kgServiceStatusUrl() {
30025
- return url("/status");
30026
- }
30027
- var KgServiceUnreachableError = class extends Error {
30028
- cause;
30029
- constructor(message, cause) {
30030
- super(message);
30031
- this.cause = cause;
30032
- this.name = "KgServiceUnreachableError";
30033
- }
30034
- };
30035
- var DEFAULT_TIMEOUT_MS3 = 5e3;
30036
- async function postJson(endpointUrl, body, timeoutMs = DEFAULT_TIMEOUT_MS3) {
30037
- let res;
30038
- try {
30039
- res = await fetch(endpointUrl, {
30040
- method: "POST",
30041
- headers: { "Content-Type": "application/json" },
30042
- body: JSON.stringify(body),
30043
- signal: AbortSignal.timeout(timeoutMs)
30044
- });
30045
- } catch (err) {
30046
- throw new KgServiceUnreachableError(`kg-service not reachable at ${endpointUrl} \u2014 run \`olam services up\` to start it.`, err);
30047
- }
30048
- if (!res.ok) {
30049
- const errBody = await res.text().catch(() => "");
30050
- throw new Error(`${endpointUrl} returned ${res.status}: ${errBody.slice(0, 500)}`);
30051
- }
30052
- return await res.json();
30053
- }
30054
- async function getJson(endpointUrl, timeoutMs = DEFAULT_TIMEOUT_MS3) {
30055
- let res;
30056
- try {
30057
- res = await fetch(endpointUrl, { signal: AbortSignal.timeout(timeoutMs) });
30058
- } catch (err) {
30059
- throw new KgServiceUnreachableError(`kg-service not reachable at ${endpointUrl} \u2014 run \`olam services up\` to start it.`, err);
30060
- }
30061
- if (!res.ok) {
30062
- throw new Error(`${endpointUrl} returned ${res.status}`);
30063
- }
30064
- return await res.json();
30065
- }
30066
- async function classify(req, opts = {}) {
30067
- return postJson(kgServiceClassifyUrl(), req, opts.timeoutMs);
30068
- }
30069
- async function health(opts = {}) {
30070
- return getJson(kgServiceHealthUrl(), opts.timeoutMs ?? 2e3);
30071
- }
30072
- async function status(opts = {}) {
30073
- return getJson(kgServiceStatusUrl(), opts.timeoutMs);
30074
- }
30075
-
30076
- // src/commands/kg-classify.ts
30077
30230
  init_output();
30078
30231
  function registerKgClassifyCommand(kg) {
30079
30232
  kg.command("classify <question>").description("Route a question to kg | grep | both via the 4-layer classifier (kg-service required)").option("--workspace <name>", "Scope the L2 probe gate to a specific workspace graph").option("--json", "Emit raw JSON instead of the formatted summary").action(async (question, opts) => {
@@ -30542,60 +30695,24 @@ function registerKgUninstallHookCommand(kg) {
30542
30695
  }
30543
30696
 
30544
30697
  // src/commands/kg-build.ts
30545
- var DEFAULT_DEVBOX_IMAGE2 = "olam-devbox:latest";
30546
30698
  function resolveWorkspace(arg) {
30547
30699
  const cwd = process.cwd();
30548
30700
  const name = arg ?? path57.basename(cwd).toLowerCase();
30549
30701
  validateWorkspaceName(name);
30550
30702
  return { name, sourcePath: cwd };
30551
30703
  }
30552
- function copyWorkspaceToScratch(source, scratch) {
30553
- if (process.platform === "darwin") {
30554
- const r2 = spawnSync20("cp", ["-c", "-r", source + "/.", scratch], {
30555
- stdio: ["ignore", "ignore", "pipe"],
30556
- encoding: "utf-8"
30557
- });
30558
- if (r2.status === 0) return "cp-c-r-reflink";
30559
- }
30560
- const r = spawnSync20("cp", ["-r", source + "/.", scratch], {
30561
- stdio: ["ignore", "ignore", "pipe"],
30562
- encoding: "utf-8"
30563
- });
30564
- if (r.status !== 0) {
30565
- throw new Error(`cp -r failed: ${(r.stderr ?? "").trim() || r.error?.message}`);
30566
- }
30567
- return "cp-r";
30568
- }
30569
- function parseNodeCount(graphifyOutDir) {
30570
- const graphPath = path57.join(graphifyOutDir, "graph.json");
30571
- if (!fs53.existsSync(graphPath)) return null;
30572
- try {
30573
- const content = fs53.readFileSync(graphPath, "utf-8");
30574
- const data = JSON.parse(content);
30575
- return Array.isArray(data.nodes) ? data.nodes.length : null;
30576
- } catch {
30577
- return null;
30704
+ function toContainerPath(hostPath) {
30705
+ const home = os30.homedir();
30706
+ const resolved = path57.resolve(hostPath);
30707
+ if (!resolved.startsWith(home + path57.sep) && resolved !== home) {
30708
+ throw new Error(
30709
+ `source path "${resolved}" is not under $HOME (${home}). kg-service can only build repos that live under your home dir (it bind-mounts $HOME:/host-home:ro at start). Move the repo or set OLAM_HOME if you need a different root.`
30710
+ );
30578
30711
  }
30579
- }
30580
- function readGraphifyVersion(image) {
30581
- const r = spawnSync20(
30582
- "docker",
30583
- [
30584
- "run",
30585
- "--rm",
30586
- "--entrypoint=/opt/graphify-venv/bin/pip",
30587
- image,
30588
- "show",
30589
- "graphifyy"
30590
- ],
30591
- { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }
30592
- );
30593
- if (r.status !== 0) return "unknown";
30594
- const m = (r.stdout ?? "").match(/^Version:\s*(\S+)/m);
30595
- return m?.[1] ?? "unknown";
30712
+ const rel = path57.relative(home, resolved);
30713
+ return rel === "" ? "/host-home" : path57.posix.join("/host-home", rel.split(path57.sep).join("/"));
30596
30714
  }
30597
30715
  async function runKgBuild(workspaceArg, options = {}) {
30598
- const image = options.image ?? DEFAULT_DEVBOX_IMAGE2;
30599
30716
  let workspace;
30600
30717
  try {
30601
30718
  workspace = resolveWorkspace(workspaceArg);
@@ -30603,82 +30720,68 @@ async function runKgBuild(workspaceArg, options = {}) {
30603
30720
  printError(err instanceof Error ? err.message : String(err));
30604
30721
  return { exitCode: 2 };
30605
30722
  }
30723
+ let containerRepoPath;
30724
+ try {
30725
+ containerRepoPath = toContainerPath(workspace.sourcePath);
30726
+ } catch (err) {
30727
+ printError(err instanceof Error ? err.message : String(err));
30728
+ return { exitCode: 2 };
30729
+ }
30606
30730
  const outDir = kgPristinePath(workspace.name);
30607
- const scratchDir = path57.join(outDir, "scratch");
30608
30731
  fs53.mkdirSync(outDir, { recursive: true });
30609
- if (fs53.existsSync(scratchDir)) fs53.rmSync(scratchDir, { recursive: true, force: true });
30610
- fs53.mkdirSync(scratchDir);
30611
30732
  const human = !options.json;
30612
30733
  if (human) {
30613
30734
  printInfo("kg build", `workspace=${workspace.name} source=${workspace.sourcePath}`);
30614
- printInfo("kg build", `scratch=${scratchDir} \u2192 out=${outDir}/graphify-out/`);
30735
+ printInfo("kg build", `\u2192 kg-service /build (container path ${containerRepoPath}) \u2192 ${outDir}/graphify-out/`);
30615
30736
  }
30616
- const started = Date.now();
30617
- let scratchStrategy;
30737
+ let resp;
30618
30738
  try {
30619
- scratchStrategy = copyWorkspaceToScratch(workspace.sourcePath, scratchDir);
30620
- if (human) printInfo("kg build", `copied via ${scratchStrategy}`);
30621
- const dockerArgs = [
30622
- "run",
30623
- "--rm",
30624
- "-v",
30625
- `${scratchDir}:/work`,
30626
- "-w",
30627
- "/work",
30628
- "--entrypoint=graphify",
30629
- image,
30630
- "update",
30631
- "."
30632
- ];
30633
- const r = human ? spawnSync20("docker", dockerArgs, { stdio: "inherit" }) : spawnSync20("docker", dockerArgs, { stdio: ["ignore", "ignore", "pipe"] });
30634
- if (r.status !== 0) {
30635
- printError(`graphify update failed (exit ${r.status})`);
30636
- return { exitCode: r.status ?? 1 };
30637
- }
30638
- const scratchOut = path57.join(scratchDir, "graphify-out");
30639
- const finalOut = path57.join(outDir, "graphify-out");
30640
- if (!fs53.existsSync(scratchOut)) {
30641
- printError(`graphify produced no graphify-out/ in scratch (${scratchOut})`);
30642
- return { exitCode: 1 };
30643
- }
30644
- if (fs53.existsSync(finalOut)) fs53.rmSync(finalOut, { recursive: true, force: true });
30645
- fs53.renameSync(scratchOut, finalOut);
30646
- const durationMs = Date.now() - started;
30647
- const nodeCount = parseNodeCount(finalOut);
30648
- const version = readGraphifyVersion(image);
30649
- const freshness = {
30650
- built_at: (/* @__PURE__ */ new Date()).toISOString(),
30651
- duration_ms: durationMs,
30652
- node_count: nodeCount,
30653
- graphify_version: version,
30739
+ resp = await build({
30654
30740
  workspace: workspace.name,
30655
- scratch_strategy: scratchStrategy
30656
- };
30657
- fs53.writeFileSync(
30658
- path57.join(outDir, "freshness.json"),
30659
- JSON.stringify(freshness, null, 2) + "\n",
30660
- "utf-8"
30741
+ repo_path: containerRepoPath
30742
+ });
30743
+ } catch (err) {
30744
+ if (err instanceof KgServiceUnreachableError) {
30745
+ printError(err.message);
30746
+ printInfo("remedy", "run `olam services up` to start kg-service, then re-run `olam kg build`.");
30747
+ return { exitCode: 3 };
30748
+ }
30749
+ printError(`kg-service /build failed: ${err instanceof Error ? err.message : String(err)}`);
30750
+ return { exitCode: 1 };
30751
+ }
30752
+ if (!resp.ok) {
30753
+ printError(`kg-service /build returned error: ${resp.error ?? "unknown"}`);
30754
+ return { exitCode: 1 };
30755
+ }
30756
+ const freshness = {
30757
+ built_at: resp.built_at ?? (/* @__PURE__ */ new Date()).toISOString(),
30758
+ duration_ms: resp.duration_ms ?? 0,
30759
+ node_count: resp.nodes ?? null,
30760
+ edge_count: resp.edges ?? null,
30761
+ workspace: workspace.name,
30762
+ graphify_path: "container"
30763
+ };
30764
+ fs53.writeFileSync(
30765
+ path57.join(outDir, "freshness.json"),
30766
+ JSON.stringify(freshness, null, 2) + "\n",
30767
+ "utf-8"
30768
+ );
30769
+ const finalOut = path57.join(outDir, "graphify-out");
30770
+ if (options.json) {
30771
+ process.stdout.write(JSON.stringify(freshness) + "\n");
30772
+ } else {
30773
+ printSuccess(
30774
+ `kg build ${workspace.name} \u2014 ${resp.nodes ?? "?"} nodes / ${resp.edges ?? "?"} edges in ${((resp.duration_ms ?? 0) / 1e3).toFixed(1)}s (via kg-service container)`
30661
30775
  );
30662
- if (options.json) {
30663
- process.stdout.write(JSON.stringify(freshness) + "\n");
30664
- } else {
30665
- printSuccess(
30666
- `kg build ${workspace.name} \u2014 ${nodeCount ?? "?"} nodes in ${(durationMs / 1e3).toFixed(1)}s (graphify ${version})`
30667
- );
30668
- printInfo("output", `${finalOut}/graph.json`);
30669
- }
30670
- return { exitCode: 0, freshness, outputDir: finalOut };
30671
- } finally {
30672
- if (fs53.existsSync(scratchDir)) {
30673
- fs53.rmSync(scratchDir, { recursive: true, force: true });
30674
- }
30776
+ printInfo("output", `${finalOut}/graph.json`);
30675
30777
  }
30778
+ return { exitCode: 0, freshness, outputDir: finalOut };
30676
30779
  }
30677
30780
  function registerKg(program2) {
30678
- const kg = program2.command("kg").description("Knowledge-graph operations (graphify-backed)");
30781
+ const kg = program2.command("kg").description("Knowledge-graph operations (kg-service container)");
30679
30782
  kg.command("build").description(
30680
- "Build pristine KG for a workspace (default: current dir). Scratch-dir pattern; ~/.olam/kg/<ws>/graphify-out/"
30681
- ).argument("[workspace]", "workspace name (lowercase alphanumeric + hyphens/underscores)").option("--image <ref>", `devbox image to invoke (default: ${DEFAULT_DEVBOX_IMAGE2})`).option("--json", "emit freshness record as JSON instead of human-readable status").action(async (workspaceArg, opts) => {
30783
+ "Build pristine KG for a workspace (default: current dir). Routes through olam-kg-service /build endpoint."
30784
+ ).argument("[workspace]", "workspace name (lowercase alphanumeric + hyphens/underscores)").option("--json", "emit freshness record as JSON instead of human-readable status").action(async (workspaceArg, opts) => {
30682
30785
  const r = await runKgBuild(workspaceArg, opts);
30683
30786
  if (r.exitCode !== 0) process.exitCode = r.exitCode;
30684
30787
  });
@@ -30692,7 +30795,7 @@ function registerKg(program2) {
30692
30795
 
30693
30796
  // src/commands/seed.ts
30694
30797
  init_output();
30695
- import { spawnSync as spawnSync21, spawn as spawnAsync2 } from "node:child_process";
30798
+ import { spawnSync as spawnSync20, spawn as spawnAsync2 } from "node:child_process";
30696
30799
  var DEFAULT_SINGLETON_CONTAINER = "olam-postgres";
30697
30800
  var DEFAULT_SINGLETON_USER = "development";
30698
30801
  function assertValidSeedName(name) {
@@ -30703,7 +30806,7 @@ function assertValidSeedName(name) {
30703
30806
  }
30704
30807
  }
30705
30808
  function singletonDocker(container, user, args, stdin) {
30706
- return spawnSync21(
30809
+ return spawnSync20(
30707
30810
  "docker",
30708
30811
  ["exec", "-i", container, "psql", "-U", user, ...args],
30709
30812
  { encoding: "utf-8", input: stdin }
@@ -30758,7 +30861,7 @@ async function handleBake(opts) {
30758
30861
  if (sources.length > 1) {
30759
30862
  throw new Error("multiple sources specified \u2014 pass exactly one of --source-container, --source-url, --source-local");
30760
30863
  }
30761
- const ping = spawnSync21("docker", ["inspect", "--format", "{{.State.Status}}", singleton], { encoding: "utf-8" });
30864
+ const ping = spawnSync20("docker", ["inspect", "--format", "{{.State.Status}}", singleton], { encoding: "utf-8" });
30762
30865
  if (ping.status !== 0 || (ping.stdout || "").trim() !== "running") {
30763
30866
  throw new Error(`singleton container "${singleton}" not running \u2014 run \`olam bootstrap\` first`);
30764
30867
  }
@@ -30940,13 +31043,13 @@ init_context();
30940
31043
  init_output();
30941
31044
  import { spawnSync as defaultSpawnSync } from "node:child_process";
30942
31045
  import * as fs54 from "node:fs";
30943
- import * as os30 from "node:os";
31046
+ import * as os31 from "node:os";
30944
31047
  import * as path58 from "node:path";
30945
31048
  function devboxContainerName(worldId) {
30946
31049
  return `olam-${worldId}-devbox`;
30947
31050
  }
30948
31051
  function olamHomeDir() {
30949
- return process.env["OLAM_HOME"] ?? path58.join(os30.homedir(), ".olam");
31052
+ return process.env["OLAM_HOME"] ?? path58.join(os31.homedir(), ".olam");
30950
31053
  }
30951
31054
  function defaultRestartContainer(name, spawn11 = defaultSpawnSync) {
30952
31055
  const r = spawn11("docker", ["restart", "--time", "30", name], {