@pleri/olam-cli 0.1.166 → 0.1.168

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/README.md +4 -2
  2. package/dist/commands/bootstrap.d.ts +6 -0
  3. package/dist/commands/bootstrap.d.ts.map +1 -1
  4. package/dist/commands/bootstrap.js +15 -0
  5. package/dist/commands/bootstrap.js.map +1 -1
  6. package/dist/commands/doctor.js +4 -4
  7. package/dist/commands/doctor.js.map +1 -1
  8. package/dist/commands/init.d.ts +4 -3
  9. package/dist/commands/init.d.ts.map +1 -1
  10. package/dist/commands/init.js +103 -81
  11. package/dist/commands/init.js.map +1 -1
  12. package/dist/commands/memory-service-container.d.ts +8 -0
  13. package/dist/commands/memory-service-container.d.ts.map +1 -1
  14. package/dist/commands/memory-service-container.js +16 -1
  15. package/dist/commands/memory-service-container.js.map +1 -1
  16. package/dist/commands/plans.d.ts +3 -0
  17. package/dist/commands/plans.d.ts.map +1 -0
  18. package/dist/commands/plans.js +211 -0
  19. package/dist/commands/plans.js.map +1 -0
  20. package/dist/commands/setup.d.ts +78 -14
  21. package/dist/commands/setup.d.ts.map +1 -1
  22. package/dist/commands/setup.js +430 -42
  23. package/dist/commands/setup.js.map +1 -1
  24. package/dist/commands/skills-source.d.ts +24 -0
  25. package/dist/commands/skills-source.d.ts.map +1 -1
  26. package/dist/commands/skills-source.js +257 -18
  27. package/dist/commands/skills-source.js.map +1 -1
  28. package/dist/commands/skills.d.ts +21 -0
  29. package/dist/commands/skills.d.ts.map +1 -1
  30. package/dist/commands/skills.js +44 -0
  31. package/dist/commands/skills.js.map +1 -1
  32. package/dist/image-digests.json +8 -7
  33. package/dist/index.js +2494 -1279
  34. package/dist/index.js.map +1 -1
  35. package/dist/lib/bootstrap-kubernetes.d.ts.map +1 -1
  36. package/dist/lib/bootstrap-kubernetes.js +178 -107
  37. package/dist/lib/bootstrap-kubernetes.js.map +1 -1
  38. package/dist/lib/health-probes.d.ts +16 -0
  39. package/dist/lib/health-probes.d.ts.map +1 -1
  40. package/dist/lib/health-probes.js +49 -0
  41. package/dist/lib/health-probes.js.map +1 -1
  42. package/dist/lib/peripheral-registry.d.ts +9 -3
  43. package/dist/lib/peripheral-registry.d.ts.map +1 -1
  44. package/dist/lib/peripheral-registry.js +4 -4
  45. package/dist/lib/peripheral-registry.js.map +1 -1
  46. package/dist/lib/plans-client.d.ts +69 -0
  47. package/dist/lib/plans-client.d.ts.map +1 -0
  48. package/dist/lib/plans-client.js +137 -0
  49. package/dist/lib/plans-client.js.map +1 -0
  50. package/dist/lib/port-forward.js +1 -1
  51. package/dist/lib/port-forward.js.map +1 -1
  52. package/dist/lib/upgrade-kubernetes.d.ts +1 -1
  53. package/dist/lib/upgrade-kubernetes.d.ts.map +1 -1
  54. package/dist/lib/upgrade-kubernetes.js +35 -21
  55. package/dist/lib/upgrade-kubernetes.js.map +1 -1
  56. package/dist/mcp-server.js +1239 -343
  57. package/hermes-bundle/version.json +1 -1
  58. package/host-cp/k8s/manifests/50-deployment.yaml +1 -1
  59. package/host-cp/k8s/manifests/auth-service/50-deployment.yaml +1 -1
  60. package/host-cp/k8s/manifests/kg-service/50-deployment.yaml +1 -1
  61. package/host-cp/k8s/manifests/mcp-auth-service/50-deployment.yaml +1 -1
  62. package/host-cp/k8s/manifests/memory-service/50-deployment.yaml +1 -1
  63. package/host-cp/src/halt-detect.mjs +43 -0
  64. package/host-cp/src/panic-counter.mjs +94 -0
  65. package/host-cp/src/plan-chat-service.mjs +12 -1
  66. package/host-cp/src/server.mjs +75 -0
  67. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
