@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.
- package/README.md +4 -2
- package/dist/commands/bootstrap.d.ts +6 -0
- package/dist/commands/bootstrap.d.ts.map +1 -1
- package/dist/commands/bootstrap.js +15 -0
- package/dist/commands/bootstrap.js.map +1 -1
- package/dist/commands/doctor.js +4 -4
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.d.ts +4 -3
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +103 -81
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/memory-service-container.d.ts +8 -0
- package/dist/commands/memory-service-container.d.ts.map +1 -1
- package/dist/commands/memory-service-container.js +16 -1
- package/dist/commands/memory-service-container.js.map +1 -1
- package/dist/commands/plans.d.ts +3 -0
- package/dist/commands/plans.d.ts.map +1 -0
- package/dist/commands/plans.js +211 -0
- package/dist/commands/plans.js.map +1 -0
- package/dist/commands/setup.d.ts +78 -14
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +430 -42
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/skills-source.d.ts +24 -0
- package/dist/commands/skills-source.d.ts.map +1 -1
- package/dist/commands/skills-source.js +257 -18
- package/dist/commands/skills-source.js.map +1 -1
- package/dist/commands/skills.d.ts +21 -0
- package/dist/commands/skills.d.ts.map +1 -1
- package/dist/commands/skills.js +44 -0
- package/dist/commands/skills.js.map +1 -1
- package/dist/image-digests.json +8 -7
- package/dist/index.js +2494 -1279
- package/dist/index.js.map +1 -1
- package/dist/lib/bootstrap-kubernetes.d.ts.map +1 -1
- package/dist/lib/bootstrap-kubernetes.js +178 -107
- package/dist/lib/bootstrap-kubernetes.js.map +1 -1
- package/dist/lib/health-probes.d.ts +16 -0
- package/dist/lib/health-probes.d.ts.map +1 -1
- package/dist/lib/health-probes.js +49 -0
- package/dist/lib/health-probes.js.map +1 -1
- package/dist/lib/peripheral-registry.d.ts +9 -3
- package/dist/lib/peripheral-registry.d.ts.map +1 -1
- package/dist/lib/peripheral-registry.js +4 -4
- package/dist/lib/peripheral-registry.js.map +1 -1
- package/dist/lib/plans-client.d.ts +69 -0
- package/dist/lib/plans-client.d.ts.map +1 -0
- package/dist/lib/plans-client.js +137 -0
- package/dist/lib/plans-client.js.map +1 -0
- package/dist/lib/port-forward.js +1 -1
- package/dist/lib/port-forward.js.map +1 -1
- package/dist/lib/upgrade-kubernetes.d.ts +1 -1
- package/dist/lib/upgrade-kubernetes.d.ts.map +1 -1
- package/dist/lib/upgrade-kubernetes.js +35 -21
- package/dist/lib/upgrade-kubernetes.js.map +1 -1
- package/dist/mcp-server.js +1239 -343
- package/hermes-bundle/version.json +1 -1
- package/host-cp/k8s/manifests/50-deployment.yaml +1 -1
- package/host-cp/k8s/manifests/auth-service/50-deployment.yaml +1 -1
- package/host-cp/k8s/manifests/kg-service/50-deployment.yaml +1 -1
- package/host-cp/k8s/manifests/mcp-auth-service/50-deployment.yaml +1 -1
- package/host-cp/k8s/manifests/memory-service/50-deployment.yaml +1 -1
- package/host-cp/src/halt-detect.mjs +43 -0
- package/host-cp/src/panic-counter.mjs +94 -0
- package/host-cp/src/plan-chat-service.mjs +12 -1
- package/host-cp/src/server.mjs +75 -0
- package/package.json +1 -1
|
@@ -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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
package/host-cp/src/server.mjs
CHANGED
|
@@ -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
|