@pleri/olam-cli 0.1.120 → 0.1.126

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.
Files changed (67) hide show
  1. package/dist/commands/bootstrap.d.ts +4 -0
  2. package/dist/commands/bootstrap.d.ts.map +1 -1
  3. package/dist/commands/bootstrap.js +64 -0
  4. package/dist/commands/bootstrap.js.map +1 -1
  5. package/dist/commands/create.d.ts.map +1 -1
  6. package/dist/commands/create.js +43 -0
  7. package/dist/commands/create.js.map +1 -1
  8. package/dist/commands/mcp/install-shared.d.ts +2 -0
  9. package/dist/commands/mcp/install-shared.d.ts.map +1 -1
  10. package/dist/commands/mcp/install-shared.js +13 -0
  11. package/dist/commands/mcp/install-shared.js.map +1 -1
  12. package/dist/commands/mcp/uninstall.d.ts.map +1 -1
  13. package/dist/commands/mcp/uninstall.js +1 -10
  14. package/dist/commands/mcp/uninstall.js.map +1 -1
  15. package/dist/commands/memory/_paths.d.ts +15 -0
  16. package/dist/commands/memory/_paths.d.ts.map +1 -0
  17. package/dist/commands/memory/_paths.js +34 -0
  18. package/dist/commands/memory/_paths.js.map +1 -0
  19. package/dist/commands/memory/index.d.ts +17 -0
  20. package/dist/commands/memory/index.d.ts.map +1 -0
  21. package/dist/commands/memory/index.js +34 -0
  22. package/dist/commands/memory/index.js.map +1 -0
  23. package/dist/commands/memory/install.d.ts +57 -0
  24. package/dist/commands/memory/install.d.ts.map +1 -0
  25. package/dist/commands/memory/install.js +114 -0
  26. package/dist/commands/memory/install.js.map +1 -0
  27. package/dist/commands/memory/logs.d.ts +15 -0
  28. package/dist/commands/memory/logs.d.ts.map +1 -0
  29. package/dist/commands/memory/logs.js +45 -0
  30. package/dist/commands/memory/logs.js.map +1 -0
  31. package/dist/commands/memory/secret.d.ts +16 -0
  32. package/dist/commands/memory/secret.d.ts.map +1 -0
  33. package/dist/commands/memory/secret.js +79 -0
  34. package/dist/commands/memory/secret.js.map +1 -0
  35. package/dist/commands/memory/start.d.ts +23 -0
  36. package/dist/commands/memory/start.d.ts.map +1 -0
  37. package/dist/commands/memory/start.js +166 -0
  38. package/dist/commands/memory/start.js.map +1 -0
  39. package/dist/commands/memory/status.d.ts +25 -0
  40. package/dist/commands/memory/status.d.ts.map +1 -0
  41. package/dist/commands/memory/status.js +101 -0
  42. package/dist/commands/memory/status.js.map +1 -0
  43. package/dist/commands/memory/stop.d.ts +12 -0
  44. package/dist/commands/memory/stop.d.ts.map +1 -0
  45. package/dist/commands/memory/stop.js +81 -0
  46. package/dist/commands/memory/stop.js.map +1 -0
  47. package/dist/commands/memory/uninstall.d.ts +19 -0
  48. package/dist/commands/memory/uninstall.d.ts.map +1 -0
  49. package/dist/commands/memory/uninstall.js +60 -0
  50. package/dist/commands/memory/uninstall.js.map +1 -0
  51. package/dist/commands/seed.d.ts +27 -0
  52. package/dist/commands/seed.d.ts.map +1 -0
  53. package/dist/commands/seed.js +303 -0
  54. package/dist/commands/seed.js.map +1 -0
  55. package/dist/image-digests.json +3 -3
  56. package/dist/index.js +1835 -259
  57. package/dist/index.js.map +1 -1
  58. package/dist/lib/memory-secret.d.ts +48 -0
  59. package/dist/lib/memory-secret.d.ts.map +1 -0
  60. package/dist/lib/memory-secret.js +92 -0
  61. package/dist/lib/memory-secret.js.map +1 -0
  62. package/dist/lib/world-mcp-register.d.ts +98 -0
  63. package/dist/lib/world-mcp-register.d.ts.map +1 -0
  64. package/dist/lib/world-mcp-register.js +117 -0
  65. package/dist/lib/world-mcp-register.js.map +1 -0
  66. package/dist/mcp-server.js +502 -65
  67. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -21,11 +21,11 @@ import * as path from "node:path";
21
21
  import { fileURLToPath } from "node:url";