1
  {
2
- "bundledAt": "2026-05-22T11:04:18.339Z",
2
+ "bundledAt": "2026-05-23T09:39:55.721Z",
3
3
  "kgFirstSha": "29a9ccce1b115d049e375c4a90eb5cf7c123e610e2d0590270a4db2cdbc64a28"
4
4
  }
@@ -111,7 +111,7 @@ spec:
111
111
  # k3d), started by `olam upgrade` Step 0.7 — not inside this Pod.
112
112
  containers:
113
113
  - name: olam-host-cp
114
- image: ghcr.io/pleri/olam-host-cp@sha256:7a49b44546d9b69c5a7448613130a43319e90e06a2999d688101657d7d851dda
114
+ image: ghcr.io/pleri/olam-host-cp@sha256:766e07263fcf7e765c3689a7b8d40c47754b4ab90c697710843265a7fc84969a
115
115
  imagePullPolicy: IfNotPresent
116
116
  securityContext:
117
117
  runAsNonRoot: true
@@ -70,7 +70,7 @@ spec:
70
70
  mountPath: /data
71
71
  containers:
72
72
  - name: olam-auth-service
73
- image: ghcr.io/pleri/olam-auth@sha256:d41a940bc9eb7016aeecc1c653e057d63d32d33c1e694d298b5340711d3d0bd8
73
+ image: ghcr.io/pleri/olam-auth@sha256:c6d163f7ac5fe1ca4652ed34afb1d8555c6f61d06398db767db65fee0944b209
74
74
  imagePullPolicy: IfNotPresent
75
75
  securityContext:
76
76
  runAsNonRoot: true
@@ -61,7 +61,7 @@ spec:
61
61
  mountPath: /data
62
62
  containers:
63
63
  - name: olam-kg-service
64
- image: ghcr.io/pleri/olam-kg-service@sha256:b9a96be3cad11f298286d011a88309ac2e495074970bf4d860c032709a5ab72f
64
+ image: ghcr.io/pleri/olam-kg-service@sha256:77fd9b19d87c6f4cba4d33d76ff476dd7677f78725f3bf75a9076009e17355cc
65
65
  imagePullPolicy: IfNotPresent
66
66
  securityContext:
67
67
  runAsNonRoot: true
@@ -68,7 +68,7 @@ spec:
68
68
  mountPath: /data
69
69
  containers:
70
70
  - name: olam-mcp-auth-service
71
- image: ghcr.io/pleri/olam-mcp-auth@sha256:0322f65701dfda84a2d0672071914fd7276927772ccccf6c5f55c4c3617cd8fe
71
+ image: ghcr.io/pleri/olam-mcp-auth@sha256:cb5b1d7caece5bca4a4723eb20522a748cd48001aa94a7e7ec106d29bd2142b0
72
72
  imagePullPolicy: IfNotPresent
73
73
  securityContext:
74
74
  runAsNonRoot: true
@@ -70,7 +70,7 @@ spec:
70
70
  # bootstrap-placeholder comment + run `npm run refresh:manifest-digests`
71
71
  # once ghcr.io/pleri/olam-memory-service has a real published digest.
72
72
  # bootstrap-placeholder: pre-publish; refresh after first release
73
- image: ghcr.io/pleri/olam-memory-service@sha256:70ae9c81efe07d8105c109aea970105709fc4daa50b0d688aa5d299a39a8b24a
73
+ image: ghcr.io/pleri/olam-memory-service@sha256:38b2c1f36e49183f5d36999c6519533a8402f3e784109ede8ad7f6e1a205c195
74
74
  imagePullPolicy: IfNotPresent
75
75
  securityContext:
76
76
  runAsNonRoot: true
