@pleri/olam-cli 0.1.141 → 0.1.143

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.
@@ -5,16 +5,10 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
9
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
10
- }) : x)(function(x) {
11
- if (typeof require !== "undefined") return require.apply(this, arguments);
12
- throw Error('Dynamic require of "' + x + '" is not supported');
13
- });
14
8
  var __esm = (fn, res) => function __init() {
15
9
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
16
10
  };
17
- var __commonJS = (cb, mod) => function __require2() {
11
+ var __commonJS = (cb, mod) => function __require() {
18
12
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
19
13
  };
20
14
  var __export = (target, all) => {
@@ -31334,19 +31328,26 @@ function defaultUrl(flavor) {
31334
31328
  function buildHookCommand(opts) {
31335
31329
  const url2 = opts.url ?? defaultUrl(opts.flavor);
31336
31330
  const extractCmd = `python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('tool_input',d).get('command',''))" 2>/dev/null`;
31337
- const emitContext = `python3 -c 'import json,sys
31331
+ const emitContext = `python3 -c 'import json,sys,os
31338
31332
  try:
31339
31333
  d=json.loads(sys.stdin.read())
31340
31334
  if d.get("route") and d["route"] != "grep":
31341
31335
  label=d.get("top_match") or d.get("reason","")
31342
- print(json.dumps({"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":f"[kg-classifier L{d.get(\\"layer\\",\\"?\\")}|{d[\\"route\\"]}] {label[:160]}"}}))
31336
+ layer=d.get("layer","?")
31337
+ route=d["route"]
31338
+ nodes=d.get("nodes_matched",0)
31339
+ saved=(d.get("savings") or {}).get("saved_tokens_est",0)
31340
+ saved_k=round(saved/1000)
31341
+ q=os.environ.get("KG_QUERY","")[:60]
31342
+ sys.stderr.write(f"\\x1b[32m\\u2713 KG hit\\x1b[0m \\"{q}\\" \\u2192 L{layer}/{route} \\u00b7 {nodes} nodes \\u00b7 ~{saved_k}k tokens saved\\n")
31343
+ print(json.dumps({"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":f"[kg-classifier L{layer}|{route}] {label[:160]}"}}))
31343
31344
  except Exception: pass' 2>/dev/null`;
31344
31345
  return [
31345
31346
  `KG_SENTINEL=${KG_HOOK_SENTINEL}`,
31346
31347
  `CMD=$(${extractCmd})`,
31347
31348
  `case "$CMD" in *grep*|*rg\\ *|*ripgrep*|*find\\ *|*fd\\ *|*ack\\ *|*ag\\ *)`,
31348
31349
  `RESP=$(curl -s --max-time 1 -X POST -H 'Content-Type: application/json' -d "{\\"q\\":\\"$(echo \\"$CMD\\" | head -c 200 | tr '\\"' ' ')\\"}" ${url2} 2>/dev/null)`,
31349
- `echo "$RESP" | ${emitContext}`,
31350
+ `KG_QUERY="$(echo \\"$CMD\\" | head -c 60 | tr '\\"' ' ')" echo "$RESP" | ${emitContext}`,
31350
31351
  `;; esac`
31351
31352
  ].join("; ");
31352
31353
  }
@@ -32300,7 +32301,7 @@ function carryUncommittedEdits(repos, workspacePath, deps = {}) {
32300
32301
  const exec = deps.exec ?? ((cmd, args, opts) => execFileSync4(cmd, args, opts));
32301
32302
  const homedir18 = deps.homedir ?? (() => os12.homedir());
32302
32303
  const existsSync27 = deps.existsSync ?? ((p) => fs19.existsSync(p));
32303
- const copyFileSync7 = deps.copyFileSync ?? ((src, dest) => fs19.copyFileSync(src, dest));
32304
+ const copyFileSync6 = deps.copyFileSync ?? ((src, dest) => fs19.copyFileSync(src, dest));
32304
32305
  const mkdirSync18 = deps.mkdirSync ?? ((dirPath, opts) => {
32305
32306
  fs19.mkdirSync(dirPath, opts);
32306
32307
  });
@@ -32376,7 +32377,7 @@ function carryUncommittedEdits(repos, workspacePath, deps = {}) {
32376
32377
  continue;
32377
32378
  try {
32378
32379
  mkdirSync18(path21.dirname(dest), { recursive: true });
32379
- copyFileSync7(src, dest);
32380
+ copyFileSync6(src, dest);
32380
32381
  } catch (err) {
32381
32382
  const msg = err instanceof Error ? err.message : String(err);
32382
32383
  console.warn(`[carry] ${plan.name}: copy untracked ${rel} failed: ${msg}`);
@@ -32403,123 +32404,96 @@ function formatBaselineSummary(result) {
32403
32404
  // ../core/dist/world/context-injection.js
32404
32405
  import * as fs20 from "node:fs";
32405
32406
  import * as path22 from "node:path";
32406
- import { fileURLToPath as fileURLToPath2 } from "node:url";
32407
- var TEMPLATES_DIR = fileURLToPath2(new URL("./templates", import.meta.url));
32407
+
32408
+ // ../core/dist/world/templates/_generated.js
32409
+ var GH_PR_CREATE = '# Creating PRs from inside an Olam world\n\n## The problem\n\nCalling `gh pr create` directly inside an Olam container **hangs forever** on a\nTTY confirmation prompt that the agent cannot answer. This is a known, reproduced issue.\n\n## The fix\n\nAlways use this exact pattern:\n\n```bash\necho y | timeout 30 gh pr create \\\n --base main \\\n --head <branch> \\\n --title "<title>" \\\n --body-file /tmp/pr-body.md\n```\n\n## Why each part matters\n\n- **`echo y |`** \u2014 answers the "Submit?" confirmation prompt up front so `gh` never pauses.\n- **`timeout 30`** \u2014 hard deadline. If `gh` hangs anyway, the command exits non-zero so the\n agent can react instead of stalling the world indefinitely.\n- **`--body-file /tmp/pr-body.md`** \u2014 write the PR body to a temp file first. Avoids\n shell-escaping bugs that occur with `--body "..."` for long or multi-line bodies.\n- **`--head` and `--base` explicitly** \u2014 removes all interactive prompts; `gh` has nothing to ask.\n\n## Troubleshooting\n\n**"no commits" or "branch not found" error:**\n\n```bash\ngit push -u origin <branch>\n# then retry:\necho y | timeout 30 gh pr create --base main --head <branch> --title "..." --body-file /tmp/pr-body.md\n```\n\n**Timeout exceeded (command exits 124):**\n\n- Check if the branch was pushed: `git log origin/<branch> --oneline -1`\n- Check GitHub CLI auth: `gh auth status`\n- Retry once; if it hangs again, open the PR via the GitHub web UI.\n';
32410
+ var LANE_ORCHESTRATION = '# Lane Orchestration\n\nOlam worlds support parallel work via **lanes** \u2014 independent Claude Code sessions\nrunning in isolated git worktrees inside the same container.\n\nUse lanes when a task has 2+ independent scopes that can work simultaneously\nwithout file conflicts.\n\n## Container API\n\nBase URL: `http://localhost:8080`\n\n### Create a lane\n\n```bash\ncurl -s -X POST \\\n -H \'Content-Type: application/json\' \\\n -d \'{"name":"auth-fix","task":"Fix auth bugs","scope":["src/auth/**"],"avoids":["tests/**"]}\' \\\n http://localhost:8080/lanes/create\n```\n\n### List lanes\n\n```bash\ncurl -s http://localhost:8080/lanes\n```\n\n### Dispatch a prompt to a lane\n\n```bash\ncurl -s -X POST \\\n -H \'Content-Type: application/json\' \\\n -d \'{"prompt":"Start by reviewing..."}\' \\\n http://localhost:8080/lanes/auth-fix/dispatch\n```\n\n### Check lane status\n\n```bash\ncurl -s http://localhost:8080/lanes/auth-fix\n```\n\n### Merge a lane\n\n```bash\ncurl -s -X POST http://localhost:8080/lanes/auth-fix/merge\n```\n\n### Destroy a lane\n\n```bash\ncurl -s -X DELETE http://localhost:8080/lanes/auth-fix\n```\n\n## When to use lanes\n\n- Task has 2+ independent scopes (different directories/modules)\n- Work can be done simultaneously without file conflicts\n- Estimated effort > ~30 tool calls per scope\n\n## When NOT to use lanes\n\n- Simple tasks touching few files\n- Sequential work where each step depends on the previous\n- Everything is in one directory\n\n## Orchestration workflow\n\n1. Analyze the task \u2014 identify independent work streams\n2. Create lanes with non-overlapping scope globs\n3. Dispatch initial prompts to each lane\n4. **Monitor progress** \u2014 poll lane status every 30 seconds:\n ```bash\n while true; do\n STATUS=$(curl -s http://localhost:8080/lanes)\n RUNNING=$(echo "$STATUS" | jq \'[.lanes[] | select(.status == "running")] | length\')\n echo "Running lanes: $RUNNING"\n if [ "$RUNNING" = "0" ]; then echo "All lanes complete!"; break; fi\n sleep 30\n done\n ```\n5. **When a lane completes** (status is "completed" or no longer "running"):\n - Check its output: `curl -s http://localhost:8080/lanes/<name>`\n - If satisfactory, merge it: `curl -s -X POST http://localhost:8080/lanes/<name>/merge`\n - The merge automatically triggers a Codex adversarial review\n6. **After ALL lanes are merged**:\n - Run tests: `npm test` or equivalent\n - Review combined changes: `git diff main...HEAD --stat`\n - Create a PR: see `~/.olam/docs/gh-pr-create.md`\n7. Clean up: `curl -s -X DELETE http://localhost:8080/lanes/<name>`\n\n## Act on completion immediately\n\nWhen all lanes are done, immediately start merging and creating the PR.\nDo not wait for the user to tell you to merge. The workflow is:\n**lanes complete \u2192 merge each \u2192 run tests \u2192 create PR \u2192 report to user.**\n';
32411
+ var WORLD_CLAUDE_MD = '# Olam World: {{worldName}}\n\n{{taskBlock}}\n\n## Environment\n- World ID: {{worldId}}\n- Branch: {{branch}}{{servicesLine}}{{pleriPlaneLine}}\n\n## Repos in this World\n{{reposList}}\n\n{{planFileBlock}}## Instructions\n1. Work on the task described above\n2. Write tests first (TDD)\n3. Commit frequently to the world branch\n4. When done, push and the work will be available for review\n5. Your `AskUserQuestion` calls are intercepted and surfaced to the operator\'s host CP automatically. The operator answers in the SPA; claude resumes when keys are injected.\n\n## Sandbox & network \u2014 what is actually true\n\nThe olam-devbox container has FULL outbound internet. Confirm any time:\n\n```bash\ncurl -sI https://registry.npmjs.org/ # \u2192 HTTP 200\ncurl -sI https://github.com/ # \u2192 HTTP 200\n```\n\nRules:\n\n- **Never claim "the sandbox blocks X" without proving it.** Capture the actual `npm install` / `curl` error verbatim. A flaky postinstall is not a network block.\n- **Retry transient failures at least once** before falling back to a manual workaround. `npm install <pkg>` flakes happen; one retry catches >90%.\n- **If you genuinely need a workaround**, say so explicitly with the error message and the retry count, NOT "sandbox network blocked it." Truthful attribution = trustable telemetry.\n- **Playwright `chromium` downloads ~140 MB** from playwright.azureedge.net. Disk-space failures (not network) are the usual cause. Check `df -h` before blaming the network.\n\n## Halt semantics \u2014 when to stop and surface\n\nStop and call `AskUserQuestion` (which surfaces in the host CP) when you hit:\n\n- **Halt-N (new pattern)**: a new dep family, framework, or architectural pattern not in the existing codebase. Don\'t silently adopt it \u2014 surface the choice.\n- **Halt-B (business deal-breaker)**: a breaking constraint that makes the task\'s goal unsatisfiable as stated. Surface the conflict; let the operator re-scope.\n- **Halt-C (complexity blowup)**: the diff is exceeding 1.5x of what the task implied. Surface for re-scoping before continuing.\n\nDon\'t halt on every uncertainty \u2014 only on these three classes. Style/naming/error-handling choices are non-load-bearing; pick the simplest option that works and continue.\n\n## Lane Orchestration (advanced \u2014 read on demand)\n\nThis world supports parallel work lanes via the container API at http://localhost:8080.\nUse only when the task has 2+ independent scopes that can run simultaneously.\n\n**Full lane API + workflow**: see `~/.olam/docs/lane-orchestration.md` (mounted into the world).\n\n## Creating PRs from inside this world\n\n`gh pr create` hangs on a TTY prompt inside the container. Always use:\n\n```bash\necho y | timeout 30 gh pr create --base main --head <branch> --title "..." --body-file /tmp/pr-body.md\n```\n\n**Why + full troubleshooting**: see `~/.olam/docs/gh-pr-create.md`.\n\n## Available Skills & Plugins\n\nYour Claude Code session includes the invoker\'s plugins, rules, and skills.\nThese are copied from the host at world creation time.\nUse `/codex:review` or `/codex:adversarial-review` for code reviews.\nUse `/codex:rescue` to delegate tasks to Codex.\n{{extraContextBlock}}';
32412
+
32413
+ // ../core/dist/world/context-injection.js
32408
32414
  function injectWorldContext(opts) {
32409
- const { world, task, linearTicketId, claudeMdExtra, taskContext, services, pleriPlaneUrl } = opts;
32415
+ const { world } = opts;
32410
32416
  const claudeDir = path22.join(world.workspacePath, ".claude");
32411
32417
  fs20.mkdirSync(claudeDir, { recursive: true });
32412
- const sections = [];
32413
- sections.push(`# Olam World: ${world.name}`);
32414
- sections.push("");
32418
+ const content = WORLD_CLAUDE_MD.replace("{{worldName}}", world.name).replace("{{worldId}}", world.id).replace("{{branch}}", world.branch).replace("{{taskBlock}}", buildTaskBlock(opts)).replace("{{reposList}}", buildReposList(world)).replace("{{servicesLine}}", buildServicesLine(opts.services)).replace("{{pleriPlaneLine}}", buildPleriPlaneLine(opts.pleriPlaneUrl)).replace("{{planFileBlock}}", buildPlanFileBlock(world)).replace("{{extraContextBlock}}", buildExtraContextBlock(opts.claudeMdExtra));
32419
+ fs20.writeFileSync(path22.join(claudeDir, "CLAUDE.md"), content);
32420
+ writeOlamDocs(world.workspacePath);
32421
+ }
32422
+ function buildTaskBlock(opts) {
32423
+ const { task, linearTicketId, taskContext, world } = opts;
32415
32424
  if (taskContext) {
32416
- sections.push("## Task");
32417
- sections.push(`**Source:** ${formatTaskSource(taskContext)}`);
32418
- if (taskContext.title) {
32419
- sections.push(`**Title:** ${taskContext.title}`);
32420
- }
32421
- sections.push(`**Branch:** ${world.branch}`);
32422
- sections.push("");
32425
+ const lines = ["## Task"];
32426
+ lines.push(`**Source:** ${formatTaskSource(taskContext)}`);
32427
+ if (taskContext.title)
32428
+ lines.push(`**Title:** ${taskContext.title}`);
32429
+ lines.push(`**Branch:** ${world.branch}`);
32430
+ lines.push("");
32423
32431
  if (taskContext.description) {
32424
- sections.push("### Description");
32425
- sections.push(taskContext.description);
32426
- sections.push("");
32432
+ lines.push("### Description", taskContext.description, "");
32427
32433
  }
32428
32434
  if (taskContext.acceptanceCriteria) {
32429
- sections.push("### Acceptance Criteria");
32430
- sections.push(taskContext.acceptanceCriteria);
32431
- sections.push("");
32435
+ lines.push("### Acceptance Criteria", taskContext.acceptanceCriteria, "");
32432
32436
  }
32433
32437
  if (taskContext.labels && taskContext.labels.length > 0) {
32434
- sections.push(`### Labels`);
32435
- sections.push(taskContext.labels.join(", "));
32436
- sections.push("");
32438
+ lines.push("### Labels", taskContext.labels.join(", "), "");
32437
32439
  }
32438
32440
  if (taskContext.assignee) {
32439
- sections.push(`### Assignee`);
32440
- sections.push(taskContext.assignee);
32441
- sections.push("");
32442
- }
32443
- } else if (task) {
32444
- sections.push("## Task");
32445
- sections.push(task);
32446
- sections.push("");
32447
- }
32448
- if (linearTicketId && !taskContext) {
32449
- sections.push("## Linear Ticket");
32450
- sections.push(`- ID: ${linearTicketId}`);
32451
- sections.push("- (Fetch full details with Linear MCP tools)");
32452
- sections.push("");
32453
- }
32454
- sections.push("## Environment");
32455
- sections.push(`- World ID: ${world.id}`);
32456
- sections.push(`- Branch: ${world.branch}`);
32457
- if (services && services.length > 0) {
32458
- const svcList = services.map((s) => `${s.name} (${s.port})`).join(", ");
32459
- sections.push(`- Services: ${svcList}`);
32460
- }
32461
- if (pleriPlaneUrl) {
32462
- sections.push(`- Pleri Plane: ${pleriPlaneUrl}`);
32463
- }
32464
- sections.push("");
32465
- sections.push("## Repos in this World");
32466
- for (const repoName of world.repos) {
32467
- sections.push(`- \`${repoName}\` \u2192 \`/home/olam/workspace/${repoName}\``);
32468
- }
32469
- sections.push("");
32470
- if (hasPlanFile(world)) {
32471
- sections.push("## Plan");
32472
- sections.push("A plan file has been provided at `docs/plans/`. Review it before starting work.");
32473
- sections.push("");
32474
- }
32475
- sections.push("## Instructions");
32476
- sections.push("1. Work on the task described above");
32477
- sections.push("2. Write tests first (TDD)");
32478
- sections.push("3. Commit frequently to the world branch");
32479
- sections.push("4. When done, push and the work will be available for review");
32480
- sections.push("5. Your `AskUserQuestion` calls are intercepted and surfaced to the operator's host CP automatically. The operator answers in the SPA; claude resumes when keys are injected.");
32481
- sections.push("");
32482
- sections.push("## Lane Orchestration (advanced \u2014 read on demand)");
32483
- sections.push("");
32484
- sections.push("This world supports parallel work lanes via the container API at http://localhost:8080.");
32485
- sections.push("Use only when the task has 2+ independent scopes that can run simultaneously.");
32486
- sections.push("");
32487
- sections.push("**Full lane API + workflow**: see `~/.olam/docs/lane-orchestration.md` (mounted into the world).");
32488
- sections.push("");
32489
- sections.push("## Creating PRs from inside this world");
32490
- sections.push("");
32491
- sections.push("`gh pr create` hangs on a TTY prompt inside the container. Always use:");
32492
- sections.push("");
32493
- sections.push("```bash");
32494
- sections.push('echo y | timeout 30 gh pr create --base main --head <branch> --title "..." --body-file /tmp/pr-body.md');
32495
- sections.push("```");
32496
- sections.push("");
32497
- sections.push("**Why + full troubleshooting**: see `~/.olam/docs/gh-pr-create.md`.");
32498
- sections.push("");
32499
- sections.push("## Available Skills & Plugins");
32500
- sections.push("");
32501
- sections.push("Your Claude Code session includes the invoker's plugins, rules, and skills.");
32502
- sections.push("These are copied from the host at world creation time.");
32503
- sections.push("Use `/codex:review` or `/codex:adversarial-review` for code reviews.");
32504
- sections.push("Use `/codex:rescue` to delegate tasks to Codex.");
32505
- sections.push("");
32506
- if (claudeMdExtra) {
32507
- sections.push("## Additional Context");
32508
- sections.push(claudeMdExtra);
32509
- sections.push("");
32510
- }
32511
- const content = sections.join("\n");
32512
- fs20.writeFileSync(path22.join(claudeDir, "CLAUDE.md"), content);
32513
- writeOlamDocs(world.workspacePath);
32441
+ lines.push("### Assignee", taskContext.assignee, "");
32442
+ }
32443
+ return lines.join("\n").trimEnd();
32444
+ }
32445
+ if (task) {
32446
+ return `## Task
32447
+ ${task}`;
32448
+ }
32449
+ if (linearTicketId) {
32450
+ return [
32451
+ "## Linear Ticket",
32452
+ `- ID: ${linearTicketId}`,
32453
+ "- (Fetch full details with Linear MCP tools)"
32454
+ ].join("\n");
32455
+ }
32456
+ return "## Task\n_(no task description provided)_";
32457
+ }
32458
+ function buildReposList(world) {
32459
+ return world.repos.map((repoName) => `- \`${repoName}\` \u2192 \`/home/olam/workspace/${repoName}\``).join("\n");
32460
+ }
32461
+ function buildServicesLine(services) {
32462
+ if (!services || services.length === 0)
32463
+ return "";
32464
+ const svcList = services.map((s) => `${s.name} (${s.port})`).join(", ");
32465
+ return `
32466
+ - Services: ${svcList}`;
32467
+ }
32468
+ function buildPleriPlaneLine(pleriPlaneUrl) {
32469
+ if (!pleriPlaneUrl)
32470
+ return "";
32471
+ return `
32472
+ - Pleri Plane: ${pleriPlaneUrl}`;
32473
+ }
32474
+ function buildPlanFileBlock(world) {
32475
+ if (!hasPlanFile(world))
32476
+ return "";
32477
+ return [
32478
+ "## Plan",
32479
+ "A plan file has been provided at `docs/plans/`. Review it before starting work.",
32480
+ "",
32481
+ ""
32482
+ ].join("\n");
32483
+ }
32484
+ function buildExtraContextBlock(extra) {
32485
+ if (!extra)
32486
+ return "";
32487
+ return `
32488
+
32489
+ ## Additional Context
32490
+ ${extra}`;
32514
32491
  }
32515
32492
  function writeOlamDocs(workspacePath) {
32516
32493
  const docsDir = path22.join(workspacePath, ".olam", "docs");
32517
32494
  fs20.mkdirSync(docsDir, { recursive: true });
32518
- for (const filename of ["lane-orchestration.md", "gh-pr-create.md"]) {
32519
- const src = path22.join(TEMPLATES_DIR, filename);
32520
- const dest = path22.join(docsDir, filename);
32521
- fs20.copyFileSync(src, dest);
32522
- }
32495
+ fs20.writeFileSync(path22.join(docsDir, "gh-pr-create.md"), GH_PR_CREATE);
32496
+ fs20.writeFileSync(path22.join(docsDir, "lane-orchestration.md"), LANE_ORCHESTRATION);
32523
32497
  }
32524
32498
  function formatTaskSource(ctx) {
32525
32499
  if (ctx.source === "linear" && ctx.ticketId) {
@@ -34134,8 +34108,7 @@ ${stderr.split("\n").slice(0, 3).join(" ")}`);
34134
34108
  if (!olamUserPresent) {
34135
34109
  const imageName = (() => {
34136
34110
  try {
34137
- const { execSync: execSync7 } = __require("node:child_process");
34138
- return execSync7(`docker inspect ${containerName} --format '{{.Config.Image}}'`, {
34111
+ return execSync5(`docker inspect ${containerName} --format '{{.Config.Image}}'`, {
34139
34112
  encoding: "utf8",
34140
34113
  timeout: 5e3
34141
34114
  }).trim() || "(unknown)";
@@ -35848,7 +35821,7 @@ import * as http2 from "node:http";
35848
35821
  import * as http from "node:http";
35849
35822
  import * as fs26 from "node:fs";
35850
35823
  import * as path29 from "node:path";
35851
- import { fileURLToPath as fileURLToPath3 } from "node:url";
35824
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
35852
35825
 
35853
35826
  // ../core/dist/dashboard/serialize.js
35854
35827
  function serializeTokenUsage(usage) {
@@ -36353,7 +36326,7 @@ function findSessionInWorld(registry2, sessionId) {
36353
36326
  }
36354
36327
  function createDashboardServer(opts) {
36355
36328
  const { port: port2, registry: registry2 } = opts;
36356
- const thisDir = path29.dirname(fileURLToPath3(import.meta.url));
36329
+ const thisDir = path29.dirname(fileURLToPath2(import.meta.url));
36357
36330
  const defaultPublicDir = path29.resolve(thisDir, "../../../control-plane/public");
36358
36331
  const publicDir = opts.publicDir ?? defaultPublicDir;
36359
36332
  let hasPublicDir = fs26.existsSync(publicDir);
@@ -24,7 +24,7 @@
24
24
  # Tear down: `docker compose -f packages/host-cp/compose.yaml down`
25
25
 
26
26
  services:
27
- host-cp:
27
+ olam-host-cp:
28
28
  container_name: olam-host-cp
29
29
  # Image-only — operator's `olam bootstrap` pulls the digest-pinned
30
30
  # `ghcr.io/pleri/olam-host-cp:latest` (digest from image-digests.json)
@@ -1847,6 +1847,20 @@ const server = http.createServer(async (req, res) => {
1847
1847
  return jsonReply(res, 200, { ok: true });
1848
1848
  }
1849
1849
 
1850
+ // GET /api/plan/conversations/:id/sidebar — list signals for a conversation.
1851
+ // Phase C C5 addition: lets the new /session/:worldId/plan SPA fetch the
1852
+ // signals state for SidebarBubble rendering. The existing dismiss/use POST
1853
+ // routes (above) already handle mutations; this GET fills the read-side gap
1854
+ // that PR #223's SSE-based UI didn't need (signals streamed via SSE).
1855
+ const planSidebarListMatch = /^\/api\/plan\/conversations\/([^/?#]+)\/sidebar$/.exec(url.pathname);
1856
+ if (planSidebarListMatch && req.method === 'GET') {
1857
+ if (!await requirePlanCredential(res)) return;
1858
+ const conversationId = decodeURIComponent(planSidebarListMatch[1]);
1859
+ const chunkIdParam = url.searchParams.get('chunk_id') ?? undefined;
1860
+ const signals = planOrchestrator.listSignals(conversationId, chunkIdParam);
1861
+ return jsonReply(res, 200, { signals });
1862
+ }
1863
+
1850
1864
  // GET /api/worlds/:id/processes
1851
1865
  // GET /api/worlds/:id/processes/stream — SSE fanout (5s cadence, per-world)
1852
1866
  // Handler: routes/process-port.mjs → handleListProcesses
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ensure-iii-engine.mjs — install (or verify) the iii-engine v0.11.2
4
+ * binary that the agentmemory CLI depends on.
5
+ *
6
+ * Why this exists:
7
+ * `agentmemory` (npm-installed) launches the iii-engine as a subprocess
8
+ * but does NOT bundle the engine binary — it expects a system-installed
9
+ * `iii` on PATH. Upstream's documented install path A is a `curl`
10
+ * recipe that drops the binary into `~/.local/bin`. We do the
11
+ * equivalent into `~/.olam/bin/iii` so the engine lives under olam's
12
+ * namespace + never collides with an operator's own iii install
13
+ * (OQ9 resolved pass 3).
14
+ *
15
+ * Pinned to iii v0.11.2 exactly. Upstream agentmemory's
16
+ * `docker-compose.yml` documents that v0.11.6+ breaks with
17
+ * "EPIPE reconnect loops and empty search after save" — bumping is
18
+ * not safe until agentmemory is refactored for the new sandbox-worker
19
+ * model.
20
+ *
21
+ * SHA256 integrity: each tarball is verified against a hash pinned
22
+ * in this file before extract. Mismatch → exit 1 (defends against
23
+ * github.com/iii-hq/iii release tampering — T9).
24
+ *
25
+ * Hashes captured 2026-05-12 from `.sha256` sidecars at
26
+ * https://github.com/iii-hq/iii/releases/tag/iii%2Fv0.11.2
27
+ *
28
+ * Plan reference: docs/plans/olam-agent-memory-distributed/phase-a-tasks.md A1
29
+ * Research: docs/research/agent-memory-distributed/REPORT.md Q1
30
+ */
31
+
32
+ import { existsSync, mkdirSync, writeFileSync, unlinkSync, chmodSync, statSync } from 'node:fs';
33
+ import { homedir, platform, arch } from 'node:os';
34
+ import { join } from 'node:path';
35
+ import { createHash } from 'node:crypto';
36
+ import { execSync } from 'node:child_process';
37
+
38
+ export const III_VERSION = '0.11.2';
39
+
40
+ /**
41
+ * Map of {process.platform-process.arch} → upstream Rust target triple.
42
+ * Only host platforms olam supports for local mode. Cloud-mode containers
43
+ * (Phase C Dockerfile) handle linux-only via TARGETARCH ARG separately.
44
+ */
45
+ export const PLATFORM_ARCH_TO_TRIPLE = Object.freeze({
46
+ 'darwin-arm64': 'aarch64-apple-darwin',
47
+ 'darwin-x64': 'x86_64-apple-darwin',
48
+ 'linux-arm64': 'aarch64-unknown-linux-gnu',
49
+ 'linux-x64': 'x86_64-unknown-linux-gnu',
50
+ });
51
+
52
+ /**
53
+ * SHA256 hashes of the upstream iii v0.11.2 release tarballs.
54
+ * Captured from `.sha256` sidecars on the iii-hq/iii GitHub release.
55
+ * If these ever fail to match after `iii v0.11.2` is published, upstream
56
+ * has tampered with the release — refuse to install + open an issue.
57
+ */
58
+ export const SHA256 = Object.freeze({
59
+ 'aarch64-apple-darwin': 'e7834c44fefb2b5343d327102a941419245f7fff447f95373857a04b033fb1bd',
60
+ 'x86_64-apple-darwin': '2b67e5f18833c415f4cb16a9e13b0e953555e0ca138682bf24894abe8b80b836',
61
+ 'aarch64-unknown-linux-gnu': 'e0d35ee54a6b6c8a46576ab661e1711d11eb4dafeb1be8e1dbd1cc0ccb48b615',
62
+ 'x86_64-unknown-linux-gnu': '9c83c47788b4ef4beeb65dd9bf37e94f993770cd3db874464c3ce1cdc92352cd',
63
+ });
64
+
65
+ export function detectTriple(plat = platform(), cpuArch = arch()) {
66
+ const key = `${plat}-${cpuArch}`;
67
+ const triple = PLATFORM_ARCH_TO_TRIPLE[key];
68
+ if (!triple) {
69
+ const supported = Object.keys(PLATFORM_ARCH_TO_TRIPLE).join(', ');
70
+ throw new Error(
71
+ `Unsupported host platform/arch combo: ${key}. ` +
72
+ `iii v${III_VERSION} ships for: ${supported}. ` +
73
+ `Run agentmemory directly via 'npx @agentmemory/agentmemory' if your platform is missing.`,
74
+ );
75
+ }
76
+ return triple;
77
+ }
78
+
79
+ export function urlForTriple(triple) {
80
+ return `https://github.com/iii-hq/iii/releases/download/iii/v${III_VERSION}/iii-${triple}.tar.gz`;
81
+ }
82
+
83
+ /**
84
+ * Idempotent install + verify. Returns { ok: true, path, cached } on
85
+ * success; throws on failure (caller decides exit code).
86
+ *
87
+ * deps is for testability:
88
+ * - fetch — substitutable in tests (default: global fetch)
89
+ * - logStream — process.stderr in prod; a fake in tests
90
+ */
91
+ export async function ensureIiiEngine({
92
+ olamHome = join(homedir(), '.olam'),
93
+ triple = detectTriple(),
94
+ fetchImpl = globalThis.fetch,
95
+ logStream = process.stderr,
96
+ } = {}) {
97
+ const binDir = join(olamHome, 'bin');
98
+ const binPath = join(binDir, 'iii');
99
+
100
+ // Cached?
101
+ if (existsSync(binPath)) {
102
+ try {
103
+ const out = execSync(`${binPath} --version`, { encoding: 'utf8', timeout: 5_000 });
104
+ if (out.includes(III_VERSION)) {
105
+ return { ok: true, path: binPath, cached: true };
106
+ }
107
+ logStream.write(`iii at ${binPath} reports unexpected version: ${out.trim()}; re-installing\n`);
108
+ } catch (err) {
109
+ logStream.write(`iii at ${binPath} not executable (${err.message}); re-installing\n`);
110
+ }
111
+ }
112
+
113
+ const expectedSha = SHA256[triple];
114
+ if (!expectedSha) {
115
+ throw new Error(`No SHA256 pinned for triple ${triple} — update ensure-iii-engine.mjs.`);
116
+ }
117
+
118
+ const url = urlForTriple(triple);
119
+ logStream.write(`Downloading iii v${III_VERSION} (${triple}) from ${url}\n`);
120
+
121
+ const response = await fetchImpl(url);
122
+ if (!response.ok) {
123
+ throw new Error(`Failed to download iii v${III_VERSION} (${triple}): HTTP ${response.status}`);
124
+ }
125
+
126
+ const buf = Buffer.from(await response.arrayBuffer());
127
+ const actualSha = createHash('sha256').update(buf).digest('hex');
128
+
129
+ if (actualSha !== expectedSha) {
130
+ throw new Error(
131
+ `iii v${III_VERSION} tarball SHA256 mismatch for ${triple}:\n` +
132
+ ` expected: ${expectedSha}\n` +
133
+ ` got: ${actualSha}\n` +
134
+ `If this is a legitimate upstream change, update SHA256 in ${import.meta.url} ` +
135
+ `and rotate the pin (see plan T9).`,
136
+ );
137
+ }
138
+
139
+ mkdirSync(binDir, { recursive: true });
140
+
141
+ // Extract via system tar — saves bundling node-tar
142
+ const tarPath = join(binDir, '.iii.tar.gz');
143
+ writeFileSync(tarPath, buf);
144
+ try {
145
+ execSync(`tar -xzf '${tarPath}' -C '${binDir}'`, { stdio: 'pipe' });
146
+ } finally {
147
+ if (existsSync(tarPath)) unlinkSync(tarPath);
148
+ }
149
+
150
+ if (!existsSync(binPath)) {
151
+ throw new Error(`tar extract succeeded but iii binary not found at ${binPath}`);
152
+ }
153
+ chmodSync(binPath, 0o755);
154
+
155
+ // Smoke
156
+ const ver = execSync(`${binPath} --version`, { encoding: 'utf8', timeout: 5_000 });
157
+ if (!ver.includes(III_VERSION)) {
158
+ throw new Error(`iii binary installed but reports unexpected version: ${ver.trim()}`);
159
+ }
160
+
161
+ return { ok: true, path: binPath, cached: false };
162
+ }
163
+
164
+ // CLI entry — `node ensure-iii-engine.mjs`
165
+ const isDirect = import.meta.url === `file://${process.argv[1]}` ||
166
+ process.argv[1]?.endsWith('/ensure-iii-engine.mjs');
167
+ if (isDirect) {
168
+ ensureIiiEngine()
169
+ .then((r) => {
170
+ process.stderr.write(
171
+ `iii v${III_VERSION} ready at ${r.path}${r.cached ? ' (cached)' : ''}\n`,
172
+ );
173
+ process.exit(0);
174
+ })
175
+ .catch((err) => {
176
+ process.stderr.write(`ensure-iii-engine failed: ${err.message}\n`);
177
+ process.exit(1);
178
+ });
179
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pleri/olam-cli",
3
- "version": "0.1.141",
3
+ "version": "0.1.143",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "olam": "./bin/olam.cjs"
@@ -9,6 +9,7 @@
9
9
  "bin",
10
10
  "dist",
11
11
  "host-cp",
12
+ "memory-service-bundle",
12
13
  "plugin",
13
14
  "README.md"
14
15
  ],
@@ -43,6 +44,7 @@
43
44
  "yaml": "^2.7.0",
44
45
  "zod-to-json-schema": "^3.24.0",
45
46
  "playwright-core": "~1.59.0",
46
- "@napi-rs/keyring": "^1.1.6"
47
+ "@napi-rs/keyring": "^1.1.6",
48
+ "@agentmemory/agentmemory": "0.9.6"
47
49
  }
48
50
  }