@pleri/olam-cli 0.1.167 → 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 (38) hide show
  1. package/dist/commands/plans.d.ts +3 -0
  2. package/dist/commands/plans.d.ts.map +1 -0
  3. package/dist/commands/plans.js +211 -0
  4. package/dist/commands/plans.js.map +1 -0
  5. package/dist/commands/setup.d.ts +16 -0
  6. package/dist/commands/setup.d.ts.map +1 -1
  7. package/dist/commands/setup.js +70 -13
  8. package/dist/commands/setup.js.map +1 -1
  9. package/dist/commands/skills-source.d.ts +24 -0
  10. package/dist/commands/skills-source.d.ts.map +1 -1
  11. package/dist/commands/skills-source.js +169 -15
  12. package/dist/commands/skills-source.js.map +1 -1
  13. package/dist/commands/skills.d.ts +21 -0
  14. package/dist/commands/skills.d.ts.map +1 -1
  15. package/dist/commands/skills.js +44 -0
  16. package/dist/commands/skills.js.map +1 -1
  17. package/dist/image-digests.json +8 -8
  18. package/dist/index.js +1877 -1166
  19. package/dist/index.js.map +1 -1
  20. package/dist/lib/bootstrap-kubernetes.d.ts.map +1 -1
  21. package/dist/lib/bootstrap-kubernetes.js +16 -2
  22. package/dist/lib/bootstrap-kubernetes.js.map +1 -1
  23. package/dist/lib/plans-client.d.ts +69 -0
  24. package/dist/lib/plans-client.d.ts.map +1 -0
  25. package/dist/lib/plans-client.js +137 -0
  26. package/dist/lib/plans-client.js.map +1 -0
  27. package/dist/mcp-server.js +269 -32
  28. package/hermes-bundle/version.json +1 -1
  29. package/host-cp/k8s/manifests/50-deployment.yaml +1 -1
  30. package/host-cp/k8s/manifests/auth-service/50-deployment.yaml +1 -1
  31. package/host-cp/k8s/manifests/kg-service/50-deployment.yaml +1 -1
  32. package/host-cp/k8s/manifests/mcp-auth-service/50-deployment.yaml +1 -1
  33. package/host-cp/k8s/manifests/memory-service/50-deployment.yaml +1 -1
  34. package/host-cp/src/halt-detect.mjs +43 -0
  35. package/host-cp/src/panic-counter.mjs +94 -0
  36. package/host-cp/src/plan-chat-service.mjs +12 -1
  37. package/host-cp/src/server.mjs +75 -0
  38. package/package.json +1 -1
@@ -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.167",
3
+ "version": "0.1.168",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "olam": "./bin/olam.cjs"