22
22
  function readCliVersion() {
23
23
  try {
24
- const here2 = path.dirname(fileURLToPath(import.meta.url));
24
+ const here3 = path.dirname(fileURLToPath(import.meta.url));
25
25
  for (const candidate of [
26
- path.join(here2, "package.json"),
27
- path.join(here2, "..", "package.json"),
28
- path.join(here2, "..", "..", "package.json")
26
+ path.join(here3, "package.json"),
27
+ path.join(here3, "..", "package.json"),
28
+ path.join(here3, "..", "..", "package.json")
29
29
  ]) {
30
30
  if (fs.existsSync(candidate)) {
31
31
  const pkg = JSON.parse(fs.readFileSync(candidate, "utf-8"));
@@ -4276,7 +4276,7 @@ var init_version2 = __esm({
4276
4276
 
4277
4277
  // ../core/dist/world/repo-manifest.js
4278
4278
  import { existsSync as existsSync2, lstatSync, readFileSync as readFileSync2 } from "node:fs";
4279
- import { join as join2 } from "node:path";
4279
+ import { join as join2, resolve, sep } from "node:path";
4280
4280
  import YAML from "yaml";
4281
4281
  function bootstrapStepCmd(entry) {
4282
4282
  return typeof entry === "string" ? entry : entry.cmd;
@@ -4322,6 +4322,23 @@ function rejectForbiddenKeys(value, path56, rejectSource) {
4322
4322
  function unknownTopLevelKeys(parsed) {
4323
4323
  return Object.keys(parsed).filter((k) => !KNOWN_TOP_LEVEL_KEYS.has(k));
4324
4324
  }
4325
+ function deepMergeManifest(base, overlay) {
4326
+ const out = { ...base };
4327
+ for (const key of Object.keys(overlay)) {
4328
+ if (FORBIDDEN_KEYS.has(key))
4329
+ continue;
4330
+ const overlayValue = overlay[key];
4331
+ if (overlayValue === void 0)
4332
+ continue;
4333
+ const baseValue = base[key];
4334
+ const bothObjects = isPlainObject(baseValue) && isPlainObject(overlayValue);
4335
+ out[key] = bothObjects ? deepMergeManifest(baseValue, overlayValue) : overlayValue;
4336
+ }
4337
+ return out;
4338
+ }
4339
+ function isPlainObject(value) {
4340
+ return value !== null && typeof value === "object" && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
4341
+ }
4325
4342
  function loadRepoManifest(repoDir) {
4326
4343
  const olamPath = join2(repoDir, ".olam.yaml");
4327
4344
  const adbPath = join2(repoDir, ".adb.yaml");
@@ -4352,7 +4369,37 @@ function loadRepoManifest(repoDir) {
4352
4369
  throw new Error(`[manifest] ${manifestPath2}: expected a YAML mapping at the top level`);
4353
4370
  }
4354
4371
  rejectForbiddenKeys(parsed, manifestPath2, true);
4355
- const body = RepoManifestSchema.parse(parsed);
4372
+ let mergedParsed = parsed;
4373
+ const inheritsRaw = parsed["inherits"];
4374
+ if (typeof inheritsRaw === "string" && inheritsRaw.length > 0) {
4375
+ if (source !== "olam") {
4376
+ throw new Error(`[manifest] ${manifestPath2}: \`inherits\` only valid in .olam.yaml (found in .adb.yaml \u2014 drop the field)`);
4377
+ }
4378
+ const inheritedAbs = resolve(repoDir, inheritsRaw);
4379
+ const repoDirAbs = resolve(repoDir);
4380
+ if (!inheritedAbs.startsWith(repoDirAbs + sep) && inheritedAbs !== repoDirAbs) {
4381
+ throw new Error(`[manifest] ${manifestPath2}: inherits target "${inheritsRaw}" escapes repo dir`);
4382
+ }
4383
+ if (!existsSync2(inheritedAbs)) {
4384
+ throw new Error(`[manifest] ${manifestPath2}: inherits target "${inheritsRaw}" not found`);
4385
+ }
4386
+ const inheritedStat = lstatSync(inheritedAbs);
4387
+ if (inheritedStat.isSymbolicLink()) {
4388
+ throw new Error(`[manifest] ${manifestPath2}: inherits target "${inheritsRaw}" is a symlink (not permitted)`);
4389
+ }
4390
+ const inheritedRaw = readFileSync2(inheritedAbs, "utf-8");
4391
+ const inheritedParsed = YAML.parse(inheritedRaw, { maxAliasCount: 100 });
4392
+ if (inheritedParsed !== null && inheritedParsed !== void 0) {
4393
+ if (typeof inheritedParsed !== "object" || Array.isArray(inheritedParsed)) {
4394
+ throw new Error(`[manifest] ${inheritedAbs}: expected a YAML mapping at the top level`);
4395
+ }
4396
+ rejectForbiddenKeys(inheritedParsed, inheritedAbs, true);
4397
+ const overlay = { ...parsed };
4398
+ delete overlay["inherits"];
4399
+ mergedParsed = deepMergeManifest(inheritedParsed, overlay);
4400
+ }
4401
+ }
4402
+ const body = RepoManifestSchema.parse(mergedParsed);
4356
4403
  if (parsed["version"] === void 0) {
4357
4404
  console.warn(`[manifest] ${manifestPath2}: missing "version: ${MANIFEST_VERSION}" field \u2014 add it to suppress this warning (backward-compat: file still parses)`);
4358
4405
  }
@@ -4390,7 +4437,44 @@ var init_repo_manifest = __esm({
4390
4437
  // - 'world' (default): container is per-world (today's behaviour).
4391
4438
  // - 'shared': container is shared across worlds (consumed by later phases).
4392
4439
  // TODO(phase-D): enforce shared-service deduplication in planManifestPipeline.
4393
- scope: external_exports.enum(["world", "shared"]).optional()
4440
+ scope: external_exports.enum(["world", "shared"]).optional(),
4441
+ // olam-hybrid-shared-postgres Phase A task A8.
4442
+ // Declares which postgres template DB(s) Olam should clone at world
4443
+ // create time. Consumed by `applyPostgresTemplateClone` (task A3 in
4444
+ // manager.ts) which runs `CREATE DATABASE <world-db> TEMPLATE <name>`
4445
+ // per entry. Accepts either a single string (single-DB repo) or an
4446
+ // array (atlas-core → ['atlas_common_seed', 'atlas_merchant_seed'];
4447
+ // atlas-pay → adds 'atlas_pay_seed' per Phase B Decision 17).
4448
+ seed_template: external_exports.union([external_exports.string().min(1), external_exports.array(external_exports.string().min(1)).nonempty()]).optional(),
4449
+ // Optional hash bound to the seed_template content for cross-machine
4450
+ // drift detection. The A1 bake script writes this value via .adb.yaml
4451
+ // post-bake (operator pastes from seed/seed-hash.txt; auto-write CI
4452
+ // tool is a Phase D follow-up per the plan's Out of scope).
4453
+ seed_template_hash: external_exports.string().optional(),
4454
+ // olam-hybrid-shared-postgres Phase A task A5 (closes OQ15 plan-killer).
4455
+ // Per-clone SQL hook: after `CREATE DATABASE <world-db> TEMPLATE <seed>`,
4456
+ // run these statements against the cloned world-DB. Atlas-core uses this
4457
+ // to UPDATE `merchants.identifier` so `Current.merchant` (which resolves
4458
+ // via `Merchant.find_by(identifier: ENV['POSTGRESQL_MERCHANT_DATABASE'])`)
4459
+ // matches the per-world merchant DB name.
4460
+ //
4461
+ // Shape: { <seed_template_name>: [<sql statement>, ...] }
4462
+ // The seed_template_name key selects which cloned DB the statements run
4463
+ // in. Order is preserved within each seed's list.
4464
+ //
4465
+ // Interpolation in statements:
4466
+ // ${WORLD_ID} → world id (e.g. frost-oak-5916)
4467
+ // ${WORLD_DB:<seed>} → corresponding world-DB name for that seed
4468
+ // (e.g. ${WORLD_DB:atlas_merchant_seed} →
4469
+ // atlas_merchant_world_frost-oak-5916)
4470
+ //
4471
+ // SECURITY INVARIANT: statements come from the operator's own checked-out
4472
+ // `.olam.yaml` (same trust class as `BootstrapStep.cmd`). They run as the
4473
+ // singleton's `development` superuser today; A4's per-world role lands
4474
+ // later in the seam. Statements are NOT exposed to operator-controlled
4475
+ // env variables — only the two whitelisted placeholders above. No shell
4476
+ // expansion, no DOLLAR-name expansion.
4477
+ post_clone_sql: external_exports.record(external_exports.string().min(1), external_exports.array(external_exports.string().min(1))).optional()
4394
4478
  }).passthrough();
4395
4479
  deploySchema = external_exports.object({
4396
4480
  tags: external_exports.array(external_exports.string()).optional()
@@ -4442,7 +4526,12 @@ var init_repo_manifest = __esm({
4442
4526
  "secrets",
4443
4527
  "bootstrap",
4444
4528
  "start",
4445
- "deploy"
4529
+ "deploy",
4530
+ // F-7 (olam-hybrid-shared-postgres dogfood finding): native inheritance —
4531
+ // `.olam.yaml` may declare `inherits: .adb.yaml` to deep-merge an adb-shaped
4532
+ // base manifest into itself. Retires the atlas-one `bin/olam-create-wrap.sh`
4533
+ // stopgap. See the inheritance resolver in `loadRepoManifest`.
4534
+ "inherits"
4446
4535
  ]);
4447
4536
  FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
4448
4537
  }
@@ -5198,7 +5287,7 @@ async function safeText(res) {
5198
5287
  }
5199
5288
  }
5200
5289
  function sleep(ms) {
5201
- return new Promise((resolve12) => setTimeout(resolve12, ms));
5290
+ return new Promise((resolve14) => setTimeout(resolve14, ms));
5202
5291
  }
5203
5292
  var DEFAULT_BASE_URL, DEFAULT_TIMEOUT_MS, RETRY_COUNT, RETRY_BACKOFF_MS, AuthClient;
5204
5293
  var init_client = __esm({
@@ -5345,12 +5434,12 @@ import { existsSync as existsSync9 } from "node:fs";
5345
5434
  import * as path9 from "node:path";
5346
5435
  import { fileURLToPath as fileURLToPath2 } from "node:url";
5347
5436
  function resolveAuthServicePath() {
5348
- const here2 = fileURLToPath2(import.meta.url);
5349
- const pkgsDir = path9.resolve(path9.dirname(here2), "..", "..", "..");
5437
+ const here3 = fileURLToPath2(import.meta.url);
5438
+ const pkgsDir = path9.resolve(path9.dirname(here3), "..", "..", "..");
5350
5439
  return path9.join(pkgsDir, "auth-service");
5351
5440
  }
5352
5441
  function sleep2(ms) {
5353
- return new Promise((resolve12) => setTimeout(resolve12, ms));
5442
+ return new Promise((resolve14) => setTimeout(resolve14, ms));
5354
5443
  }
5355
5444
  var DEFAULT_PORT, DEFAULT_VOLUME, DEFAULT_CONTAINER, DEFAULT_IMAGE, AuthContainerController;
5356
5445
  var init_container = __esm({
@@ -5648,7 +5737,7 @@ var init_network = __esm({
5648
5737
  // ../adapters/dist/docker/pull.js
5649
5738
  import { spawn } from "node:child_process";
5650
5739
  function spawnAsync(cmd, args, opts = {}) {
5651
- return new Promise((resolve12) => {
5740
+ return new Promise((resolve14) => {
5652
5741
  const child = spawn(cmd, [...args], {
5653
5742
  stdio: ["ignore", "pipe", "pipe"],
5654
5743
  signal: opts.signal
@@ -5662,10 +5751,10 @@ function spawnAsync(cmd, args, opts = {}) {
5662
5751
  stderr += chunk.toString();
5663
5752
  });
5664
5753
  child.on("error", (err) => {
5665
- resolve12({ exitCode: -1, stdout, stderr: stderr + err.message });
5754
+ resolve14({ exitCode: -1, stdout, stderr: stderr + err.message });
5666
5755
  });
5667
5756
  child.on("close", (code) => {
5668
- resolve12({ exitCode: code ?? -1, stdout, stderr });
5757
+ resolve14({ exitCode: code ?? -1, stdout, stderr });
5669
5758
  });
5670
5759
  });
5671
5760
  }
@@ -5781,6 +5870,17 @@ function readHostCpToken() {
5781
5870
  return "";
5782
5871
  }
5783
5872
  }
5873
+ function readMemorySecret() {
5874
+ const fromEnv = process.env["OLAM_MEMORY_SECRET"];
5875
+ if (fromEnv && fromEnv.length > 0)
5876
+ return fromEnv;
5877
+ const file = path11.join(os6.homedir(), ".olam", "memory-secret");
5878
+ try {
5879
+ return fs10.readFileSync(file, "utf-8").trim();
5880
+ } catch {
5881
+ return "";
5882
+ }
5883
+ }
5784
5884
  function sanitizeContainerName(name) {
5785
5885
  return name.replace(/[^a-zA-Z0-9_.-]/g, "-");
5786
5886
  }
@@ -5828,7 +5928,7 @@ async function auditPortsForZombies(docker2, hostPorts) {
5828
5928
  }
5829
5929
  }
5830
5930
  }
5831
- var realPullImage, serviceContainerName, worldContainerName, miseCacheVolumeName, PACKAGE_MANAGER_CACHE_DIRS, createServiceContainer, DEFAULT_IMAGE2, CONTROL_PLANE_PORT, HOST_CONTROL_PLANE_BASE, HOST_APP_PORT_BASE_DELTA, DEFAULT_MEMORY_BYTES, PortHeldByZombieError, createWorldContainer, stopAndRemove;
5931
+ var realPullImage, AGENTMEMORY_HOST_INTERNAL_URL, serviceContainerName, worldContainerName, miseCacheVolumeName, PACKAGE_MANAGER_CACHE_DIRS, createServiceContainer, DEFAULT_IMAGE2, CONTROL_PLANE_PORT, HOST_CONTROL_PLANE_BASE, HOST_APP_PORT_BASE_DELTA, DEFAULT_MEMORY_BYTES, PortHeldByZombieError, createWorldContainer, stopAndRemove;
5832
5932
  var init_container2 = __esm({
5833
5933
  "../adapters/dist/docker/container.js"() {
5834
5934
  "use strict";
@@ -5836,6 +5936,7 @@ var init_container2 = __esm({
5836
5936
  init_pull();
5837
5937
  init_volume();
5838
5938
  realPullImage = (imageRef, platform) => pullImageWithRetry(imageRef, void 0, platform ? { perAttemptTimeoutMs: 18e4, retryOnce: true, platform } : { perAttemptTimeoutMs: 18e4, retryOnce: true });
5939
+ AGENTMEMORY_HOST_INTERNAL_URL = "http://host.docker.internal:3111";
5839
5940
  serviceContainerName = (worldId, serviceName) => `olam-${sanitizeContainerName(worldId)}-${sanitizeContainerName(serviceName)}`;
5840
5941
  worldContainerName = (worldId) => `olam-${sanitizeContainerName(worldId)}-devbox`;
5841
5942
  miseCacheVolumeName = (arch2 = process.arch) => `olam-mise-cache-${arch2}`;
@@ -5918,6 +6019,7 @@ var init_container2 = __esm({
5918
6019
  const labels = olamLabels(worldId, worldName);
5919
6020
  const authSecret = readAuthSecret();
5920
6021
  const hostCpToken = readHostCpToken();
6022
+ const memorySecret = readMemorySecret();
5921
6023
  const worldEnv = {
5922
6024
  OLAM_WORLD_ID: worldId,
5923
6025
  OLAM_WORLD_NAME: worldName,
@@ -5934,6 +6036,14 @@ var init_container2 = __esm({
5934
6036
  OLAM_HOST_CP_URL: "http://host.docker.internal:19000",
5935
6037
  ...authSecret ? { OLAM_AUTH_SECRET: authSecret } : {},
5936
6038
  ...hostCpToken ? { OLAM_HOST_CP_TOKEN: hostCpToken } : {},
6039
+ // Phase B1 — agent-memory wiring. Both vars are injected together
6040
+ // (or neither): without a secret the URL is useless. The in-world
6041
+ // @agentmemory/mcp shim (Phase B2/B3) reads these env vars to find
6042
+ // the host's REST endpoint.
6043
+ ...memorySecret ? {
6044
+ AGENTMEMORY_URL: AGENTMEMORY_HOST_INTERNAL_URL,
6045
+ AGENTMEMORY_SECRET: memorySecret
6046
+ } : {},
5937
6047
  ...env
5938
6048
  };
5939
6049
  const envList = Object.entries(worldEnv).map(([k, v]) => `${k}=${v}`);
@@ -6050,7 +6160,7 @@ var demuxStream, execInContainer;
6050
6160
  var init_exec = __esm({
6051
6161
  "../adapters/dist/docker/exec.js"() {
6052
6162
  "use strict";
6053
- demuxStream = (stream) => new Promise((resolve12, reject) => {
6163
+ demuxStream = (stream) => new Promise((resolve14, reject) => {
6054
6164
  const stdoutChunks = [];
6055
6165
  const stderrChunks = [];
6056
6166
  const stdout = new PassThrough();
@@ -6064,7 +6174,7 @@ var init_exec = __esm({
6064
6174
  stream.pipe(stdout);
6065
6175
  }
6066
6176
  stream.on("end", () => {
6067
- resolve12({
6177
+ resolve14({
6068
6178
  stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
6069
6179
  stderr: Buffer.concat(stderrChunks).toString("utf-8")
6070
6180
  });
@@ -6260,6 +6370,19 @@ var init_provider = __esm({
6260
6370
  }
6261
6371
  const mergedEnv = { ...serviceEnv, ...config.env };
6262
6372
  const container = await createWorldContainer(this.docker, id, name, config.image, mergedEnv, config.resources, config.workspacePath, config.portOffset, config.appPorts, config.cacheArch);
6373
+ for (const networkName3 of config.extraNetworks ?? []) {
6374
+ try {
6375
+ const network = this.docker.getNetwork(networkName3);
6376
+ await network.connect({ Container: container.id });
6377
+ } catch (err) {
6378
+ const statusCode = err.statusCode;
6379
+ if (statusCode === 404) {
6380
+ throw new Error(`extra network "${networkName3}" not found \u2014 run \`olam bootstrap\` to recreate it`);
6381
+ }
6382
+ if (statusCode !== 403)
6383
+ throw err;
6384
+ }
6385
+ }
6263
6386
  return new DockerWorld(id, name, container, "running");
6264
6387
  }
6265
6388
  // -----------------------------------------------------------------------
@@ -6496,7 +6619,7 @@ var init_connection = __esm({
6496
6619
  // -----------------------------------------------------------------------
6497
6620
  async exec(host, command) {
6498
6621
  const client = await this.getConnection(host);
6499
- return new Promise((resolve12, reject) => {
6622
+ return new Promise((resolve14, reject) => {
6500
6623
  client.exec(command, (err, stream) => {
6501
6624
  if (err) {
6502
6625
  reject(new Error(`SSH exec failed on ${host}: ${err.message}`));
@@ -6511,7 +6634,7 @@ var init_connection = __esm({
6511
6634
  stderr += data.toString();
6512
6635
  });
6513
6636
  stream.on("close", (code) => {
6514
- resolve12({
6637
+ resolve14({
6515
6638
  exitCode: code ?? 0,
6516
6639
  stdout: stdout.trimEnd(),
6517
6640
  stderr: stderr.trimEnd()
@@ -6542,10 +6665,10 @@ var init_connection = __esm({
6542
6665
  throw new Error(`No SSH configuration found for host: ${host}`);
6543
6666
  }
6544
6667
  const client = new SSHClient();
6545
- return new Promise((resolve12, reject) => {
6668
+ return new Promise((resolve14, reject) => {
6546
6669
  client.on("ready", () => {
6547
6670
  this.connections.set(host, client);
6548
- resolve12(client);
6671
+ resolve14(client);
6549
6672
  }).on("error", (err) => {
6550
6673
  this.connections.delete(host);
6551
6674
  reject(new Error(`SSH connection to ${host} failed: ${err.message}`));
@@ -7270,6 +7393,7 @@ function rowToMetadata(row) {
7270
7393
  let readinessChain;
7271
7394
  let expectedServices;
7272
7395
  let appPortUrls;
7396
+ let worldDbNames;
7273
7397
  try {
7274
7398
  if (row.readiness_chain)
7275
7399
  readinessChain = JSON.parse(row.readiness_chain);
@@ -7285,6 +7409,11 @@ function rowToMetadata(row) {
7285
7409
  appPortUrls = JSON.parse(row.app_port_urls);
7286
7410
  } catch {
7287
7411
  }
7412
+ try {
7413
+ if (row.world_db_names)
7414
+ worldDbNames = JSON.parse(row.world_db_names);
7415
+ } catch {
7416
+ }
7288
7417
  return {
7289
7418
  id: row.id,
7290
7419
  name: row.name,
@@ -7307,7 +7436,9 @@ function rowToMetadata(row) {
7307
7436
  autoDestroyOnMerge: (row.auto_destroy_on_merge ?? 1) !== 0,
7308
7437
  ...readinessChain !== void 0 ? { readinessChain } : {},
7309
7438
  ...expectedServices !== void 0 ? { expectedServices } : {},
7310
- ...appPortUrls !== void 0 ? { appPortUrls } : {}
7439
+ ...appPortUrls !== void 0 ? { appPortUrls } : {},
7440
+ ...worldDbNames !== void 0 ? { worldDbNames } : {},
7441
+ ...row.world_role_name ? { worldRoleName: row.world_role_name } : {}
7311
7442
  };
7312
7443
  }
7313
7444
  var _require, _Database, SCHEMA_VERSION, CREATE_TABLE, CREATE_META, WorldRegistry;
@@ -7359,7 +7490,17 @@ CREATE TABLE IF NOT EXISTS meta (
7359
7490
  this.db.pragma("foreign_keys = ON");
7360
7491
  this.db.exec(CREATE_TABLE);
7361
7492
  this.db.exec(CREATE_META);
7362
- for (const col of ["readiness_chain TEXT", "expected_services TEXT", "app_port_urls TEXT", "tailscale_paths TEXT"]) {
7493
+ for (const col of [
7494
+ "readiness_chain TEXT",
7495
+ "expected_services TEXT",
7496
+ "app_port_urls TEXT",
7497
+ "tailscale_paths TEXT",
7498
+ // olam-hybrid-shared-postgres Phase A task A7. JSON-serialised string for
7499
+ // world_db_names (SQLite has no native array; matches repos column shape).
7500
+ // TEXT[] would fail at DDL time (closes OQ13/OQ22 / FS-03).
7501
+ "world_db_names TEXT",
7502
+ "world_role_name TEXT"
7503
+ ]) {
7363
7504
  try {
7364
7505
  this.db.exec(`ALTER TABLE worlds ADD COLUMN ${col}`);
7365
7506
  } catch {
@@ -7385,8 +7526,9 @@ CREATE TABLE IF NOT EXISTS meta (
7385
7526
  (id, name, status, repos, branch, port_offset, workspace_path,
7386
7527
  compute_provider, total_cost_usd, thought_count, created_at, updated_at,
7387
7528
  pr_url, pr_number, pr_repo, pr_created_at, pr_state, pr_merged_at, auto_destroy_on_merge,
7388
- readiness_chain, expected_services, app_port_urls)
7389
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(world.id, world.name, world.status, JSON.stringify(world.repos), world.branch, world.portOffset, world.workspacePath, world.computeProvider, world.totalCostUsd, world.thoughtCount, world.createdAt, world.updatedAt, world.prUrl ?? null, world.prNumber ?? null, world.prRepo ?? null, world.prCreatedAt ?? null, world.prState ?? "none", world.prMergedAt ?? null, world.autoDestroyOnMerge === false ? 0 : 1, world.readinessChain !== void 0 ? JSON.stringify(world.readinessChain) : null, world.expectedServices !== void 0 ? JSON.stringify(world.expectedServices) : null, world.appPortUrls !== void 0 ? JSON.stringify(world.appPortUrls) : null);
7529
+ readiness_chain, expected_services, app_port_urls,
7530
+ world_db_names, world_role_name)
7531
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(world.id, world.name, world.status, JSON.stringify(world.repos), world.branch, world.portOffset, world.workspacePath, world.computeProvider, world.totalCostUsd, world.thoughtCount, world.createdAt, world.updatedAt, world.prUrl ?? null, world.prNumber ?? null, world.prRepo ?? null, world.prCreatedAt ?? null, world.prState ?? "none", world.prMergedAt ?? null, world.autoDestroyOnMerge === false ? 0 : 1, world.readinessChain !== void 0 ? JSON.stringify(world.readinessChain) : null, world.expectedServices !== void 0 ? JSON.stringify(world.expectedServices) : null, world.appPortUrls !== void 0 ? JSON.stringify(world.appPortUrls) : null, world.worldDbNames !== void 0 ? JSON.stringify(world.worldDbNames) : null, world.worldRoleName ?? null);
7390
7532
  }
7391
7533
  update(worldId, updates) {
7392
7534
  const setClauses = [];
@@ -7411,9 +7553,12 @@ CREATE TABLE IF NOT EXISTS meta (
7411
7553
  autoDestroyOnMerge: "auto_destroy_on_merge",
7412
7554
  readinessChain: "readiness_chain",
7413
7555
  expectedServices: "expected_services",
7414
- appPortUrls: "app_port_urls"
7556
+ appPortUrls: "app_port_urls",
7557
+ // olam-hybrid-shared-postgres Phase A task A7.
7558
+ worldDbNames: "world_db_names",
7559
+ worldRoleName: "world_role_name"
7415
7560
  };
7416
- const jsonColumns = /* @__PURE__ */ new Set(["repos", "readinessChain", "expectedServices", "appPortUrls"]);
7561
+ const jsonColumns = /* @__PURE__ */ new Set(["repos", "readinessChain", "expectedServices", "appPortUrls", "worldDbNames"]);
7417
7562
  for (const [key, col] of Object.entries(columnMap)) {
7418
7563
  const val = updates[key];
7419
7564
  if (val !== void 0) {
@@ -8300,7 +8445,7 @@ var init_workspace_name = __esm({
8300
8445
 
8301
8446
  // ../core/dist/kg/storage-paths.js
8302
8447
  import { homedir as homedir9 } from "node:os";
8303
- import { join as join16, resolve as resolve4 } from "node:path";
8448
+ import { join as join16, resolve as resolve5 } from "node:path";
8304
8449
  function olamHome() {
8305
8450
  return process.env.OLAM_HOME ?? join16(homedir9(), ".olam");
8306
8451
  }
@@ -8318,7 +8463,7 @@ function assertWithinPrefix(path56, prefix, label) {
8318
8463
  function kgPristinePath(workspace) {
8319
8464
  validateWorkspaceName(workspace);
8320
8465
  const root = kgRoot();
8321
- const path56 = resolve4(join16(root, workspace));
8466
+ const path56 = resolve5(join16(root, workspace));
8322
8467
  assertWithinPrefix(path56, root, "kgPristinePath");
8323
8468
  return path56;
8324
8469
  }
@@ -8471,8 +8616,8 @@ import { execFileSync as execFileSync5 } from "node:child_process";
8471
8616
  import * as fs15 from "node:fs";
8472
8617
  import * as os9 from "node:os";
8473
8618
  import * as path16 from "node:path";
8474
- function expandHome(p, homedir30) {
8475
- return p.replace(/^~(?=$|\/|\\)/, homedir30());
8619
+ function expandHome(p, homedir32) {
8620
+ return p.replace(/^~(?=$|\/|\\)/, homedir32());
8476
8621
  }
8477
8622
  function sanitizeRepoFilename(name) {
8478
8623
  const sanitized = name.replace(/[^A-Za-z0-9._-]/g, "_");
@@ -8495,7 +8640,7 @@ ${stderr}`;
8495
8640
  }
8496
8641
  function snapshotBaselineDiff(repos, workspacePath, deps = {}) {
8497
8642
  const exec = deps.exec ?? ((cmd, args, opts) => execFileSync5(cmd, args, opts));
8498
- const homedir30 = deps.homedir ?? (() => os9.homedir());
8643
+ const homedir32 = deps.homedir ?? (() => os9.homedir());
8499
8644
  const baselineDir = path16.join(workspacePath, ".olam", "baseline");
8500
8645
  try {
8501
8646
  fs15.mkdirSync(baselineDir, { recursive: true });
@@ -8511,7 +8656,7 @@ function snapshotBaselineDiff(repos, workspacePath, deps = {}) {
8511
8656
  continue;
8512
8657
  const filename = `${sanitizeRepoFilename(repo.name)}.diff`;
8513
8658
  const outPath = path16.join(baselineDir, filename);
8514
- const repoPath = expandHome(repo.path, homedir30);
8659
+ const repoPath = expandHome(repo.path, homedir32);
8515
8660
  if (!fs15.existsSync(repoPath)) {
8516
8661
  writeBaselineFile(outPath, `# repo: ${repo.name}
8517
8662
  # (skipped: path ${repoPath} does not exist)
@@ -9411,8 +9556,8 @@ function computeGemsFingerprint(repoDir, imageDigest) {
9411
9556
  return hashBuffers(entries);
9412
9557
  }
9413
9558
  function computeNodeFingerprint(repoDir, imageDigest) {
9414
- const candidates = ["yarn.lock", "pnpm-lock.yaml", "package-lock.json"];
9415
- for (const name of candidates) {
9559
+ const candidates2 = ["yarn.lock", "pnpm-lock.yaml", "package-lock.json"];
9560
+ for (const name of candidates2) {
9416
9561
  const lockfile = path18.join(repoDir, name);
9417
9562
  if (fs17.existsSync(lockfile)) {
9418
9563
  const entries = [
@@ -10566,7 +10711,7 @@ async function startSupervisedApps(containerName, worldId, repos, exec, options
10566
10711
  const probeTimeoutMs = options.probeTimeoutMs ?? 3e4;
10567
10712
  const probeIntervalMs = options.probeIntervalMs ?? 1e3;
10568
10713
  const clock = options.clock ?? Date.now;
10569
- const sleep4 = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
10714
+ const sleep5 = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
10570
10715
  for (const repo of repos) {
10571
10716
  if (isPortBound(exec, containerName, repo.hostPort)) {
10572
10717
  throw new PortInUseError(repo.hostPort, repo.name, repo.manifestPath);
@@ -10591,7 +10736,7 @@ async function startSupervisedApps(containerName, worldId, repos, exec, options
10591
10736
  probeTimeoutMs,
10592
10737
  probeIntervalMs,
10593
10738
  clock,
10594
- sleep: sleep4
10739
+ sleep: sleep5
10595
10740
  })));
10596
10741
  return {
10597
10742
  sessionName,
@@ -10658,16 +10803,20 @@ __export(manager_exports, {
10658
10803
  WorkspaceNotFoundError: () => WorkspaceNotFoundError,
10659
10804
  WorldManager: () => WorldManager,
10660
10805
  applyPostgresNetworkOverrides: () => applyPostgresNetworkOverrides,
10806
+ applyPostgresTemplateClone: () => applyPostgresTemplateClone,
10807
+ applyPostgresWorldRole: () => applyPostgresWorldRole,
10661
10808
  buildManifestRuntimeForTest: () => buildManifestRuntime,
10662
10809
  cleanupWorldTailscale: () => cleanupWorldTailscale,
10663
10810
  deriveReadinessChain: () => deriveReadinessChain,
10811
+ deriveWorldRoleName: () => deriveWorldRoleName,
10664
10812
  exposeWorldOverTailscale: () => exposeWorldOverTailscale,
10665
10813
  getTokenScopes: () => getTokenScopes,
10666
10814
  planManifestPipeline: () => planManifestPipeline,
10815
+ redactCreateRolePassword: () => redactCreateRolePassword,
10667
10816
  runManifestRuntime: () => runManifestRuntime
10668
10817
  });
10669
10818
  import * as crypto5 from "node:crypto";
10670
- import { execSync as execSync5 } from "node:child_process";
10819
+ import { execSync as execSync5, spawnSync as spawnSync4 } from "node:child_process";
10671
10820
  import * as fs23 from "node:fs";
10672
10821
  import * as os13 from "node:os";
10673
10822
  import * as path24 from "node:path";
@@ -11059,13 +11208,13 @@ function cleanupWorldTailscale(worldId, registry, _exec = execSync5) {
11059
11208
  }
11060
11209
  }
11061
11210
  function resolveTailscaleBin(_exec = execSync5) {
11062
- const candidates = [
11211
+ const candidates2 = [
11063
11212
  process.env["TAILSCALE_BIN"],
11064
11213
  "/Applications/Tailscale.app/Contents/MacOS/Tailscale",
11065
11214
  "/usr/local/bin/tailscale",
11066
11215
  "/opt/homebrew/bin/tailscale"
11067
11216
  ];
11068
- for (const c of candidates) {
11217
+ for (const c of candidates2) {
11069
11218
  if (!c)
11070
11219
  continue;
11071
11220
  try {
@@ -11076,16 +11225,268 @@ function resolveTailscaleBin(_exec = execSync5) {
11076
11225
  }
11077
11226
  return void 0;
11078
11227
  }
11079
- function applyPostgresNetworkOverrides(worldEnv, enrichedRepos) {
11228
+ function applyPostgresNetworkOverrides(worldEnv, enrichedRepos, worldId) {
11080
11229
  const hasPostgres = enrichedRepos.some((r) => r.manifest?.services?.["postgres"] !== void 0);
11081
11230
  if (!hasPostgres)
11082
11231
  return;
11083
- worldEnv["POSTGRESQL_HOST"] = "postgres";
11232
+ const hasSeedTemplate = enrichedRepos.some((r) => {
11233
+ const pg = r.manifest?.services?.["postgres"];
11234
+ return pg?.seed_template !== void 0;
11235
+ });
11236
+ const host = hasSeedTemplate ? "olam-postgres" : "postgres";
11237
+ worldEnv["POSTGRESQL_HOST"] = host;
11084
11238
  worldEnv["POSTGRESQL_PORT"] = "5432";
11085
- if ("POSTGRESQL_COMMON_HOST" in worldEnv)
11086
- worldEnv["POSTGRESQL_COMMON_HOST"] = "postgres";
11087
- if ("POSTGRESQL_COMMON_PORT" in worldEnv)
11239
+ if (hasSeedTemplate) {
11240
+ worldEnv["POSTGRESQL_COMMON_HOST"] = host;
11088
11241
  worldEnv["POSTGRESQL_COMMON_PORT"] = "5432";
11242
+ if (worldId !== void 0) {
11243
+ assertSafeWorldId(worldId);
11244
+ const seedTemplates = /* @__PURE__ */ new Set();
11245
+ for (const repo of enrichedRepos) {
11246
+ const pg = repo.manifest?.services?.["postgres"];
11247
+ const raw = pg?.seed_template;
11248
+ if (typeof raw === "string")
11249
+ seedTemplates.add(raw);
11250
+ else if (Array.isArray(raw)) {
11251
+ for (const s of raw)
11252
+ if (typeof s === "string")
11253
+ seedTemplates.add(s);
11254
+ }
11255
+ }
11256
+ for (const seed of seedTemplates) {
11257
+ const match2 = seed.match(/^atlas_([a-z][a-z0-9_]*?)_seed$/i);
11258
+ if (match2 && match2[1] !== void 0) {
11259
+ const purpose = match2[1].toUpperCase();
11260
+ const envKey = `POSTGRESQL_${purpose}_DATABASE`;
11261
+ worldEnv[envKey] = deriveWorldDbName(seed, worldId);
11262
+ }
11263
+ }
11264
+ }
11265
+ } else {
11266
+ if ("POSTGRESQL_COMMON_HOST" in worldEnv)
11267
+ worldEnv["POSTGRESQL_COMMON_HOST"] = "postgres";
11268
+ if ("POSTGRESQL_COMMON_PORT" in worldEnv)
11269
+ worldEnv["POSTGRESQL_COMMON_PORT"] = "5432";
11270
+ }
11271
+ }
11272
+ function applyPostgresTemplateClone(worldId, enrichedRepos, options = {}) {
11273
+ assertSafeWorldId(worldId);
11274
+ const container = options.singletonContainer ?? "olam-postgres";
11275
+ const user = options.postgresUser ?? "development";
11276
+ const seedTemplates = [];
11277
+ const postCloneSqlBySeed = /* @__PURE__ */ new Map();
11278
+ for (const repo of enrichedRepos) {
11279
+ const pg = repo.manifest?.services?.["postgres"];
11280
+ if (!pg?.seed_template)
11281
+ continue;
11282
+ const list = Array.isArray(pg.seed_template) ? pg.seed_template : [pg.seed_template];
11283
+ for (const t of list) {
11284
+ if (typeof t === "string" && t.length > 0 && !seedTemplates.includes(t)) {
11285
+ seedTemplates.push(t);
11286
+ }
11287
+ }
11288
+ if (pg.post_clone_sql && typeof pg.post_clone_sql === "object") {
11289
+ for (const [seedKey, sqlList] of Object.entries(pg.post_clone_sql)) {
11290
+ if (!Array.isArray(sqlList))
11291
+ continue;
11292
+ const existing = postCloneSqlBySeed.get(seedKey) ?? [];
11293
+ for (const stmt of sqlList) {
11294
+ if (typeof stmt === "string" && stmt.length > 0)
11295
+ existing.push(stmt);
11296
+ }
11297
+ postCloneSqlBySeed.set(seedKey, existing);
11298
+ }
11299
+ }
11300
+ }
11301
+ if (seedTemplates.length === 0) {
11302
+ return { worldDbNames: [] };
11303
+ }
11304
+ const spawn11 = options.spawn ?? spawnSync4;
11305
+ const seedToWorldDb = /* @__PURE__ */ new Map();
11306
+ for (const seed of seedTemplates) {
11307
+ seedToWorldDb.set(seed, deriveWorldDbName(seed, worldId));
11308
+ }
11309
+ const created = [];
11310
+ for (const seed of seedTemplates) {
11311
+ const worldDb = seedToWorldDb.get(seed);
11312
+ const exists = spawn11("docker", [
11313
+ "exec",
11314
+ container,
11315
+ "psql",
11316
+ "-U",
11317
+ user,
11318
+ "-tAc",
11319
+ `SELECT 1 FROM pg_database WHERE datname='${worldDb}'`
11320
+ ], { encoding: "utf-8" });
11321
+ const dbAlreadyExists = exists.status === 0 && (exists.stdout || "").trim() === "1";
11322
+ if (!dbAlreadyExists) {
11323
+ const sql = `CREATE DATABASE "${worldDb}" TEMPLATE "${seed}"`;
11324
+ const create = spawn11("docker", ["exec", container, "psql", "-U", user, "-d", "postgres", "-v", "ON_ERROR_STOP=1", "-c", sql], { encoding: "utf-8" });
11325
+ if (create.status !== 0) {
11326
+ throw new Error(`failed to CREATE DATABASE "${worldDb}" TEMPLATE "${seed}": ${(create.stderr || "").trim()}`);
11327
+ }
11328
+ }
11329
+ created.push(worldDb);
11330
+ const stmts = postCloneSqlBySeed.get(seed);
11331
+ if (stmts !== void 0 && stmts.length > 0) {
11332
+ for (const rawStmt of stmts) {
11333
+ const stmt = interpolatePostCloneSql(rawStmt, worldId, seedToWorldDb);
11334
+ const fixup = spawn11("docker", ["exec", container, "psql", "-U", user, "-d", worldDb, "-v", "ON_ERROR_STOP=1", "-c", stmt], { encoding: "utf-8" });
11335
+ if (fixup.status !== 0) {
11336
+ throw new Error(`post_clone_sql against "${worldDb}" failed (statement: ${truncate(stmt, 120)}): ${(fixup.stderr || "").trim()}`);
11337
+ }
11338
+ }
11339
+ }
11340
+ }
11341
+ return { worldDbNames: created };
11342
+ }
11343
+ function interpolatePostCloneSql(stmt, worldId, seedToWorldDb) {
11344
+ return stmt.replace(/\$\{([^}]+)\}/g, (_, expr) => {
11345
+ if (expr === "WORLD_ID")
11346
+ return worldId;
11347
+ const dbMatch = expr.match(/^WORLD_DB:(.+)$/);
11348
+ if (dbMatch && dbMatch[1] !== void 0) {
11349
+ const target = seedToWorldDb.get(dbMatch[1]);
11350
+ if (target === void 0) {
11351
+ throw new Error(`post_clone_sql references seed "${dbMatch[1]}" not declared in seed_template`);
11352
+ }
11353
+ return target;
11354
+ }
11355
+ throw new Error(`post_clone_sql contains unknown placeholder "\${${expr}}" \u2014 only \${WORLD_ID} and \${WORLD_DB:<seed>} are supported`);
11356
+ });
11357
+ }
11358
+ function truncate(s, max) {
11359
+ return s.length <= max ? s : s.slice(0, max - 1) + "\u2026";
11360
+ }
11361
+ function deriveWorldRoleName(worldId) {
11362
+ return `olam_world_${worldId}`;
11363
+ }
11364
+ function applyPostgresWorldRole(worldId, worldDbNames, options = {}) {
11365
+ assertSafeWorldId(worldId);
11366
+ if (worldDbNames.length === 0) {
11367
+ throw new Error(`applyPostgresWorldRole called with empty worldDbNames for "${worldId}" \u2014 should only run when applyPostgresTemplateClone produced clones`);
11368
+ }
11369
+ const spawn11 = options.spawn ?? spawnSync4;
11370
+ const container = options.singletonContainer ?? "olam-postgres";
11371
+ const user = options.postgresUser ?? "development";
11372
+ const genPassword = options.generatePassword ?? defaultPasswordGenerator;
11373
+ const worldRoleName = deriveWorldRoleName(worldId);
11374
+ const password = genPassword();
11375
+ const exists = spawn11("docker", [
11376
+ "exec",
11377
+ container,
11378
+ "psql",
11379
+ "-U",
11380
+ user,
11381
+ "-tAc",
11382
+ `SELECT 1 FROM pg_roles WHERE rolname='${escapeSqlLiteral(worldRoleName)}'`
11383
+ ], { encoding: "utf-8" });
11384
+ const roleExists = exists.status === 0 && (exists.stdout || "").trim() === "1";
11385
+ const escapedPassword = escapeSqlLiteral(password);
11386
+ const roleDdl = roleExists ? `ALTER ROLE "${worldRoleName}" WITH LOGIN PASSWORD '${escapedPassword}' NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION` : `CREATE ROLE "${worldRoleName}" WITH LOGIN PASSWORD '${escapedPassword}' NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION`;
11387
+ const dml = spawn11("docker", ["exec", container, "psql", "-U", user, "-d", "postgres", "-v", "ON_ERROR_STOP=1", "-c", roleDdl], { encoding: "utf-8" });
11388
+ if (dml.status !== 0) {
11389
+ throw new Error(`failed to ${roleExists ? "ALTER" : "CREATE"} ROLE "${worldRoleName}": ` + redactCreateRolePassword((dml.stderr || "").trim()));
11390
+ }
11391
+ for (const worldDb of worldDbNames) {
11392
+ const grantConnect = spawn11("docker", [
11393
+ "exec",
11394
+ container,
11395
+ "psql",
11396
+ "-U",
11397
+ user,
11398
+ "-d",
11399
+ "postgres",
11400
+ "-v",
11401
+ "ON_ERROR_STOP=1",
11402
+ "-c",
11403
+ `GRANT CONNECT ON DATABASE "${worldDb}" TO "${worldRoleName}"`
11404
+ ], { encoding: "utf-8" });
11405
+ if (grantConnect.status !== 0) {
11406
+ throw new Error(`failed to GRANT CONNECT on "${worldDb}" to "${worldRoleName}": ${(grantConnect.stderr || "").trim()}`);
11407
+ }
11408
+ const perDbGrants = [
11409
+ `GRANT USAGE ON SCHEMA public TO "${worldRoleName}"`,
11410
+ `GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "${worldRoleName}"`,
11411
+ `GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO "${worldRoleName}"`,
11412
+ // DEFAULT PRIVILEGES for future objects — Rails migrations create new
11413
+ // tables/sequences post-spawn and they need to be grantable.
11414
+ `ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO "${worldRoleName}"`,
11415
+ `ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO "${worldRoleName}"`
11416
+ ].join("; ");
11417
+ const grantResult = spawn11("docker", ["exec", container, "psql", "-U", user, "-d", worldDb, "-v", "ON_ERROR_STOP=1", "-c", perDbGrants], { encoding: "utf-8" });
11418
+ if (grantResult.status !== 0) {
11419
+ throw new Error(`failed to GRANT privileges on "${worldDb}" to "${worldRoleName}": ${(grantResult.stderr || "").trim()}`);
11420
+ }
11421
+ }
11422
+ return { worldRoleName, password };
11423
+ }
11424
+ function defaultPasswordGenerator() {
11425
+ return crypto5.randomBytes(24).toString("base64url");
11426
+ }
11427
+ function escapeSqlLiteral(s) {
11428
+ return s.replace(/'/g, "''");
11429
+ }
11430
+ function redactCreateRolePassword(s) {
11431
+ return s.replace(/PASSWORD\s+'[^']*'/g, "PASSWORD '<scrubbed>'");
11432
+ }
11433
+ function dropPostgresWorldDbs(worldId, worldDbNames, options = {}) {
11434
+ if (worldDbNames.length === 0)
11435
+ return;
11436
+ assertSafeWorldId(worldId);
11437
+ const spawn11 = options.spawn ?? spawnSync4;
11438
+ const container = options.container ?? "olam-postgres";
11439
+ const user = options.user ?? "development";
11440
+ for (const db of worldDbNames) {
11441
+ const drop = spawn11("docker", [
11442
+ "exec",
11443
+ container,
11444
+ "psql",
11445
+ "-U",
11446
+ user,
11447
+ "-d",
11448
+ "postgres",
11449
+ "-c",
11450
+ `DROP DATABASE IF EXISTS "${db}" WITH (FORCE)`
11451
+ ], { encoding: "utf-8" });
11452
+ if (drop.status !== 0) {
11453
+ console.warn(`[manager] destroyWorld(${worldId}): failed to DROP "${db}" from singleton: ${(drop.stderr || "").trim()}`);
11454
+ }
11455
+ }
11456
+ }
11457
+ function dropPostgresWorldRole(worldId, worldRoleName, options = {}) {
11458
+ if (!worldRoleName)
11459
+ return;
11460
+ assertSafeWorldId(worldId);
11461
+ const spawn11 = options.spawn ?? spawnSync4;
11462
+ const container = options.container ?? "olam-postgres";
11463
+ const user = options.user ?? "development";
11464
+ const cleanup = spawn11("docker", [
11465
+ "exec",
11466
+ container,
11467
+ "psql",
11468
+ "-U",
11469
+ user,
11470
+ "-d",
11471
+ "postgres",
11472
+ "-c",
11473
+ `DROP OWNED BY "${worldRoleName}" CASCADE; DROP ROLE IF EXISTS "${worldRoleName}"`
11474
+ ], { encoding: "utf-8" });
11475
+ if (cleanup.status !== 0) {
11476
+ console.warn(`[manager] destroyWorld(${worldId}): failed to DROP role "${worldRoleName}" from singleton: ${(cleanup.stderr || "").trim()}`);
11477
+ }
11478
+ }
11479
+ function assertSafeWorldId(worldId) {
11480
+ if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(worldId)) {
11481
+ throw new Error(`world id "${worldId}" contains characters outside [a-z0-9-] \u2014 hybrid-postgres + post_clone_sql interpolation require the name-generator's [a-z0-9-]+ alphabet`);
11482
+ }
11483
+ }
11484
+ function deriveWorldDbName(seed, worldId) {
11485
+ if (seed.endsWith("_seed")) {
11486
+ const prefix = seed.slice(0, -"_seed".length);
11487
+ return `${prefix}_world_${worldId}`;
11488
+ }
11489
+ return `${seed}_world_${worldId}`;
11089
11490
  }
11090
11491
  var BotIdentityError, ADJECTIVES, NOUNS, AuthPreflightError, WorkspaceNotFoundError, WorldManager;
11091
11492
  var init_manager = __esm({
@@ -11472,6 +11873,8 @@ ${detail}`);
11472
11873
  const hostPort = override !== void 0 ? override : ap.port + 1e4 + portOffset;
11473
11874
  return { repoName: ap.name, internalPort: ap.port, hostPort, url: `http://localhost:${hostPort}` };
11474
11875
  });
11876
+ let worldDbNames = void 0;
11877
+ let worldRoleName = void 0;
11475
11878
  try {
11476
11879
  const worldEnv = {};
11477
11880
  if (opts.task)
@@ -11572,7 +11975,27 @@ ${detail}`);
11572
11975
  }
11573
11976
  }
11574
11977
  }
11575
- applyPostgresNetworkOverrides(worldEnv, enrichedRepos);
11978
+ applyPostgresNetworkOverrides(worldEnv, enrichedRepos, worldId);
11979
+ const cloneResult = applyPostgresTemplateClone(worldId, enrichedRepos);
11980
+ worldDbNames = cloneResult.worldDbNames.length > 0 ? cloneResult.worldDbNames : void 0;
11981
+ if (worldDbNames !== void 0) {
11982
+ const roleResult = applyPostgresWorldRole(worldId, worldDbNames);
11983
+ worldRoleName = roleResult.worldRoleName;
11984
+ worldEnv["POSTGRESQL_USERNAME"] = roleResult.worldRoleName;
11985
+ worldEnv["POSTGRESQL_PASSWORD"] = roleResult.password;
11986
+ worldEnv["POSTGRESQL_COMMON_USERNAME"] = roleResult.worldRoleName;
11987
+ worldEnv["POSTGRESQL_COMMON_PASSWORD"] = roleResult.password;
11988
+ worldEnv["POSTGRESQL_MERCHANT_USERNAME"] = roleResult.worldRoleName;
11989
+ worldEnv["POSTGRESQL_MERCHANT_PASSWORD"] = roleResult.password;
11990
+ }
11991
+ if (worldDbNames !== void 0 || worldRoleName !== void 0) {
11992
+ this.registry.update(worldId, {
11993
+ ...worldDbNames !== void 0 ? { worldDbNames: [...worldDbNames] } : {},
11994
+ ...worldRoleName !== void 0 ? { worldRoleName } : {}
11995
+ });
11996
+ }
11997
+ const hybridActive = worldDbNames !== void 0;
11998
+ const extraNetworks = hybridActive ? ["olam-shared"] : void 0;
11576
11999
  await this.provider.createWorld({
11577
12000
  id: worldId,
11578
12001
  name: opts.name,
@@ -11582,6 +12005,7 @@ ${detail}`);
11582
12005
  portOffset,
11583
12006
  workspacePath,
11584
12007
  appPorts: appPorts.length > 0 ? appPorts : void 0,
12008
+ ...extraNetworks ? { extraNetworks } : {},
11585
12009
  // Phase 7 A1: per-world cost ceiling sourced from config.cost.
11586
12010
  // Cloudflare provider forwards this to /session/start so the DO
11587
12011
  // can enforce kill-switch on `cost_update` events. Other providers
@@ -11605,7 +12029,16 @@ ${detail}`);
11605
12029
  sm.transition("running");
11606
12030
  this.registry.update(worldId, {
11607
12031
  status: "running",
11608
- ...appPortUrls.length > 0 ? { appPortUrls } : {}
12032
+ ...appPortUrls.length > 0 ? { appPortUrls } : {},
12033
+ // olam-hybrid-shared-postgres A3: persist the cloned per-world DB
12034
+ // names so olam destroy + olam gc can find them later (A7's
12035
+ // world_db_names TEXT JSON column).
12036
+ ...worldDbNames !== void 0 ? { worldDbNames: [...worldDbNames] } : {},
12037
+ // olam-hybrid-shared-postgres A4: persist the role name so olam destroy
12038
+ // can DROP it after the world's DBs are gone (A7's world_role_name
12039
+ // column). Password is NOT persisted — it lives only in the world
12040
+ // container's env and the in-memory return value.
12041
+ ...worldRoleName !== void 0 ? { worldRoleName } : {}
11609
12042
  });
11610
12043
  const containerName = `olam-${worldId}-devbox`;
11611
12044
  try {
@@ -11905,6 +12338,14 @@ ${opts.task}`;
11905
12338
  cleanupWorldTailscale(worldId, this.registry);
11906
12339
  } catch {
11907
12340
  }
12341
+ const worldDbNames = world.worldDbNames;
12342
+ if (worldDbNames !== void 0 && worldDbNames.length > 0) {
12343
+ dropPostgresWorldDbs(worldId, worldDbNames);
12344
+ }
12345
+ const worldRoleName = world.worldRoleName;
12346
+ if (typeof worldRoleName === "string" && worldRoleName.length > 0) {
12347
+ dropPostgresWorldRole(worldId, worldRoleName);
12348
+ }
11908
12349
  try {
11909
12350
  await this.provider.destroyWorld(worldId);
11910
12351
  } catch {
@@ -13306,7 +13747,7 @@ function isCloudflaredAvailable() {
13306
13747
  }
13307
13748
  }
13308
13749
  function startTunnel(port) {
13309
- return new Promise((resolve12, reject) => {
13750
+ return new Promise((resolve14, reject) => {
13310
13751
  const child = spawn3("cloudflared", ["tunnel", "--url", `http://localhost:${port}`], {
13311
13752
  stdio: ["ignore", "pipe", "pipe"],
13312
13753
  detached: false
@@ -13328,7 +13769,7 @@ function startTunnel(port) {
13328
13769
  if (match2) {
13329
13770
  resolved = true;
13330
13771
  clearTimeout(timeout);
13331
- resolve12(match2[0]);
13772
+ resolve14(match2[0]);
13332
13773
  }
13333
13774
  }
13334
13775
  child.stdout?.on("data", scan);
@@ -13415,8 +13856,8 @@ var init_dashboard = __esm({
13415
13856
  }
13416
13857
  throw err;
13417
13858
  }
13418
- await new Promise((resolve12, reject) => {
13419
- this.server.on("listening", resolve12);
13859
+ await new Promise((resolve14, reject) => {
13860
+ this.server.on("listening", resolve14);
13420
13861
  this.server.on("error", reject);
13421
13862
  });
13422
13863
  this.info = { localUrl: `http://localhost:${port}` };
@@ -13462,8 +13903,8 @@ var init_dashboard = __esm({
13462
13903
  async stop() {
13463
13904
  stopTunnel();
13464
13905
  if (this.server) {
13465
- await new Promise((resolve12) => {
13466
- this.server.close(() => resolve12());
13906
+ await new Promise((resolve14) => {
13907
+ this.server.close(() => resolve14());
13467
13908
  });
13468
13909
  this.server = null;
13469
13910
  }
@@ -13689,10 +14130,10 @@ import * as crypto6 from "node:crypto";
13689
14130
  import * as fs26 from "node:fs";
13690
14131
  import * as os15 from "node:os";
13691
14132
  import * as path28 from "node:path";
13692
- import { spawnSync as spawnSync4 } from "node:child_process";
14133
+ import { spawnSync as spawnSync5 } from "node:child_process";
13693
14134
  import Dockerode2 from "dockerode";
13694
14135
  function findComposeFile() {
13695
- const candidates = [
14136
+ const candidates2 = [
13696
14137
  // Bundled path: dist/index.js lives at <pkg>/dist/; host-cp/ is a sibling of dist/
13697
14138
  path28.resolve(path28.dirname(new URL(import.meta.url).pathname), "../host-cp/compose.yaml"),
13698
14139
  // Source-mode: cwd is monorepo root
@@ -13700,7 +14141,7 @@ function findComposeFile() {
13700
14141
  // Source-mode: cwd is one level inside the monorepo
13701
14142
  path28.resolve(process.cwd(), "../packages/host-cp/compose.yaml")
13702
14143
  ];
13703
- for (const c of candidates) {
14144
+ for (const c of candidates2) {
13704
14145
  if (fs26.existsSync(c)) return c;
13705
14146
  }
13706
14147
  return path28.resolve(process.cwd(), "packages/host-cp/compose.yaml");
@@ -13875,7 +14316,7 @@ async function probeHealth() {
13875
14316
  }
13876
14317
  }
13877
14318
  function runCompose(args, composeFile, extraEnv = {}) {
13878
- const result = spawnSync4("docker", ["compose", "-f", composeFile, ...args], {
14319
+ const result = spawnSync5("docker", ["compose", "-f", composeFile, ...args], {
13879
14320
  encoding: "utf-8",
13880
14321
  stdio: ["ignore", "pipe", "pipe"],
13881
14322
  env: { ...process.env, ...extraEnv }
@@ -13902,7 +14343,7 @@ function buildComposeEnv(authSecret, ghToken) {
13902
14343
  }
13903
14344
  function captureGhToken() {
13904
14345
  try {
13905
- const result = spawnSync4("gh", ["auth", "token"], {
14346
+ const result = spawnSync5("gh", ["auth", "token"], {
13906
14347
  encoding: "utf-8",
13907
14348
  stdio: ["ignore", "pipe", "pipe"]
13908
14349
  });
@@ -14282,15 +14723,15 @@ __export(install_root_exports, {
14282
14723
  resolveBuildScript: () => resolveBuildScript
14283
14724
  });
14284
14725
  import { existsSync as existsSync24 } from "node:fs";
14285
- import { dirname as dirname17, join as join30, resolve as resolve9 } from "node:path";
14726
+ import { dirname as dirname17, join as join30, resolve as resolve10 } from "node:path";
14286
14727
  import { fileURLToPath as fileURLToPath5 } from "node:url";
14287
14728
  function installRoot(metaUrl = import.meta.url) {
14288
- const here2 = fileURLToPath5(metaUrl);
14289
- return resolve9(dirname17(here2), "..");
14729
+ const here3 = fileURLToPath5(metaUrl);
14730
+ return resolve10(dirname17(here3), "..");
14290
14731
  }
14291
14732
  function isDevMode(env = process.env, installRootDir = installRoot()) {
14292
14733
  if (env.OLAM_DEV !== "1") return false;
14293
- const repoRoot = resolve9(installRootDir, "..", "..");
14734
+ const repoRoot = resolve10(installRootDir, "..", "..");
14294
14735
  return existsSync24(join30(repoRoot, "packages")) && existsSync24(join30(repoRoot, "package.json"));
14295
14736
  }
14296
14737
  function resolveBuildScript(input) {
@@ -14298,7 +14739,7 @@ function resolveBuildScript(input) {
14298
14739
  if (!isDevMode(env, installRootDir)) {
14299
14740
  throw new MissingBuildScriptError(scriptRelPath);
14300
14741
  }
14301
- const repoRoot = resolve9(installRootDir, "..", "..");
14742
+ const repoRoot = resolve10(installRootDir, "..", "..");
14302
14743
  return join30(repoRoot, scriptRelPath);
14303
14744
  }
14304
14745
  var MissingBuildScriptError;
@@ -14329,7 +14770,7 @@ __export(protocol_version_exports, {
14329
14770
  inspectImageProtocolVersions: () => inspectImageProtocolVersions,
14330
14771
  parseProtocolVersionsLabel: () => parseProtocolVersionsLabel
14331
14772
  });
14332
- import { spawnSync as spawnSync5 } from "node:child_process";
14773
+ import { spawnSync as spawnSync6 } from "node:child_process";
14333
14774
  function parseProtocolVersionsLabel(labelValue) {
14334
14775
  if (!labelValue) return [];
14335
14776
  const parts = labelValue.split(",").map((s) => s.trim()).filter(Boolean);
@@ -14400,7 +14841,7 @@ var init_protocol_version = __esm({
14400
14841
  "use strict";
14401
14842
  OLAM_PROTOCOL_VERSIONS_SUPPORTED = [1];
14402
14843
  realDockerInspect = (imageRef) => {
14403
- const result = spawnSync5(
14844
+ const result = spawnSync6(
14404
14845
  "docker",
14405
14846
  ["inspect", imageRef, "--format", '{{ index .Config.Labels "olam.protocol.versions" }}'],
14406
14847
  { encoding: "utf8", timeout: 1e4 }
@@ -14414,6 +14855,158 @@ var init_protocol_version = __esm({
14414
14855
  }
14415
14856
  });
14416
14857
 
14858
+ // ../core/dist/auth/postgres-init-helpers.js
14859
+ var postgres_init_helpers_exports = {};
14860
+ __export(postgres_init_helpers_exports, {
14861
+ DEFAULT_POSTGRES_CONTAINER: () => DEFAULT_POSTGRES_CONTAINER,
14862
+ DEFAULT_POSTGRES_DB: () => DEFAULT_POSTGRES_DB,
14863
+ DEFAULT_POSTGRES_HOST_PORT: () => DEFAULT_POSTGRES_HOST_PORT,
14864
+ DEFAULT_POSTGRES_IMAGE: () => DEFAULT_POSTGRES_IMAGE,
14865
+ DEFAULT_POSTGRES_NETWORK: () => DEFAULT_POSTGRES_NETWORK,
14866
+ DEFAULT_POSTGRES_PASSWORD: () => DEFAULT_POSTGRES_PASSWORD,
14867
+ DEFAULT_POSTGRES_READY_TIMEOUT_MS: () => DEFAULT_POSTGRES_READY_TIMEOUT_MS,
14868
+ DEFAULT_POSTGRES_USER: () => DEFAULT_POSTGRES_USER,
14869
+ ensureOlamPostgresSingleton: () => ensureOlamPostgresSingleton,
14870
+ ensureOlamSharedNetwork: () => ensureOlamSharedNetwork,
14871
+ pullPostgresImage: () => pullPostgresImage,
14872
+ startPostgresSingleton: () => startPostgresSingleton,
14873
+ statusPostgresSingleton: () => statusPostgresSingleton,
14874
+ waitPostgresReady: () => waitPostgresReady
14875
+ });
14876
+ import { spawnSync as spawnSync7 } from "node:child_process";
14877
+ function resolve11(options) {
14878
+ return {
14879
+ network: options.network ?? DEFAULT_POSTGRES_NETWORK,
14880
+ containerName: options.containerName ?? DEFAULT_POSTGRES_CONTAINER,
14881
+ image: options.image ?? DEFAULT_POSTGRES_IMAGE,
14882
+ hostPort: options.hostPort ?? DEFAULT_POSTGRES_HOST_PORT,
14883
+ user: options.user ?? DEFAULT_POSTGRES_USER,
14884
+ password: options.password ?? DEFAULT_POSTGRES_PASSWORD,
14885
+ db: options.db ?? DEFAULT_POSTGRES_DB,
14886
+ readyTimeoutMs: options.readyTimeoutMs ?? DEFAULT_POSTGRES_READY_TIMEOUT_MS,
14887
+ spawn: options.spawn ?? spawnSync7
14888
+ };
14889
+ }
14890
+ function statusPostgresSingleton(options = {}) {
14891
+ const opts = resolve11(options);
14892
+ const inspect = opts.spawn("docker", ["inspect", "--format", "{{.State.Status}}", opts.containerName], { encoding: "utf-8" });
14893
+ if (inspect.status !== 0)
14894
+ return "missing";
14895
+ const raw = (inspect.stdout || "").trim();
14896
+ return raw === "running" ? "running" : "stopped";
14897
+ }
14898
+ function ensureOlamSharedNetwork(options = {}) {
14899
+ const opts = resolve11(options);
14900
+ const create = opts.spawn("docker", ["network", "create", opts.network], {
14901
+ encoding: "utf-8"
14902
+ });
14903
+ if (create.status !== 0 && !(create.stderr || "").includes("already exists")) {
14904
+ throw new Error(`failed to create docker network ${opts.network}: ${(create.stderr || "").trim()}`);
14905
+ }
14906
+ const inspect = opts.spawn("docker", ["network", "inspect", opts.network, "--format", "{{.Driver}}"], { encoding: "utf-8" });
14907
+ if (inspect.status !== 0) {
14908
+ throw new Error(`failed to inspect docker network ${opts.network} after create: ${(inspect.stderr || "").trim()}`);
14909
+ }
14910
+ const driver = (inspect.stdout || "").trim();
14911
+ if (driver !== "bridge") {
14912
+ throw new Error(`docker network ${opts.network} exists with driver=${driver}, expected bridge. Run \`docker network rm ${opts.network} && olam init\` to recreate.`);
14913
+ }
14914
+ }
14915
+ function pullPostgresImage(options = {}) {
14916
+ const opts = resolve11(options);
14917
+ const pull = opts.spawn("docker", ["pull", "--platform", "linux/amd64", opts.image], { encoding: "utf-8" });
14918
+ if (pull.status !== 0) {
14919
+ throw new Error(`failed to pull postgres image ${opts.image}: ${(pull.stderr || "").trim()}`);
14920
+ }
14921
+ }
14922
+ function startPostgresSingleton(options = {}) {
14923
+ const opts = resolve11(options);
14924
+ const state = statusPostgresSingleton(options);
14925
+ if (state === "running")
14926
+ return;
14927
+ if (state === "stopped") {
14928
+ const start = opts.spawn("docker", ["start", opts.containerName], {
14929
+ encoding: "utf-8"
14930
+ });
14931
+ if (start.status !== 0) {
14932
+ throw new Error(`failed to start existing postgres container ${opts.containerName}: ${(start.stderr || "").trim()}`);
14933
+ }
14934
+ return;
14935
+ }
14936
+ const run = opts.spawn("docker", [
14937
+ "run",
14938
+ "--detach",
14939
+ "--name",
14940
+ opts.containerName,
14941
+ "--network",
14942
+ opts.network,
14943
+ "--network-alias",
14944
+ opts.containerName,
14945
+ "--restart",
14946
+ "unless-stopped",
14947
+ "--publish",
14948
+ `127.0.0.1:${opts.hostPort}:5432`,
14949
+ "--env",
14950
+ `POSTGRES_USER=${opts.user}`,
14951
+ "--env",
14952
+ `POSTGRES_PASSWORD=${opts.password}`,
14953
+ "--env",
14954
+ `POSTGRES_DB=${opts.db}`,
14955
+ "--env",
14956
+ `TZ=Asia/Singapore`,
14957
+ opts.image
14958
+ ], { encoding: "utf-8" });
14959
+ if (run.status !== 0) {
14960
+ throw new Error(`failed to start postgres singleton ${opts.containerName}: ${(run.stderr || "").trim()}`);
14961
+ }
14962
+ }
14963
+ async function waitPostgresReady(options = {}) {
14964
+ const opts = resolve11(options);
14965
+ const deadline = Date.now() + opts.readyTimeoutMs;
14966
+ while (Date.now() < deadline) {
14967
+ const ready = opts.spawn("docker", ["exec", opts.containerName, "pg_isready", "-U", opts.user], { encoding: "utf-8" });
14968
+ if (ready.status === 0)
14969
+ return true;
14970
+ await sleep3(1e3);
14971
+ }
14972
+ return false;
14973
+ }
14974
+ async function ensureOlamPostgresSingleton(options = {}) {
14975
+ const opts = resolve11(options);
14976
+ ensureOlamSharedNetwork(options);
14977
+ pullPostgresImage(options);
14978
+ startPostgresSingleton(options);
14979
+ const ready = await waitPostgresReady(options);
14980
+ if (!ready) {
14981
+ throw new Error(`postgres singleton ${opts.containerName} failed pg_isready within ${opts.readyTimeoutMs}ms. Inspect with \`docker logs ${opts.containerName}\`.`);
14982
+ }
14983
+ const finalState = statusPostgresSingleton(options);
14984
+ return {
14985
+ state: finalState,
14986
+ network: opts.network,
14987
+ container: opts.containerName,
14988
+ hostPort: opts.hostPort,
14989
+ ready
14990
+ };
14991
+ }
14992
+ function sleep3(ms) {
14993
+ return new Promise((resolve14) => setTimeout(resolve14, ms));
14994
+ }
14995
+ 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;
14996
+ var init_postgres_init_helpers = __esm({
14997
+ "../core/dist/auth/postgres-init-helpers.js"() {
14998
+ "use strict";
14999
+ DEFAULT_POSTGRES_NETWORK = "olam-shared";
15000
+ DEFAULT_POSTGRES_CONTAINER = "olam-postgres";
15001
+ DEFAULT_POSTGRES_HOST_PORT = 5433;
15002
+ DEFAULT_POSTGRES_IMAGE = "odidev/postgis:13-3.1-alpine";
15003
+ DEFAULT_POSTGRES_USER = "development";
15004
+ DEFAULT_POSTGRES_PASSWORD = "development";
15005
+ DEFAULT_POSTGRES_DB = "postgres";
15006
+ DEFAULT_POSTGRES_READY_TIMEOUT_MS = 6e4;
15007
+ }
15008
+ });
15009
+
14417
15010
  // src/registry-allowlist.ts
14418
15011
  var registry_allowlist_exports = {};
14419
15012
  __export(registry_allowlist_exports, {
@@ -15256,7 +15849,7 @@ init_host_cp();
15256
15849
  init_auth();
15257
15850
  import * as fs27 from "node:fs";
15258
15851
  import * as path29 from "node:path";
15259
- import { spawnSync as spawnSync7 } from "node:child_process";
15852
+ import { spawnSync as spawnSync9 } from "node:child_process";
15260
15853
  import ora2 from "ora";
15261
15854
  import pc7 from "picocolors";
15262
15855
 
@@ -15266,7 +15859,7 @@ init_install_root();
15266
15859
  init_exit_codes();
15267
15860
  init_protocol_version();
15268
15861
  init_output();
15269
- import { spawnSync as spawnSync6 } from "node:child_process";
15862
+ import { spawnSync as spawnSync8 } from "node:child_process";
15270
15863
  import { existsSync as existsSync25, readFileSync as readFileSync19 } from "node:fs";
15271
15864
  import { join as join31 } from "node:path";
15272
15865
  import ora from "ora";
@@ -15298,7 +15891,7 @@ function loadImageDigests(installRootDir = installRoot()) {
15298
15891
  return parsed;
15299
15892
  }
15300
15893
  var REAL_OLAM_SUBCOMMAND = (args) => {
15301
- const result = spawnSync6(process.execPath, [
15894
+ const result = spawnSync8(process.execPath, [
15302
15895
  process.argv[1] ?? "olam",
15303
15896
  ...args
15304
15897
  ], {
@@ -15460,6 +16053,48 @@ async function runBootstrap2(opts, deps = {}) {
15460
16053
  return { exitCode: EXIT_GENERIC_ERROR, summary: "auth up failed" };
15461
16054
  }
15462
16055
  authUpSpinner.succeed("auth-service running");
16056
+ if (opts.skipPostgresSingleton || process.env.OLAM_BOOTSTRAP_SKIP_POSTGRES === "1") {
16057
+ printInfo("postgres singleton", "skipped");
16058
+ } else {
16059
+ const pgSpinner = ora("Starting olam-postgres singleton").start();
16060
+ try {
16061
+ const { ensureOlamPostgresSingleton: ensureOlamPostgresSingleton2 } = await Promise.resolve().then(() => (init_postgres_init_helpers(), postgres_init_helpers_exports));
16062
+ const result = await ensureOlamPostgresSingleton2();
16063
+ pgSpinner.succeed(
16064
+ `olam-postgres ${result.state} on network ${result.network} \u2192 127.0.0.1:${result.hostPort}`
16065
+ );
16066
+ } catch (err) {
16067
+ pgSpinner.fail("olam-postgres singleton bring-up failed");
16068
+ process.stderr.write(` ${err instanceof Error ? err.message : String(err)}
16069
+ `);
16070
+ process.stderr.write(` Re-run \`olam bootstrap\` after resolving, or skip via OLAM_BOOTSTRAP_SKIP_POSTGRES=1.
16071
+ `);
16072
+ return { exitCode: EXIT_GENERIC_ERROR, summary: "postgres singleton bring-up failed" };
16073
+ }
16074
+ }
16075
+ if (opts.skipMemory || process.env.OLAM_BOOTSTRAP_SKIP_MEMORY === "1") {
16076
+ printInfo("agent-memory", "skipped");
16077
+ } else {
16078
+ const memSpinner = ora("Starting agent-memory host-process").start();
16079
+ const mem = runOlam(["memory", "start"]);
16080
+ if (mem.exitCode !== 0) {
16081
+ memSpinner.fail("agent-memory start failed");
16082
+ if (mem.stdout.trim().length > 0) {
16083
+ process.stderr.write(` stdout: ${mem.stdout.split("\n").slice(0, 20).join("\n ")}
16084
+ `);
16085
+ }
16086
+ if (mem.stderr.trim().length > 0) {
16087
+ process.stderr.write(` stderr: ${mem.stderr.split("\n").slice(0, 20).join("\n ")}
16088
+ `);
16089
+ }
16090
+ process.stderr.write(
16091
+ ` Re-run \`olam bootstrap\` after resolving, or skip via OLAM_BOOTSTRAP_SKIP_MEMORY=1.
16092
+ `
16093
+ );
16094
+ return { exitCode: EXIT_GENERIC_ERROR, summary: "agent-memory start failed" };
16095
+ }
16096
+ memSpinner.succeed("agent-memory running");
16097
+ }
15463
16098
  if (opts.skipAuthLogin || process.env.OLAM_BOOTSTRAP_SKIP_AUTH_LOGIN === "1") {
15464
16099
  printInfo("auth login", "skipped");
15465
16100
  } else {
@@ -15496,6 +16131,8 @@ async function runBootstrap2(opts, deps = {}) {
15496
16131
  printHeader("Stack ready");
15497
16132
  printInfo("olam-host-cp", `running (${digests["host-cp"].slice(0, 19)}\u2026)`);
15498
16133
  printInfo("olam-auth", `running (${digests.auth.slice(0, 19)}\u2026)`);
16134
+ const memorySkipped = opts.skipMemory || process.env.OLAM_BOOTSTRAP_SKIP_MEMORY === "1";
16135
+ printInfo("agent-memory", memorySkipped ? "skipped" : "running");
15499
16136
  printInfo("olam-devbox", `pulled (${digests.devbox.slice(0, 19)}\u2026)`);
15500
16137
  printInfo("next", '`olam create --task "your task"` to spawn a world');
15501
16138
  return { exitCode: 0, summary: "stack ready" };
@@ -15503,7 +16140,7 @@ async function runBootstrap2(opts, deps = {}) {
15503
16140
  function registerBootstrap(program2) {
15504
16141
  program2.command("bootstrap").description(
15505
16142
  "Bootstrap the olam stack: pull 3 images by digest, verify protocol handshake, start host-cp + auth, run auth login."
15506
- ).option("--with-smoke", "After bootstrap, create a smoke-test world to verify end-to-end").option("--skip-auth-login", "Skip the interactive PKCE step (CI / scripted use only)").option(
16143
+ ).option("--with-smoke", "After bootstrap, create a smoke-test world to verify end-to-end").option("--skip-auth-login", "Skip the interactive PKCE step (CI / scripted use only)").option("--skip-postgres-singleton", "Skip the olam-postgres singleton bring-up (operators with host postgres opt-out, or CI)").option("--skip-memory", "Skip the agent-memory host-process bring-up (CI / scripted use, or unsupported arch)").option(
15507
16144
  "--registry <ref>",
15508
16145
  "Override the registry prefix (default: read from image-digests.json or fall back to ghcr.io/pleri)"
15509
16146
  ).action(async (opts) => {
@@ -15511,6 +16148,8 @@ function registerBootstrap(program2) {
15511
16148
  const result = await runBootstrap2({
15512
16149
  withSmoke: opts.withSmoke === true,
15513
16150
  skipAuthLogin: opts.skipAuthLogin === true,
16151
+ skipPostgresSingleton: opts.skipPostgresSingleton === true,
16152
+ skipMemory: opts.skipMemory === true,
15514
16153
  registry: opts.registry
15515
16154
  });
15516
16155
  process.exitCode = result.exitCode;
@@ -15594,7 +16233,7 @@ async function smokeTestCodexProvider(authSecret) {
15594
16233
  function runStep(label, cmd, args, opts = {}) {
15595
16234
  const start = Date.now();
15596
16235
  process.stdout.write(` ${pc7.dim(label.padEnd(34))}`);
15597
- const result = spawnSync7(cmd, [...args], {
16236
+ const result = spawnSync9(cmd, [...args], {
15598
16237
  encoding: "utf-8",
15599
16238
  stdio: ["ignore", "pipe", "pipe"],
15600
16239
  cwd: opts.cwd ?? process.cwd(),
@@ -15616,10 +16255,10 @@ async function confirm(message) {
15616
16255
  if (!process.stdin.isTTY) return true;
15617
16256
  const { createInterface: createInterface6 } = await import("node:readline");
15618
16257
  const rl = createInterface6({ input: process.stdin, output: process.stdout });
15619
- return new Promise((resolve12) => {
16258
+ return new Promise((resolve14) => {
15620
16259
  rl.question(`${message} [y/N] `, (answer) => {
15621
16260
  rl.close();
15622
- resolve12(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
16261
+ resolve14(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
15623
16262
  });
15624
16263
  });
15625
16264
  }
@@ -15714,7 +16353,7 @@ async function runAuthUpgradePullByDigest(deps = {}) {
15714
16353
  }
15715
16354
  function defaultTagAuth(from, to) {
15716
16355
  try {
15717
- const r = spawnSync7("docker", ["tag", from, to], {
16356
+ const r = spawnSync9("docker", ["tag", from, to], {
15718
16357
  encoding: "utf-8",
15719
16358
  stdio: ["ignore", "ignore", "pipe"]
15720
16359
  });
@@ -15732,11 +16371,11 @@ function defaultTagAuth(from, to) {
15732
16371
  }
15733
16372
  async function defaultRecreateAuth() {
15734
16373
  try {
15735
- spawnSync7("docker", ["stop", "olam-auth"], {
16374
+ spawnSync9("docker", ["stop", "olam-auth"], {
15736
16375
  encoding: "utf-8",
15737
16376
  stdio: ["ignore", "ignore", "ignore"]
15738
16377
  });
15739
- spawnSync7("docker", ["rm", "olam-auth"], {
16378
+ spawnSync9("docker", ["rm", "olam-auth"], {
15740
16379
  encoding: "utf-8",
15741
16380
  stdio: ["ignore", "ignore", "ignore"]
15742
16381
  });
@@ -15805,7 +16444,7 @@ ${imageResult.stderr}`);
15805
16444
  }
15806
16445
  const stopStart = Date.now();
15807
16446
  process.stdout.write(` ${pc7.dim("docker stop olam-auth".padEnd(34))}`);
15808
- spawnSync7("docker", ["stop", "olam-auth"], {
16447
+ spawnSync9("docker", ["stop", "olam-auth"], {
15809
16448
  encoding: "utf-8",
15810
16449
  stdio: ["ignore", "pipe", "pipe"]
15811
16450
  });
@@ -15815,7 +16454,7 @@ ${imageResult.stderr}`);
15815
16454
  timings.push({ label: "container stop", durationMs: stopDurationMs });
15816
16455
  const rmStart = Date.now();
15817
16456
  process.stdout.write(` ${pc7.dim("docker rm olam-auth".padEnd(34))}`);
15818
- spawnSync7("docker", ["rm", "olam-auth"], {
16457
+ spawnSync9("docker", ["rm", "olam-auth"], {
15819
16458
  encoding: "utf-8",
15820
16459
  stdio: ["ignore", "pipe", "pipe"]
15821
16460
  });
@@ -15907,7 +16546,7 @@ function registerAuthUpgrade(auth) {
15907
16546
  // src/commands/services.ts
15908
16547
  init_auth();
15909
16548
  init_output();
15910
- import { execFileSync as execFileSync7, spawnSync as spawnSync8 } from "node:child_process";
16549
+ import { execFileSync as execFileSync7, spawnSync as spawnSync10 } from "node:child_process";
15911
16550
  import pc8 from "picocolors";
15912
16551
  var MCP_AUTH_PORT = 9998;
15913
16552
  var MCP_AUTH_VOLUME = "olam-mcp-auth-data";
@@ -15919,7 +16558,7 @@ var MCP_AUTH_HEALTH_TIMEOUT_MS = 6e4;
15919
16558
  var McpAuthContainerController = class {
15920
16559
  imageTag = MCP_AUTH_LOCAL_TAG;
15921
16560
  status() {
15922
- const r = spawnSync8(
16561
+ const r = spawnSync10(
15923
16562
  "docker",
15924
16563
  ["inspect", "--format", "{{.State.Status}}|{{.Id}}", MCP_AUTH_CONTAINER],
15925
16564
  { encoding: "utf-8" }
@@ -15935,7 +16574,7 @@ var McpAuthContainerController = class {
15935
16574
  return { state: "missing", port: MCP_AUTH_PORT };
15936
16575
  }
15937
16576
  imageExists(tag = this.imageTag) {
15938
- return spawnSync8("docker", ["image", "inspect", tag], { encoding: "utf-8" }).status === 0;
16577
+ return spawnSync10("docker", ["image", "inspect", tag], { encoding: "utf-8" }).status === 0;
15939
16578
  }
15940
16579
  start() {
15941
16580
  const current = this.status();
@@ -15977,7 +16616,7 @@ var McpAuthContainerController = class {
15977
16616
  execFileSync7("docker", ["stop", MCP_AUTH_CONTAINER], { stdio: "pipe" });
15978
16617
  }
15979
16618
  remove() {
15980
- spawnSync8("docker", ["rm", "-f", MCP_AUTH_CONTAINER], { stdio: "pipe" });
16619
+ spawnSync10("docker", ["rm", "-f", MCP_AUTH_CONTAINER], { stdio: "pipe" });
15981
16620
  }
15982
16621
  async waitForReady(timeoutMs = MCP_AUTH_HEALTH_TIMEOUT_MS) {
15983
16622
  const deadline = Date.now() + timeoutMs;
@@ -15987,17 +16626,17 @@ var McpAuthContainerController = class {
15987
16626
  if (res.ok) return true;
15988
16627
  } catch {
15989
16628
  }
15990
- await sleep3(500);
16629
+ await sleep4(500);
15991
16630
  }
15992
16631
  return false;
15993
16632
  }
15994
16633
  };
15995
- function sleep3(ms) {
15996
- return new Promise((resolve12) => setTimeout(resolve12, ms));
16634
+ function sleep4(ms) {
16635
+ return new Promise((resolve14) => setTimeout(resolve14, ms));
15997
16636
  }
15998
16637
  function dumpContainerLogs(container, tail = 40) {
15999
16638
  try {
16000
- const result = spawnSync8("docker", ["logs", "--tail", String(tail), container], {
16639
+ const result = spawnSync10("docker", ["logs", "--tail", String(tail), container], {
16001
16640
  encoding: "utf-8",
16002
16641
  stdio: ["ignore", "pipe", "pipe"]
16003
16642
  });
@@ -16270,9 +16909,9 @@ ${pc9.dim("Next: olam create --name my-world")}`);
16270
16909
 
16271
16910
  // src/commands/create.ts
16272
16911
  init_manager();
16273
- import { spawnSync as spawnSync9 } from "node:child_process";
16274
- import { existsSync as existsSync28 } from "node:fs";
16275
- import { dirname as dirname18, resolve as resolve10 } from "node:path";
16912
+ import { spawnSync as spawnSync12 } from "node:child_process";
16913
+ import { existsSync as existsSync29 } from "node:fs";
16914
+ import { dirname as dirname19, resolve as resolve12 } from "node:path";
16276
16915
  import ora3 from "ora";
16277
16916
  import pc10 from "picocolors";
16278
16917
 
@@ -16401,17 +17040,17 @@ function inferRepos(input) {
16401
17040
  proceed: true
16402
17041
  };
16403
17042
  }
16404
- const candidates = extractCandidates(input.prompt);
16405
- if (candidates.length === 0) {
17043
+ const candidates2 = extractCandidates(input.prompt);
17044
+ if (candidates2.length === 0) {
16406
17045
  return { repos: [], confidence: 0, proceed: false };
16407
17046
  }
16408
17047
  if (input.catalog && input.catalog.length > 0) {
16409
17048
  const catalogSet = new Set(input.catalog.map((c) => c.toLowerCase()));
16410
- const matched = candidates.filter((c) => catalogSet.has(c));
17049
+ const matched = candidates2.filter((c) => catalogSet.has(c));
16411
17050
  if (matched.length === 0) {
16412
17051
  return { repos: [], confidence: 0.2, proceed: false };
16413
17052
  }
16414
- const ratio = matched.length / candidates.length;
17053
+ const ratio = matched.length / candidates2.length;
16415
17054
  const confidence = Math.min(0.95, 0.5 + 0.5 * ratio);
16416
17055
  return {
16417
17056
  repos: matched,
@@ -16420,7 +17059,7 @@ function inferRepos(input) {
16420
17059
  };
16421
17060
  }
16422
17061
  return {
16423
- repos: candidates,
17062
+ repos: candidates2,
16424
17063
  confidence: 0.6,
16425
17064
  proceed: false
16426
17065
  };
@@ -16506,6 +17145,132 @@ function decideWorkspaceMatch(input) {
16506
17145
  init_context();
16507
17146
  init_output();
16508
17147
  init_host_cp();
17148
+
17149
+ // src/lib/memory-secret.ts
17150
+ import { existsSync as existsSync28, mkdirSync as mkdirSync17, readFileSync as readFileSync20, statSync as statSync7, writeFileSync as writeFileSync13, renameSync as renameSync4, chmodSync as chmodSync3 } from "node:fs";
17151
+ import { homedir as homedir17 } from "node:os";
17152
+ import { join as join34, dirname as dirname18 } from "node:path";
17153
+ import { randomBytes as randomBytes6 } from "node:crypto";
17154
+ var MEMORY_SECRET_PATH = join34(homedir17(), ".olam", "memory-secret");
17155
+ var SECRET_LEN_BYTES = 32;
17156
+ function generateSecret() {
17157
+ return randomBytes6(SECRET_LEN_BYTES).toString("hex");
17158
+ }
17159
+ function writeSecretAtPath(path56, value) {
17160
+ mkdirSync17(dirname18(path56), { recursive: true });
17161
+ const tmp = `${path56}.tmp.${process.pid}`;
17162
+ writeFileSync13(tmp, value, { mode: 384 });
17163
+ chmodSync3(tmp, 384);
17164
+ renameSync4(tmp, path56);
17165
+ }
17166
+ function readSecretAtPathOrNull(path56) {
17167
+ if (!existsSync28(path56)) return null;
17168
+ const mode = statSync7(path56).mode & 511;
17169
+ if (mode !== 384) {
17170
+ process.stderr.write(
17171
+ `warn: ${path56} has mode 0${mode.toString(8)}; expected 0600. Run 'olam memory secret rotate' to regenerate.
17172
+ `
17173
+ );
17174
+ }
17175
+ return readFileSync20(path56, "utf8").trim();
17176
+ }
17177
+ function readSecretAtPath(path56) {
17178
+ const v = readSecretAtPathOrNull(path56);
17179
+ if (v === null) {
17180
+ throw new Error(
17181
+ `Secret not found at ${path56}. Run 'olam memory start' to generate it.`
17182
+ );
17183
+ }
17184
+ return v;
17185
+ }
17186
+ function ensureMemorySecret(path56 = MEMORY_SECRET_PATH) {
17187
+ const existing = readSecretAtPathOrNull(path56);
17188
+ if (existing) return existing;
17189
+ const fresh = generateSecret();
17190
+ writeSecretAtPath(path56, fresh);
17191
+ return fresh;
17192
+ }
17193
+ function readMemorySecretOrNull(path56 = MEMORY_SECRET_PATH) {
17194
+ return readSecretAtPathOrNull(path56);
17195
+ }
17196
+ function readMemorySecret2(path56 = MEMORY_SECRET_PATH) {
17197
+ return readSecretAtPath(path56);
17198
+ }
17199
+ function rotateMemorySecret(path56 = MEMORY_SECRET_PATH) {
17200
+ const fresh = generateSecret();
17201
+ writeSecretAtPath(path56, fresh);
17202
+ return fresh;
17203
+ }
17204
+ function hasMemorySecret(path56 = MEMORY_SECRET_PATH) {
17205
+ return existsSync28(path56);
17206
+ }
17207
+
17208
+ // src/lib/world-mcp-register.ts
17209
+ import { spawnSync as spawnSync11 } from "node:child_process";
17210
+ var DEFAULT_DOCKER_EXEC_DEPS = {
17211
+ spawn: spawnSync11,
17212
+ log: (msg) => console.log(msg)
17213
+ };
17214
+ var MCP_NAME = "agentmemory";
17215
+ var MCP_BIN = "agentmemory-mcp";
17216
+ function probeMcpListed(containerName, deps) {
17217
+ const result = deps.spawn(
17218
+ "docker",
17219
+ ["exec", containerName, "claude", "mcp", "list"],
17220
+ { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }
17221
+ );
17222
+ if (result.status !== 0) return "unknown";
17223
+ const combined = `${result.stdout ?? ""}
17224
+ ${result.stderr ?? ""}`;
17225
+ return new RegExp(`^\\s*${MCP_NAME}\\b`, "m").test(combined) ? "present" : "absent";
17226
+ }
17227
+ function registerAgentMemoryMcp(opts, deps = DEFAULT_DOCKER_EXEC_DEPS) {
17228
+ if (!opts.agentmemorySecret || opts.agentmemorySecret.length === 0) {
17229
+ return {
17230
+ outcome: "skipped-no-env",
17231
+ reason: "world has no AGENTMEMORY_SECRET (memory service was absent or --skip-memory active at spawn time)"
17232
+ };
17233
+ }
17234
+ const probe2 = probeMcpListed(opts.containerName, deps);
17235
+ if (probe2 === "present") {
17236
+ return { outcome: "already-registered", scope: "user" };
17237
+ }
17238
+ const args = [
17239
+ "exec",
17240
+ opts.containerName,
17241
+ "claude",
17242
+ "mcp",
17243
+ "add",
17244
+ MCP_NAME,
17245
+ "--scope",
17246
+ "user",
17247
+ "--env",
17248
+ `AGENTMEMORY_URL=${opts.agentmemoryUrl}`,
17249
+ "--env",
17250
+ `AGENTMEMORY_SECRET=${opts.agentmemorySecret}`,
17251
+ "--",
17252
+ MCP_BIN
17253
+ ];
17254
+ const redacted = args.map(
17255
+ (a) => a.startsWith("AGENTMEMORY_SECRET=") ? "AGENTMEMORY_SECRET=<redacted>" : a
17256
+ );
17257
+ deps.log?.(`Wiring agent-memory MCP in ${opts.containerName}: docker ${redacted.join(" ")}`);
17258
+ const result = deps.spawn("docker", args, {
17259
+ encoding: "utf8",
17260
+ stdio: ["ignore", "pipe", "pipe"]
17261
+ });
17262
+ if (result.status === 0) {
17263
+ return { outcome: "registered", scope: "user" };
17264
+ }
17265
+ const detail = (result.stderr?.trim() || result.stdout?.trim() || "(no output)").replace(
17266
+ /AGENTMEMORY_SECRET=\S+/g,
17267
+ "AGENTMEMORY_SECRET=<redacted>"
17268
+ );
17269
+ return { outcome: "failed", rc: result.status ?? 1, detail };
17270
+ }
17271
+
17272
+ // src/commands/create.ts
17273
+ var AGENTMEMORY_LOCAL_URL = "http://host.docker.internal:3111";
16509
17274
  var HOST_CP_URL = "http://127.0.0.1:19000";
16510
17275
  async function readHostCpTokenForCreate() {
16511
17276
  try {
@@ -16542,7 +17307,7 @@ function registerCreate(program2) {
16542
17307
  if (decision.stderrLine) {
16543
17308
  process.stderr.write(decision.stderrLine + "\n");
16544
17309
  }
16545
- spawnSync9("docker", ["pull", overrideRef], { stdio: "pipe" });
17310
+ spawnSync12("docker", ["pull", overrideRef], { stdio: "pipe" });
16546
17311
  const { inspectImageProtocolVersions: inspectImageProtocolVersions2, checkProtocolOverlap: checkProtocolOverlap2 } = await Promise.resolve().then(() => (init_protocol_version(), protocol_version_exports));
16547
17312
  const inspect = inspectImageProtocolVersions2(overrideRef);
16548
17313
  if (inspect.inspectFailed) {
@@ -16659,7 +17424,7 @@ function registerCreate(program2) {
16659
17424
  throw err;
16660
17425
  }
16661
17426
  const spinner2 = ora3("Rebuilding olam-devbox:latest\u2026").start();
16662
- const rebuild = spawnSync9(
17427
+ const rebuild = spawnSync12(
16663
17428
  "bash",
16664
17429
  [buildScript],
16665
17430
  { cwd: repoRoot, stdio: "inherit" }
@@ -16696,6 +17461,34 @@ function registerCreate(program2) {
16696
17461
  ...opts.runbook ? { runbookName: opts.runbook } : {}
16697
17462
  });
16698
17463
  spinner.succeed("World created");
17464
+ try {
17465
+ const memorySecret = readMemorySecretOrNull() ?? "";
17466
+ const containerName = `olam-${world.id}-devbox`;
17467
+ const result = registerAgentMemoryMcp({
17468
+ containerName,
17469
+ agentmemoryUrl: AGENTMEMORY_LOCAL_URL,
17470
+ agentmemorySecret: memorySecret
17471
+ });
17472
+ switch (result.outcome) {
17473
+ case "registered":
17474
+ printInfo("Agent memory", `MCP registered in-world (--scope ${result.scope})`);
17475
+ break;
17476
+ case "already-registered":
17477
+ printInfo("Agent memory", `MCP already registered (idempotent skip)`);
17478
+ break;
17479
+ case "skipped-no-env":
17480
+ break;
17481
+ case "failed":
17482
+ printWarning(
17483
+ `Agent memory MCP registration failed (rc=${result.rc}): ${result.detail}. World is still usable; the in-world @agentmemory/mcp shim falls back to local InMemoryKV.`
17484
+ );
17485
+ break;
17486
+ }
17487
+ } catch (err) {
17488
+ printWarning(
17489
+ `Agent memory MCP registration threw: ${err instanceof Error ? err.message : String(err)}. World is still usable.`
17490
+ );
17491
+ }
16699
17492
  if (opts.keepAfterMerge) {
16700
17493
  const { WorldRegistry: WorldRegistry2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
16701
17494
  const reg2 = new WorldRegistry2();
@@ -16866,10 +17659,10 @@ ${pc10.cyan("Host CP UI:")} ${worldUrl}`);
16866
17659
  function resolveRepoRoot(start) {
16867
17660
  let cur = start;
16868
17661
  while (true) {
16869
- if (existsSync28(resolve10(cur, "packages")) && existsSync28(resolve10(cur, "package.json"))) {
17662
+ if (existsSync29(resolve12(cur, "packages")) && existsSync29(resolve12(cur, "package.json"))) {
16870
17663
  return cur;
16871
17664
  }
16872
- const parent = dirname18(cur);
17665
+ const parent = dirname19(cur);
16873
17666
  if (parent === cur) return start;
16874
17667
  cur = parent;
16875
17668
  }
@@ -17103,7 +17896,7 @@ import * as path31 from "node:path";
17103
17896
  var CLI_VERSION2 = process.env["OLAM_CLI_VERSION"] ?? "0.0.0";
17104
17897
  var HOST_CP_PORT2 = 19e3;
17105
17898
  async function getMachineStatus(_probe, _loadCtx, _readToken) {
17106
- const probe = _probe ?? (async () => {
17899
+ const probe2 = _probe ?? (async () => {
17107
17900
  const { probeHostCp: probeHostCp2 } = await Promise.resolve().then(() => (init_host_cp(), host_cp_exports));
17108
17901
  return probeHostCp2();
17109
17902
  });
@@ -17118,7 +17911,7 @@ async function getMachineStatus(_probe, _loadCtx, _readToken) {
17118
17911
  const { loadContext: loadContext2 } = await Promise.resolve().then(() => (init_context(), context_exports));
17119
17912
  return loadContext2();
17120
17913
  });
17121
- const probeResult = await probe();
17914
+ const probeResult = await probe2();
17122
17915
  const tokenPresent = readToken2() !== null;
17123
17916
  const hostCpRunning = probeResult !== null;
17124
17917
  let worldCount = 0;
@@ -17553,14 +18346,14 @@ function printTable(entries) {
17553
18346
  async function confirmInteractive() {
17554
18347
  process.stdout.write(" Type `yes` to proceed: ");
17555
18348
  const buf = [];
17556
- return new Promise((resolve12) => {
18349
+ return new Promise((resolve14) => {
17557
18350
  const onData = (chunk) => {
17558
18351
  buf.push(chunk);
17559
18352
  if (Buffer.concat(buf).toString("utf-8").includes("\n")) {
17560
18353
  process.stdin.removeListener("data", onData);
17561
18354
  process.stdin.pause();
17562
18355
  const answer = Buffer.concat(buf).toString("utf-8").trim();
17563
- resolve12(answer.toLowerCase() === "yes");
18356
+ resolve14(answer.toLowerCase() === "yes");
17564
18357
  }
17565
18358
  };
17566
18359
  process.stdin.resume();
@@ -19507,8 +20300,8 @@ var path35 = {
19507
20300
  win32: { sep: "\\" },
19508
20301
  posix: { sep: "/" }
19509
20302
  };
19510
- var sep = defaultPlatform === "win32" ? path35.win32.sep : path35.posix.sep;
19511
- minimatch.sep = sep;
20303
+ var sep2 = defaultPlatform === "win32" ? path35.win32.sep : path35.posix.sep;
20304
+ minimatch.sep = sep2;
19512
20305
  var GLOBSTAR = /* @__PURE__ */ Symbol("globstar **");
19513
20306
  minimatch.GLOBSTAR = GLOBSTAR;
19514
20307
  var qmark2 = "[^/]";
@@ -20333,7 +21126,7 @@ function registerPolicyCheck(program2) {
20333
21126
  }
20334
21127
 
20335
21128
  // src/commands/worldspec/compile.ts
20336
- import { existsSync as existsSync33, mkdirSync as mkdirSync18, readFileSync as readFileSync22, writeFileSync as writeFileSync14 } from "node:fs";
21129
+ import { existsSync as existsSync34, mkdirSync as mkdirSync19, readFileSync as readFileSync23, writeFileSync as writeFileSync15 } from "node:fs";
20337
21130
  import { resolve as resolvePath } from "node:path";
20338
21131
  import YAML5 from "yaml";
20339
21132
 
@@ -20660,10 +21453,10 @@ function deriveSuggestion(issue) {
20660
21453
  if (firstKey === void 0)
20661
21454
  return void 0;
20662
21455
  const isTopLevel = issue.path.length === 0;
20663
- const candidates = isTopLevel ? KNOWN_TOP_LEVEL_KEYS2 : [];
20664
- if (candidates.length === 0)
21456
+ const candidates2 = isTopLevel ? KNOWN_TOP_LEVEL_KEYS2 : [];
21457
+ if (candidates2.length === 0)
20665
21458
  return void 0;
20666
- const closest = nearestMatch(firstKey, [...candidates]);
21459
+ const closest = nearestMatch(firstKey, [...candidates2]);
20667
21460
  return closest ? `did you mean "${closest}"?` : void 0;
20668
21461
  }
20669
21462
  if (issue.code === "invalid_union_discriminator") {
@@ -20671,10 +21464,10 @@ function deriveSuggestion(issue) {
20671
21464
  }
20672
21465
  return void 0;
20673
21466
  }
20674
- function nearestMatch(input, candidates) {
21467
+ function nearestMatch(input, candidates2) {
20675
21468
  let best;
20676
21469
  let bestDistance = 3;
20677
- for (const c of candidates) {
21470
+ for (const c of candidates2) {
20678
21471
  const d = levenshtein(input.toLowerCase(), c.toLowerCase());
20679
21472
  if (d < bestDistance) {
20680
21473
  bestDistance = d;
@@ -21136,13 +21929,13 @@ function registerWorldspecCompile(parent) {
21136
21929
  const sourcePolicies = [];
21137
21930
  for (const p of paths) {
21138
21931
  const abs = resolvePath(process.cwd(), p);
21139
- if (!existsSync33(abs)) {
21932
+ if (!existsSync34(abs)) {
21140
21933
  printError(`worldspec not found at ${abs}`);
21141
21934
  process.exit(EXIT_WORLDSPEC_FILE_NOT_FOUND);
21142
21935
  }
21143
21936
  let yaml;
21144
21937
  try {
21145
- yaml = YAML5.parse(readFileSync22(abs, "utf8"));
21938
+ yaml = YAML5.parse(readFileSync23(abs, "utf8"));
21146
21939
  } catch (err) {
21147
21940
  printError(
21148
21941
  `${p}: YAML parse error: ${err.message}`
@@ -21198,13 +21991,13 @@ Resolutions:
21198
21991
  if (opts.out) {
21199
21992
  const outDir = resolvePath(process.cwd(), opts.out);
21200
21993
  try {
21201
- mkdirSync18(outDir, { recursive: true });
21202
- writeFileSync14(
21994
+ mkdirSync19(outDir, { recursive: true });
21995
+ writeFileSync15(
21203
21996
  resolvePath(outDir, "execution-graph.json"),
21204
21997
  graphJson,
21205
21998
  "utf8"
21206
21999
  );
21207
- writeFileSync14(
22000
+ writeFileSync15(
21208
22001
  resolvePath(outDir, "lockfile.json"),
21209
22002
  lockfileJson,
21210
22003
  "utf8"
@@ -21227,7 +22020,7 @@ function getPkgVersion() {
21227
22020
  // src/commands/worldspec/init.ts
21228
22021
  init_exit_codes();
21229
22022
  init_output();
21230
- import { existsSync as existsSync34, mkdirSync as mkdirSync19, readFileSync as readFileSync23, writeFileSync as writeFileSync15 } from "node:fs";
22023
+ import { existsSync as existsSync35, mkdirSync as mkdirSync20, readFileSync as readFileSync24, writeFileSync as writeFileSync16 } from "node:fs";
21231
22024
  import { execSync as execSync10 } from "node:child_process";
21232
22025
  import { basename as basename3, resolve as resolvePath2 } from "node:path";
21233
22026
  function registerWorldspecInit(parent) {
@@ -21245,7 +22038,7 @@ function registerWorldspecInit(parent) {
21245
22038
  const cwd = process.cwd();
21246
22039
  const targetDir = resolvePath2(cwd, ".olam/worldspecs");
21247
22040
  const targetFile = resolvePath2(targetDir, "default.yaml");
21248
- if (existsSync34(targetFile) && !opts.force) {
22041
+ if (existsSync35(targetFile) && !opts.force) {
21249
22042
  printError(
21250
22043
  `${targetFile} already exists; pass --force to overwrite`
21251
22044
  );
@@ -21260,8 +22053,8 @@ function registerWorldspecInit(parent) {
21260
22053
  const shape = detectProjectShape(cwd, opts.fromAdbYaml);
21261
22054
  const yaml = renderWorldspecYaml(shape);
21262
22055
  try {
21263
- mkdirSync19(targetDir, { recursive: true });
21264
- writeFileSync15(targetFile, yaml, {
22056
+ mkdirSync20(targetDir, { recursive: true });
22057
+ writeFileSync16(targetFile, yaml, {
21265
22058
  encoding: "utf8",
21266
22059
  flag: opts.force ? "w" : "wx"
21267
22060
  });
@@ -21279,14 +22072,14 @@ function registerWorldspecInit(parent) {
21279
22072
  });
21280
22073
  }
21281
22074
  function detectProjectShape(root, useAdbYaml) {
21282
- const hasGemfile = existsSync34(resolvePath2(root, "Gemfile"));
21283
- const hasNodePackageJson = existsSync34(resolvePath2(root, "package.json"));
21284
- const hasAdbYaml = existsSync34(resolvePath2(root, ".adb.yaml"));
22075
+ const hasGemfile = existsSync35(resolvePath2(root, "Gemfile"));
22076
+ const hasNodePackageJson = existsSync35(resolvePath2(root, "package.json"));
22077
+ const hasAdbYaml = existsSync35(resolvePath2(root, ".adb.yaml"));
21285
22078
  let rubyVersion = null;
21286
22079
  const rubyVersionPath = resolvePath2(root, ".ruby-version");
21287
- if (existsSync34(rubyVersionPath)) {
22080
+ if (existsSync35(rubyVersionPath)) {
21288
22081
  try {
21289
- const raw = readFileSync23(rubyVersionPath, "utf8").trim();
22082
+ const raw = readFileSync24(rubyVersionPath, "utf8").trim();
21290
22083
  const match2 = raw.match(/(\d+\.\d+\.\d+)/);
21291
22084
  if (match2) rubyVersion = match2[1] ?? null;
21292
22085
  } catch {
@@ -21358,7 +22151,7 @@ function relativeFromCwd(absPath, cwd) {
21358
22151
  }
21359
22152
 
21360
22153
  // src/commands/worldspec/schema.ts
21361
- import { writeFileSync as writeFileSync16 } from "node:fs";
22154
+ import { writeFileSync as writeFileSync17 } from "node:fs";
21362
22155
  import { resolve as resolvePath3 } from "node:path";
21363
22156
 
21364
22157
  // ../../node_modules/zod-to-json-schema/dist/esm/Options.js
@@ -22673,7 +23466,7 @@ function registerWorldspecSchema(parent) {
22673
23466
  if (opts.out) {
22674
23467
  const absPath = resolvePath3(process.cwd(), opts.out);
22675
23468
  try {
22676
- writeFileSync16(absPath, json + "\n", "utf8");
23469
+ writeFileSync17(absPath, json + "\n", "utf8");
22677
23470
  } catch (err) {
22678
23471
  printError(
22679
23472
  `failed to write ${absPath}: ${err.message}`
@@ -22689,7 +23482,7 @@ function registerWorldspecSchema(parent) {
22689
23482
  }
22690
23483
 
22691
23484
  // src/commands/worldspec/validate.ts
22692
- import { existsSync as existsSync35, readFileSync as readFileSync24 } from "node:fs";
23485
+ import { existsSync as existsSync36, readFileSync as readFileSync25 } from "node:fs";
22693
23486
  import { resolve as resolvePath4 } from "node:path";
22694
23487
  init_exit_codes();
22695
23488
  init_output();
@@ -22706,13 +23499,13 @@ function registerWorldspecValidate(parent) {
22706
23499
  "human"
22707
23500
  ).action(async (pathArg, opts) => {
22708
23501
  const absPath = resolvePath4(process.cwd(), pathArg);
22709
- if (!existsSync35(absPath)) {
23502
+ if (!existsSync36(absPath)) {
22710
23503
  printError(`worldspec not found at ${absPath}`);
22711
23504
  process.exit(EXIT_WORLDSPEC_FILE_NOT_FOUND);
22712
23505
  }
22713
23506
  let yamlSource;
22714
23507
  try {
22715
- yamlSource = readFileSync24(absPath, "utf8");
23508
+ yamlSource = readFileSync25(absPath, "utf8");
22716
23509
  } catch (err) {
22717
23510
  printError(
22718
23511
  `failed to read ${absPath}: ${err.message}`
@@ -22760,7 +23553,7 @@ init_output();
22760
23553
  init_host_cp();
22761
23554
  import * as fs35 from "node:fs";
22762
23555
  import * as path38 from "node:path";
22763
- import { spawnSync as spawnSync11 } from "node:child_process";
23556
+ import { spawnSync as spawnSync14 } from "node:child_process";
22764
23557
  import ora7 from "ora";
22765
23558
  import pc18 from "picocolors";
22766
23559
 
@@ -22768,7 +23561,7 @@ import pc18 from "picocolors";
22768
23561
  import * as fs33 from "node:fs";
22769
23562
  import * as os19 from "node:os";
22770
23563
  import * as path36 from "node:path";
22771
- import { spawnSync as spawnSync10 } from "node:child_process";
23564
+ import { spawnSync as spawnSync13 } from "node:child_process";
22772
23565
  var LOCK_FILE_PATH = path36.join(os19.homedir(), ".olam", ".upgrade.lock");
22773
23566
  var STALE_LOCK_TIMEOUT_MS = 5 * 60 * 1e3;
22774
23567
  function readLockFile(lockPath) {
@@ -22793,7 +23586,7 @@ function isPidAlive2(pid) {
22793
23586
  }
22794
23587
  var PS_UNAVAILABLE = "__ps_unavailable__";
22795
23588
  function getPidCommand(pid) {
22796
- const result = spawnSync10("ps", ["-p", String(pid), "-o", "comm="], {
23589
+ const result = spawnSync13("ps", ["-p", String(pid), "-o", "comm="], {
22797
23590
  encoding: "utf-8",
22798
23591
  stdio: ["ignore", "pipe", "ignore"]
22799
23592
  });
@@ -23052,7 +23845,7 @@ function extractBundleHash(indexHtml) {
23052
23845
  function runStep2(label, cmd, args, opts = {}) {
23053
23846
  const start = Date.now();
23054
23847
  process.stdout.write(` ${pc18.dim(label.padEnd(34))}`);
23055
- const result = spawnSync11(cmd, [...args], {
23848
+ const result = spawnSync14(cmd, [...args], {
23056
23849
  encoding: "utf-8",
23057
23850
  stdio: ["ignore", "pipe", "pipe"],
23058
23851
  cwd: opts.cwd ?? process.cwd(),
@@ -23071,7 +23864,7 @@ function runStep2(label, cmd, args, opts = {}) {
23071
23864
  };
23072
23865
  }
23073
23866
  function isGitDirty(cwd) {
23074
- const result = spawnSync11("git", ["status", "--porcelain"], {
23867
+ const result = spawnSync14("git", ["status", "--porcelain"], {
23075
23868
  encoding: "utf-8",
23076
23869
  stdio: ["ignore", "pipe", "pipe"],
23077
23870
  cwd
@@ -23079,7 +23872,7 @@ function isGitDirty(cwd) {
23079
23872
  return (result.stdout ?? "").trim().length > 0;
23080
23873
  }
23081
23874
  function hasGitUpstream(cwd) {
23082
- const result = spawnSync11("git", ["rev-parse", "--abbrev-ref", "@{u}"], {
23875
+ const result = spawnSync14("git", ["rev-parse", "--abbrev-ref", "@{u}"], {
23083
23876
  encoding: "utf-8",
23084
23877
  stdio: ["ignore", "pipe", "pipe"],
23085
23878
  cwd
@@ -23087,7 +23880,7 @@ function hasGitUpstream(cwd) {
23087
23880
  return result.status === 0;
23088
23881
  }
23089
23882
  function captureHeadSha(cwd) {
23090
- const result = spawnSync11("git", ["rev-parse", "HEAD"], {
23883
+ const result = spawnSync14("git", ["rev-parse", "HEAD"], {
23091
23884
  encoding: "utf-8",
23092
23885
  stdio: ["ignore", "pipe", "pipe"],
23093
23886
  cwd
@@ -23102,7 +23895,7 @@ function abbreviateSha(sha) {
23102
23895
  }
23103
23896
  function imageExists(tag) {
23104
23897
  try {
23105
- const result = spawnSync11("docker", ["image", "inspect", "--format", "{{.Id}}", tag], {
23898
+ const result = spawnSync14("docker", ["image", "inspect", "--format", "{{.Id}}", tag], {
23106
23899
  encoding: "utf-8",
23107
23900
  stdio: ["ignore", "pipe", "ignore"]
23108
23901
  });
@@ -23118,7 +23911,7 @@ function checkRollbackSetExists(plan) {
23118
23911
  }
23119
23912
  var SMOKE_DOCKER_TIMEOUT_MS = 3e4;
23120
23913
  function smokeImage(image, targetSha) {
23121
- const createResult = spawnSync11("docker", ["create", "--name", `olam-smoke-${Date.now()}`, image], {
23914
+ const createResult = spawnSync14("docker", ["create", "--name", `olam-smoke-${Date.now()}`, image], {
23122
23915
  encoding: "utf-8",
23123
23916
  stdio: ["ignore", "pipe", "pipe"],
23124
23917
  timeout: SMOKE_DOCKER_TIMEOUT_MS
@@ -23132,7 +23925,7 @@ function smokeImage(image, targetSha) {
23132
23925
  };
23133
23926
  }
23134
23927
  const containerId = (createResult.stdout ?? "").trim();
23135
- const inspectResult = spawnSync11(
23928
+ const inspectResult = spawnSync14(
23136
23929
  "docker",
23137
23930
  ["inspect", "--format", '{{index .Config.Labels "olam.build.sha"}}', image],
23138
23931
  {
@@ -23142,7 +23935,7 @@ function smokeImage(image, targetSha) {
23142
23935
  }
23143
23936
  );
23144
23937
  if (containerId.length > 0) {
23145
- spawnSync11("docker", ["rm", "-f", containerId], {
23938
+ spawnSync14("docker", ["rm", "-f", containerId], {
23146
23939
  encoding: "utf-8",
23147
23940
  stdio: ["ignore", "ignore", "ignore"],
23148
23941
  timeout: SMOKE_DOCKER_TIMEOUT_MS
@@ -23182,7 +23975,7 @@ var PRODUCTION_SWAP_PLAN = [
23182
23975
  ];
23183
23976
  function dockerTag(source, dest) {
23184
23977
  try {
23185
- const result = spawnSync11("docker", ["tag", source, dest], {
23978
+ const result = spawnSync14("docker", ["tag", source, dest], {
23186
23979
  encoding: "utf-8",
23187
23980
  stdio: ["ignore", "ignore", "pipe"]
23188
23981
  });
@@ -23275,10 +24068,10 @@ async function confirm2(message) {
23275
24068
  if (!process.stdin.isTTY) return true;
23276
24069
  const { createInterface: createInterface6 } = await import("node:readline");
23277
24070
  const rl = createInterface6({ input: process.stdin, output: process.stdout });
23278
- return new Promise((resolve12) => {
24071
+ return new Promise((resolve14) => {
23279
24072
  rl.question(`${message} [y/N] `, (answer) => {
23280
24073
  rl.close();
23281
- resolve12(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
24074
+ resolve14(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
23282
24075
  });
23283
24076
  });
23284
24077
  }
@@ -23346,11 +24139,11 @@ async function waitForAuthHealthLocal(timeoutMs = AUTH_HEALTH_TIMEOUT_MS) {
23346
24139
  async function recreateAuthService() {
23347
24140
  const start = Date.now();
23348
24141
  try {
23349
- spawnSync11("docker", ["stop", "olam-auth"], {
24142
+ spawnSync14("docker", ["stop", "olam-auth"], {
23350
24143
  encoding: "utf-8",
23351
24144
  stdio: ["ignore", "ignore", "ignore"]
23352
24145
  });
23353
- spawnSync11("docker", ["rm", "olam-auth"], {
24146
+ spawnSync14("docker", ["rm", "olam-auth"], {
23354
24147
  encoding: "utf-8",
23355
24148
  stdio: ["ignore", "ignore", "ignore"]
23356
24149
  });
@@ -23609,11 +24402,11 @@ async function defaultRecreateMcpAuthForUpgrade() {
23609
24402
  }
23610
24403
  async function defaultRecreateAuthForUpgrade() {
23611
24404
  try {
23612
- spawnSync11("docker", ["stop", "olam-auth"], {
24405
+ spawnSync14("docker", ["stop", "olam-auth"], {
23613
24406
  encoding: "utf-8",
23614
24407
  stdio: ["ignore", "ignore", "ignore"]
23615
24408
  });
23616
- spawnSync11("docker", ["rm", "olam-auth"], {
24409
+ spawnSync14("docker", ["rm", "olam-auth"], {
23617
24410
  encoding: "utf-8",
23618
24411
  stdio: ["ignore", "ignore", "ignore"]
23619
24412
  });
@@ -23959,7 +24752,7 @@ ${spaResult.stderr}`);
23959
24752
  process.stdout.write(` ${pc18.dim(step.label.padEnd(34))}
23960
24753
  `);
23961
24754
  const start = Date.now();
23962
- const result = spawnSync11("bash", [scriptPath], {
24755
+ const result = spawnSync14("bash", [scriptPath], {
23963
24756
  stdio: "inherit",
23964
24757
  cwd,
23965
24758
  env: { ...process.env, ...olamTagEnv }
@@ -24308,7 +25101,7 @@ function registerLogs(program2) {
24308
25101
  init_context();
24309
25102
  init_output();
24310
25103
  import pc20 from "picocolors";
24311
- import { spawnSync as spawnSync12 } from "node:child_process";
25104
+ import { spawnSync as spawnSync15 } from "node:child_process";
24312
25105
  var SAFE_IDENT4 = /^[a-z0-9][a-z0-9-]{0,63}$/;
24313
25106
  function parseDockerTop(stdout) {
24314
25107
  const trimmed = stdout.trim();
@@ -24329,8 +25122,8 @@ function parseDockerTop(stdout) {
24329
25122
  titles = (lines[0] ?? "").trim().toLowerCase().split(/\s+/);
24330
25123
  processes = lines.slice(1).map((l) => l.trim().split(/\s+/));
24331
25124
  }
24332
- const idx = (candidates) => {
24333
- for (const c of candidates) {
25125
+ const idx = (candidates2) => {
25126
+ for (const c of candidates2) {
24334
25127
  const i = titles.indexOf(c);
24335
25128
  if (i !== -1) return i;
24336
25129
  }
@@ -24408,7 +25201,7 @@ function registerPs(program2) {
24408
25201
  const containerName = `olam-${worldId}-devbox`;
24409
25202
  let watchInterval;
24410
25203
  function fetchAndPrint() {
24411
- const result = spawnSync12(
25204
+ const result = spawnSync15(
24412
25205
  "docker",
24413
25206
  ["top", containerName, "pid", "user", "pcpu", "pmem", "stime", "stat", "cmd"],
24414
25207
  { encoding: "utf-8", timeout: 3e3 }
@@ -24901,7 +25694,7 @@ init_output();
24901
25694
  import * as fs39 from "node:fs";
24902
25695
  import * as os22 from "node:os";
24903
25696
  import * as path42 from "node:path";
24904
- import { spawnSync as spawnSync13 } from "node:child_process";
25697
+ import { spawnSync as spawnSync16 } from "node:child_process";
24905
25698
  import ora8 from "ora";
24906
25699
 
24907
25700
  // src/commands/refresh-helpers.ts
@@ -24945,7 +25738,7 @@ var RESTART_TIMEOUT_S = 30;
24945
25738
  var HEALTH_POLL_MS = 500;
24946
25739
  var HEALTH_TIMEOUT_MS = 3e4;
24947
25740
  function docker(args) {
24948
- const result = spawnSync13("docker", args, {
25741
+ const result = spawnSync16("docker", args, {
24949
25742
  encoding: "utf-8",
24950
25743
  stdio: ["ignore", "pipe", "pipe"]
24951
25744
  });
@@ -25293,9 +26086,9 @@ function registerDiagnose(program2) {
25293
26086
  }
25294
26087
 
25295
26088
  // src/lib/health-probes.ts
25296
- import { spawnSync as spawnSync14 } from "node:child_process";
25297
- import { existsSync as existsSync44, readdirSync as readdirSync11, readFileSync as readFileSync31, statSync as statSync13 } from "node:fs";
25298
- import { homedir as homedir23 } from "node:os";
26089
+ import { spawnSync as spawnSync17 } from "node:child_process";
26090
+ import { existsSync as existsSync45, readdirSync as readdirSync11, readFileSync as readFileSync32, statSync as statSync14 } from "node:fs";
26091
+ import { homedir as homedir24 } from "node:os";
25299
26092
  import path44 from "node:path";
25300
26093
 
25301
26094
  // src/lib/kg-caps.ts
@@ -25305,7 +26098,7 @@ var HARD_CAP_BYTES = 5e9;
25305
26098
  // src/lib/health-probes.ts
25306
26099
  var HEALTH_TIMEOUT_MS2 = 5e3;
25307
26100
  var defaultDockerExec2 = (cmd, args) => {
25308
- const r = spawnSync14(cmd, [...args], {
26101
+ const r = spawnSync17(cmd, [...args], {
25309
26102
  encoding: "utf-8",
25310
26103
  stdio: ["ignore", "pipe", "pipe"]
25311
26104
  });
@@ -25391,7 +26184,7 @@ function isHostCpHealthEnvelope(body) {
25391
26184
  }
25392
26185
  async function probeAuthVault(olamHomeOverride) {
25393
26186
  const vaultPath = resolveAccountsPath(olamHomeOverride);
25394
- if (!existsSync44(vaultPath)) {
26187
+ if (!existsSync45(vaultPath)) {
25395
26188
  return {
25396
26189
  ok: false,
25397
26190
  message: `auth vault missing at ${vaultPath}`,
@@ -25400,7 +26193,7 @@ async function probeAuthVault(olamHomeOverride) {
25400
26193
  }
25401
26194
  let parsed;
25402
26195
  try {
25403
- parsed = JSON.parse(readFileSync31(vaultPath, "utf-8"));
26196
+ parsed = JSON.parse(readFileSync32(vaultPath, "utf-8"));
25404
26197
  } catch (err) {
25405
26198
  return {
25406
26199
  ok: false,
@@ -25430,7 +26223,7 @@ async function probeAuthVault(olamHomeOverride) {
25430
26223
  function resolveAccountsPath(olamHomeOverride) {
25431
26224
  const explicit = process.env.OLAM_AUTH_DATA_PATH;
25432
26225
  if (explicit) return explicit;
25433
- const olamHome5 = olamHomeOverride ?? process.env.OLAM_HOME ?? path44.join(homedir23(), ".olam");
26226
+ const olamHome5 = olamHomeOverride ?? process.env.OLAM_HOME ?? path44.join(homedir24(), ".olam");
25434
26227
  return path44.join(olamHome5, "auth-data", "accounts.json");
25435
26228
  }
25436
26229
  function effectiveState2(account, now) {
@@ -25444,7 +26237,7 @@ function effectiveState2(account, now) {
25444
26237
  return persisted;
25445
26238
  }
25446
26239
  async function probeKgStorage(olamHomeOverride) {
25447
- const olamHome5 = olamHomeOverride ?? process.env.OLAM_HOME ?? path44.join(homedir23(), ".olam");
26240
+ const olamHome5 = olamHomeOverride ?? process.env.OLAM_HOME ?? path44.join(homedir24(), ".olam");
25448
26241
  const kgRoot3 = path44.join(olamHome5, "kg");
25449
26242
  const worldsRoot3 = path44.join(olamHome5, "worlds");
25450
26243
  const kgBytes = enumerateGraphifyOut(kgRoot3, "pristine");
@@ -25466,7 +26259,7 @@ async function probeKgStorage(olamHomeOverride) {
25466
26259
  return { ok: true, message: `KG storage ${formatBytes4(totalBytes)} (under cap)` };
25467
26260
  }
25468
26261
  function enumerateGraphifyOut(root, layout) {
25469
- if (!existsSync44(root)) return 0;
26262
+ if (!existsSync45(root)) return 0;
25470
26263
  let total = 0;
25471
26264
  let entries;
25472
26265
  try {
@@ -25495,7 +26288,7 @@ function enumerateGraphifyOut(root, layout) {
25495
26288
  return total;
25496
26289
  }
25497
26290
  function dirSizeBytes(dir) {
25498
- if (!existsSync44(dir)) return 0;
26291
+ if (!existsSync45(dir)) return 0;
25499
26292
  let total = 0;
25500
26293
  const stack = [dir];
25501
26294
  while (stack.length > 0) {
@@ -25514,7 +26307,7 @@ function dirSizeBytes(dir) {
25514
26307
  continue;
25515
26308
  }
25516
26309
  try {
25517
- total += statSync13(full).size;
26310
+ total += statSync14(full).size;
25518
26311
  } catch {
25519
26312
  }
25520
26313
  }
@@ -25751,20 +26544,20 @@ function registerCompletion(program2) {
25751
26544
  // src/commands/setup.ts
25752
26545
  init_cli_version();
25753
26546
  import { spawn as spawn5 } from "node:child_process";
25754
- import { existsSync as existsSync46 } from "node:fs";
25755
- import { homedir as homedir24 } from "node:os";
26547
+ import { existsSync as existsSync47 } from "node:fs";
26548
+ import { homedir as homedir25 } from "node:os";
25756
26549
  import path46 from "node:path";
25757
26550
  import { createInterface as createInterface3 } from "node:readline";
25758
26551
 
25759
26552
  // src/lib/shell-rc.ts
25760
- import { copyFileSync as copyFileSync5, existsSync as existsSync45, readFileSync as readFileSync32, renameSync as renameSync4, writeFileSync as writeFileSync20 } from "node:fs";
26553
+ import { copyFileSync as copyFileSync5, existsSync as existsSync46, readFileSync as readFileSync33, renameSync as renameSync5, writeFileSync as writeFileSync21 } from "node:fs";
25761
26554
  import path45 from "node:path";
25762
26555
  function appendIdempotent(opts) {
25763
26556
  const { rcPath, marker, contentLine, clock = () => /* @__PURE__ */ new Date() } = opts;
25764
- if (!existsSync45(rcPath)) {
26557
+ if (!existsSync46(rcPath)) {
25765
26558
  return { status: "no-rc-file", backupPath: null };
25766
26559
  }
25767
- const content = readFileSync32(rcPath, "utf-8");
26560
+ const content = readFileSync33(rcPath, "utf-8");
25768
26561
  if (content.includes(marker)) {
25769
26562
  return { status: "already-present", backupPath: null };
25770
26563
  }
@@ -25775,8 +26568,8 @@ function appendIdempotent(opts) {
25775
26568
  const trailing = contentLine.endsWith("\n") ? "" : "\n";
25776
26569
  const nextContent = `${content}${separator}${contentLine}${trailing}`;
25777
26570
  const tmpPath = `${rcPath}.olam-tmp.${process.pid}`;
25778
- writeFileSync20(tmpPath, nextContent, { encoding: "utf-8" });
25779
- renameSync4(tmpPath, rcPath);
26571
+ writeFileSync21(tmpPath, nextContent, { encoding: "utf-8" });
26572
+ renameSync5(tmpPath, rcPath);
25780
26573
  return { status: "appended", backupPath };
25781
26574
  }
25782
26575
  function resolveShellRc(home, shellEnv) {
@@ -25795,10 +26588,10 @@ var NEXT_STEPS_DOCS = [
25795
26588
  "docs/architecture/manifest-spec.md \u2014 per-repo .adb.yaml schema",
25796
26589
  "docs/architecture/config-spec.md \u2014 workspace .olam/config.yaml schema"
25797
26590
  ];
25798
- var defaultSpawn = (cmd, args) => new Promise((resolve12) => {
26591
+ var defaultSpawn = (cmd, args) => new Promise((resolve14) => {
25799
26592
  const child = spawn5(cmd, [...args], { stdio: "inherit" });
25800
- child.on("exit", (code) => resolve12({ status: code }));
25801
- child.on("error", () => resolve12({ status: 1 }));
26593
+ child.on("exit", (code) => resolve14({ status: code }));
26594
+ child.on("error", () => resolve14({ status: 1 }));
25802
26595
  });
25803
26596
  var defaultPrompt = (question, defaultYes) => {
25804
26597
  if (!process.stdin.isTTY) {
@@ -25808,18 +26601,18 @@ var defaultPrompt = (question, defaultYes) => {
25808
26601
  );
25809
26602
  return Promise.resolve(defaultYes);
25810
26603
  }
25811
- return new Promise((resolve12) => {
26604
+ return new Promise((resolve14) => {
25812
26605
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
25813
26606
  const suffix = defaultYes ? " [Y/n]: " : " [y/N]: ";
25814
26607
  rl.question(`${question}${suffix}`, (answer) => {
25815
26608
  rl.close();
25816
26609
  const t = answer.trim().toLowerCase();
25817
- if (t === "") resolve12(defaultYes);
25818
- else if (t === "y" || t === "yes") resolve12(true);
25819
- else if (t === "n" || t === "no") resolve12(false);
25820
- else resolve12(defaultYes);
26610
+ if (t === "") resolve14(defaultYes);
26611
+ else if (t === "y" || t === "yes") resolve14(true);
26612
+ else if (t === "n" || t === "no") resolve14(false);
26613
+ else resolve14(defaultYes);
25821
26614
  });
25822
- rl.on("close", () => resolve12(defaultYes));
26615
+ rl.on("close", () => resolve14(defaultYes));
25823
26616
  });
25824
26617
  };
25825
26618
  async function phase1SystemCheck(deps) {
@@ -25847,9 +26640,9 @@ function parseNodeMajor(version) {
25847
26640
  const n = Number.parseInt(m[1], 10);
25848
26641
  return Number.isFinite(n) ? n : null;
25849
26642
  }
25850
- function phaseFromProbe(probe) {
25851
- if (probe.ok) return { ok: true, message: probe.message };
25852
- return { ok: false, message: probe.message, remedy: probe.remedy };
26643
+ function phaseFromProbe(probe2) {
26644
+ if (probe2.ok) return { ok: true, message: probe2.message };
26645
+ return { ok: false, message: probe2.message, remedy: probe2.remedy };
25853
26646
  }
25854
26647
  async function phase2CliSanity(deps) {
25855
26648
  const version = deps.olamCliVersion ?? safeReadCliVersion();
@@ -25885,7 +26678,7 @@ async function phase4ShellInit(opts, deps) {
25885
26678
  if (opts.skipShellInit) {
25886
26679
  return { ok: true, skipped: true, message: "skipped via --skip-shell-init" };
25887
26680
  }
25888
- const home = deps.home ?? homedir24();
26681
+ const home = deps.home ?? homedir25();
25889
26682
  const shellEnv = deps.shellEnv ?? process.env.SHELL;
25890
26683
  const rcPath = resolveShellRc(home, shellEnv);
25891
26684
  if (rcPath === null) {
@@ -25922,7 +26715,7 @@ async function phase5InitProject(opts, deps) {
25922
26715
  const cwd = deps.cwd ?? process.cwd();
25923
26716
  const projectRoot = findProjectRoot(cwd);
25924
26717
  const configPath = path46.join(projectRoot, ".olam", "config.yaml");
25925
- if (existsSync46(configPath)) {
26718
+ if (existsSync47(configPath)) {
25926
26719
  return { ok: true, message: `${configPath} present (no change)` };
25927
26720
  }
25928
26721
  const promptFn = deps.prompt ?? defaultPrompt;
@@ -26788,18 +27581,18 @@ function registerWorldUpgrade(program2) {
26788
27581
 
26789
27582
  // src/commands/mcp/serve.ts
26790
27583
  init_output();
26791
- import { existsSync as existsSync51 } from "node:fs";
26792
- import { dirname as dirname25, resolve as resolve11 } from "node:path";
27584
+ import { existsSync as existsSync52 } from "node:fs";
27585
+ import { dirname as dirname26, resolve as resolve13 } from "node:path";
26793
27586
  import { fileURLToPath as fileURLToPath6 } from "node:url";
26794
- var here = dirname25(fileURLToPath6(import.meta.url));
27587
+ var here = dirname26(fileURLToPath6(import.meta.url));
26795
27588
  var BUNDLE_PATH_CANDIDATES = [
26796
27589
  // bundled (`dist/index.js` after bundle-cli.mjs) — sibling
26797
- resolve11(here, "mcp-server.js"),
27590
+ resolve13(here, "mcp-server.js"),
26798
27591
  // dev / tsc-only (`dist/commands/mcp/serve.js`) — up two levels
26799
- resolve11(here, "..", "..", "mcp-server.js")
27592
+ resolve13(here, "..", "..", "mcp-server.js")
26800
27593
  ];
26801
- function resolveBundlePath(candidates = BUNDLE_PATH_CANDIDATES, exists = existsSync51) {
26802
- return candidates.find(exists) ?? null;
27594
+ function resolveBundlePath(candidates2 = BUNDLE_PATH_CANDIDATES, exists = existsSync52) {
27595
+ return candidates2.find(exists) ?? null;
26803
27596
  }
26804
27597
  var MISSING_BUNDLE_REMEDY = "olam mcp server bundle missing. Searched: " + BUNDLE_PATH_CANDIDATES.join(", ") + ". For local dev, run: node packages/cli/scripts/bundle-mcp-server.mjs. A fresh `npm install -g @pleri/olam-cli@latest` should always include the bundle (see prepublishOnly in packages/cli/package.json); file an issue if it does not.";
26805
27598
  function registerMcpServe(cmd) {
@@ -26820,14 +27613,14 @@ function registerMcpServe(cmd) {
26820
27613
  init_output();
26821
27614
 
26822
27615
  // src/commands/mcp/install-shared.ts
26823
- import { spawnSync as spawnSync15 } from "node:child_process";
27616
+ import { spawnSync as spawnSync18 } from "node:child_process";
26824
27617
  var DEFAULT_CLAUDE_SHELL_DEPS = {
26825
- spawn: spawnSync15,
27618
+ spawn: spawnSync18,
26826
27619
  log: (msg) => console.log(msg)
26827
27620
  };
26828
27621
  function isOnPath(deps, bin) {
26829
- const probe = process.platform === "win32" ? "where" : "which";
26830
- const result = deps.spawn(probe, [bin], {
27622
+ const probe2 = process.platform === "win32" ? "where" : "which";
27623
+ const result = deps.spawn(probe2, [bin], {
26831
27624
  encoding: "utf8",
26832
27625
  stdio: ["ignore", "pipe", "ignore"]
26833
27626
  });
@@ -26838,6 +27631,10 @@ function normaliseScope(raw) {
26838
27631
  return value === "user" || value === "project" || value === "local" ? value : null;
26839
27632
  }
26840
27633
  var REMEDY_CLAUDE_MISSING = "`claude` not found on PATH. Install Claude Code (https://docs.claude.com/claude-code) or paste the JSON snippet from docs/architecture/mcp-as-npx-served.md.";
27634
+ var NOT_INSTALLED_PATTERN = /no\s+(?:\S+\s+)*mcp\s+server\s+found/i;
27635
+ function looksLikeNotInstalled(text) {
27636
+ return NOT_INSTALLED_PATTERN.test(text);
27637
+ }
26841
27638
 
26842
27639
  // src/commands/mcp/install.ts
26843
27640
  var NPM_PACKAGE_NAME = "@pleri/olam-cli";
@@ -26889,10 +27686,6 @@ function registerMcpInstall(cmd) {
26889
27686
 
26890
27687
  // src/commands/mcp/uninstall.ts
26891
27688
  init_output();
26892
- var NOT_INSTALLED_PATTERN = /no\s+(?:\S+\s+)*mcp\s+server\s+found/i;
26893
- function looksLikeNotInstalled(text) {
26894
- return NOT_INSTALLED_PATTERN.test(text);
26895
- }
26896
27689
  function buildClaudeMcpRemoveArgs(scope) {
26897
27690
  return {
26898
27691
  command: "claude",
@@ -27043,7 +27836,7 @@ function registerMcpLogin(cmd) {
27043
27836
  init_output();
27044
27837
  import * as readline2 from "node:readline";
27045
27838
  async function readTokenSilent(prompt) {
27046
- return new Promise((resolve12, reject) => {
27839
+ return new Promise((resolve14, reject) => {
27047
27840
  const rl = readline2.createInterface({
27048
27841
  input: process.stdin,
27049
27842
  output: process.stdout,
@@ -27061,7 +27854,7 @@ async function readTokenSilent(prompt) {
27061
27854
  process.stdin.removeListener("data", onData);
27062
27855
  process.stdout.write("\n");
27063
27856
  rl.close();
27064
- resolve12(token);
27857
+ resolve14(token);
27065
27858
  } else if (char === "") {
27066
27859
  if (process.stdin.isTTY) process.stdin.setRawMode(false);
27067
27860
  process.stdin.removeListener("data", onData);
@@ -27336,7 +28129,7 @@ async function discoverMcpSources(repoPaths) {
27336
28129
  import { spawn as spawn7 } from "node:child_process";
27337
28130
  var VALIDATION_TIMEOUT_MS = 5e3;
27338
28131
  async function validateMcpEntry(entry) {
27339
- return new Promise((resolve12) => {
28132
+ return new Promise((resolve14) => {
27340
28133
  let stdout = "";
27341
28134
  let timedOut = false;
27342
28135
  let child;
@@ -27346,7 +28139,7 @@ async function validateMcpEntry(entry) {
27346
28139
  env: { ...process.env, ...entry.env ?? {} }
27347
28140
  });
27348
28141
  } catch (err) {
27349
- resolve12({
28142
+ resolve14({
27350
28143
  name: entry.name,
27351
28144
  validated: false,
27352
28145
  reason: err instanceof Error ? err.message : "spawn failed"
@@ -27363,11 +28156,11 @@ async function validateMcpEntry(entry) {
27363
28156
  child.on("close", (code) => {
27364
28157
  clearTimeout(timer);
27365
28158
  if (timedOut) {
27366
- resolve12({ name: entry.name, validated: false, reason: "timeout (5s)" });
28159
+ resolve14({ name: entry.name, validated: false, reason: "timeout (5s)" });
27367
28160
  return;
27368
28161
  }
27369
28162
  const validated = code === 0 && stdout.trim().length > 0;
27370
- resolve12({
28163
+ resolve14({
27371
28164
  name: entry.name,
27372
28165
  validated,
27373
28166
  reason: validated ? "ok" : `exit code ${code ?? "null"}`
@@ -27375,7 +28168,7 @@ async function validateMcpEntry(entry) {
27375
28168
  });
27376
28169
  child.on("error", (err) => {
27377
28170
  clearTimeout(timer);
27378
- resolve12({ name: entry.name, validated: false, reason: err.message });
28171
+ resolve14({ name: entry.name, validated: false, reason: err.message });
27379
28172
  });
27380
28173
  });
27381
28174
  }
@@ -27390,11 +28183,11 @@ async function multiSelectPicker(entries) {
27390
28183
  );
27391
28184
  });
27392
28185
  console.log("\n" + pc29.dim('Enter numbers to import (e.g. 1,2,3 or "all" or Enter to skip):'));
27393
- const answer = await new Promise((resolve12) => {
28186
+ const answer = await new Promise((resolve14) => {
27394
28187
  const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
27395
28188
  rl.question("> ", (ans) => {
27396
28189
  rl.close();
27397
- resolve12(ans.trim());
28190
+ resolve14(ans.trim());
27398
28191
  });
27399
28192
  });
27400
28193
  if (!answer || answer === "") return [];
@@ -27421,21 +28214,21 @@ function registerMcpImport(cmd) {
27421
28214
  }
27422
28215
  printInfo("Sources", sources.length > 0 ? sources.join(", ") : "none");
27423
28216
  printInfo("Found", `${entries.length} MCP server(s) in ${Math.round(durationMs)}ms`);
27424
- let candidates = entries;
28217
+ let candidates2 = entries;
27425
28218
  let skippedCount = 0;
27426
28219
  if (!opts.reimport) {
27427
28220
  const filtered = entries.filter((e) => !existingIds.has(e.name));
27428
28221
  skippedCount = entries.length - filtered.length;
27429
- candidates = filtered;
28222
+ candidates2 = filtered;
27430
28223
  }
27431
28224
  if (skippedCount > 0) {
27432
28225
  console.log(pc29.dim(`skipped: ${skippedCount} already registered`));
27433
28226
  }
27434
- if (candidates.length === 0) {
28227
+ if (candidates2.length === 0) {
27435
28228
  console.log(pc29.dim("Nothing new to import. Use --reimport to force."));
27436
28229
  return;
27437
28230
  }
27438
- const selected = await multiSelectPicker(candidates);
28231
+ const selected = await multiSelectPicker(candidates2);
27439
28232
  if (selected.length === 0) {
27440
28233
  console.log(pc29.dim("No servers selected."));
27441
28234
  return;
@@ -27535,47 +28328,584 @@ function registerMcp(program2) {
27535
28328
  registerMcpRevoke(mcp);
27536
28329
  }
27537
28330
 
27538
- // src/commands/kg-build.ts
27539
- init_storage_paths();
27540
- init_workspace_name();
28331
+ // src/commands/memory/start.ts
27541
28332
  init_output();
27542
- import { spawnSync as spawnSync16 } from "node:child_process";
27543
- import fs49 from "node:fs";
27544
- import path54 from "node:path";
28333
+ import { spawn as spawn8 } from "node:child_process";
28334
+ import { existsSync as existsSync54, mkdirSync as mkdirSync30, openSync as openSync3, readFileSync as readFileSync38, writeFileSync as writeFileSync24 } from "node:fs";
28335
+ import { join as join52 } from "node:path";
28336
+
28337
+ // src/commands/memory/_paths.ts
28338
+ import { homedir as homedir30 } from "node:os";
28339
+ import { join as join51, dirname as dirname27 } from "node:path";
28340
+ import { fileURLToPath as fileURLToPath7 } from "node:url";
28341
+ var OLAM_HOME = join51(homedir30(), ".olam");
28342
+ var OLAM_BIN_DIR = join51(OLAM_HOME, "bin");
28343
+ var III_BINARY_PATH = join51(OLAM_BIN_DIR, "iii");
28344
+ var MEMORY_PID_PATH = join51(OLAM_HOME, "memory.pid");
28345
+ var MEMORY_LOG_PATH = join51(OLAM_HOME, "memory-service.log");
28346
+ var MEMORY_DATA_DIR = join51(OLAM_HOME, "memory-data");
28347
+ var MEMORY_REST_PORT = 3111;
28348
+ var MEMORY_LIVEZ_URL = `http://localhost:${MEMORY_REST_PORT}/agentmemory/livez`;
28349
+ var here2 = dirname27(fileURLToPath7(import.meta.url));
28350
+ var candidates = [
28351
+ // Workspace dev: packages/cli/src/commands/memory/_paths.ts (run via tsx) — unlikely
28352
+ // Workspace built: packages/cli/dist/commands/memory/_paths.js → packages/cli → packages/memory-service
28353
+ join51(here2, "..", "..", "..", "..", "memory-service"),
28354
+ // Bundled: dist/index.js (esbuild) → packages/cli → packages/memory-service
28355
+ join51(here2, "..", "..", "memory-service"),
28356
+ // Fallback: cwd-relative
28357
+ join51(process.cwd(), "packages", "memory-service")
28358
+ ];
28359
+ var MEMORY_SERVICE_CANDIDATES = candidates;
27545
28360
 
27546
- // src/commands/kg-status.ts
27547
- init_storage_paths();
27548
- init_workspace_name();
27549
- import fs47 from "node:fs";
27550
- import { homedir as homedir29 } from "node:os";
27551
- import path52 from "node:path";
27552
- init_output();
27553
- function olamHome4() {
27554
- return process.env.OLAM_HOME ?? path52.join(homedir29(), ".olam");
28361
+ // src/commands/memory/start.ts
28362
+ var READINESS_TIMEOUT_MS = 3e4;
28363
+ var READINESS_POLL_MS = 500;
28364
+ function resolveMemoryServiceDir() {
28365
+ for (const c of MEMORY_SERVICE_CANDIDATES) {
28366
+ if (existsSync54(c)) return c;
28367
+ }
28368
+ throw new Error(
28369
+ `Could not find packages/memory-service/. Searched: ${MEMORY_SERVICE_CANDIDATES.join(", ")}. If running from a published @pleri/olam-cli tarball, this is a packaging bug \u2014 please file an issue.`
28370
+ );
27555
28371
  }
27556
- function kgRoot2() {
27557
- return path52.join(olamHome4(), "kg");
28372
+ function resolveAgentMemoryBin(serviceDir) {
28373
+ const candidates2 = [
28374
+ join52(serviceDir, "node_modules", ".bin", "agentmemory"),
28375
+ join52(serviceDir, "..", "..", "node_modules", ".bin", "agentmemory"),
28376
+ join52(serviceDir, "..", "..", "..", "node_modules", ".bin", "agentmemory")
28377
+ ];
28378
+ for (const c of candidates2) {
28379
+ if (existsSync54(c)) return c;
28380
+ }
28381
+ throw new Error(
28382
+ `Could not find agentmemory bin. Searched: ${candidates2.join(", ")}. Run 'npm install' from the repo root.`
28383
+ );
27558
28384
  }
27559
- function worldsRoot2() {
27560
- return path52.join(olamHome4(), "worlds");
28385
+ function isProcessAlive(pid) {
28386
+ try {
28387
+ process.kill(pid, 0);
28388
+ return true;
28389
+ } catch {
28390
+ return false;
28391
+ }
27561
28392
  }
27562
- function dirSizeBytes2(dir) {
27563
- if (!fs47.existsSync(dir)) return 0;
27564
- let total = 0;
27565
- const stack = [dir];
27566
- while (stack.length > 0) {
27567
- const cur = stack.pop();
27568
- let entries;
27569
- try {
27570
- entries = fs47.readdirSync(cur, { withFileTypes: true });
27571
- } catch {
27572
- continue;
27573
- }
27574
- for (const entry of entries) {
27575
- const full = path52.join(cur, entry.name);
27576
- if (entry.isSymbolicLink()) continue;
27577
- if (entry.isDirectory()) {
27578
- stack.push(full);
28393
+ function readPidFromFile() {
28394
+ if (!existsSync54(MEMORY_PID_PATH)) return null;
28395
+ const raw = readFileSync38(MEMORY_PID_PATH, "utf8").trim();
28396
+ const pid = parseInt(raw, 10);
28397
+ if (!Number.isFinite(pid) || pid <= 0) return null;
28398
+ return pid;
28399
+ }
28400
+ async function probeLivez(secret, signal) {
28401
+ try {
28402
+ const resp = await fetch(MEMORY_LIVEZ_URL, {
28403
+ headers: { authorization: `Bearer ${secret}` },
28404
+ signal
28405
+ });
28406
+ if (!resp.ok) return false;
28407
+ const body = await resp.json();
28408
+ return body.status === "ok";
28409
+ } catch {
28410
+ return false;
28411
+ }
28412
+ }
28413
+ async function waitForReady(secret) {
28414
+ const deadline = Date.now() + READINESS_TIMEOUT_MS;
28415
+ while (Date.now() < deadline) {
28416
+ if (await probeLivez(secret)) return true;
28417
+ await new Promise((r) => setTimeout(r, READINESS_POLL_MS));
28418
+ }
28419
+ return false;
28420
+ }
28421
+ async function runMemoryStart() {
28422
+ printHeader("olam memory start");
28423
+ if (!existsSync54(III_BINARY_PATH)) {
28424
+ printError(
28425
+ `iii binary missing at ${III_BINARY_PATH}. Run: node packages/memory-service/scripts/ensure-iii-engine.mjs`
28426
+ );
28427
+ return 1;
28428
+ }
28429
+ printInfo("iii binary", III_BINARY_PATH);
28430
+ const secret = ensureMemorySecret();
28431
+ printInfo("secret", "~/.olam/memory-secret (0600)");
28432
+ const serviceDir = resolveMemoryServiceDir();
28433
+ const agentmemoryBin = resolveAgentMemoryBin(serviceDir);
28434
+ printInfo("agentmemory", agentmemoryBin);
28435
+ const existingPid = readPidFromFile();
28436
+ if (existingPid !== null && isProcessAlive(existingPid)) {
28437
+ const ready2 = await probeLivez(secret);
28438
+ if (ready2) {
28439
+ printSuccess(`already running (pid ${existingPid}); /agentmemory/livez ok`);
28440
+ return 0;
28441
+ }
28442
+ printError(
28443
+ `pid ${existingPid} is alive but /agentmemory/livez isn't responding. Run 'olam memory stop' to clear, then retry.`
28444
+ );
28445
+ return 1;
28446
+ }
28447
+ mkdirSync30(OLAM_HOME, { recursive: true });
28448
+ mkdirSync30(MEMORY_DATA_DIR, { recursive: true });
28449
+ const logFd = openSync3(MEMORY_LOG_PATH, "a");
28450
+ const child = spawn8(agentmemoryBin, [], {
28451
+ cwd: OLAM_HOME,
28452
+ env: {
28453
+ ...process.env,
28454
+ AGENTMEMORY_SECRET: secret,
28455
+ PATH: `${OLAM_BIN_DIR}:${process.env.PATH ?? ""}`
28456
+ },
28457
+ stdio: ["ignore", logFd, logFd],
28458
+ detached: true
28459
+ });
28460
+ child.unref();
28461
+ if (child.pid === void 0) {
28462
+ printError("spawn returned no pid (process failed to start)");
28463
+ return 1;
28464
+ }
28465
+ writeFileSync24(MEMORY_PID_PATH, `${child.pid}
28466
+ `, { mode: 420 });
28467
+ printInfo("pid", `${child.pid}`);
28468
+ printInfo("readiness", `polling ${MEMORY_LIVEZ_URL}`);
28469
+ const ready = await waitForReady(secret);
28470
+ if (!ready) {
28471
+ printError(
28472
+ `agentmemory failed to come up within ${READINESS_TIMEOUT_MS / 1e3}s. Inspect ${MEMORY_LOG_PATH} for engine errors.`
28473
+ );
28474
+ return 1;
28475
+ }
28476
+ printSuccess(`memory service ready (pid ${child.pid}; logs at ${MEMORY_LOG_PATH})`);
28477
+ return 0;
28478
+ }
28479
+ function registerMemoryStart(cmd) {
28480
+ cmd.command("start").description("Start the host agent-memory process (idempotent; polls livez until ready)").action(async () => {
28481
+ const rc = await runMemoryStart();
28482
+ if (rc !== 0) process.exitCode = rc;
28483
+ });
28484
+ }
28485
+
28486
+ // src/commands/memory/stop.ts
28487
+ init_output();
28488
+ import { existsSync as existsSync55, readFileSync as readFileSync39, unlinkSync as unlinkSync9 } from "node:fs";
28489
+ var SIGTERM_GRACE_MS = 1e4;
28490
+ var POLL_MS = 250;
28491
+ function isAlive(pid) {
28492
+ try {
28493
+ process.kill(pid, 0);
28494
+ return true;
28495
+ } catch {
28496
+ return false;
28497
+ }
28498
+ }
28499
+ async function runMemoryStop() {
28500
+ printHeader("olam memory stop");
28501
+ if (!existsSync55(MEMORY_PID_PATH)) {
28502
+ printSuccess("no pidfile present (nothing to stop)");
28503
+ return 0;
28504
+ }
28505
+ const pid = parseInt(readFileSync39(MEMORY_PID_PATH, "utf8").trim(), 10);
28506
+ if (!Number.isFinite(pid) || pid <= 0) {
28507
+ printWarning(`pidfile contained invalid value; removing`);
28508
+ unlinkSync9(MEMORY_PID_PATH);
28509
+ return 0;
28510
+ }
28511
+ if (!isAlive(pid)) {
28512
+ printSuccess(`pid ${pid} is not running (stale pidfile); cleaned up`);
28513
+ unlinkSync9(MEMORY_PID_PATH);
28514
+ return 0;
28515
+ }
28516
+ printInfo("pid", `${pid}`);
28517
+ try {
28518
+ process.kill(pid, "SIGTERM");
28519
+ } catch (err) {
28520
+ printWarning(`SIGTERM to pid ${pid} failed: ${err.message}`);
28521
+ }
28522
+ const deadline = Date.now() + SIGTERM_GRACE_MS;
28523
+ while (Date.now() < deadline) {
28524
+ if (!isAlive(pid)) break;
28525
+ await new Promise((r) => setTimeout(r, POLL_MS));
28526
+ }
28527
+ if (isAlive(pid)) {
28528
+ printWarning(`pid ${pid} did not exit within ${SIGTERM_GRACE_MS / 1e3}s; sending SIGKILL`);
28529
+ try {
28530
+ process.kill(pid, "SIGKILL");
28531
+ } catch (err) {
28532
+ printWarning(`SIGKILL to pid ${pid} failed: ${err.message}`);
28533
+ }
28534
+ await new Promise((r) => setTimeout(r, 500));
28535
+ }
28536
+ if (existsSync55(MEMORY_PID_PATH)) {
28537
+ unlinkSync9(MEMORY_PID_PATH);
28538
+ }
28539
+ printSuccess(`stopped (pid ${pid})`);
28540
+ return 0;
28541
+ }
28542
+ function registerMemoryStop(cmd) {
28543
+ cmd.command("stop").description("Stop the host agent-memory process (SIGTERM \u2192 SIGKILL after 10s; idempotent)").action(async () => {
28544
+ const rc = await runMemoryStop();
28545
+ if (rc !== 0) process.exitCode = rc;
28546
+ });
28547
+ }
28548
+
28549
+ // src/commands/memory/status.ts
28550
+ init_output();
28551
+ import { existsSync as existsSync56, readFileSync as readFileSync40 } from "node:fs";
28552
+ function isAlive2(pid) {
28553
+ try {
28554
+ process.kill(pid, 0);
28555
+ return true;
28556
+ } catch {
28557
+ return false;
28558
+ }
28559
+ }
28560
+ async function probe(secret) {
28561
+ try {
28562
+ const headers = {};
28563
+ if (secret) headers.authorization = `Bearer ${secret}`;
28564
+ const resp = await fetch(MEMORY_LIVEZ_URL, { headers });
28565
+ if (resp.status === 401) return "unauthorized";
28566
+ if (!resp.ok) return "unknown";
28567
+ const body = await resp.json();
28568
+ return body.status === "ok" ? "ok" : "unknown";
28569
+ } catch {
28570
+ return "unreachable";
28571
+ }
28572
+ }
28573
+ async function collectMemoryStatus() {
28574
+ let pid = null;
28575
+ if (existsSync56(MEMORY_PID_PATH)) {
28576
+ const raw = readFileSync40(MEMORY_PID_PATH, "utf8").trim();
28577
+ const parsed = parseInt(raw, 10);
28578
+ pid = Number.isFinite(parsed) && parsed > 0 ? parsed : null;
28579
+ }
28580
+ const alive = pid === null ? null : isAlive2(pid);
28581
+ const secret = readMemorySecretOrNull();
28582
+ const livez = await probe(secret);
28583
+ return {
28584
+ pid,
28585
+ alive,
28586
+ livez,
28587
+ secretSet: hasMemorySecret(),
28588
+ iiiBinary: existsSync56(III_BINARY_PATH) ? III_BINARY_PATH : null,
28589
+ port: MEMORY_REST_PORT
28590
+ };
28591
+ }
28592
+ async function runMemoryStatus(opts = {}) {
28593
+ const s = await collectMemoryStatus();
28594
+ if (opts.json) {
28595
+ process.stdout.write(JSON.stringify(s, null, 2) + "\n");
28596
+ return s.livez === "ok" ? 0 : 1;
28597
+ }
28598
+ printHeader("olam memory status");
28599
+ printInfo("pid", s.pid === null ? "(no pidfile)" : `${s.pid}`);
28600
+ printInfo("alive", s.alive === null ? "n/a" : s.alive ? "yes" : "no (stale pidfile)");
28601
+ printInfo("livez", s.livez);
28602
+ printInfo("secret", s.secretSet ? "~/.olam/memory-secret (set)" : "(missing)");
28603
+ printInfo("iii", s.iiiBinary ?? "(not installed)");
28604
+ printInfo("port", `${s.port}`);
28605
+ if (s.livez === "ok" && s.alive) return 0;
28606
+ if (s.livez === "unauthorized") {
28607
+ printWarning("livez returned 401 \u2014 the memory service is up but the local secret does not match. Try `olam memory secret rotate`.");
28608
+ return 1;
28609
+ }
28610
+ if (s.alive === false && s.pid !== null) {
28611
+ printWarning("pidfile points at a dead process; run `olam memory stop` to clean up");
28612
+ return 1;
28613
+ }
28614
+ if (!s.iiiBinary) {
28615
+ printWarning("iii binary missing; run `node packages/memory-service/scripts/ensure-iii-engine.mjs`");
28616
+ }
28617
+ if (!s.secretSet) {
28618
+ printWarning("secret missing; `olam memory start` will generate it");
28619
+ }
28620
+ return s.livez === "ok" ? 0 : 1;
28621
+ }
28622
+ function registerMemoryStatus(cmd) {
28623
+ cmd.command("status").description("Diagnostic dump for the host memory service (pid + livez + secret-set check)").option("--json", "Machine-readable JSON output", false).action(async (opts) => {
28624
+ const rc = await runMemoryStatus(opts);
28625
+ if (rc !== 0) process.exitCode = rc;
28626
+ });
28627
+ }
28628
+
28629
+ // src/commands/memory/logs.ts
28630
+ init_output();
28631
+ import { existsSync as existsSync57 } from "node:fs";
28632
+ import { spawn as spawn9 } from "node:child_process";
28633
+ async function runMemoryLogs(opts) {
28634
+ if (!existsSync57(MEMORY_LOG_PATH)) {
28635
+ printWarning(`no log at ${MEMORY_LOG_PATH} (start the service first via 'olam memory start')`);
28636
+ return 1;
28637
+ }
28638
+ const tailN = opts.tail ? parseInt(opts.tail, 10) : 50;
28639
+ if (!Number.isFinite(tailN) || tailN < 0) {
28640
+ printWarning(`invalid --tail value: ${opts.tail}`);
28641
+ return 1;
28642
+ }
28643
+ const args = ["-n", `${tailN}`];
28644
+ if (opts.follow) args.push("-f");
28645
+ args.push(MEMORY_LOG_PATH);
28646
+ printHeader(`olam memory logs (${opts.follow ? "follow" : `tail -n ${tailN}`})`);
28647
+ const child = spawn9("tail", args, { stdio: "inherit" });
28648
+ return new Promise((resolve14) => {
28649
+ child.on("exit", (code) => resolve14(code ?? 0));
28650
+ });
28651
+ }
28652
+ function registerMemoryLogs(cmd) {
28653
+ cmd.command("logs").description("Tail the memory service log file at ~/.olam/memory-service.log").option("-f, --follow", "Stream new log lines (Ctrl-C to stop)", false).option("--tail <n>", "Number of trailing lines (default 50)", "50").action(async (opts) => {
28654
+ const rc = await runMemoryLogs(opts);
28655
+ if (rc !== 0) process.exitCode = rc;
28656
+ });
28657
+ }
28658
+
28659
+ // src/commands/memory/secret.ts
28660
+ import { existsSync as existsSync58 } from "node:fs";
28661
+ init_output();
28662
+ async function runMemorySecretShow() {
28663
+ if (!hasMemorySecret()) {
28664
+ printError(
28665
+ `secret missing at ${MEMORY_SECRET_PATH}. Run 'olam memory start' to generate it.`
28666
+ );
28667
+ return 1;
28668
+ }
28669
+ process.stdout.write(`${readMemorySecret2()}
28670
+ `);
28671
+ return 0;
28672
+ }
28673
+ async function runMemorySecretRotate() {
28674
+ printHeader("olam memory secret rotate");
28675
+ const wasRunning = existsSync58(MEMORY_PID_PATH);
28676
+ if (wasRunning) {
28677
+ printInfo("current state", "service running; will restart with new secret");
28678
+ const stopRc = await runMemoryStop();
28679
+ if (stopRc !== 0) {
28680
+ printError("failed to stop the running service; rotation aborted to avoid split-brain");
28681
+ return stopRc;
28682
+ }
28683
+ } else {
28684
+ printInfo("current state", "service stopped");
28685
+ }
28686
+ const fresh = rotateMemorySecret();
28687
+ printSuccess(`secret rotated (${fresh.length / 2}-byte hex written to ${MEMORY_SECRET_PATH})`);
28688
+ if (wasRunning) {
28689
+ printInfo("restarting", "memory service with new secret");
28690
+ const startRc = await runMemoryStart();
28691
+ if (startRc !== 0) {
28692
+ printError(
28693
+ "service failed to restart after rotation. The new secret is on disk; run `olam memory start` once you fix the underlying issue."
28694
+ );
28695
+ return startRc;
28696
+ }
28697
+ } else {
28698
+ printWarning("service was not running; new secret will be picked up on next `olam memory start`");
28699
+ }
28700
+ printWarning(
28701
+ "worlds created BEFORE this rotation still have the old secret in their env. Re-create them (olam destroy + olam create) to pick up the new value."
28702
+ );
28703
+ return 0;
28704
+ }
28705
+ function registerMemorySecret(cmd) {
28706
+ const secret = cmd.command("secret").description("Show or rotate the bearer secret at ~/.olam/memory-secret");
28707
+ secret.command("show").description("Print the current secret value (use with care; do not pipe to logs)").action(async () => {
28708
+ const rc = await runMemorySecretShow();
28709
+ if (rc !== 0) process.exitCode = rc;
28710
+ });
28711
+ secret.command("rotate").description(
28712
+ "Regenerate the secret and restart the running memory service (worlds created before rotation must be re-created)"
28713
+ ).action(async () => {
28714
+ const rc = await runMemorySecretRotate();
28715
+ if (rc !== 0) process.exitCode = rc;
28716
+ });
28717
+ }
28718
+
28719
+ // src/commands/memory/install.ts
28720
+ init_output();
28721
+ var MCP_NAME2 = "agentmemory";
28722
+ var NPM_PACKAGE_NAME2 = "@agentmemory/mcp";
28723
+ var DEFAULT_SECRET_READER = () => readMemorySecretOrNull();
28724
+ function buildClaudeMcpAddArgs2(scope, agentmemoryUrl, secret) {
28725
+ return {
28726
+ command: "claude",
28727
+ args: [
28728
+ "mcp",
28729
+ "add",
28730
+ MCP_NAME2,
28731
+ "--scope",
28732
+ scope,
28733
+ "--env",
28734
+ `AGENTMEMORY_URL=${agentmemoryUrl}`,
28735
+ "--env",
28736
+ `AGENTMEMORY_SECRET=${secret}`,
28737
+ "--",
28738
+ "npx",
28739
+ "-y",
28740
+ NPM_PACKAGE_NAME2
28741
+ ]
28742
+ };
28743
+ }
28744
+ var REMEDY_SECRET_MISSING = `No memory-secret found at ${MEMORY_SECRET_PATH}. Run 'olam memory start' first \u2014 it generates the secret on first launch.`;
28745
+ function redactSecretInText(text) {
28746
+ return text.replace(/AGENTMEMORY_SECRET=\S+/g, "AGENTMEMORY_SECRET=<redacted>");
28747
+ }
28748
+ async function runInstall2(opts, deps = DEFAULT_CLAUDE_SHELL_DEPS) {
28749
+ if (!isOnPath(deps, "claude")) {
28750
+ printError(REMEDY_CLAUDE_MISSING);
28751
+ return 1;
28752
+ }
28753
+ const readSecret = opts.readSecret ?? DEFAULT_SECRET_READER;
28754
+ const secret = opts.secret ?? readSecret();
28755
+ if (!secret) {
28756
+ printError(REMEDY_SECRET_MISSING);
28757
+ return 1;
28758
+ }
28759
+ const agentmemoryUrl = `http://localhost:${MEMORY_REST_PORT}`;
28760
+ const { command, args } = buildClaudeMcpAddArgs2(opts.scope, agentmemoryUrl, secret);
28761
+ const redacted = args.map(
28762
+ (a) => a.startsWith("AGENTMEMORY_SECRET=") ? "AGENTMEMORY_SECRET=<redacted>" : a
28763
+ );
28764
+ deps.log(`Wiring: ${command} ${redacted.join(" ")}`);
28765
+ const result = deps.spawn(command, args, {
28766
+ encoding: "utf8",
28767
+ stdio: ["ignore", "pipe", "pipe"]
28768
+ });
28769
+ if (result.status !== 0) {
28770
+ const detail = redactSecretInText(
28771
+ result.stderr?.trim() || result.stdout?.trim() || "(no output)"
28772
+ );
28773
+ printError(`claude mcp add failed (rc=${result.status ?? "unknown"}): ${detail}`);
28774
+ return result.status ?? 1;
28775
+ }
28776
+ printSuccess(
28777
+ `agentmemory MCP registered with claude (--scope ${opts.scope}; url ${agentmemoryUrl}).`
28778
+ );
28779
+ return 0;
28780
+ }
28781
+ function registerMemoryInstall(cmd) {
28782
+ cmd.command("install").description(
28783
+ "Register agentmemory as an MCP server with the operator's host claude (shells to `claude mcp add`)"
28784
+ ).option("--scope <scope>", "claude mcp scope: user | project | local", "user").action(async (rawOpts) => {
28785
+ const scope = normaliseScope(rawOpts.scope);
28786
+ if (scope === null) {
28787
+ printError(`--scope must be one of: user, project, local (got: ${rawOpts.scope})`);
28788
+ process.exitCode = 1;
28789
+ return;
28790
+ }
28791
+ const rc = await runInstall2({ scope });
28792
+ if (rc !== 0) {
28793
+ process.exitCode = rc;
28794
+ }
28795
+ });
28796
+ }
28797
+
28798
+ // src/commands/memory/uninstall.ts
28799
+ init_output();
28800
+ var MCP_NAME3 = "agentmemory";
28801
+ function buildClaudeMcpRemoveArgs2(scope) {
28802
+ return {
28803
+ command: "claude",
28804
+ args: ["mcp", "remove", MCP_NAME3, "--scope", scope]
28805
+ };
28806
+ }
28807
+ async function runUninstall2(opts, deps = DEFAULT_CLAUDE_SHELL_DEPS) {
28808
+ if (!isOnPath(deps, "claude")) {
28809
+ printError(REMEDY_CLAUDE_MISSING);
28810
+ return 1;
28811
+ }
28812
+ const { command, args } = buildClaudeMcpRemoveArgs2(opts.scope);
28813
+ deps.log(`Unwiring: ${command} ${args.join(" ")}`);
28814
+ const result = deps.spawn(command, args, {
28815
+ encoding: "utf8",
28816
+ stdio: ["ignore", "pipe", "pipe"]
28817
+ });
28818
+ if (result.status === 0) {
28819
+ printSuccess(`agentmemory MCP removed from claude (--scope ${opts.scope}).`);
28820
+ return 0;
28821
+ }
28822
+ const stderr = result.stderr?.trim() ?? "";
28823
+ const stdout = result.stdout?.trim() ?? "";
28824
+ const combined = `${stderr}
28825
+ ${stdout}`.trim();
28826
+ if (looksLikeNotInstalled(combined)) {
28827
+ printWarning(
28828
+ `agentmemory MCP not registered with claude (--scope ${opts.scope}); nothing to remove.`
28829
+ );
28830
+ return 0;
28831
+ }
28832
+ printError(
28833
+ `claude mcp remove failed (rc=${result.status ?? "unknown"}): ${combined || "(no output)"}`
28834
+ );
28835
+ return result.status ?? 1;
28836
+ }
28837
+ function registerMemoryUninstall(cmd) {
28838
+ cmd.command("uninstall").description(
28839
+ "Remove agentmemory from the operator's host claude (symmetric with `install`; idempotent)"
28840
+ ).option("--scope <scope>", "claude mcp scope: user | project | local", "user").action(async (rawOpts) => {
28841
+ const scope = normaliseScope(rawOpts.scope);
28842
+ if (scope === null) {
28843
+ printError(`--scope must be one of: user, project, local (got: ${rawOpts.scope})`);
28844
+ process.exitCode = 1;
28845
+ return;
28846
+ }
28847
+ const rc = await runUninstall2({ scope });
28848
+ if (rc !== 0) {
28849
+ process.exitCode = rc;
28850
+ }
28851
+ });
28852
+ }
28853
+
28854
+ // src/commands/memory/index.ts
28855
+ function registerMemory(program2) {
28856
+ const memory = program2.command("memory").description(
28857
+ "Host-process agent-memory service for the olam fleet (start, stop, status, logs, secret, install, uninstall)"
28858
+ );
28859
+ registerMemoryStart(memory);
28860
+ registerMemoryStop(memory);
28861
+ registerMemoryStatus(memory);
28862
+ registerMemoryLogs(memory);
28863
+ registerMemorySecret(memory);
28864
+ registerMemoryInstall(memory);
28865
+ registerMemoryUninstall(memory);
28866
+ }
28867
+
28868
+ // src/commands/kg-build.ts
28869
+ init_storage_paths();
28870
+ init_workspace_name();
28871
+ init_output();
28872
+ import { spawnSync as spawnSync19 } from "node:child_process";
28873
+ import fs49 from "node:fs";
28874
+ import path54 from "node:path";
28875
+
28876
+ // src/commands/kg-status.ts
28877
+ init_storage_paths();
28878
+ init_workspace_name();
28879
+ import fs47 from "node:fs";
28880
+ import { homedir as homedir31 } from "node:os";
28881
+ import path52 from "node:path";
28882
+ init_output();
28883
+ function olamHome4() {
28884
+ return process.env.OLAM_HOME ?? path52.join(homedir31(), ".olam");
28885
+ }
28886
+ function kgRoot2() {
28887
+ return path52.join(olamHome4(), "kg");
28888
+ }
28889
+ function worldsRoot2() {
28890
+ return path52.join(olamHome4(), "worlds");
28891
+ }
28892
+ function dirSizeBytes2(dir) {
28893
+ if (!fs47.existsSync(dir)) return 0;
28894
+ let total = 0;
28895
+ const stack = [dir];
28896
+ while (stack.length > 0) {
28897
+ const cur = stack.pop();
28898
+ let entries;
28899
+ try {
28900
+ entries = fs47.readdirSync(cur, { withFileTypes: true });
28901
+ } catch {
28902
+ continue;
28903
+ }
28904
+ for (const entry of entries) {
28905
+ const full = path52.join(cur, entry.name);
28906
+ if (entry.isSymbolicLink()) continue;
28907
+ if (entry.isDirectory()) {
28908
+ stack.push(full);
27579
28909
  continue;
27580
28910
  }
27581
28911
  try {
@@ -27806,7 +29136,7 @@ function registerKgStatusCommand(kg) {
27806
29136
  init_storage_paths();
27807
29137
  init_workspace_name();
27808
29138
  init_output();
27809
- import { spawn as spawn8 } from "node:child_process";
29139
+ import { spawn as spawn10 } from "node:child_process";
27810
29140
  import fs48 from "node:fs";
27811
29141
  import path53 from "node:path";
27812
29142
  function pidFilePath(workspace) {
@@ -27876,7 +29206,7 @@ async function runKgWatch(workspaceArg, opts, deps = {}) {
27876
29206
  if (pidState.status === "stale-reclaimed") {
27877
29207
  printInfo("stale-pid", `reclaimed dead PID file at ${pidFilePath(name)}`);
27878
29208
  }
27879
- const spawnFn = deps.spawnImpl ?? spawn8;
29209
+ const spawnFn = deps.spawnImpl ?? spawn10;
27880
29210
  const child = spawnFn(
27881
29211
  "graphify",
27882
29212
  [cwd, "--watch", "--update", "--graph", graphPath],
@@ -27905,16 +29235,16 @@ async function runKgWatch(workspaceArg, opts, deps = {}) {
27905
29235
  process.on("SIGINT", () => forward("SIGINT"));
27906
29236
  process.on("SIGTERM", () => forward("SIGTERM"));
27907
29237
  }
27908
- return new Promise((resolve12) => {
29238
+ return new Promise((resolve14) => {
27909
29239
  child.on("exit", (code, signal) => {
27910
29240
  removePidFile(name);
27911
29241
  const exitCode = typeof code === "number" ? code : signal === "SIGINT" || signal === "SIGTERM" ? 0 : 1;
27912
- resolve12({ exitCode, pidWritten: true });
29242
+ resolve14({ exitCode, pidWritten: true });
27913
29243
  });
27914
29244
  child.on("error", (err) => {
27915
29245
  removePidFile(name);
27916
29246
  printError(`graphify subprocess error: ${err.message}`);
27917
- resolve12({ exitCode: 1, pidWritten: true });
29247
+ resolve14({ exitCode: 1, pidWritten: true });
27918
29248
  });
27919
29249
  });
27920
29250
  }
@@ -27937,13 +29267,13 @@ function resolveWorkspace(arg) {
27937
29267
  }
27938
29268
  function copyWorkspaceToScratch(source, scratch) {
27939
29269
  if (process.platform === "darwin") {
27940
- const r2 = spawnSync16("cp", ["-c", "-r", source + "/.", scratch], {
29270
+ const r2 = spawnSync19("cp", ["-c", "-r", source + "/.", scratch], {
27941
29271
  stdio: ["ignore", "ignore", "pipe"],
27942
29272
  encoding: "utf-8"
27943
29273
  });
27944
29274
  if (r2.status === 0) return "cp-c-r-reflink";
27945
29275
  }
27946
- const r = spawnSync16("cp", ["-r", source + "/.", scratch], {
29276
+ const r = spawnSync19("cp", ["-r", source + "/.", scratch], {
27947
29277
  stdio: ["ignore", "ignore", "pipe"],
27948
29278
  encoding: "utf-8"
27949
29279
  });
@@ -27964,7 +29294,7 @@ function parseNodeCount(graphifyOutDir) {
27964
29294
  }
27965
29295
  }
27966
29296
  function readGraphifyVersion(image) {
27967
- const r = spawnSync16(
29297
+ const r = spawnSync19(
27968
29298
  "docker",
27969
29299
  [
27970
29300
  "run",
@@ -28016,7 +29346,7 @@ async function runKgBuild(workspaceArg, options = {}) {
28016
29346
  "update",
28017
29347
  "."
28018
29348
  ];
28019
- const r = human ? spawnSync16("docker", dockerArgs, { stdio: "inherit" }) : spawnSync16("docker", dockerArgs, { stdio: ["ignore", "ignore", "pipe"] });
29349
+ const r = human ? spawnSync19("docker", dockerArgs, { stdio: "inherit" }) : spawnSync19("docker", dockerArgs, { stdio: ["ignore", "ignore", "pipe"] });
28020
29350
  if (r.status !== 0) {
28021
29351
  printError(`graphify update failed (exit ${r.status})`);
28022
29352
  return { exitCode: r.status ?? 1 };
@@ -28072,6 +29402,250 @@ function registerKg(program2) {
28072
29402
  registerKgWatchCommand(kg);
28073
29403
  }
28074
29404
 
29405
+ // src/commands/seed.ts
29406
+ init_output();
29407
+ import { spawnSync as spawnSync20, spawn as spawnAsync2 } from "node:child_process";
29408
+ var DEFAULT_SINGLETON_CONTAINER = "olam-postgres";
29409
+ var DEFAULT_SINGLETON_USER = "development";
29410
+ function assertValidSeedName(name) {
29411
+ if (!/^[a-z][a-z0-9_]*$/.test(name)) {
29412
+ throw new Error(
29413
+ `invalid seed name "${name}" \u2014 must match [a-z][a-z0-9_]* (no hyphens, no uppercase)`
29414
+ );
29415
+ }
29416
+ }
29417
+ function singletonDocker(container, user, args, stdin) {
29418
+ return spawnSync20(
29419
+ "docker",
29420
+ ["exec", "-i", container, "psql", "-U", user, ...args],
29421
+ { encoding: "utf-8", input: stdin }
29422
+ );
29423
+ }
29424
+ function singletonHasDb(container, user, db) {
29425
+ const r = singletonDocker(container, user, [
29426
+ "-d",
29427
+ "postgres",
29428
+ "-tAc",
29429
+ `SELECT 1 FROM pg_database WHERE datname='${db.replace(/'/g, "''")}'`
29430
+ ]);
29431
+ return r.status === 0 && (r.stdout || "").trim() === "1";
29432
+ }
29433
+ function singletonListSeeds(container, user) {
29434
+ const r = singletonDocker(container, user, [
29435
+ "-d",
29436
+ "postgres",
29437
+ "-tAc",
29438
+ "SELECT datname || '|' || pg_size_pretty(pg_database_size(datname)) FROM pg_database WHERE datname LIKE '%\\_seed' ORDER BY datname"
29439
+ ]);
29440
+ if (r.status !== 0) {
29441
+ throw new Error(`failed to list seeds: ${(r.stderr || "").trim()}`);
29442
+ }
29443
+ const lines = (r.stdout || "").trim().split("\n").filter((l) => l.length > 0);
29444
+ return lines.map((line) => {
29445
+ const [name = "", size = ""] = line.split("|");
29446
+ return { name, size };
29447
+ });
29448
+ }
29449
+ function singletonListClonesOf(container, user, seed) {
29450
+ const prefix = seed.endsWith("_seed") ? seed.slice(0, -"_seed".length) : seed;
29451
+ const r = singletonDocker(container, user, [
29452
+ "-d",
29453
+ "postgres",
29454
+ "-tAc",
29455
+ `SELECT datname FROM pg_database WHERE datname LIKE '${prefix.replace(/'/g, "''")}_world_%' ORDER BY datname`
29456
+ ]);
29457
+ if (r.status !== 0) return [];
29458
+ return (r.stdout || "").trim().split("\n").filter((l) => l.length > 0);
29459
+ }
29460
+ async function handleBake(opts) {
29461
+ assertValidSeedName(opts.as);
29462
+ const singleton = opts.singletonContainer ?? DEFAULT_SINGLETON_CONTAINER;
29463
+ const singletonUser = opts.singletonUser ?? DEFAULT_SINGLETON_USER;
29464
+ const sources = [opts.sourceContainer, opts.sourceUrl, opts.sourceLocal].filter(Boolean);
29465
+ if (sources.length === 0) {
29466
+ throw new Error(
29467
+ "no source specified \u2014 pass exactly one of --source-container, --source-url, --source-local"
29468
+ );
29469
+ }
29470
+ if (sources.length > 1) {
29471
+ throw new Error("multiple sources specified \u2014 pass exactly one of --source-container, --source-url, --source-local");
29472
+ }
29473
+ const ping = spawnSync20("docker", ["inspect", "--format", "{{.State.Status}}", singleton], { encoding: "utf-8" });
29474
+ if (ping.status !== 0 || (ping.stdout || "").trim() !== "running") {
29475
+ throw new Error(`singleton container "${singleton}" not running \u2014 run \`olam bootstrap\` first`);
29476
+ }
29477
+ if (singletonHasDb(singleton, singletonUser, opts.as)) {
29478
+ if (!opts.force) {
29479
+ throw new Error(`seed "${opts.as}" already exists \u2014 pass --force to overwrite (DROP + recreate)`);
29480
+ }
29481
+ const clones = singletonListClonesOf(singleton, singletonUser, opts.as);
29482
+ if (clones.length > 0) {
29483
+ throw new Error(
29484
+ `cannot --force overwrite "${opts.as}" \u2014 ${clones.length} per-world clone(s) still depend on it: ${clones.join(", ")}
29485
+ Destroy those worlds first, then re-bake.`
29486
+ );
29487
+ }
29488
+ printWarning(`Dropping existing seed "${opts.as}" (--force)`);
29489
+ const drop = singletonDocker(singleton, singletonUser, ["-d", "postgres", "-c", `DROP DATABASE "${opts.as}"`]);
29490
+ if (drop.status !== 0) {
29491
+ throw new Error(`DROP DATABASE failed: ${(drop.stderr || "").trim()}`);
29492
+ }
29493
+ }
29494
+ process.stdout.write(` Creating empty target DB "${opts.as}" on singleton
29495
+ `);
29496
+ const create = singletonDocker(singleton, singletonUser, ["-d", "postgres", "-c", `CREATE DATABASE "${opts.as}"`]);
29497
+ if (create.status !== 0) {
29498
+ throw new Error(`CREATE DATABASE "${opts.as}" failed: ${(create.stderr || "").trim()}`);
29499
+ }
29500
+ let dumpArgs;
29501
+ let dumpEnv = { PATH: process.env["PATH"] || "" };
29502
+ if (opts.sourceContainer) {
29503
+ const srcDb = opts.sourceDb ?? "postgres";
29504
+ const srcUser = opts.sourceUser ?? DEFAULT_SINGLETON_USER;
29505
+ process.stdout.write(` Source: container ${opts.sourceContainer} DB=${srcDb} user=${srcUser}
29506
+ `);
29507
+ dumpArgs = ["exec", opts.sourceContainer, "pg_dump", "-U", srcUser, "-d", srcDb, "--no-owner", "--no-privileges"];
29508
+ dumpEnv = { PATH: process.env["PATH"] || "" };
29509
+ } else if (opts.sourceLocal) {
29510
+ if (!singletonHasDb(singleton, singletonUser, opts.sourceLocal)) {
29511
+ throw new Error(`--source-local "${opts.sourceLocal}" does not exist on singleton`);
29512
+ }
29513
+ process.stdout.write(` Source: singleton local DB ${opts.sourceLocal}
29514
+ `);
29515
+ dumpArgs = ["exec", singleton, "pg_dump", "-U", singletonUser, "-d", opts.sourceLocal, "--no-owner", "--no-privileges"];
29516
+ dumpEnv = { PATH: process.env["PATH"] || "" };
29517
+ } else {
29518
+ process.stdout.write(` Source: ${opts.sourceUrl.replace(/\/\/[^:]+:[^@]+@/, "//<creds>@")}
29519
+ `);
29520
+ dumpArgs = ["exec", "-e", `PGURL=${opts.sourceUrl}`, singleton, "sh", "-c", 'pg_dump --no-owner --no-privileges "$PGURL"'];
29521
+ dumpEnv = { PATH: process.env["PATH"] || "" };
29522
+ }
29523
+ const dumper = spawnAsync2("docker", dumpArgs, { stdio: ["ignore", "pipe", "inherit"], env: dumpEnv });
29524
+ const loader = spawnAsync2(
29525
+ "docker",
29526
+ ["exec", "-i", singleton, "psql", "-U", singletonUser, "-d", opts.as, "-v", "ON_ERROR_STOP=1", "-q"],
29527
+ { stdio: ["pipe", "inherit", "inherit"], env: { PATH: process.env["PATH"] || "" } }
29528
+ );
29529
+ dumper.stdout.pipe(loader.stdin);
29530
+ const [dumpExit, loadExit] = await Promise.all([
29531
+ new Promise((res) => dumper.on("close", (code) => res(code ?? 1))),
29532
+ new Promise((res) => loader.on("close", (code) => res(code ?? 1)))
29533
+ ]);
29534
+ if (dumpExit !== 0) {
29535
+ throw new Error(`pg_dump exited with code ${dumpExit}`);
29536
+ }
29537
+ if (loadExit !== 0) {
29538
+ throw new Error(`psql load exited with code ${loadExit}`);
29539
+ }
29540
+ const size = singletonDocker(singleton, singletonUser, [
29541
+ "-d",
29542
+ "postgres",
29543
+ "-tAc",
29544
+ `SELECT pg_size_pretty(pg_database_size('${opts.as}'))`
29545
+ ]);
29546
+ const sizeStr = (size.stdout || "").trim() || "unknown";
29547
+ printSuccess(`Seed "${opts.as}" baked (${sizeStr})`);
29548
+ }
29549
+ function handleList3(opts) {
29550
+ const singleton = opts.singletonContainer ?? DEFAULT_SINGLETON_CONTAINER;
29551
+ const singletonUser = opts.singletonUser ?? DEFAULT_SINGLETON_USER;
29552
+ const seeds = singletonListSeeds(singleton, singletonUser);
29553
+ if (opts.json) {
29554
+ process.stdout.write(JSON.stringify(seeds, null, 2) + "\n");
29555
+ return;
29556
+ }
29557
+ if (seeds.length === 0) {
29558
+ process.stdout.write("No seeds found on singleton.\n");
29559
+ return;
29560
+ }
29561
+ printHeader(`${seeds.length} seed(s) on ${singleton}`);
29562
+ const nameWidth = Math.max(...seeds.map((s) => s.name.length), 4);
29563
+ for (const s of seeds) {
29564
+ process.stdout.write(` ${s.name.padEnd(nameWidth)} ${s.size}
29565
+ `);
29566
+ }
29567
+ }
29568
+ function handleRemove(name, opts) {
29569
+ assertValidSeedName(name);
29570
+ const singleton = opts.singletonContainer ?? DEFAULT_SINGLETON_CONTAINER;
29571
+ const singletonUser = opts.singletonUser ?? DEFAULT_SINGLETON_USER;
29572
+ if (!singletonHasDb(singleton, singletonUser, name)) {
29573
+ printWarning(`Seed "${name}" does not exist on singleton \u2014 nothing to do`);
29574
+ return;
29575
+ }
29576
+ const clones = singletonListClonesOf(singleton, singletonUser, name);
29577
+ if (clones.length > 0 && !opts.force) {
29578
+ throw new Error(
29579
+ `seed "${name}" has ${clones.length} active world clone(s): ${clones.join(", ")}
29580
+ Destroy those worlds first, or re-run with --force to drop anyway (CAUTION: leaves world DBs orphaned).`
29581
+ );
29582
+ }
29583
+ const drop = singletonDocker(singleton, singletonUser, ["-d", "postgres", "-c", `DROP DATABASE "${name}"`]);
29584
+ if (drop.status !== 0) {
29585
+ throw new Error(`DROP DATABASE "${name}" failed: ${(drop.stderr || "").trim()}`);
29586
+ }
29587
+ printSuccess(`Removed seed "${name}"`);
29588
+ }
29589
+ function handleVerify(name, opts) {
29590
+ assertValidSeedName(name);
29591
+ const singleton = opts.singletonContainer ?? DEFAULT_SINGLETON_CONTAINER;
29592
+ const singletonUser = opts.singletonUser ?? DEFAULT_SINGLETON_USER;
29593
+ if (!singletonHasDb(singleton, singletonUser, name)) {
29594
+ throw new Error(`seed "${name}" does not exist on singleton`);
29595
+ }
29596
+ const heartbeat = singletonDocker(singleton, singletonUser, ["-d", name, "-tAc", "SELECT 1"]);
29597
+ if (heartbeat.status !== 0 || (heartbeat.stdout || "").trim() !== "1") {
29598
+ throw new Error(`heartbeat failed: ${(heartbeat.stderr || "").trim()}`);
29599
+ }
29600
+ const meta = singletonDocker(singleton, singletonUser, [
29601
+ "-d",
29602
+ name,
29603
+ "-tAc",
29604
+ "SELECT pg_size_pretty(pg_database_size(current_database())) || '|' || (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public')"
29605
+ ]);
29606
+ const [size = "?", tableCount = "?"] = (meta.stdout || "").trim().split("|");
29607
+ const clones = singletonListClonesOf(singleton, singletonUser, name);
29608
+ printSuccess(`Seed "${name}" OK`);
29609
+ printInfo("size", size);
29610
+ printInfo("tables", `${tableCount} (public schema)`);
29611
+ printInfo("clones", `${clones.length}${clones.length > 0 ? " (" + clones.join(", ") + ")" : ""}`);
29612
+ }
29613
+ function registerSeed(program2) {
29614
+ const seed = program2.command("seed").description("Manage postgres seed templates on the olam-postgres singleton");
29615
+ seed.command("bake").description("Bake a source DB into the singleton as a named seed template").requiredOption("--as <name>", "Target seed name (e.g. atlas_common_seed); [a-z][a-z0-9_]*").option("--source-container <name>", "Source: pg_dump inside this docker container").option("--source-url <url>", "Source: pg_dump via postgres:// URL (libpq)").option("--source-local <db>", "Source: pg_dump a DB already on the singleton").option("--source-db <db>", "DB name to dump (when --source-container; default: postgres)").option("--source-user <user>", "Source-side user (when --source-container; default: development)").option("--singleton-container <name>", "Override singleton container name (default: olam-postgres)").option("--singleton-user <user>", "Override singleton user (default: development)").option("--force", "Drop + recreate if the seed already exists").action(async (opts) => {
29616
+ try {
29617
+ await handleBake(opts);
29618
+ } catch (err) {
29619
+ printError(err instanceof Error ? err.message : String(err));
29620
+ process.exit(1);
29621
+ }
29622
+ });
29623
+ seed.command("list").description("List seed templates on the singleton").option("--json", "Emit machine-parseable JSON").option("--singleton-container <name>", "Override singleton container name (default: olam-postgres)").option("--singleton-user <user>", "Override singleton user (default: development)").action((opts) => {
29624
+ try {
29625
+ handleList3(opts);
29626
+ } catch (err) {
29627
+ printError(err instanceof Error ? err.message : String(err));
29628
+ process.exit(1);
29629
+ }
29630
+ });
29631
+ seed.command("remove <name>").description("Remove a seed template (refuses by default if any per-world clones exist)").option("--force", "DROP even if active clones exist (clones will be orphaned)").option("--singleton-container <name>", "Override singleton container name (default: olam-postgres)").option("--singleton-user <user>", "Override singleton user (default: development)").action((name, opts) => {
29632
+ try {
29633
+ handleRemove(name, opts);
29634
+ } catch (err) {
29635
+ printError(err instanceof Error ? err.message : String(err));
29636
+ process.exit(1);
29637
+ }
29638
+ });
29639
+ seed.command("verify <name>").description("Sanity-check a seed (existence, connectivity, size, clone count)").option("--singleton-container <name>", "Override singleton container name (default: olam-postgres)").option("--singleton-user <user>", "Override singleton user (default: development)").action((name, opts) => {
29640
+ try {
29641
+ handleVerify(name, opts);
29642
+ } catch (err) {
29643
+ printError(err instanceof Error ? err.message : String(err));
29644
+ process.exit(1);
29645
+ }
29646
+ });
29647
+ }
29648
+
28075
29649
  // src/pleri-config.ts
28076
29650
  import * as fs50 from "node:fs";
28077
29651
  import * as path55 from "node:path";
@@ -28122,6 +29696,7 @@ registerCrystallize(program, { hidden: !isPleriConfigured() });
28122
29696
  registerPr(program);
28123
29697
  registerWorkspace(program);
28124
29698
  registerHostCp(program);
29699
+ registerSeed(program);
28125
29700
  registerLanes(program);
28126
29701
  registerPolicyCheck(program);
28127
29702
  registerWorldspec(program);
@@ -28141,6 +29716,7 @@ registerBegin(program);
28141
29716
  registerStop(program);
28142
29717
  registerWorldUpgrade(program);
28143
29718
  registerMcp(program);
29719
+ registerMemory(program);
28144
29720
  registerKg(program);
28145
29721
  registerConfig(program);
28146
29722
  registerRepos(program);