@@ -0,0 +1,43 @@
1
+ // W4 — Halt-shape detection for the host-cp chunk-write proxy.
2
+ //
3
+ // When plan-DO's dispatchPlanningAgent (W1) trips a guardrail, it
4
+ // emits a chunk with chunk_type='goal_mode_assumption' and content
5
+ // matching: `[assumption: <cap>-tripped — spent $X.XXXX of $Y]` (or
6
+ // similar shape per GuardrailState.haltChunkText()).
7
+ //
8
+ // host-cp's /api/plan-chat proxy passes the chunk through to the
9
+ // chunks substrate AND, if it detects a halt-shaped chunk, broadcasts
10
+ // a typed `plan.halted` event on host-stream so the SPA's
11
+ // PlanHaltBanner subscriber fires.
12
+ //
13
+ // Extracted as a pure fn so it can be unit-tested without booting
14
+ // the host-cp server.
15
+
16
+ const HALT_RE =
17
+ /^\[assumption:\s*(usd|turns|tool_calls|wall_clock)-tripped(?:\s*—\s*spent\s*\$([0-9.]+))?/;
18
+
19
+ /**
20
+ * Detect a halt-shaped chunk + extract its components.
21
+ *
22
+ * Returns null when:
23
+ * - chunk is null/undefined
24
+ * - chunk_type isn't 'goal_mode_assumption'
25
+ * - content doesn't match the halt regex
26
+ *
27
+ * Returns the parsed payload otherwise. Caller broadcasts this as
28
+ * the `plan.halted` event payload.
29
+ */
30
+ export function detectHaltChunk(chunk) {
31
+ if (!chunk || typeof chunk !== 'object') return null;
32
+ if (chunk.chunk_type !== 'goal_mode_assumption') return null;
33
+ if (typeof chunk.chunk !== 'string') return null;
34
+ const m = chunk.chunk.match(HALT_RE);
35
+ if (!m) return null;
36
+ return {
37
+ plan_id: chunk.session_id ?? 'unknown',
38
+ operator_id: chunk.operator_id ?? 'unknown',
39
+ halt_reason: m[1],
40
+ usd_spent_so_far: m[2] ? Number.parseFloat(m[2]) : undefined,
41
+ halted_at: Date.now(),
42
+ };
43
+ }
@@ -0,0 +1,94 @@
1
+ // C4 — macOS panic-log counter.
2
+ //
3
+ // Tracker drift note: phase-c-tasks.md lists path as
4
+ // packages/control-plane/app/src/lib/panic-counter.ts (a browser SPA
5
+ // surface). The SPA can't shell out — `child_process` is Node-only.
6
+ // Real home is host-cp (Node) which already brokers operator-machine
7
+ // state through host-stream. host-cp exposes a typed event consumers
8
+ // can subscribe to.
9
+ //
10
+ // Implementation:
11
+ // `log show --predicate 'eventMessage CONTAINS "panic"' --last <N>d`
12
+ // pipes to stdout; we count newlines (each panic event = 1 line).
13
+ //
14
+ // Platform guard:
15
+ // On non-darwin platforms, getPanicCount returns null + emits a
16
+ // `[panic-counter]` warning to stderr. Callers branch on null →
17
+ // skip the delta + don't emit the Slack message.
18
+ //
19
+ // Sampling cadence:
20
+ // Baseline: at olam-cli startup OR on first /plan/new visit
21
+ // Per-session: at plan completion (cloud-mode only)
22
+ //
23
+ // Cost note:
24
+ // `log show` is expensive (~200ms-2s depending on system log size).
25
+ // Cache the baseline + only re-sample on demand. Don't poll.
26
+
27
+ import { execFile } from 'node:child_process';
28
+ import { promisify } from 'node:util';
29
+ import { platform } from 'node:os';
30
+
31
+ const execFileP = promisify(execFile);
32
+
33
+ const PANIC_PREDICATE = 'eventMessage CONTAINS "panic"';
34
+ const DEFAULT_TIMEOUT_MS = 30_000;
35
+
36
+ /**
37
+ * Return the count of `panic`-containing log entries over the last N
38
+ * days. Returns null on non-darwin platforms OR on `log` command
39
+ * failure (caller treats null as "no signal; skip the delta").
40
+ */
41
+ export async function getPanicCount(last_n_days = 7, opts = {}) {
42
+ if (platform() !== 'darwin') {
43
+ if (!opts.silent) {
44
+ process.stderr.write(
45
+ `[panic-counter] platform=${platform()} is not darwin; returning null\n`,
46
+ );
47
+ }
48
+ return null;
49
+ }
50
+ const execImpl = opts.execFileFn ?? execFileP;
51
+ try {
52
+ const { stdout } = await execImpl(
53
+ 'log',
54
+ ['show', '--predicate', PANIC_PREDICATE, '--last', `${last_n_days}d`],
55
+ { timeout: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS, maxBuffer: 10 * 1024 * 1024 },
56
+ );
57
+ // `log show` prepends a header + may emit an "is empty" sentinel.
58
+ // Count lines that look like log entries: start with a timestamp.
59
+ const lines = stdout.split('\n').filter((line) => /^\d{4}-\d{2}-\d{2}/.test(line));
60
+ return lines.length;
61
+ } catch (err) {
62
+ if (!opts.silent) {
63
+ const msg = err instanceof Error ? err.message : String(err);
64
+ process.stderr.write(`[panic-counter] log command failed: ${msg}\n`);
65
+ }
66
+ return null;
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Pure delta math. Returns null if either input is null (no signal).
72
+ * Negative deltas (panics increased) are valid — caller frames the
73
+ * Slack message appropriately.
74
+ */
75
+ export function computePanicDelta(before, after) {
76
+ if (before === null || after === null) return null;
77
+ if (typeof before !== 'number' || typeof after !== 'number') return null;
78
+ return after - before;
79
+ }
80
+
81
+ /** Format the delta for a Slack message body. Plain English; no jargon. */
82
+ export function formatDeltaSummary(before, after) {
83
+ const delta = computePanicDelta(before, after);
84
+ if (delta === null) {
85
+ return 'Panic delta: n/a (counter unavailable this session).';
86
+ }
87
+ if (delta === 0) {
88
+ return `Panic count steady: ${before} → ${after} (no change this session).`;
89
+ }
90
+ if (delta < 0) {
91
+ return `Panic count down ${Math.abs(delta)}: ${before} → ${after}.`;
92
+ }
93
+ return `Panic count up ${delta}: ${before} → ${after}.`;
94
+ }
@@ -44,7 +44,18 @@ const DEFAULT_ELECTRIC_URL = 'http://localhost:30001';
44
44
 
45
45
  const ACTOR_TYPES = new Set(['agent', 'operator', 'codex', 'system']);
46
46
  const ROLES = new Set(['user', 'assistant', 'tool', 'system']);
47
- const CHUNK_TYPES = new Set(['text', 'tool_use']);
47
+ // B3 schema-v2: expanded chunk_type enum mirrors @olam/chunks/src/schema.ts
48
+ // CHUNK_TYPES. host-cp duplicates the Set rather than importing because
49
+ // this file is plain JS .mjs and the chunks package ships as TS — keeping
50
+ // them in sync is the audit:do-schema-parity-style discipline (cross-
51
+ // package check queued as a B3 follow-up; for now the parity is by
52
+ // inspection + the integration test in __tests__/chunk-type-validator).
53
+ const CHUNK_TYPES = new Set([
54
+ 'text',
55
+ 'tool_use',
56
+ 'goal_mode_assumption',
57
+ 'dispatch_overflow',
58
+ ]);
48
59
 
49
60
  // PB2 — scope-ID shape. world_id + session_id query params are interpolated
50
61
  // into the upstream Electric `where` clause; the regex IS the SQL-injection
@@ -35,6 +35,7 @@ import { createPrCache } from './pr-cache.mjs';
35
35
  import { fetchContainerSecret } from './container-secret-fetcher.mjs';
36
36
  import { subscribeDockerEvents } from './docker-events.mjs';
37
37
  import { createHostStream, newStreamId } from './host-stream.mjs';
38
+ import { detectHaltChunk } from './halt-detect.mjs';
38
39
  import { spawnUpgraderContainer } from './upgrade-spawner.mjs';
39
40
  import { parseProxyPath, perWorldBase, proxyToWorld } from './proxy.mjs';
40
41
  import { resolveHostCpEngine } from './engine-identity.mjs';
@@ -2015,6 +2016,22 @@ const server = http.createServer(instrumentHandler('host-cp', async (req, res) =
2015
2016
  for await (const c of req) chunks.push(c);
2016
2017
  body = Buffer.concat(chunks);
2017
2018
  }
2019
+
2020
+ // W4 — Sniff chunk-write requests for halt-shaped chunks. The
2021
+ // halt chunk emitted by plan-DO's dispatchPlanningAgent (W1)
2022
+ // arrives here as a POST to /api/plan-chat/v1/chunks. We
2023
+ // FORWARD the chunk verbatim AND broadcast a typed plan.halted
2024
+ // event so the SPA's PlanHaltBanner subscriber fires.
2025
+ if (body && req.method === 'POST' && subPath === '/v1/chunks') {
2026
+ try {
2027
+ const parsed = JSON.parse(body.toString('utf8'));
2028
+ const haltPayload = detectHaltChunk(parsed);
2029
+ if (haltPayload) hostStream.broadcast('plan.halted', haltPayload);
2030
+ } catch {
2031
+ // Body not JSON — pass through to upstream as-is. No event.
2032
+ }
2033
+ }
2034
+
2018
2035
  const upstreamRes = await fetch(upstreamUrl.toString(), {
2019
2036
  method: req.method,
2020
2037
  headers,
@@ -2044,6 +2061,64 @@ const server = http.createServer(instrumentHandler('host-cp', async (req, res) =
2044
2061
  }
2045
2062
  }
2046
2063
 
2064
+ // W3 — /api/cloud-dispatch: proxy from the SPA's writeCloudDispatch
2065
+ // to the deployed plan-agent-do `/v1/dispatch` endpoint. Host-cp
2066
+ // holds the URL + showcase password so the browser bundle stays
2067
+ // auth-free. Config:
2068
+ // OLAM_CLOUD_URL — e.g. https://olam-plan-agent.<acct>.workers.dev
2069
+ // OLAM_SHOWCASE_PASSWORD — showcase Basic auth password (same as
2070
+ // B5's CLI uses).
2071
+ // When unset, returns 503 with a clear setup hint instead of failing
2072
+ // silently — operators wire when they're ready for cloud-mode dogfood.
2073
+ if (url.pathname === '/api/cloud-dispatch' && req.method === 'POST') {
2074
+ const cloudUrl = process.env.OLAM_CLOUD_URL;
2075
+ const showcasePw = process.env.OLAM_SHOWCASE_PASSWORD;
2076
+ if (!cloudUrl || !showcasePw) {
2077
+ return jsonReply(res, 503, {
2078
+ error: 'cloud_dispatch_not_configured',
2079
+ message:
2080
+ 'host-cp needs OLAM_CLOUD_URL + OLAM_SHOWCASE_PASSWORD to proxy cloud dispatches. ' +
2081
+ 'Set both env vars + restart host-cp.',
2082
+ });
2083
+ }
2084
+ try {
2085
+ const chunks = [];
2086
+ for await (const c of req) chunks.push(c);
2087
+ const body = Buffer.concat(chunks).toString('utf8');
2088
+ let parsed;
2089
+ try { parsed = JSON.parse(body); } catch {
2090
+ return jsonReply(res, 400, { error: 'body_not_json' });
2091
+ }
2092
+ // The SPA posts `world_id` + `session_id`; plan-DO's
2093
+ // /v1/dispatch is keyed by `plan_id` query param. Use
2094
+ // session_id as plan_id for v1 (per-session = per-plan in the
2095
+ // current SPA model; A11 vault-sync can refine the mapping).
2096
+ const planId = parsed.session_id ?? 'default';
2097
+ const basicAuth = Buffer.from(`operator:${showcasePw}`).toString('base64');
2098
+ const upstream = await fetch(
2099
+ `${cloudUrl.replace(/\/+$/, '')}/v1/dispatch?plan_id=${encodeURIComponent(planId)}`,
2100
+ {
2101
+ method: 'POST',
2102
+ headers: {
2103
+ 'Authorization': `Basic ${basicAuth}`,
2104
+ 'content-type': 'application/json',
2105
+ },
2106
+ body,
2107
+ },
2108
+ );
2109
+ const upstreamBody = await upstream.text();
2110
+ res.statusCode = upstream.status;
2111
+ res.setHeader('content-type', upstream.headers.get('content-type') ?? 'application/json');
2112
+ res.setHeader('cache-control', 'no-store');
2113
+ return res.end(upstreamBody);
2114
+ } catch (err) {
2115
+ return jsonReply(res, 502, {
2116
+ error: 'cloud_dispatch_proxy_failed',
2117
+ message: err.message,
2118
+ });
2119
+ }
2120
+ }
2121
+
2047
2122
  // GET /api/worlds/:id/processes
2048
2123
  // GET /api/worlds/:id/processes/stream — SSE fanout (5s cadence, per-world)
2049
2124
  // Handler: routes/process-port.mjs → handleListProcesses
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pleri/olam-cli",
3
- "version": "0.1.166",
3
+ "version": "0.1.168",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "olam": "./bin/olam.cjs"