@openclawbrain/openclaw 0.3.0 → 0.3.1
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 +2 -1
- package/dist/extension/index.js +9 -1
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/runtime-guard.js +1 -1
- package/dist/extension/runtime-guard.js.map +1 -1
- package/dist/src/cli.d.ts +11 -5
- package/dist/src/cli.js +1006 -269
- package/dist/src/cli.js.map +1 -1
- package/dist/src/openclaw-home-layout.d.ts +17 -0
- package/dist/src/openclaw-home-layout.js +182 -0
- package/dist/src/openclaw-home-layout.js.map +1 -0
- package/dist/src/provider-config.d.ts +36 -0
- package/dist/src/provider-config.js +181 -25
- package/dist/src/provider-config.js.map +1 -1
- package/dist/src/resolve-activation-root.d.ts +3 -3
- package/dist/src/resolve-activation-root.js +21 -26
- package/dist/src/resolve-activation-root.js.map +1 -1
- package/dist/src/session-store.js +16 -5
- package/dist/src/session-store.js.map +1 -1
- package/dist/src/session-tail.js +14 -2
- package/dist/src/session-tail.js.map +1 -1
- package/extension/index.ts +17 -0
- package/extension/runtime-guard.ts +1 -1
- package/package.json +1 -1
package/dist/src/cli.js
CHANGED
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execFileSync, execSync } from "node:child_process";
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, readSync, openSync, closeSync, realpathSync, rmSync, statSync, writeFileSync,
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, readSync, openSync, closeSync, realpathSync, rmSync, statSync, writeFileSync, symlinkSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
6
|
const __filename = fileURLToPath(import.meta.url);
|
|
7
7
|
const __dirname = path.dirname(__filename);
|
|
8
|
+
import { DEFAULT_OLLAMA_EMBEDDING_MODEL } from "@openclawbrain/compiler";
|
|
8
9
|
import { parseDaemonArgs, runDaemonCommand } from "./daemon.js";
|
|
9
10
|
import { exportBrain, importBrain } from "./import-export.js";
|
|
10
11
|
import { buildNormalizedEventExport } from "@openclawbrain/contracts";
|
|
11
12
|
import { buildTeacherSupervisionArtifactsFromNormalizedEventExport, createAlwaysOnLearningRuntimeState, describeAlwaysOnLearningRuntimeState, drainAlwaysOnLearningRuntime, loadOrInitBaseline, materializeAlwaysOnLearningCandidatePack, persistBaseline } from "@openclawbrain/learner";
|
|
12
13
|
import { inspectActivationState, loadPackFromActivation, promoteCandidatePack, readLearningSpineLogEntries, stageCandidatePack } from "@openclawbrain/pack-format";
|
|
13
14
|
import { resolveActivationRoot } from "./resolve-activation-root.js";
|
|
14
|
-
import {
|
|
15
|
+
import { describeOpenClawHomeInspection, discoverOpenClawHomes, formatOpenClawHomeLayout, formatOpenClawHomeProfileSource, inspectOpenClawHome } from "./openclaw-home-layout.js";
|
|
16
|
+
import { buildNormalizedEventExportFromScannedEvents, bootstrapRuntimeAttach, buildOperatorSurfaceReport, compileRuntimeContext, createAsyncTeacherLiveLoop, createOpenClawLocalSessionTail, createRuntimeEventExportScanner, describeCurrentProfileBrainStatus, formatOperatorRollbackReport, loadWatchTeacherSnapshotState, loadRuntimeEventExportBundle, persistWatchTeacherSnapshot, rollbackRuntimeAttach, resolveOperatorTeacherSnapshotPath, resolveAsyncTeacherLiveLoopSnapshotPath, resolveWatchSessionTailCursorPath, resolveWatchStateRoot, resolveWatchTeacherSnapshotPath, scanLiveEventExport, scanRecordedSession, summarizeLearningPathFromMaterialization, summarizeNormalizedEventExportLabelFlow, writeScannedEventExportBundle } from "./index.js";
|
|
15
17
|
import { appendLearningUpdateLogs } from "./learning-spine.js";
|
|
16
18
|
import { buildPassiveLearningSessionExportFromOpenClawSessionStore } from "./local-session-passive-learning.js";
|
|
17
19
|
import { discoverOpenClawSessionStores, loadOpenClawSessionIndex, readOpenClawSessionFile } from "./session-store.js";
|
|
18
|
-
import { readOpenClawBrainProviderConfig } from "./provider-config.js";
|
|
20
|
+
import { readOpenClawBrainProviderConfig, readOpenClawBrainProviderConfigFromSources, resolveOpenClawBrainProviderDefaultsPath } from "./provider-config.js";
|
|
21
|
+
const OPENCLAWBRAIN_INSTALL_SKIP_EMBEDDER_PROVISION_ENV = "OPENCLAWBRAIN_INSTALL_SKIP_EMBEDDER_PROVISION";
|
|
22
|
+
const INSTALL_COMPATIBLE_LOCAL_TEACHER_MODEL_PREFIXES = [
|
|
23
|
+
"qwen3.5:9b",
|
|
24
|
+
"qwen3.5:8b",
|
|
25
|
+
"qwen3:8b",
|
|
26
|
+
"qwen2.5:7b"
|
|
27
|
+
];
|
|
19
28
|
function quoteShellArg(value) {
|
|
20
29
|
return `'${value.replace(/'/g, `"'"'`)}'`;
|
|
21
30
|
}
|
|
@@ -26,23 +35,18 @@ function normalizeOptionalCliString(value) {
|
|
|
26
35
|
const trimmed = value.trim();
|
|
27
36
|
return trimmed.length > 0 ? trimmed : null;
|
|
28
37
|
}
|
|
38
|
+
function readTruthyEnvFlag(name, env = process.env) {
|
|
39
|
+
const value = normalizeOptionalCliString(env[name]);
|
|
40
|
+
if (value === null) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
return ["1", "true", "yes", "on"].includes(value.toLowerCase());
|
|
44
|
+
}
|
|
29
45
|
function getCliHomeDir() {
|
|
30
46
|
return process.env.HOME ?? process.env.USERPROFILE ?? "~";
|
|
31
47
|
}
|
|
32
48
|
function discoverInstallCandidateOpenClawHomes(homeDir = getCliHomeDir()) {
|
|
33
|
-
|
|
34
|
-
let entries;
|
|
35
|
-
try {
|
|
36
|
-
entries = readdirSync(resolvedHomeDir, { withFileTypes: true });
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
return [];
|
|
40
|
-
}
|
|
41
|
-
return entries
|
|
42
|
-
.filter((entry) => entry.isDirectory() && entry.name.startsWith(".openclaw-"))
|
|
43
|
-
.map((entry) => path.join(resolvedHomeDir, entry.name))
|
|
44
|
-
.filter((candidate) => existsSync(path.join(candidate, "openclaw.json")))
|
|
45
|
-
.sort((left, right) => left.localeCompare(right));
|
|
49
|
+
return discoverOpenClawHomes(homeDir).map((inspection) => inspection.openclawHome);
|
|
46
50
|
}
|
|
47
51
|
function formatInstallOpenClawHomeSource(source) {
|
|
48
52
|
switch (source) {
|
|
@@ -50,8 +54,8 @@ function formatInstallOpenClawHomeSource(source) {
|
|
|
50
54
|
return "--openclaw-home";
|
|
51
55
|
case "env":
|
|
52
56
|
return "OPENCLAW_HOME";
|
|
53
|
-
case "
|
|
54
|
-
return "single discovered
|
|
57
|
+
case "discovered_single_home":
|
|
58
|
+
return "single discovered install target";
|
|
55
59
|
default:
|
|
56
60
|
return source;
|
|
57
61
|
}
|
|
@@ -75,24 +79,24 @@ function resolveInstallOpenClawHome(explicitOpenclawHome) {
|
|
|
75
79
|
if (discoveredHomes.length === 1) {
|
|
76
80
|
return {
|
|
77
81
|
openclawHome: path.resolve(discoveredHomes[0]),
|
|
78
|
-
openclawHomeSource: "
|
|
82
|
+
openclawHomeSource: "discovered_single_home"
|
|
79
83
|
};
|
|
80
84
|
}
|
|
81
85
|
if (discoveredHomes.length > 1) {
|
|
82
86
|
const installPrefix = detectConsumerSafeOperatorCliPrefix();
|
|
83
|
-
const targetChoices =
|
|
84
|
-
.map((
|
|
85
|
-
const resolvedCandidate = path.resolve(
|
|
86
|
-
return ` - ${resolvedCandidate}\n ${installPrefix} install --openclaw-home ${quoteShellArg(resolvedCandidate)}`;
|
|
87
|
+
const targetChoices = discoverOpenClawHomes()
|
|
88
|
+
.map((inspection) => {
|
|
89
|
+
const resolvedCandidate = path.resolve(inspection.openclawHome);
|
|
90
|
+
return ` - ${resolvedCandidate} (${describeOpenClawHomeInspection(inspection)})\n ${installPrefix} install --openclaw-home ${quoteShellArg(resolvedCandidate)}`;
|
|
87
91
|
})
|
|
88
92
|
.join("\n");
|
|
89
93
|
throw new Error([
|
|
90
|
-
"Refusing ambiguous
|
|
94
|
+
"Refusing ambiguous OpenClaw install targets.",
|
|
91
95
|
targetChoices,
|
|
92
|
-
"Pass --openclaw-home <path> or set OPENCLAW_HOME to pin one
|
|
96
|
+
"Pass --openclaw-home <path> or set OPENCLAW_HOME to pin one OpenClaw home."
|
|
93
97
|
].join("\n"));
|
|
94
98
|
}
|
|
95
|
-
throw new Error("No OpenClaw
|
|
99
|
+
throw new Error("No OpenClaw home found. Pass --openclaw-home <path> or set OPENCLAW_HOME.");
|
|
96
100
|
}
|
|
97
101
|
function resolveInstallActivationRoot(openclawHome, explicitActivationRoot) {
|
|
98
102
|
const normalizedExplicitActivationRoot = normalizeOptionalCliString(explicitActivationRoot);
|
|
@@ -115,18 +119,24 @@ function resolveInstallWorkspaceId(openclawHome, explicitWorkspaceId) {
|
|
|
115
119
|
source: "explicit"
|
|
116
120
|
};
|
|
117
121
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
122
|
+
const inspection = inspectOpenClawHome(openclawHome);
|
|
123
|
+
if (inspection.profileId !== null) {
|
|
124
|
+
return {
|
|
125
|
+
workspaceId: inspection.profileId,
|
|
126
|
+
source: inspection.profileSource === "directory_name"
|
|
127
|
+
? "openclaw_home_dir"
|
|
128
|
+
: inspection.profileSource === "openclaw_json_profile"
|
|
129
|
+
? "openclaw_json_profile"
|
|
130
|
+
: inspection.profileSource === "openclaw_json_single_profile_key"
|
|
131
|
+
? "openclaw_json_single_profile_key"
|
|
132
|
+
: "fallback"
|
|
133
|
+
};
|
|
127
134
|
}
|
|
128
|
-
|
|
129
|
-
|
|
135
|
+
if (inspection.layout === "shared_home_profiles_in_config" || inspection.layout === "single_openclaw_home") {
|
|
136
|
+
return {
|
|
137
|
+
workspaceId: "current_profile",
|
|
138
|
+
source: "current_profile_boundary"
|
|
139
|
+
};
|
|
130
140
|
}
|
|
131
141
|
const dirName = path.basename(openclawHome);
|
|
132
142
|
if (dirName === ".openclaw") {
|
|
@@ -147,6 +157,24 @@ function resolveInstallWorkspaceId(openclawHome, explicitWorkspaceId) {
|
|
|
147
157
|
source: "fallback"
|
|
148
158
|
};
|
|
149
159
|
}
|
|
160
|
+
function resolveInstallEmbedderProvisionSkip(explicitSkip) {
|
|
161
|
+
if (explicitSkip) {
|
|
162
|
+
return {
|
|
163
|
+
skipEmbedderProvision: true,
|
|
164
|
+
skipEmbedderProvisionSource: "flag"
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
if (readTruthyEnvFlag(OPENCLAWBRAIN_INSTALL_SKIP_EMBEDDER_PROVISION_ENV)) {
|
|
168
|
+
return {
|
|
169
|
+
skipEmbedderProvision: true,
|
|
170
|
+
skipEmbedderProvisionSource: "env"
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
skipEmbedderProvision: false,
|
|
175
|
+
skipEmbedderProvisionSource: null
|
|
176
|
+
};
|
|
177
|
+
}
|
|
150
178
|
function formatInstallActivationRootSource(source) {
|
|
151
179
|
if (source === "explicit") {
|
|
152
180
|
return "explicit --activation-root";
|
|
@@ -159,6 +187,10 @@ function formatInstallWorkspaceIdSource(source) {
|
|
|
159
187
|
return "explicit --workspace-id";
|
|
160
188
|
case "openclaw_json_profile":
|
|
161
189
|
return "from openclaw.json profile";
|
|
190
|
+
case "openclaw_json_single_profile_key":
|
|
191
|
+
return "from the only openclaw.json profiles entry";
|
|
192
|
+
case "current_profile_boundary":
|
|
193
|
+
return "current_profile boundary for a shared OpenClaw home";
|
|
162
194
|
case "openclaw_home_dir":
|
|
163
195
|
return "from OpenClaw home dir";
|
|
164
196
|
default:
|
|
@@ -289,29 +321,29 @@ function operatorCliHelp() {
|
|
|
289
321
|
return [
|
|
290
322
|
"Usage:",
|
|
291
323
|
" openclawbrain install [--openclaw-home <path>] [options]",
|
|
324
|
+
" openclawbrain attach --openclaw-home <path> [options]",
|
|
325
|
+
" openclawbrain <status|rollback> [--activation-root <path>|--openclaw-home <path>] [options]",
|
|
326
|
+
" openclawbrain watch --activation-root <path> [--scan-root <path>] [--interval <seconds>]",
|
|
327
|
+
" openclawbrain daemon <start|stop|status|logs> --activation-root <path> [--json]",
|
|
292
328
|
" openclawbrain detach --openclaw-home <path> [options]",
|
|
293
329
|
" openclawbrain uninstall --openclaw-home <path> [--keep-data|--purge-data] [options]",
|
|
294
|
-
" openclawbrain attach --activation-root <path> [options]",
|
|
295
|
-
" openclawbrain <status|rollback> [--activation-root <path>|--openclaw-home <path>] [options]",
|
|
296
330
|
" openclawbrain context \"message\" [--activation-root <path>|--openclaw-home <path>]",
|
|
297
331
|
" openclawbrain history [--activation-root <path>|--openclaw-home <path>] [--limit N] [--json]",
|
|
298
332
|
" openclawbrain scan --session <trace.json> --root <path> [options]",
|
|
299
333
|
" openclawbrain scan --live <event-export-path> --workspace <workspace.json> [options]",
|
|
300
334
|
" openclawbrain learn [--activation-root <path>|--openclaw-home <path>] [--json]",
|
|
301
|
-
" openclawbrain watch --activation-root <path> [--scan-root <path>] [--interval <seconds>]",
|
|
302
|
-
" openclawbrain daemon <start|stop|status|logs> --activation-root <path> [--json]",
|
|
303
335
|
" openclawbrain-ops <status|rollback> [--activation-root <path>|--openclaw-home <path>] [options] # compatibility alias",
|
|
304
336
|
" openclawbrain-ops scan --session <trace.json> --root <path> [options] # compatibility alias",
|
|
305
337
|
"",
|
|
306
338
|
"Options:",
|
|
307
|
-
" --openclaw-home <path> OpenClaw
|
|
308
|
-
" --shared Set brain-attachment-policy to shared instead of dedicated (install only).",
|
|
309
|
-
|
|
339
|
+
" --openclaw-home <path> OpenClaw home dir for install/attach/detach/uninstall (e.g. ~/.openclaw-Tern or ~/.openclaw). Also pins status/rollback/context/history/learn to that installed target when applicable.",
|
|
340
|
+
" --shared Set brain-attachment-policy to shared instead of dedicated (install/attach only).",
|
|
341
|
+
` --skip-embedder-provision Skip the default Ollama ${DEFAULT_OLLAMA_EMBEDDING_MODEL} pull before install/attach bootstrap. Use only when intentionally deferring embedder setup. Also supports ${OPENCLAWBRAIN_INSTALL_SKIP_EMBEDDER_PROVISION_ENV}=1.`,
|
|
342
|
+
" --activation-root <path> Explicit activation root for attach/watch/daemon and other stateful commands; install/attach default to sibling .openclawbrain/activation next to the selected OpenClaw home.",
|
|
310
343
|
" --keep-data Preserve activation data on uninstall; detach always behaves this way.",
|
|
311
344
|
" --purge-data Remove activation data on uninstall; requires the installed profile hook or --activation-root.",
|
|
312
345
|
" --restart <never|safe|external> Restart guidance mode for detach/uninstall. 'safe' is conservative; 'never' leaves restart entirely to the operator.",
|
|
313
|
-
" --
|
|
314
|
-
" --workspace-id <id> Workspace identifier for attach/install provenance; install defaults to openclaw.json.profile or the profile name, attach defaults to 'workspace'.",
|
|
346
|
+
" --workspace-id <id> Workspace identifier for install/attach provenance; defaults to the detected profile target from openclaw.json when possible, otherwise the profile name or current_profile boundary.",
|
|
315
347
|
" --event-export <path> Event-export bundle root or normalized export JSON payload.",
|
|
316
348
|
" --teacher-snapshot <path> Canonical watch teacher snapshot JSON or raw async teacher snapshot JSON; keeps live-first, principal-priority, and passive-backfill learner truth explicit.",
|
|
317
349
|
" --updated-at <iso> Observation time to use for freshness checks.",
|
|
@@ -321,7 +353,7 @@ function operatorCliHelp() {
|
|
|
321
353
|
" --session <path> Sanitized recorded-session trace JSON to replay.",
|
|
322
354
|
" --live <path> Runtime event-export bundle root or normalized export JSON to scan once.",
|
|
323
355
|
" --root <path> Output root for scan --session replay artifacts.",
|
|
324
|
-
" --workspace <path> Workspace metadata JSON for scan --live candidate
|
|
356
|
+
" --workspace <path> Workspace metadata JSON for scan --live candidate materialization.",
|
|
325
357
|
" --pack-label <label> Candidate-pack label for scan --live. Defaults to scanner-live-cli.",
|
|
326
358
|
" --observed-at <iso> Observation time for scan --live freshness checks.",
|
|
327
359
|
" --snapshot-out <path> Write the one-shot scan --live snapshot JSON.",
|
|
@@ -331,25 +363,31 @@ function operatorCliHelp() {
|
|
|
331
363
|
" --json Emit machine-readable JSON instead of text.",
|
|
332
364
|
" --help Show this help.",
|
|
333
365
|
"",
|
|
334
|
-
"
|
|
335
|
-
"
|
|
336
|
-
"
|
|
337
|
-
"
|
|
338
|
-
"
|
|
339
|
-
"
|
|
340
|
-
"
|
|
341
|
-
"
|
|
342
|
-
"
|
|
343
|
-
"
|
|
344
|
-
"
|
|
345
|
-
"
|
|
366
|
+
"Lifecycle flow:",
|
|
367
|
+
" 1. install openclawbrain install — safe first-time default; pass --openclaw-home when more than one OpenClaw home/layout is present",
|
|
368
|
+
" 2. attach openclawbrain attach --openclaw-home <path> [--activation-root <path>] — explicit reattach/manual hook path for known brain data; use install first",
|
|
369
|
+
" 3. status openclawbrain status --activation-root <path> — answer \"How's the brain?\" for that boundary",
|
|
370
|
+
" 4. status --detailed openclawbrain status --activation-root <path> --detailed — explain serve path, freshness, backlog, and failure mode",
|
|
371
|
+
" 5. watch openclawbrain watch --activation-root <path> — run the foreground learning/watch loop",
|
|
372
|
+
" 6. daemon start openclawbrain daemon start --activation-root <path> — keep watch running in the background on macOS",
|
|
373
|
+
" 7. daemon status openclawbrain daemon status --activation-root <path> — inspect the background watch state",
|
|
374
|
+
" 8. detach openclawbrain detach --openclaw-home <path> — remove the profile hookup only and keep brain data",
|
|
375
|
+
" 9. uninstall openclawbrain uninstall --openclaw-home <path> --keep-data|--purge-data — remove the hookup and choose the data outcome explicitly",
|
|
376
|
+
"",
|
|
377
|
+
"Advanced/operator surfaces:",
|
|
378
|
+
" context preview the brain context that would be injected for a message",
|
|
379
|
+
" rollback preview or apply active <- previous, active -> candidate pointer movement",
|
|
380
|
+
" scan inspect one recorded session or live event export without claiming a daemon is running",
|
|
381
|
+
" learn one-shot local-session learning pass against the resolved activation root",
|
|
346
382
|
" status --teacher-snapshot keeps the current live-first / principal-priority / passive-backfill learner order visible when that snapshot exists",
|
|
347
383
|
" watch/daemon persist their operator snapshot at <activation-root>/watch/teacher-snapshot.json; --teacher-snapshot overrides the default path",
|
|
348
|
-
" watch
|
|
384
|
+
" watch teacher defaults come from install-written provider-defaults.json under the activation root; OPENCLAWBRAIN_TEACHER_* and OPENCLAWBRAIN_EMBEDDER_* are host-shell overrides only, not live gateway wiring",
|
|
349
385
|
"",
|
|
350
386
|
"Exit codes:",
|
|
387
|
+
" install: 0 on successful profile hookup/bootstrap, 1 on input/read failure.",
|
|
351
388
|
" status: 0 on successful inspection, 1 on input/read failure.",
|
|
352
389
|
" rollback: 0 when ready/applied, 1 when blocked or on input/read failure.",
|
|
390
|
+
" attach: 0 on successful profile hookup/bootstrap, 1 on input/read failure.",
|
|
353
391
|
" detach: 0 on successful unhook, 1 on input/read failure.",
|
|
354
392
|
" uninstall: 0 on successful unhook/cleanup, 1 on input/read failure.",
|
|
355
393
|
" scan: 0 on successful replay/scan, 1 on input/read failure."
|
|
@@ -391,7 +429,8 @@ function formatLearningBuckets(report) {
|
|
|
391
429
|
return `pi:${buckets.principal_immediate},pb:${buckets.principal_backfill},live:${buckets.live},backfill:${buckets.backfill}`;
|
|
392
430
|
}
|
|
393
431
|
function formatLearningWarnings(report) {
|
|
394
|
-
|
|
432
|
+
const warnings = report.learning.warningStates.filter((warning) => warning !== "teacher_snapshot_unavailable");
|
|
433
|
+
return warnings.length === 0 ? "none" : warnings.join("|");
|
|
395
434
|
}
|
|
396
435
|
function formatLabelFlowSummary(labelFlow) {
|
|
397
436
|
return `source=${labelFlow.source} human=${labelFlow.humanLabelCount ?? "none"} self=${labelFlow.selfLabelCount ?? "none"} implicitPositive=${labelFlow.implicitPositiveCount ?? "none"} teacherArtifacts=${labelFlow.asyncTeacherArtifactCount ?? "none"}`;
|
|
@@ -399,11 +438,318 @@ function formatLabelFlowSummary(labelFlow) {
|
|
|
399
438
|
function formatLearningPathSummary(learningPath) {
|
|
400
439
|
return `source=${learningPath.source} pg=${learningPath.policyGradientVersion} method=${learningPath.policyGradientMethod ?? "none"} target=${learningPath.targetConstruction ?? "none"} connect=${learningPath.connectOpsFired ?? "none"} trajectories=${learningPath.reconstructedTrajectoryCount ?? "none"}`;
|
|
401
440
|
}
|
|
402
|
-
function
|
|
441
|
+
function formatTeacherLoopSummary(report) {
|
|
442
|
+
const parts = [
|
|
443
|
+
`snapshot=${report.teacherLoop.sourcePath ?? "none"}`,
|
|
444
|
+
`kind=${report.teacherLoop.sourceKind}`,
|
|
445
|
+
`lastRun=${report.teacherLoop.lastRunAt ?? "none"}`,
|
|
446
|
+
`artifacts=${report.teacherLoop.artifactCount ?? "none"}`,
|
|
447
|
+
`freshness=${report.teacherLoop.latestFreshness}`,
|
|
448
|
+
`queue=${report.teacherLoop.queueDepth ?? "none"}/${report.teacherLoop.queueCapacity ?? "none"}`,
|
|
449
|
+
`running=${yesNo(report.teacherLoop.running)}`
|
|
450
|
+
];
|
|
451
|
+
if (report.teacherLoop.lastNoOpReason !== "none") {
|
|
452
|
+
parts.push(`noOp=${report.teacherLoop.lastNoOpReason}`);
|
|
453
|
+
}
|
|
454
|
+
if (report.teacherLoop.failureMode !== "none") {
|
|
455
|
+
const failureDetail = report.teacherLoop.failureDetail === null
|
|
456
|
+
? report.teacherLoop.failureMode
|
|
457
|
+
: `${report.teacherLoop.failureMode}(${report.teacherLoop.failureDetail})`;
|
|
458
|
+
parts.push(`failure=${failureDetail}`);
|
|
459
|
+
}
|
|
460
|
+
return parts.join(" ");
|
|
461
|
+
}
|
|
462
|
+
function formatCompactValue(value, maxLength = 64) {
|
|
463
|
+
return value.length <= maxLength ? value : `${value.slice(0, maxLength - 1)}...`;
|
|
464
|
+
}
|
|
465
|
+
function formatCompactList(values, maxItems = 2, maxLength = 64) {
|
|
466
|
+
if (values.length === 0) {
|
|
467
|
+
return "none";
|
|
468
|
+
}
|
|
469
|
+
const visible = values.slice(0, maxItems).map((value) => formatCompactValue(value, maxLength));
|
|
470
|
+
return values.length > maxItems ? `${visible.join("|")}+${values.length - maxItems}more` : visible.join("|");
|
|
471
|
+
}
|
|
472
|
+
const SERVICE_RISK_FINDING_CODES = new Set([
|
|
473
|
+
"activation_broken_install",
|
|
474
|
+
"activation_stale_incomplete",
|
|
475
|
+
"active_missing",
|
|
476
|
+
"active_unhealthy",
|
|
477
|
+
"learned_route_missing",
|
|
478
|
+
"serve_path_fail_open",
|
|
479
|
+
"serve_path_hard_fail",
|
|
480
|
+
"serve_path_route_evidence_missing"
|
|
481
|
+
]);
|
|
482
|
+
const DEGRADED_BRAIN_FINDING_CODES = new Set([
|
|
483
|
+
"bootstrap_waiting_for_first_export",
|
|
484
|
+
"serve_path_unprobed",
|
|
485
|
+
"brain_context_kernel_only",
|
|
486
|
+
"candidate_unhealthy",
|
|
487
|
+
"promotion_blocked",
|
|
488
|
+
"supervision_not_flowing",
|
|
489
|
+
"scan_surfaces_missing"
|
|
490
|
+
]);
|
|
491
|
+
const COSMETIC_FINDING_CODES = new Set([
|
|
492
|
+
"last_promotion_unknown",
|
|
493
|
+
"rollback_blocked",
|
|
494
|
+
"supervision_unavailable",
|
|
495
|
+
"turn_attribution_partial",
|
|
496
|
+
"teacher_snapshot_unavailable"
|
|
497
|
+
]);
|
|
498
|
+
const LEARNING_WARNING_MESSAGES = {
|
|
499
|
+
awaiting_first_export: "awaiting first export",
|
|
500
|
+
principal_live_backlog: "principal live backlog is ahead of serving",
|
|
501
|
+
principal_backfill_pending: "principal backfill is still queued",
|
|
502
|
+
active_pack_behind_latest_principal: "active pack is behind the latest principal correction",
|
|
503
|
+
passive_backfill_pending: "passive backfill remains queued",
|
|
504
|
+
teacher_queue_full: "teacher queue is full",
|
|
505
|
+
teacher_labels_stale: "teacher labels are stale",
|
|
506
|
+
teacher_no_artifacts: "teacher produced no artifacts",
|
|
507
|
+
teacher_snapshot_unavailable: "teacher snapshot is unavailable"
|
|
508
|
+
};
|
|
509
|
+
function summarizeStatusInstallHook(openclawHome) {
|
|
510
|
+
if (openclawHome === null) {
|
|
511
|
+
return {
|
|
512
|
+
state: "unknown",
|
|
513
|
+
detail: "profile hook state is unknown from activation-root-only status; pin --openclaw-home to prove install state"
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
const extensionDir = path.join(path.resolve(openclawHome), "extensions", "openclawbrain");
|
|
517
|
+
const indexPath = path.join(extensionDir, "index.ts");
|
|
518
|
+
const runtimeGuardPath = path.join(extensionDir, "runtime-guard.js");
|
|
519
|
+
const manifestPath = path.join(extensionDir, "openclaw.plugin.json");
|
|
520
|
+
if (existsSync(indexPath) && existsSync(runtimeGuardPath) && existsSync(manifestPath)) {
|
|
521
|
+
return {
|
|
522
|
+
state: "installed",
|
|
523
|
+
detail: `profile hook is installed at ${shortenPath(extensionDir)}`
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
return {
|
|
527
|
+
state: "not_installed",
|
|
528
|
+
detail: `profile hook is not present at ${shortenPath(extensionDir)}`
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
function runOllamaProbe(args, baseUrl) {
|
|
532
|
+
try {
|
|
533
|
+
execFileSync("ollama", [...args], {
|
|
534
|
+
stdio: "pipe",
|
|
535
|
+
timeout: 2_000,
|
|
536
|
+
env: {
|
|
537
|
+
...process.env,
|
|
538
|
+
OLLAMA_HOST: baseUrl
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
return {
|
|
542
|
+
detected: true,
|
|
543
|
+
detail: `ollama responded to ${args.join(" ")} at ${baseUrl}`
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
catch (error) {
|
|
547
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
548
|
+
return {
|
|
549
|
+
detected: false,
|
|
550
|
+
detail: "ollama CLI was not found on PATH"
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
return {
|
|
554
|
+
detected: true,
|
|
555
|
+
detail: describeExecFailure(error)
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
function summarizeStatusEmbeddings(report, providerConfig) {
|
|
560
|
+
let embeddedEntryCount = null;
|
|
561
|
+
let totalEntryCount = null;
|
|
562
|
+
let models = [];
|
|
563
|
+
let liveState = "unknown";
|
|
564
|
+
let liveDetail = "no activation-ready active pack is available for embedding inspection";
|
|
565
|
+
if (report.active !== null && report.active.activationReady) {
|
|
566
|
+
try {
|
|
567
|
+
const activePack = loadPackFromActivation(report.activationRoot, "active", {
|
|
568
|
+
requireActivationReady: true
|
|
569
|
+
});
|
|
570
|
+
if (activePack !== null) {
|
|
571
|
+
totalEntryCount = activePack.vectors.entries.length;
|
|
572
|
+
embeddedEntryCount = activePack.vectors.entries.filter((entry) => entry.embedding !== undefined).length;
|
|
573
|
+
models = [...new Set(activePack.vectors.entries.flatMap((entry) => (entry.embedding === undefined ? [] : [entry.embedding.model])))].sort((left, right) => left.localeCompare(right));
|
|
574
|
+
liveState = embeddedEntryCount > 0 ? "yes" : "no";
|
|
575
|
+
liveDetail = `active pack stores ${embeddedEntryCount}/${totalEntryCount} numeric embeddings`;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
catch (error) {
|
|
579
|
+
liveDetail = `embedding inspection failed: ${toErrorMessage(error)}`;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
if (providerConfig.embedder.provider === "off") {
|
|
583
|
+
return {
|
|
584
|
+
provider: providerConfig.embedder.provider,
|
|
585
|
+
model: providerConfig.embedder.model,
|
|
586
|
+
provisionedState: "off",
|
|
587
|
+
liveState,
|
|
588
|
+
embeddedEntryCount,
|
|
589
|
+
totalEntryCount,
|
|
590
|
+
models,
|
|
591
|
+
detail: `${liveDetail}; embedder provider is off`
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
if (providerConfig.embedder.provider === "keywords") {
|
|
595
|
+
return {
|
|
596
|
+
provider: providerConfig.embedder.provider,
|
|
597
|
+
model: providerConfig.embedder.model,
|
|
598
|
+
provisionedState: "builtin",
|
|
599
|
+
liveState,
|
|
600
|
+
embeddedEntryCount,
|
|
601
|
+
totalEntryCount,
|
|
602
|
+
models,
|
|
603
|
+
detail: `${liveDetail}; keyword embedder needs no Ollama model provision`
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
const modelProbe = runOllamaProbe(["show", providerConfig.embedder.model], providerConfig.embedderBaseUrl);
|
|
607
|
+
return {
|
|
608
|
+
provider: providerConfig.embedder.provider,
|
|
609
|
+
model: providerConfig.embedder.model,
|
|
610
|
+
provisionedState: modelProbe.detected && /responded to/.test(modelProbe.detail) ? "confirmed" : "not_confirmed",
|
|
611
|
+
liveState,
|
|
612
|
+
embeddedEntryCount,
|
|
613
|
+
totalEntryCount,
|
|
614
|
+
models,
|
|
615
|
+
detail: `${liveDetail}; ollama model check: ${modelProbe.detail}`
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
function summarizeStatusLocalLlm(providerConfig) {
|
|
619
|
+
const detection = runOllamaProbe(["--version"], providerConfig.teacherBaseUrl);
|
|
620
|
+
const enabled = providerConfig.teacher.provider === "ollama";
|
|
621
|
+
if (enabled) {
|
|
622
|
+
return {
|
|
623
|
+
detected: detection.detected,
|
|
624
|
+
enabled,
|
|
625
|
+
provider: providerConfig.teacher.provider,
|
|
626
|
+
model: providerConfig.teacher.model,
|
|
627
|
+
detail: detection.detected
|
|
628
|
+
? `teacher provider is ollama and the local LLM surface answered at ${providerConfig.teacherBaseUrl}`
|
|
629
|
+
: `teacher provider is ollama but the local LLM surface was not detected (${detection.detail})`
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
return {
|
|
633
|
+
detected: detection.detected,
|
|
634
|
+
enabled,
|
|
635
|
+
provider: providerConfig.teacher.provider,
|
|
636
|
+
model: providerConfig.teacher.model,
|
|
637
|
+
detail: detection.detected
|
|
638
|
+
? `local Ollama is detectable, but teacher labeling is ${providerConfig.teacher.provider}`
|
|
639
|
+
: `teacher labeling is ${providerConfig.teacher.provider}; no local Ollama CLI was detected`
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
function pushUniqueAlert(target, value) {
|
|
643
|
+
const normalized = value.trim();
|
|
644
|
+
if (normalized.length === 0) {
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
if (target.includes(normalized) === false) {
|
|
648
|
+
target.push(normalized);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
function summarizeStatusAlerts(report, providerConfig, embeddings, localLlm) {
|
|
652
|
+
const buckets = {
|
|
653
|
+
serviceRisk: [],
|
|
654
|
+
degradedBrain: [],
|
|
655
|
+
cosmeticNoise: []
|
|
656
|
+
};
|
|
657
|
+
for (const finding of report.findings) {
|
|
658
|
+
if (finding.severity === "pass") {
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
if (SERVICE_RISK_FINDING_CODES.has(finding.code)) {
|
|
662
|
+
pushUniqueAlert(buckets.serviceRisk, finding.summary);
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
if (DEGRADED_BRAIN_FINDING_CODES.has(finding.code)) {
|
|
666
|
+
pushUniqueAlert(buckets.degradedBrain, finding.summary);
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
if (COSMETIC_FINDING_CODES.has(finding.code)) {
|
|
670
|
+
pushUniqueAlert(buckets.cosmeticNoise, finding.summary);
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
pushUniqueAlert(finding.severity === "fail" ? buckets.serviceRisk : buckets.degradedBrain, finding.summary);
|
|
674
|
+
}
|
|
675
|
+
for (const warningState of report.learning.warningStates) {
|
|
676
|
+
const message = LEARNING_WARNING_MESSAGES[warningState];
|
|
677
|
+
if (message === undefined) {
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
if (warningState === "teacher_snapshot_unavailable") {
|
|
681
|
+
pushUniqueAlert(buckets.cosmeticNoise, message);
|
|
682
|
+
}
|
|
683
|
+
else {
|
|
684
|
+
pushUniqueAlert(buckets.degradedBrain, message);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
if (providerConfig.warnings.length > 0) {
|
|
688
|
+
pushUniqueAlert(buckets.cosmeticNoise, "provider env warnings forced fallback defaults");
|
|
689
|
+
}
|
|
690
|
+
if (localLlm.enabled && !localLlm.detected) {
|
|
691
|
+
pushUniqueAlert(buckets.degradedBrain, "local LLM is enabled but not detected");
|
|
692
|
+
}
|
|
693
|
+
if (embeddings.provider === "ollama" && embeddings.provisionedState !== "confirmed") {
|
|
694
|
+
pushUniqueAlert(buckets.degradedBrain, `embedder model ${embeddings.model} is not confirmed on Ollama`);
|
|
695
|
+
}
|
|
696
|
+
if (embeddings.provider === "ollama" && embeddings.liveState === "no") {
|
|
697
|
+
pushUniqueAlert(buckets.degradedBrain, "embedder is provisioned but the active pack has no live numeric embeddings");
|
|
698
|
+
}
|
|
699
|
+
return buckets;
|
|
700
|
+
}
|
|
701
|
+
function summarizeStatusWatchState(report) {
|
|
702
|
+
if (!report.teacherLoop.available || report.teacherLoop.sourceKind !== "watch_snapshot") {
|
|
703
|
+
return "not_visible";
|
|
704
|
+
}
|
|
705
|
+
return report.teacherLoop.running === true ? "running" : "snapshot_only";
|
|
706
|
+
}
|
|
707
|
+
function summarizeStatusServeReality(status) {
|
|
708
|
+
if (status.brainStatus.serveState === "serving_active_pack") {
|
|
709
|
+
return "proven_active_pack";
|
|
710
|
+
}
|
|
711
|
+
return status.brainStatus.serveState;
|
|
712
|
+
}
|
|
713
|
+
function formatStatusAlertLine(values) {
|
|
714
|
+
const normalized = values.map((value) => value.trim()).filter((value) => value.length > 0);
|
|
715
|
+
return normalized.length === 0 ? "none" : formatCompactList(normalized, 2, 64);
|
|
716
|
+
}
|
|
717
|
+
function summarizeStatusStartupToken(status) {
|
|
718
|
+
if (status.attachment.state !== "attached") {
|
|
719
|
+
return "BRAIN_NOT_YET_LOADED";
|
|
720
|
+
}
|
|
721
|
+
if (status.brainStatus.activationState === "broken_install" || status.brainStatus.activationState === "stale_incomplete" || status.brainStatus.activationState === "detached") {
|
|
722
|
+
return "BRAIN_NOT_YET_LOADED";
|
|
723
|
+
}
|
|
724
|
+
return status.brainStatus.serveState === "serving_active_pack" ? "BRAIN_LOADED" : "BRAIN_NOT_YET_LOADED";
|
|
725
|
+
}
|
|
726
|
+
function buildCompactStatusHeader(status, report, options) {
|
|
727
|
+
const installHook = summarizeStatusInstallHook(options.openclawHome);
|
|
728
|
+
const embeddings = summarizeStatusEmbeddings(report, options.providerConfig);
|
|
729
|
+
const localLlm = summarizeStatusLocalLlm(options.providerConfig);
|
|
730
|
+
const alerts = summarizeStatusAlerts(report, options.providerConfig, embeddings, localLlm);
|
|
731
|
+
const promoted = status.brain.state === "pg_promoted_pack_authoritative" ? "yes" : "no";
|
|
732
|
+
const liveModels = embeddings.models.length === 0 ? "none" : embeddings.models.join("|");
|
|
733
|
+
return [
|
|
734
|
+
`reality hook=${installHook.state} attach=${status.attachment.state} watch=${summarizeStatusWatchState(report)} promoted=${promoted} serve=${summarizeStatusServeReality(status)}`,
|
|
735
|
+
`startup ${summarizeStatusStartupToken(status)} init=${status.brainStatus.activationState} proof=status_probe`,
|
|
736
|
+
`explain ${status.brain.summary}`,
|
|
737
|
+
`embeddings provider=${embeddings.provider} provisioned=${embeddings.provisionedState} live=${embeddings.liveState} stored=${embeddings.embeddedEntryCount ?? "none"}/${embeddings.totalEntryCount ?? "none"} models=${liveModels}`,
|
|
738
|
+
`localLLM detected=${yesNo(localLlm.detected)} enabled=${yesNo(localLlm.enabled)} provider=${localLlm.provider} model=${localLlm.model}`,
|
|
739
|
+
`alerts service_risk=${formatStatusAlertLine(alerts.serviceRisk)} degraded_brain=${formatStatusAlertLine(alerts.degradedBrain)} cosmetic_noise=${formatStatusAlertLine(alerts.cosmeticNoise)}`
|
|
740
|
+
];
|
|
741
|
+
}
|
|
742
|
+
function formatCurrentProfileStatusSummary(status, report, targetInspection, options) {
|
|
403
743
|
const profileIdSuffix = status.profile.profileId === null ? "" : ` id=${status.profile.profileId}`;
|
|
744
|
+
const targetLine = targetInspection === null
|
|
745
|
+
? `target activation=${status.host.activationRoot} source=activation_root_only`
|
|
746
|
+
: `target activation=${status.host.activationRoot} ${formatOpenClawTargetLine(targetInspection)} hook=${shortenPath(path.join(targetInspection.openclawHome, "extensions", "openclawbrain", "index.ts"))}`;
|
|
404
747
|
return [
|
|
405
748
|
`STATUS ${status.brainStatus.status}`,
|
|
749
|
+
...buildCompactStatusHeader(status, report, options),
|
|
406
750
|
`answer ${status.brain.summary}`,
|
|
751
|
+
targetLine,
|
|
752
|
+
...(targetInspection === null ? [] : [`preflight ${formatOpenClawTargetExplanation(targetInspection)}`]),
|
|
407
753
|
`host runtime=${status.host.runtimeOwner} activation=${status.host.activationRoot}`,
|
|
408
754
|
`profile selector=${status.profile.selector}${profileIdSuffix} attachment=${status.attachment.state} policy=${status.attachment.policyMode}`,
|
|
409
755
|
`manyProfile surface=${report.manyProfile.operatorSurface} policy=${report.manyProfile.declaredAttachmentPolicy} intent=${report.manyProfile.sameGatewayIntent} checkedProof=${report.manyProfile.checkedInProofTopology} sameGatewayProof=${yesNo(report.manyProfile.sameGatewayProof)} sharedWriteProof=${yesNo(report.manyProfile.sharedWriteSafetyProof)}`,
|
|
@@ -419,7 +765,7 @@ function formatCurrentProfileStatusSummary(status, report) {
|
|
|
419
765
|
`graph source=${report.graph.runtimePlasticitySource ?? "none"} ops=${formatStructuralOps(report)} changed=${yesNo(report.graph.changed)} pruned=${report.graph.prunedBlockCount ?? "none"} strongest=${report.graph.strongestBlockId ?? "none"} summary=${report.graph.operatorSummary ?? report.graph.detail}`,
|
|
420
766
|
`path ${formatLearningPathSummary(report.learningPath)}`,
|
|
421
767
|
`learning state=${report.learning.backlogState} bootstrapped=${yesNo(report.learning.bootstrapped)} mode=${report.learning.mode} next=${report.learning.nextPriorityLane} priority=${report.learning.nextPriorityBucket} pending=${report.learning.pendingLive ?? "none"}/${report.learning.pendingBackfill ?? "none"} buckets=${formatLearningBuckets(report)} warn=${formatLearningWarnings(report)} lastPack=${report.learning.lastMaterializedPackId ?? "none"} detail=${report.learning.detail}`,
|
|
422
|
-
`teacher
|
|
768
|
+
`teacher ${formatTeacherLoopSummary(report)}`,
|
|
423
769
|
`passive cadence=${report.teacherLoop.learningCadence} scan=${report.teacherLoop.scanPolicy} slices=${report.teacherLoop.liveSlicesPerCycle ?? "none"}/${report.teacherLoop.backfillSlicesPerCycle ?? "none"} replayed=${report.teacherLoop.replayedBundleCount ?? "none"}/${report.teacherLoop.replayedEventCount ?? "none"} exported=${report.teacherLoop.exportedBundleCount ?? "none"}/${report.teacherLoop.exportedEventCount ?? "none"} tail=${report.teacherLoop.sessionTailSessionsTracked ?? "none"}/${report.teacherLoop.sessionTailBridgedEventCount ?? "none"} tailState=${report.teacherLoop.localSessionTailNoopReason ?? "none"} lastJob=${report.teacherLoop.lastAppliedMaterializationJobId ?? "none"} lastPack=${report.teacherLoop.lastMaterializedPackId ?? "none"}`,
|
|
424
770
|
`rollback ready=${yesNo(report.rollback.allowed)} state=${report.rollback.state} previous=${report.rollback.previousPackId ?? "none"}`,
|
|
425
771
|
`proof lastExport=${status.brain.lastExportAt ?? "none"} lastLearningUpdate=${status.brain.lastLearningUpdateAt ?? "none"} lastPromotion=${status.brain.lastPromotionAt ?? "none"}`,
|
|
@@ -437,15 +783,322 @@ function shortenPath(fullPath) {
|
|
|
437
783
|
}
|
|
438
784
|
return fullPath;
|
|
439
785
|
}
|
|
786
|
+
function formatOpenClawTargetLine(inspection) {
|
|
787
|
+
const profilePart = inspection.profileId === null
|
|
788
|
+
? "profile=current_profile"
|
|
789
|
+
: `profile=${inspection.profileId} via ${formatOpenClawHomeProfileSource(inspection.profileSource)}`;
|
|
790
|
+
return `home=${shortenPath(inspection.openclawHome)} layout=${formatOpenClawHomeLayout(inspection.layout)} ${profilePart}`;
|
|
791
|
+
}
|
|
792
|
+
function formatOpenClawTargetExplanation(inspection) {
|
|
793
|
+
return describeOpenClawHomeInspection(inspection);
|
|
794
|
+
}
|
|
440
795
|
function buildInstallStatusCommand(activationRoot) {
|
|
441
796
|
return `openclawbrain status --activation-root ${quoteShellArg(activationRoot)}`;
|
|
442
797
|
}
|
|
443
798
|
function buildInstallCommand(openclawHome) {
|
|
444
799
|
return `openclawbrain install --openclaw-home ${quoteShellArg(openclawHome)}`;
|
|
445
800
|
}
|
|
801
|
+
function buildAttachCommand(openclawHome, activationRoot = null) {
|
|
802
|
+
const parts = ["openclawbrain", "attach", "--openclaw-home", quoteShellArg(openclawHome)];
|
|
803
|
+
if (activationRoot !== null) {
|
|
804
|
+
parts.push("--activation-root", quoteShellArg(activationRoot));
|
|
805
|
+
}
|
|
806
|
+
return parts.join(" ");
|
|
807
|
+
}
|
|
808
|
+
function buildInstallEmbedderProvisionCommand(baseUrl, model) {
|
|
809
|
+
return `OLLAMA_HOST=${quoteShellArg(baseUrl)} ollama pull ${quoteShellArg(model)}`;
|
|
810
|
+
}
|
|
811
|
+
function describeExecOutput(value) {
|
|
812
|
+
if (typeof value === "string") {
|
|
813
|
+
const normalized = value.trim();
|
|
814
|
+
return normalized.length > 0 ? normalized : null;
|
|
815
|
+
}
|
|
816
|
+
if (value instanceof Buffer) {
|
|
817
|
+
const normalized = value.toString("utf8").trim();
|
|
818
|
+
return normalized.length > 0 ? normalized : null;
|
|
819
|
+
}
|
|
820
|
+
return null;
|
|
821
|
+
}
|
|
822
|
+
function describeExecFailure(error) {
|
|
823
|
+
if (error instanceof Error) {
|
|
824
|
+
const childError = error;
|
|
825
|
+
if (childError.code === "ENOENT") {
|
|
826
|
+
return "ollama was not found on PATH";
|
|
827
|
+
}
|
|
828
|
+
const stderr = describeExecOutput(childError.stderr);
|
|
829
|
+
if (stderr !== null) {
|
|
830
|
+
return stderr;
|
|
831
|
+
}
|
|
832
|
+
const stdout = describeExecOutput(childError.stdout);
|
|
833
|
+
if (stdout !== null) {
|
|
834
|
+
return stdout;
|
|
835
|
+
}
|
|
836
|
+
const message = childError.message.trim();
|
|
837
|
+
if (message.length > 0) {
|
|
838
|
+
return message;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
return String(error);
|
|
842
|
+
}
|
|
843
|
+
function toErrorMessage(error) {
|
|
844
|
+
return error instanceof Error ? error.message : String(error);
|
|
845
|
+
}
|
|
846
|
+
function ensureInstallEmbedderReady(parsed) {
|
|
847
|
+
const providerConfig = readOpenClawBrainProviderConfig(process.env);
|
|
848
|
+
const model = DEFAULT_OLLAMA_EMBEDDING_MODEL;
|
|
849
|
+
const baseUrl = providerConfig.embedderBaseUrl;
|
|
850
|
+
if (parsed.skipEmbedderProvision) {
|
|
851
|
+
const skipReason = parsed.skipEmbedderProvisionSource === "flag"
|
|
852
|
+
? "--skip-embedder-provision"
|
|
853
|
+
: `${OPENCLAWBRAIN_INSTALL_SKIP_EMBEDDER_PROVISION_ENV}=1`;
|
|
854
|
+
return {
|
|
855
|
+
state: "skipped",
|
|
856
|
+
model,
|
|
857
|
+
baseUrl,
|
|
858
|
+
detail: `Skipped default embedder provisioning (${skipReason}); ${parsed.command} continued only because the operator explicitly opted out. ` +
|
|
859
|
+
`Provision it later with ${buildInstallEmbedderProvisionCommand(baseUrl, model)}.`
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
try {
|
|
863
|
+
execFileSync("ollama", ["pull", model], {
|
|
864
|
+
stdio: "pipe",
|
|
865
|
+
env: {
|
|
866
|
+
...process.env,
|
|
867
|
+
OLLAMA_HOST: baseUrl
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
catch (error) {
|
|
872
|
+
const detail = describeExecFailure(error);
|
|
873
|
+
throw new Error(`Default embedder provisioning failed before brain init. Tried ${buildInstallEmbedderProvisionCommand(baseUrl, model)}. ` +
|
|
874
|
+
`${parsed.command === "install" ? "Install" : "Attach"} stops here so the bootstrap path does not quietly continue without ${model}. ` +
|
|
875
|
+
`Fix Ollama and rerun ${parsed.command}, or explicitly skip with --skip-embedder-provision or ${OPENCLAWBRAIN_INSTALL_SKIP_EMBEDDER_PROVISION_ENV}=1. ` +
|
|
876
|
+
`Detail: ${detail}`);
|
|
877
|
+
}
|
|
878
|
+
return {
|
|
879
|
+
state: "ensured",
|
|
880
|
+
model,
|
|
881
|
+
baseUrl,
|
|
882
|
+
detail: `Ensured default embedder before brain bootstrap: ${buildInstallEmbedderProvisionCommand(baseUrl, model)}`
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
function parseOllamaListModelNames(output) {
|
|
886
|
+
return output
|
|
887
|
+
.split(/\r?\n/u)
|
|
888
|
+
.map((line) => line.trim())
|
|
889
|
+
.filter((line) => line.length > 0 && !/^name\s+/iu.test(line))
|
|
890
|
+
.map((line) => line.split(/\s+/u)[0] ?? "")
|
|
891
|
+
.filter((name) => name.length > 0);
|
|
892
|
+
}
|
|
893
|
+
function selectCompatibleLocalTeacherModel(models) {
|
|
894
|
+
const normalized = models.map((model) => model.trim()).filter((model) => model.length > 0);
|
|
895
|
+
for (const prefix of INSTALL_COMPATIBLE_LOCAL_TEACHER_MODEL_PREFIXES) {
|
|
896
|
+
const exact = normalized.find((model) => model === prefix);
|
|
897
|
+
if (exact !== undefined) {
|
|
898
|
+
return exact;
|
|
899
|
+
}
|
|
900
|
+
const variant = normalized.find((model) => model.startsWith(`${prefix}-`) ||
|
|
901
|
+
model.startsWith(`${prefix}_`) ||
|
|
902
|
+
model.startsWith(`${prefix}.`));
|
|
903
|
+
if (variant !== undefined) {
|
|
904
|
+
return variant;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
return null;
|
|
908
|
+
}
|
|
909
|
+
function detectInstallTeacherDefaults(baseUrl) {
|
|
910
|
+
try {
|
|
911
|
+
const output = execFileSync("ollama", ["list"], {
|
|
912
|
+
stdio: "pipe",
|
|
913
|
+
env: {
|
|
914
|
+
...process.env,
|
|
915
|
+
OLLAMA_HOST: baseUrl
|
|
916
|
+
}
|
|
917
|
+
}).toString("utf8");
|
|
918
|
+
const availableModels = parseOllamaListModelNames(output);
|
|
919
|
+
const model = selectCompatibleLocalTeacherModel(availableModels);
|
|
920
|
+
if (model === null) {
|
|
921
|
+
return {
|
|
922
|
+
provider: "heuristic",
|
|
923
|
+
model: null,
|
|
924
|
+
baseUrl,
|
|
925
|
+
availableModels,
|
|
926
|
+
detectionDetail: availableModels.length === 0
|
|
927
|
+
? `No compatible local Ollama teacher model detected on ${baseUrl}; watch keeps heuristic teacher defaults.`
|
|
928
|
+
: `No compatible local Ollama teacher model detected on ${baseUrl}; saw ${availableModels.join(", ")} and kept heuristic teacher defaults.`
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
return {
|
|
932
|
+
provider: "ollama",
|
|
933
|
+
model,
|
|
934
|
+
baseUrl,
|
|
935
|
+
availableModels,
|
|
936
|
+
detectionDetail: `Detected compatible local Ollama teacher model ${model} on ${baseUrl}; watch will enable it by default from the installed activation root.`
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
catch (error) {
|
|
940
|
+
const detail = describeExecFailure(error);
|
|
941
|
+
return {
|
|
942
|
+
provider: "heuristic",
|
|
943
|
+
model: null,
|
|
944
|
+
baseUrl,
|
|
945
|
+
availableModels: [],
|
|
946
|
+
detectionDetail: `Local Ollama teacher autodetect failed on ${baseUrl}; kept heuristic teacher defaults. Detail: ${detail}`
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
function writeInstallProviderDefaults(parsed) {
|
|
951
|
+
const providerConfig = readOpenClawBrainProviderConfig(process.env);
|
|
952
|
+
const teacherDetection = detectInstallTeacherDefaults(providerConfig.teacherBaseUrl);
|
|
953
|
+
const defaultsPath = resolveOpenClawBrainProviderDefaultsPath(parsed.activationRoot);
|
|
954
|
+
const defaults = {
|
|
955
|
+
contract: "openclawbrain_provider_defaults.v1",
|
|
956
|
+
writtenAt: new Date().toISOString(),
|
|
957
|
+
source: "install",
|
|
958
|
+
teacherBaseUrl: providerConfig.teacherBaseUrl,
|
|
959
|
+
embedderBaseUrl: providerConfig.embedderBaseUrl,
|
|
960
|
+
teacher: {
|
|
961
|
+
provider: teacherDetection.provider,
|
|
962
|
+
model: teacherDetection.model,
|
|
963
|
+
detectedLocally: teacherDetection.provider === "ollama",
|
|
964
|
+
detectedFromModel: teacherDetection.model
|
|
965
|
+
},
|
|
966
|
+
embedder: {
|
|
967
|
+
provider: "ollama",
|
|
968
|
+
model: DEFAULT_OLLAMA_EMBEDDING_MODEL
|
|
969
|
+
}
|
|
970
|
+
};
|
|
971
|
+
writeFileSync(defaultsPath, JSON.stringify(defaults, null, 2) + "\n", "utf8");
|
|
972
|
+
return {
|
|
973
|
+
path: defaultsPath,
|
|
974
|
+
defaults,
|
|
975
|
+
detail: `Wrote local provider defaults: ${teacherDetection.detectionDetail}`,
|
|
976
|
+
lifecycleSummary: teacherDetection.provider === "ollama" && teacherDetection.model !== null
|
|
977
|
+
? `Teacher: auto-enabled local Ollama model ${teacherDetection.model} from install-written defaults`
|
|
978
|
+
: "Teacher: no compatible local Ollama model detected; watch stays heuristic unless explicitly overridden"
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
function buildInstallBrainFeedbackSummary(input) {
|
|
982
|
+
const providerDefaultsPath = resolveOpenClawBrainProviderDefaultsPath(input.parsed.activationRoot);
|
|
983
|
+
const embedderState = input.embedderProvision === null ? "unchanged" : input.embedderProvision.state;
|
|
984
|
+
const teacherDefaults = input.providerDefaults?.defaults.teacher;
|
|
985
|
+
const teacherProvider = teacherDefaults?.provider ?? "unknown";
|
|
986
|
+
const teacherModel = teacherDefaults?.model ?? null;
|
|
987
|
+
const detectedLocalLlm = teacherDefaults?.detectedLocally ?? null;
|
|
988
|
+
const provedNow = input.activationPlan.action === "bootstrap"
|
|
989
|
+
? `hook written, activation root ready, seed/current-profile attach bootstrapped, provider defaults ${input.providerDefaults === null ? "kept" : "written"}`
|
|
990
|
+
: `hook written, activation root kept, active pack ${input.activationPlan.activePackId ?? "unknown"} preserved${input.providerDefaults === null ? "" : ", provider defaults written"}`;
|
|
991
|
+
const notYetProved = input.activationPlan.action === "bootstrap"
|
|
992
|
+
? `OpenClaw has not reloaded this hook yet; restart plus status still must prove live startup/load and the first exported turn`
|
|
993
|
+
: `OpenClaw has not reloaded this hook yet; this ${input.parsed.command} run does not itself prove live startup/load after restart`;
|
|
994
|
+
return {
|
|
995
|
+
hookPath: input.extensionDir,
|
|
996
|
+
providerDefaultsPath,
|
|
997
|
+
embedder: {
|
|
998
|
+
provider: "ollama",
|
|
999
|
+
model: DEFAULT_OLLAMA_EMBEDDING_MODEL,
|
|
1000
|
+
state: embedderState
|
|
1001
|
+
},
|
|
1002
|
+
teacher: {
|
|
1003
|
+
provider: teacherProvider,
|
|
1004
|
+
model: teacherModel,
|
|
1005
|
+
detectedLocalLlm
|
|
1006
|
+
},
|
|
1007
|
+
startup: {
|
|
1008
|
+
token: "BRAIN_NOT_YET_LOADED",
|
|
1009
|
+
proof: "restart_required"
|
|
1010
|
+
},
|
|
1011
|
+
provedNow,
|
|
1012
|
+
notYetProved,
|
|
1013
|
+
lines: [
|
|
1014
|
+
`target ${formatOpenClawTargetLine(input.targetInspection)} source=${formatInstallOpenClawHomeSource(input.parsed.openclawHomeSource)}`,
|
|
1015
|
+
`hook written=${shortenPath(input.extensionDir)}`,
|
|
1016
|
+
`activation root=${shortenPath(input.parsed.activationRoot)} source=${formatInstallActivationRootSource(input.parsed.activationRootSource)}`,
|
|
1017
|
+
`defaults provider-defaults=${shortenPath(providerDefaultsPath)} state=${input.providerDefaults === null ? "unchanged" : "written"}`,
|
|
1018
|
+
`embedder provider=ollama model=${DEFAULT_OLLAMA_EMBEDDING_MODEL} state=${embedderState}`,
|
|
1019
|
+
`teacher provider=${teacherProvider} model=${teacherModel ?? "none"} localLLM=${detectedLocalLlm === null ? "unknown" : yesNo(detectedLocalLlm)}`,
|
|
1020
|
+
"startup BRAIN_NOT_YET_LOADED proof=restart_required",
|
|
1021
|
+
`provedNow ${provedNow}`,
|
|
1022
|
+
`notYet ${notYetProved}`
|
|
1023
|
+
]
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
446
1026
|
function buildInstallReloadGuidance() {
|
|
447
1027
|
return "If this OpenClaw profile is currently running, restart it before expecting the new brain hook to load. If it is stopped, the next launch will pick it up.";
|
|
448
1028
|
}
|
|
1029
|
+
const LEGACY_PROFILE_NOTE_FILENAMES = ["BRAIN.md", "brain.md"];
|
|
1030
|
+
const LEGACY_BRAIN_AGENTS_LINE = "5. Read `BRAIN.md` — your learning brain context";
|
|
1031
|
+
function isLegacyBrainAdvisoryContent(content) {
|
|
1032
|
+
return content.includes("## OpenClawBrain")
|
|
1033
|
+
&& content.includes("You have a learning brain attached at ")
|
|
1034
|
+
&& content.includes("openclawbrain status --activation-root")
|
|
1035
|
+
&& content.includes("openclawbrain rollback --activation-root");
|
|
1036
|
+
}
|
|
1037
|
+
function writeUpdatedTextFile(filePath, nextText, previousText) {
|
|
1038
|
+
const normalizedNextText = previousText.endsWith("\n") ? `${nextText}\n` : nextText;
|
|
1039
|
+
writeFileSync(filePath, normalizedNextText, "utf8");
|
|
1040
|
+
}
|
|
1041
|
+
function collectProfileResidueDirs(openclawHome) {
|
|
1042
|
+
const directories = [path.resolve(openclawHome)];
|
|
1043
|
+
try {
|
|
1044
|
+
const entries = readdirSync(openclawHome, { withFileTypes: true });
|
|
1045
|
+
for (const entry of entries) {
|
|
1046
|
+
if (entry.isDirectory() && entry.name.startsWith("workspace-")) {
|
|
1047
|
+
directories.push(path.join(openclawHome, entry.name));
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
catch {
|
|
1052
|
+
// Residue cleanup stays best-effort.
|
|
1053
|
+
}
|
|
1054
|
+
return directories;
|
|
1055
|
+
}
|
|
1056
|
+
function removeLegacyProfileResidue(openclawHome) {
|
|
1057
|
+
const removedNotes = [];
|
|
1058
|
+
const updatedAgents = [];
|
|
1059
|
+
for (const directory of collectProfileResidueDirs(openclawHome)) {
|
|
1060
|
+
for (const fileName of LEGACY_PROFILE_NOTE_FILENAMES) {
|
|
1061
|
+
const notePath = path.join(directory, fileName);
|
|
1062
|
+
if (!existsSync(notePath)) {
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
1065
|
+
try {
|
|
1066
|
+
const content = readFileSync(notePath, "utf8");
|
|
1067
|
+
if (!isLegacyBrainAdvisoryContent(content)) {
|
|
1068
|
+
continue;
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
catch {
|
|
1072
|
+
continue;
|
|
1073
|
+
}
|
|
1074
|
+
rmSync(notePath, { force: true });
|
|
1075
|
+
removedNotes.push(notePath);
|
|
1076
|
+
}
|
|
1077
|
+
const agentsPath = path.join(directory, "AGENTS.md");
|
|
1078
|
+
if (!existsSync(agentsPath)) {
|
|
1079
|
+
continue;
|
|
1080
|
+
}
|
|
1081
|
+
let agentsContent;
|
|
1082
|
+
try {
|
|
1083
|
+
agentsContent = readFileSync(agentsPath, "utf8");
|
|
1084
|
+
}
|
|
1085
|
+
catch {
|
|
1086
|
+
continue;
|
|
1087
|
+
}
|
|
1088
|
+
const nextContent = agentsContent
|
|
1089
|
+
.split("\n")
|
|
1090
|
+
.filter((line) => line.trim() !== LEGACY_BRAIN_AGENTS_LINE)
|
|
1091
|
+
.join("\n");
|
|
1092
|
+
if (nextContent !== agentsContent) {
|
|
1093
|
+
writeUpdatedTextFile(agentsPath, nextContent, agentsContent);
|
|
1094
|
+
updatedAgents.push(agentsPath);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
return {
|
|
1098
|
+
removedNotes,
|
|
1099
|
+
updatedAgents
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
449
1102
|
function buildCleanupRestartGuidance(restart) {
|
|
450
1103
|
if (restart === "never") {
|
|
451
1104
|
return "No restart requested. If this OpenClaw profile is currently running, it may keep the previous hook state until the next restart.";
|
|
@@ -478,43 +1131,16 @@ function buildStatusNextStep(status, report) {
|
|
|
478
1131
|
}
|
|
479
1132
|
return `Use \`openclawbrain status --activation-root ${activationRootArg} --detailed\` when you need the full lifecycle, serve-path, and backlog proof.`;
|
|
480
1133
|
}
|
|
481
|
-
function formatHumanFriendlyStatus(status, report) {
|
|
482
|
-
// Brain status line
|
|
483
|
-
const brainActive = status.brainStatus.status === "ok" || status.brainStatus.serveState === "serving_active_pack";
|
|
484
|
-
const brainIcon = brainActive ? "Active ✓" : status.brainStatus.status === "fail" ? "Inactive ✗" : `${status.brainStatus.status}`;
|
|
485
|
-
// Pack line
|
|
486
|
-
const packId = status.brain.activePackId ?? "none";
|
|
487
|
-
const packShort = packId.length > 9 ? packId.slice(0, 9) : packId;
|
|
488
|
-
const state = status.brain.state ?? "unknown";
|
|
489
|
-
// Activation root
|
|
490
|
-
const activationPath = shortenPath(status.host.activationRoot);
|
|
491
|
-
// Policy
|
|
492
|
-
const policy = status.attachment.policyMode ?? report.manyProfile.declaredAttachmentPolicy ?? "undeclared";
|
|
493
|
-
const principalFrontier = formatPrincipalCheckpointFrontier(report);
|
|
494
|
-
const pendingLive = String(report.learning.pendingLive ?? "none");
|
|
495
|
-
const pendingBackfill = String(report.learning.pendingBackfill ?? "none");
|
|
496
|
-
const nextLane = report.learning.nextPriorityLane ?? "none";
|
|
497
|
-
const nextBucket = report.learning.nextPriorityBucket ?? "none";
|
|
1134
|
+
function formatHumanFriendlyStatus(status, report, targetInspection, options) {
|
|
498
1135
|
const lines = [
|
|
499
|
-
`
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
`
|
|
506
|
-
`Labels: ${formatLabelFlowSummary(report.labelFlow)}`,
|
|
507
|
-
`Teacher: lastRun=${report.teacherLoop.lastRunAt ?? "none"} artifacts=${report.teacherLoop.artifactCount ?? "none"} exported=${report.teacherLoop.exportedBundleCount ?? "none"}/${report.teacherLoop.exportedEventCount ?? "none"} cadence=${report.teacherLoop.learningCadence}/${report.teacherLoop.scanPolicy} failure=${report.teacherLoop.failureMode}`,
|
|
508
|
-
`Learning: ${formatLearningPathSummary(report.learningPath)}`
|
|
1136
|
+
`STATUS ${status.brainStatus.status}`,
|
|
1137
|
+
...buildCompactStatusHeader(status, report, options),
|
|
1138
|
+
...(targetInspection === null ? [] : [
|
|
1139
|
+
`target ${formatOpenClawTargetLine(targetInspection)}`,
|
|
1140
|
+
`preflight ${formatOpenClawTargetExplanation(targetInspection)}`
|
|
1141
|
+
]),
|
|
1142
|
+
`next ${buildStatusNextStep(status, report)}`
|
|
509
1143
|
];
|
|
510
|
-
// Add learning/serve warnings if relevant
|
|
511
|
-
if (report.learning.warningStates.length > 0) {
|
|
512
|
-
lines.push(`Warnings: ${report.learning.warningStates.join(", ")}`);
|
|
513
|
-
}
|
|
514
|
-
if (status.brainStatus.awaitingFirstExport) {
|
|
515
|
-
lines.push(`Note: Awaiting first event export`);
|
|
516
|
-
}
|
|
517
|
-
lines.push(`Next: ${buildStatusNextStep(status, report)}`);
|
|
518
1144
|
return lines.join("\n");
|
|
519
1145
|
}
|
|
520
1146
|
function requireActivationRoot(input, openclawHome, command) {
|
|
@@ -560,10 +1186,18 @@ function formatScanSessionSummary(result) {
|
|
|
560
1186
|
function formatScanLiveSummary(result, snapshotOutPath) {
|
|
561
1187
|
const materializedPackId = result.snapshot.learner.lastMaterialization?.candidate.summary.packId ?? "none";
|
|
562
1188
|
const materializationReason = result.snapshot.learner.lastMaterialization?.reason ?? "none";
|
|
1189
|
+
const teacherSummary = [
|
|
1190
|
+
`artifacts=${result.snapshot.teacher.artifactCount}`,
|
|
1191
|
+
`freshness=${result.snapshot.teacher.latestFreshness}`,
|
|
1192
|
+
`humanLabels=${result.supervision.humanLabelCount}`
|
|
1193
|
+
];
|
|
1194
|
+
if (result.snapshot.diagnostics.lastNoOpReason !== "none") {
|
|
1195
|
+
teacherSummary.push(`noop=${result.snapshot.diagnostics.lastNoOpReason}`);
|
|
1196
|
+
}
|
|
563
1197
|
return [
|
|
564
1198
|
"SCAN live ok",
|
|
565
1199
|
`source digest=${result.supervision.exportDigest} session=${result.supervision.sessionId ?? "none"} channel=${result.supervision.channel ?? "none"} range=${result.supervision.eventRange.start}-${result.supervision.eventRange.end}/${result.supervision.eventRange.count}`,
|
|
566
|
-
`teacher
|
|
1200
|
+
`teacher ${teacherSummary.join(" ")}`,
|
|
567
1201
|
`labels source=${result.labelFlow.source} human=${result.labelFlow.humanLabelCount ?? "none"} self=${result.labelFlow.selfLabelCount ?? "none"} implicitPositive=${result.labelFlow.implicitPositiveCount ?? "none"} teacherArtifacts=${result.labelFlow.asyncTeacherArtifactCount ?? "none"}`,
|
|
568
1202
|
`path source=${result.learningPath.source} pg=${result.learningPath.policyGradientVersion} method=${result.learningPath.policyGradientMethod ?? "none"} target=${result.learningPath.targetConstruction ?? "none"} connect=${result.learningPath.connectOpsFired ?? "none"} trajectories=${result.learningPath.reconstructedTrajectoryCount ?? "none"}`,
|
|
569
1203
|
`learner packLabel=${result.packLabel} materialized=${materializedPackId} reason=${materializationReason}`,
|
|
@@ -583,12 +1217,12 @@ export function parseOperatorCliArgs(argv) {
|
|
|
583
1217
|
let rootDir = null;
|
|
584
1218
|
let workspacePath = null;
|
|
585
1219
|
let packLabel = null;
|
|
586
|
-
let packRoot = null;
|
|
587
1220
|
let workspaceId = null;
|
|
588
1221
|
let observedAt = null;
|
|
589
1222
|
let snapshotOutPath = null;
|
|
590
1223
|
let openclawHome = null;
|
|
591
1224
|
let shared = false;
|
|
1225
|
+
let skipEmbedderProvision = false;
|
|
592
1226
|
let keepData = false;
|
|
593
1227
|
let purgeData = false;
|
|
594
1228
|
let restart = "safe";
|
|
@@ -992,6 +1626,10 @@ export function parseOperatorCliArgs(argv) {
|
|
|
992
1626
|
shared = true;
|
|
993
1627
|
continue;
|
|
994
1628
|
}
|
|
1629
|
+
if (arg === "--skip-embedder-provision") {
|
|
1630
|
+
skipEmbedderProvision = true;
|
|
1631
|
+
continue;
|
|
1632
|
+
}
|
|
995
1633
|
if (arg === "--keep-data") {
|
|
996
1634
|
keepData = true;
|
|
997
1635
|
continue;
|
|
@@ -1125,14 +1763,6 @@ export function parseOperatorCliArgs(argv) {
|
|
|
1125
1763
|
index += 1;
|
|
1126
1764
|
continue;
|
|
1127
1765
|
}
|
|
1128
|
-
if (arg === "--pack-root") {
|
|
1129
|
-
if (next === undefined) {
|
|
1130
|
-
throw new Error("--pack-root requires a value");
|
|
1131
|
-
}
|
|
1132
|
-
packRoot = next;
|
|
1133
|
-
index += 1;
|
|
1134
|
-
continue;
|
|
1135
|
-
}
|
|
1136
1766
|
if (arg === "--workspace-id") {
|
|
1137
1767
|
if (next === undefined) {
|
|
1138
1768
|
throw new Error("--workspace-id requires a value");
|
|
@@ -1146,12 +1776,27 @@ export function parseOperatorCliArgs(argv) {
|
|
|
1146
1776
|
if (command !== "detach" && command !== "uninstall" && restartExplicitlySet) {
|
|
1147
1777
|
throw new Error("--restart only applies to detach/uninstall");
|
|
1148
1778
|
}
|
|
1779
|
+
if (command !== "install" && command !== "attach" && shared) {
|
|
1780
|
+
throw new Error("--shared only applies to install/attach");
|
|
1781
|
+
}
|
|
1782
|
+
if (command !== "install" && command !== "attach" && skipEmbedderProvision) {
|
|
1783
|
+
throw new Error("--skip-embedder-provision only applies to install/attach");
|
|
1784
|
+
}
|
|
1149
1785
|
if (command !== "uninstall" && keepData) {
|
|
1150
1786
|
throw new Error("--keep-data only applies to uninstall; use detach to preserve activation data");
|
|
1151
1787
|
}
|
|
1152
1788
|
if (command !== "uninstall" && purgeData) {
|
|
1153
1789
|
throw new Error("--purge-data only applies to uninstall");
|
|
1154
1790
|
}
|
|
1791
|
+
if (command !== "install" && command !== "attach" && workspaceId !== null) {
|
|
1792
|
+
throw new Error("--workspace-id only applies to install/attach");
|
|
1793
|
+
}
|
|
1794
|
+
if (command !== "scan" && packLabel !== null) {
|
|
1795
|
+
throw new Error("--pack-label only applies to scan --live");
|
|
1796
|
+
}
|
|
1797
|
+
if ((command === "install" || command === "attach") && brainAttachmentPolicy !== null) {
|
|
1798
|
+
throw new Error(`${command} uses dedicated by default or --shared for shared mode; --brain-attachment-policy only applies to status/rollback inspection`);
|
|
1799
|
+
}
|
|
1155
1800
|
if (command === "install") {
|
|
1156
1801
|
if (help) {
|
|
1157
1802
|
return {
|
|
@@ -1163,6 +1808,8 @@ export function parseOperatorCliArgs(argv) {
|
|
|
1163
1808
|
shared: false,
|
|
1164
1809
|
workspaceId: "",
|
|
1165
1810
|
workspaceIdSource: "explicit",
|
|
1811
|
+
skipEmbedderProvision: false,
|
|
1812
|
+
skipEmbedderProvisionSource: null,
|
|
1166
1813
|
json,
|
|
1167
1814
|
help
|
|
1168
1815
|
};
|
|
@@ -1170,6 +1817,7 @@ export function parseOperatorCliArgs(argv) {
|
|
|
1170
1817
|
const resolvedOpenclawHome = resolveInstallOpenClawHome(openclawHome);
|
|
1171
1818
|
const resolvedActivationRoot = resolveInstallActivationRoot(resolvedOpenclawHome.openclawHome, activationRoot);
|
|
1172
1819
|
const resolvedWorkspaceId = resolveInstallWorkspaceId(resolvedOpenclawHome.openclawHome, workspaceId);
|
|
1820
|
+
const resolvedEmbedderProvisionSkip = resolveInstallEmbedderProvisionSkip(skipEmbedderProvision);
|
|
1173
1821
|
return {
|
|
1174
1822
|
command,
|
|
1175
1823
|
openclawHome: resolvedOpenclawHome.openclawHome,
|
|
@@ -1179,6 +1827,47 @@ export function parseOperatorCliArgs(argv) {
|
|
|
1179
1827
|
shared,
|
|
1180
1828
|
workspaceId: resolvedWorkspaceId.workspaceId,
|
|
1181
1829
|
workspaceIdSource: resolvedWorkspaceId.source,
|
|
1830
|
+
skipEmbedderProvision: resolvedEmbedderProvisionSkip.skipEmbedderProvision,
|
|
1831
|
+
skipEmbedderProvisionSource: resolvedEmbedderProvisionSkip.skipEmbedderProvisionSource,
|
|
1832
|
+
json,
|
|
1833
|
+
help
|
|
1834
|
+
};
|
|
1835
|
+
}
|
|
1836
|
+
if (command === "attach") {
|
|
1837
|
+
if (help) {
|
|
1838
|
+
return {
|
|
1839
|
+
command,
|
|
1840
|
+
openclawHome: "",
|
|
1841
|
+
openclawHomeSource: "explicit",
|
|
1842
|
+
activationRoot: "",
|
|
1843
|
+
activationRootSource: "explicit",
|
|
1844
|
+
shared: false,
|
|
1845
|
+
workspaceId: "",
|
|
1846
|
+
workspaceIdSource: "explicit",
|
|
1847
|
+
skipEmbedderProvision: false,
|
|
1848
|
+
skipEmbedderProvisionSource: null,
|
|
1849
|
+
json,
|
|
1850
|
+
help
|
|
1851
|
+
};
|
|
1852
|
+
}
|
|
1853
|
+
if (openclawHome === null || openclawHome.trim().length === 0) {
|
|
1854
|
+
throw new Error("--openclaw-home is required for attach; use install for the first-time default path");
|
|
1855
|
+
}
|
|
1856
|
+
const resolvedOpenclawHome = path.resolve(openclawHome);
|
|
1857
|
+
const resolvedActivationRoot = resolveInstallActivationRoot(resolvedOpenclawHome, activationRoot);
|
|
1858
|
+
const resolvedWorkspaceId = resolveInstallWorkspaceId(resolvedOpenclawHome, workspaceId);
|
|
1859
|
+
const resolvedEmbedderProvisionSkip = resolveInstallEmbedderProvisionSkip(skipEmbedderProvision);
|
|
1860
|
+
return {
|
|
1861
|
+
command,
|
|
1862
|
+
openclawHome: resolvedOpenclawHome,
|
|
1863
|
+
openclawHomeSource: "explicit",
|
|
1864
|
+
activationRoot: resolvedActivationRoot.activationRoot,
|
|
1865
|
+
activationRootSource: resolvedActivationRoot.source,
|
|
1866
|
+
shared,
|
|
1867
|
+
workspaceId: resolvedWorkspaceId.workspaceId,
|
|
1868
|
+
workspaceIdSource: resolvedWorkspaceId.source,
|
|
1869
|
+
skipEmbedderProvision: resolvedEmbedderProvisionSkip.skipEmbedderProvision,
|
|
1870
|
+
skipEmbedderProvisionSource: resolvedEmbedderProvisionSkip.skipEmbedderProvisionSource,
|
|
1182
1871
|
json,
|
|
1183
1872
|
help
|
|
1184
1873
|
};
|
|
@@ -1240,30 +1929,6 @@ export function parseOperatorCliArgs(argv) {
|
|
|
1240
1929
|
help
|
|
1241
1930
|
};
|
|
1242
1931
|
}
|
|
1243
|
-
if (command === "attach") {
|
|
1244
|
-
if (help) {
|
|
1245
|
-
return { command, activationRoot: "", packRoot: "", packLabel: "", workspaceId: "", brainAttachmentPolicy: null, json, help };
|
|
1246
|
-
}
|
|
1247
|
-
if (activationRoot === null || activationRoot.trim().length === 0) {
|
|
1248
|
-
throw new Error("--activation-root is required for attach");
|
|
1249
|
-
}
|
|
1250
|
-
const resolvedActivationRoot = path.resolve(activationRoot);
|
|
1251
|
-
const resolvedPackRoot = packRoot !== null
|
|
1252
|
-
? path.resolve(packRoot)
|
|
1253
|
-
: path.resolve(resolvedActivationRoot, "packs", "initial");
|
|
1254
|
-
const resolvedWorkspaceId = workspaceId ?? "workspace";
|
|
1255
|
-
const resolvedPackLabel = packLabel ?? "cli-attach";
|
|
1256
|
-
return {
|
|
1257
|
-
command,
|
|
1258
|
-
activationRoot: resolvedActivationRoot,
|
|
1259
|
-
packRoot: resolvedPackRoot,
|
|
1260
|
-
packLabel: resolvedPackLabel,
|
|
1261
|
-
workspaceId: resolvedWorkspaceId,
|
|
1262
|
-
brainAttachmentPolicy: brainAttachmentPolicy,
|
|
1263
|
-
json,
|
|
1264
|
-
help
|
|
1265
|
-
};
|
|
1266
|
-
}
|
|
1267
1932
|
if (command === "scan") {
|
|
1268
1933
|
if ((sessionPath === null && livePath === null) || (sessionPath !== null && livePath !== null)) {
|
|
1269
1934
|
throw new Error("scan requires exactly one of --session or --live");
|
|
@@ -1347,6 +2012,7 @@ function resolveExtensionRuntimeGuardPath() {
|
|
|
1347
2012
|
path.resolve(__dirname, "..", "..", "extension", "runtime-guard.ts"),
|
|
1348
2013
|
];
|
|
1349
2014
|
const jsCandidates = [
|
|
2015
|
+
path.resolve(__dirname, "..", "dist", "extension", "runtime-guard.js"),
|
|
1350
2016
|
path.resolve(__dirname, "extension", "runtime-guard.js"),
|
|
1351
2017
|
path.resolve(__dirname, "..", "extension", "runtime-guard.js"),
|
|
1352
2018
|
];
|
|
@@ -1736,15 +2402,33 @@ function runHistoryCommand(parsed) {
|
|
|
1736
2402
|
}
|
|
1737
2403
|
return 0;
|
|
1738
2404
|
}
|
|
1739
|
-
function
|
|
2405
|
+
function runProfileHookAttachCommand(parsed) {
|
|
1740
2406
|
const steps = [];
|
|
1741
2407
|
const commandLabel = parsed.command.toUpperCase();
|
|
1742
|
-
|
|
2408
|
+
const isInstall = parsed.command === "install";
|
|
2409
|
+
const targetInspection = inspectOpenClawHome(parsed.openclawHome);
|
|
2410
|
+
const extensionDir = path.join(parsed.openclawHome, "extensions", "openclawbrain");
|
|
2411
|
+
steps.push(`Target OpenClaw home: ${parsed.openclawHome} (${formatInstallOpenClawHomeSource(parsed.openclawHomeSource)})`);
|
|
2412
|
+
steps.push(isInstall
|
|
2413
|
+
? "Lifecycle mode: install is the safe first-time default for wiring one profile to one activation root."
|
|
2414
|
+
: "Lifecycle mode: attach is the explicit reattach/manual profile-hook path; use install for first-time setup.");
|
|
2415
|
+
steps.push(`Detected layout: ${formatOpenClawTargetExplanation(targetInspection)}`);
|
|
2416
|
+
steps.push(`Target hook path: ${extensionDir}`);
|
|
1743
2417
|
// 1. Validate --openclaw-home exists and has openclaw.json
|
|
1744
2418
|
validateOpenClawHome(parsed.openclawHome);
|
|
1745
2419
|
// 2. Inspect the activation root before writing profile hook artifacts.
|
|
1746
2420
|
const activationPlan = inspectInstallActivationPlan(parsed);
|
|
1747
|
-
// 3.
|
|
2421
|
+
// 3. Ensure the default embedder exists before bootstrap unless the operator explicitly opts out.
|
|
2422
|
+
const embedderProvision = activationPlan.action === "bootstrap"
|
|
2423
|
+
? ensureInstallEmbedderReady(parsed)
|
|
2424
|
+
: null;
|
|
2425
|
+
if (embedderProvision === null) {
|
|
2426
|
+
steps.push("Skipped bootstrap-time embedder provisioning because attach/install is reusing healthy activation state.");
|
|
2427
|
+
}
|
|
2428
|
+
else {
|
|
2429
|
+
steps.push(embedderProvision.detail);
|
|
2430
|
+
}
|
|
2431
|
+
// 4. Create activation root if needed
|
|
1748
2432
|
if (activationPlan.createActivationRoot) {
|
|
1749
2433
|
mkdirSync(parsed.activationRoot, { recursive: true });
|
|
1750
2434
|
steps.push(`Created activation root: ${parsed.activationRoot}`);
|
|
@@ -1753,7 +2437,17 @@ function runInstallCommand(parsed) {
|
|
|
1753
2437
|
steps.push(`Activation root exists: ${parsed.activationRoot}`);
|
|
1754
2438
|
}
|
|
1755
2439
|
steps.push(activationPlan.inspectionStep);
|
|
1756
|
-
//
|
|
2440
|
+
// 5. Persist install-written local provider defaults so watch/learning surfaces do not depend on gateway env wiring.
|
|
2441
|
+
const providerDefaults = isInstall || activationPlan.action === "bootstrap"
|
|
2442
|
+
? writeInstallProviderDefaults(parsed)
|
|
2443
|
+
: null;
|
|
2444
|
+
if (providerDefaults === null) {
|
|
2445
|
+
steps.push("Skipped provider-default refresh because explicit attach is reusing existing activation data.");
|
|
2446
|
+
}
|
|
2447
|
+
else {
|
|
2448
|
+
steps.push(providerDefaults.detail);
|
|
2449
|
+
}
|
|
2450
|
+
// 6. Bootstrap only for safe empty first-state roots; otherwise keep the inspected healthy state.
|
|
1757
2451
|
if (activationPlan.action === "bootstrap") {
|
|
1758
2452
|
const packRoot = path.resolve(parsed.activationRoot, "packs", "initial");
|
|
1759
2453
|
mkdirSync(packRoot, { recursive: true });
|
|
@@ -1763,13 +2457,13 @@ function runInstallCommand(parsed) {
|
|
|
1763
2457
|
brainAttachmentPolicy,
|
|
1764
2458
|
activationRoot: parsed.activationRoot,
|
|
1765
2459
|
packRoot,
|
|
1766
|
-
packLabel: "install-cli",
|
|
2460
|
+
packLabel: isInstall ? "install-cli" : "attach-cli",
|
|
1767
2461
|
workspace: {
|
|
1768
2462
|
workspaceId: parsed.workspaceId,
|
|
1769
|
-
snapshotId: `${parsed.workspaceId}
|
|
2463
|
+
snapshotId: `${parsed.workspaceId}@${parsed.command}-${new Date().toISOString().slice(0, 10)}`,
|
|
1770
2464
|
capturedAt: new Date().toISOString(),
|
|
1771
2465
|
rootDir: parsed.openclawHome,
|
|
1772
|
-
revision: "cli-install-v1"
|
|
2466
|
+
revision: isInstall ? "cli-install-v1" : "cli-attach-v1"
|
|
1773
2467
|
},
|
|
1774
2468
|
interactionEvents: [],
|
|
1775
2469
|
feedbackEvents: []
|
|
@@ -1777,10 +2471,11 @@ function runInstallCommand(parsed) {
|
|
|
1777
2471
|
steps.push(`Bootstrapped brain attach: state=${result.currentProfile.brain.state} awaitingFirstExport=${yesNo(result.currentProfile.brainStatus.awaitingFirstExport)}`);
|
|
1778
2472
|
}
|
|
1779
2473
|
else {
|
|
1780
|
-
steps.push(
|
|
2474
|
+
steps.push(isInstall
|
|
2475
|
+
? `Kept inspected activation state: active pack ${activationPlan.activePackId}`
|
|
2476
|
+
: `Reused inspected activation state for explicit attach: active pack ${activationPlan.activePackId}`);
|
|
1781
2477
|
}
|
|
1782
|
-
//
|
|
1783
|
-
const extensionDir = path.join(parsed.openclawHome, "extensions", "openclawbrain");
|
|
2478
|
+
// 7-10. Write extension files
|
|
1784
2479
|
mkdirSync(extensionDir, { recursive: true });
|
|
1785
2480
|
// 5. Write index.ts
|
|
1786
2481
|
const indexTsPath = path.join(extensionDir, "index.ts");
|
|
@@ -1833,91 +2528,40 @@ function runInstallCommand(parsed) {
|
|
|
1833
2528
|
const manifestPath = path.join(extensionDir, "openclaw.plugin.json");
|
|
1834
2529
|
writeFileSync(manifestPath, buildExtensionPluginManifest(), "utf8");
|
|
1835
2530
|
steps.push(`Wrote manifest: ${manifestPath}`);
|
|
1836
|
-
// 9. Write BRAIN.md to workspace directories
|
|
1837
|
-
const brainMdContent = [
|
|
1838
|
-
"## OpenClawBrain",
|
|
1839
|
-
`You have a learning brain attached at ${parsed.activationRoot}.`,
|
|
1840
|
-
"- It learns automatically from your conversations",
|
|
1841
|
-
'- Corrections matter — "no, actually X" teaches the brain X',
|
|
1842
|
-
"- You don't manage it — background daemon handles learning",
|
|
1843
|
-
`- Check: \`openclawbrain status --activation-root ${quoteShellArg(parsed.activationRoot)}\``,
|
|
1844
|
-
`- Rollback: \`openclawbrain rollback --activation-root ${quoteShellArg(parsed.activationRoot)}\``,
|
|
1845
|
-
'- See what brain knows: `openclawbrain context "your question"`',
|
|
1846
|
-
""
|
|
1847
|
-
].join("\n");
|
|
1848
|
-
const agentsMdBrainRef = "\n5. Read `BRAIN.md` — your learning brain context\n";
|
|
1849
|
-
try {
|
|
1850
|
-
const entries = readdirSync(parsed.openclawHome, { withFileTypes: true });
|
|
1851
|
-
const workspaceDirs = entries
|
|
1852
|
-
.filter(e => e.isDirectory() && e.name.startsWith("workspace-"))
|
|
1853
|
-
.map(e => path.join(parsed.openclawHome, e.name));
|
|
1854
|
-
// If no workspace-* dirs found, check if openclawHome itself is a workspace
|
|
1855
|
-
if (workspaceDirs.length === 0) {
|
|
1856
|
-
workspaceDirs.push(parsed.openclawHome);
|
|
1857
|
-
}
|
|
1858
|
-
for (const wsDir of workspaceDirs) {
|
|
1859
|
-
const brainMdPath = path.join(wsDir, "BRAIN.md");
|
|
1860
|
-
writeFileSync(brainMdPath, brainMdContent, "utf8");
|
|
1861
|
-
steps.push(`Wrote BRAIN.md: ${brainMdPath}`);
|
|
1862
|
-
// If AGENTS.md exists, append brain reference to startup sequence
|
|
1863
|
-
const agentsMdPath = path.join(wsDir, "AGENTS.md");
|
|
1864
|
-
if (existsSync(agentsMdPath)) {
|
|
1865
|
-
const agentsContent = readFileSync(agentsMdPath, "utf8");
|
|
1866
|
-
if (!agentsContent.includes("BRAIN.md")) {
|
|
1867
|
-
// Find the startup sequence section and append after the last numbered item
|
|
1868
|
-
const startupMarker = "## Session Startup";
|
|
1869
|
-
if (agentsContent.includes(startupMarker)) {
|
|
1870
|
-
// Find the numbered list in the startup section and append after last item
|
|
1871
|
-
const lines = agentsContent.split("\n");
|
|
1872
|
-
let lastNumberedIdx = -1;
|
|
1873
|
-
let inStartup = false;
|
|
1874
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1875
|
-
const line = lines[i] ?? "";
|
|
1876
|
-
if (line.includes(startupMarker)) {
|
|
1877
|
-
inStartup = true;
|
|
1878
|
-
continue;
|
|
1879
|
-
}
|
|
1880
|
-
if (inStartup && /^\d+\.\s/.test(line.trim())) {
|
|
1881
|
-
lastNumberedIdx = i;
|
|
1882
|
-
}
|
|
1883
|
-
if (inStartup && line.startsWith("## ") && !line.includes(startupMarker)) {
|
|
1884
|
-
break;
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
1887
|
-
if (lastNumberedIdx >= 0) {
|
|
1888
|
-
lines.splice(lastNumberedIdx + 1, 0, agentsMdBrainRef.trimEnd());
|
|
1889
|
-
writeFileSync(agentsMdPath, lines.join("\n"), "utf8");
|
|
1890
|
-
steps.push(`Updated AGENTS.md startup sequence: ${agentsMdPath}`);
|
|
1891
|
-
}
|
|
1892
|
-
else {
|
|
1893
|
-
appendFileSync(agentsMdPath, agentsMdBrainRef, "utf8");
|
|
1894
|
-
steps.push(`Appended BRAIN.md reference to AGENTS.md: ${agentsMdPath}`);
|
|
1895
|
-
}
|
|
1896
|
-
}
|
|
1897
|
-
else {
|
|
1898
|
-
appendFileSync(agentsMdPath, agentsMdBrainRef, "utf8");
|
|
1899
|
-
steps.push(`Appended BRAIN.md reference to AGENTS.md: ${agentsMdPath}`);
|
|
1900
|
-
}
|
|
1901
|
-
}
|
|
1902
|
-
else {
|
|
1903
|
-
steps.push(`AGENTS.md already references BRAIN.md: ${agentsMdPath}`);
|
|
1904
|
-
}
|
|
1905
|
-
}
|
|
1906
|
-
}
|
|
1907
|
-
}
|
|
1908
|
-
catch (err) {
|
|
1909
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1910
|
-
steps.push(`BRAIN.md generation failed (non-fatal): ${message}`);
|
|
1911
|
-
}
|
|
1912
2531
|
const restartGuidance = buildInstallReloadGuidance();
|
|
1913
2532
|
const nextSteps = [
|
|
1914
2533
|
restartGuidance,
|
|
1915
|
-
`Check status: ${buildInstallStatusCommand(parsed.activationRoot)}
|
|
2534
|
+
`Check status: ${buildInstallStatusCommand(parsed.activationRoot)}`,
|
|
2535
|
+
embedderProvision !== null && embedderProvision.state === "skipped"
|
|
2536
|
+
? `Provision default embedder later: ${buildInstallEmbedderProvisionCommand(embedderProvision.baseUrl, embedderProvision.model)}`
|
|
2537
|
+
: null
|
|
2538
|
+
].filter((step) => step !== null);
|
|
2539
|
+
const preflightSummary = [
|
|
2540
|
+
`Hook: installed at ${shortenPath(extensionDir)}`,
|
|
2541
|
+
activationPlan.action === "bootstrap"
|
|
2542
|
+
? "Attachment: seed/current-profile attach created; restart plus status will prove later serve-path use"
|
|
2543
|
+
: `Attachment: existing active pack ${activationPlan.activePackId} kept in place; restart plus status will prove later serve-path use`,
|
|
2544
|
+
embedderProvision === null
|
|
2545
|
+
? "Embedder: unchanged because no bootstrap was needed"
|
|
2546
|
+
: embedderProvision.state === "ensured"
|
|
2547
|
+
? `Embedder: default Ollama model ${embedderProvision.model} was ensured before bootstrap`
|
|
2548
|
+
: `Embedder: default Ollama model ${embedderProvision.model} was intentionally skipped`,
|
|
2549
|
+
`Serve path: install alone does not prove serving; restart the profile and run ${buildInstallStatusCommand(parsed.activationRoot)}`
|
|
1916
2550
|
];
|
|
1917
2551
|
const lifecycleSummary = [
|
|
1918
|
-
|
|
2552
|
+
isInstall
|
|
2553
|
+
? "Lifecycle mode: install (safe first-time/default profile hookup)"
|
|
2554
|
+
: "Lifecycle mode: attach (explicit reattach/manual profile hookup)",
|
|
2555
|
+
`OpenClaw target: ${shortenPath(parsed.openclawHome)} (${formatInstallOpenClawHomeSource(parsed.openclawHomeSource)})`,
|
|
2556
|
+
`Detected layout: ${formatOpenClawTargetExplanation(targetInspection)}`,
|
|
1919
2557
|
`Activation root: ${shortenPath(parsed.activationRoot)} (${formatInstallActivationRootSource(parsed.activationRootSource)})`,
|
|
1920
2558
|
`Workspace ID: ${parsed.workspaceId} (${formatInstallWorkspaceIdSource(parsed.workspaceIdSource)})`,
|
|
2559
|
+
embedderProvision === null
|
|
2560
|
+
? "Embedder: unchanged because no bootstrap was needed"
|
|
2561
|
+
: embedderProvision.state === "ensured"
|
|
2562
|
+
? `Embedder: ensured default Ollama model ${embedderProvision.model} before brain init`
|
|
2563
|
+
: `Embedder: skipped default Ollama model ${embedderProvision.model} via ${parsed.skipEmbedderProvisionSource === "flag" ? "--skip-embedder-provision" : OPENCLAWBRAIN_INSTALL_SKIP_EMBEDDER_PROVISION_ENV}`,
|
|
2564
|
+
...(providerDefaults === null ? [] : [`${providerDefaults.lifecycleSummary} (${shortenPath(providerDefaults.path)})`]),
|
|
1921
2565
|
`Profile hook: installed at ${shortenPath(extensionDir)}`,
|
|
1922
2566
|
activationPlan.resolution === "new_root"
|
|
1923
2567
|
? `Activation data: initialized at ${shortenPath(parsed.activationRoot)}`
|
|
@@ -1928,18 +2572,35 @@ function runInstallCommand(parsed) {
|
|
|
1928
2572
|
: `Activation data: reused healthy state at ${shortenPath(parsed.activationRoot)}`,
|
|
1929
2573
|
activationPlan.action === "bootstrap"
|
|
1930
2574
|
? activationPlan.resolution === "new_root"
|
|
1931
|
-
? "
|
|
2575
|
+
? `${isInstall ? "Install" : "Attach"}: bootstrapped a seed/current-profile brain`
|
|
1932
2576
|
: activationPlan.resolution === "missing_pointers"
|
|
1933
|
-
? "
|
|
1934
|
-
: "
|
|
1935
|
-
:
|
|
2577
|
+
? `${isInstall ? "Install" : "Attach"}: repaired missing activation pointers and bootstrapped a seed/current-profile brain`
|
|
2578
|
+
: `${isInstall ? "Install" : "Attach"}: repaired empty activation pointers and bootstrapped a seed/current-profile brain`
|
|
2579
|
+
: isInstall
|
|
2580
|
+
? `Install: kept healthy active pack ${activationPlan.activePackId} in place`
|
|
2581
|
+
: `Attach: rewired the profile hook to healthy active pack ${activationPlan.activePackId}`
|
|
1936
2582
|
];
|
|
2583
|
+
const brainFeedback = buildInstallBrainFeedbackSummary({
|
|
2584
|
+
parsed,
|
|
2585
|
+
targetInspection,
|
|
2586
|
+
extensionDir,
|
|
2587
|
+
activationPlan,
|
|
2588
|
+
embedderProvision,
|
|
2589
|
+
providerDefaults
|
|
2590
|
+
});
|
|
1937
2591
|
// 9. Print summary
|
|
1938
2592
|
if (parsed.json) {
|
|
1939
2593
|
console.log(JSON.stringify({
|
|
1940
2594
|
command: parsed.command,
|
|
1941
2595
|
openclawHome: parsed.openclawHome,
|
|
1942
2596
|
openclawHomeSource: parsed.openclawHomeSource,
|
|
2597
|
+
openclawTarget: {
|
|
2598
|
+
layout: targetInspection.layout,
|
|
2599
|
+
detail: describeOpenClawHomeInspection(targetInspection),
|
|
2600
|
+
profileId: targetInspection.profileId,
|
|
2601
|
+
profileSource: targetInspection.profileSource,
|
|
2602
|
+
configuredProfileIds: targetInspection.configuredProfileIds
|
|
2603
|
+
},
|
|
1943
2604
|
activationRoot: parsed.activationRoot,
|
|
1944
2605
|
resolvedInputs: {
|
|
1945
2606
|
activationRoot: {
|
|
@@ -1953,8 +2614,47 @@ function runInstallCommand(parsed) {
|
|
|
1953
2614
|
},
|
|
1954
2615
|
workspaceId: parsed.workspaceId,
|
|
1955
2616
|
shared: parsed.shared,
|
|
2617
|
+
embedderProvision: embedderProvision === null
|
|
2618
|
+
? null
|
|
2619
|
+
: {
|
|
2620
|
+
skipped: parsed.skipEmbedderProvision,
|
|
2621
|
+
source: parsed.skipEmbedderProvisionSource,
|
|
2622
|
+
model: embedderProvision.model,
|
|
2623
|
+
baseUrl: embedderProvision.baseUrl
|
|
2624
|
+
},
|
|
2625
|
+
providerDefaults: providerDefaults === null
|
|
2626
|
+
? null
|
|
2627
|
+
: {
|
|
2628
|
+
path: providerDefaults.path,
|
|
2629
|
+
teacher: providerDefaults.defaults.teacher === undefined
|
|
2630
|
+
? null
|
|
2631
|
+
: {
|
|
2632
|
+
provider: providerDefaults.defaults.teacher.provider ?? null,
|
|
2633
|
+
model: providerDefaults.defaults.teacher.model ?? null,
|
|
2634
|
+
detectedLocally: providerDefaults.defaults.teacher.detectedLocally ?? false
|
|
2635
|
+
},
|
|
2636
|
+
embedder: providerDefaults.defaults.embedder === undefined
|
|
2637
|
+
? null
|
|
2638
|
+
: {
|
|
2639
|
+
provider: providerDefaults.defaults.embedder.provider ?? null,
|
|
2640
|
+
model: providerDefaults.defaults.embedder.model ?? null
|
|
2641
|
+
},
|
|
2642
|
+
teacherBaseUrl: providerDefaults.defaults.teacherBaseUrl ?? null,
|
|
2643
|
+
embedderBaseUrl: providerDefaults.defaults.embedderBaseUrl ?? null
|
|
2644
|
+
},
|
|
2645
|
+
brainFeedback: {
|
|
2646
|
+
hookPath: brainFeedback.hookPath,
|
|
2647
|
+
providerDefaultsPath: brainFeedback.providerDefaultsPath,
|
|
2648
|
+
embedder: brainFeedback.embedder,
|
|
2649
|
+
teacher: brainFeedback.teacher,
|
|
2650
|
+
startup: brainFeedback.startup,
|
|
2651
|
+
provedNow: brainFeedback.provedNow,
|
|
2652
|
+
notYetProved: brainFeedback.notYetProved,
|
|
2653
|
+
lines: brainFeedback.lines
|
|
2654
|
+
},
|
|
1956
2655
|
extensionDir,
|
|
1957
2656
|
lifecycleSummary,
|
|
2657
|
+
preflightSummary,
|
|
1958
2658
|
restartGuidance,
|
|
1959
2659
|
nextSteps,
|
|
1960
2660
|
steps
|
|
@@ -1962,19 +2662,24 @@ function runInstallCommand(parsed) {
|
|
|
1962
2662
|
}
|
|
1963
2663
|
else {
|
|
1964
2664
|
console.log(`${commandLabel} complete\n`);
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
}
|
|
1968
|
-
console.log("");
|
|
1969
|
-
console.log("Lifecycle:");
|
|
1970
|
-
for (const line of lifecycleSummary) {
|
|
2665
|
+
console.log("Brain feedback:");
|
|
2666
|
+
for (const line of brainFeedback.lines) {
|
|
1971
2667
|
console.log(` ${line}`);
|
|
1972
2668
|
}
|
|
1973
2669
|
console.log(`Next: ${restartGuidance}`);
|
|
1974
2670
|
console.log(`Check: ${buildInstallStatusCommand(parsed.activationRoot)}`);
|
|
2671
|
+
if (embedderProvision !== null && embedderProvision.state === "skipped") {
|
|
2672
|
+
console.log(`Embedder: ${buildInstallEmbedderProvisionCommand(embedderProvision.baseUrl, embedderProvision.model)}`);
|
|
2673
|
+
}
|
|
1975
2674
|
}
|
|
1976
2675
|
return 0;
|
|
1977
2676
|
}
|
|
2677
|
+
function runInstallCommand(parsed) {
|
|
2678
|
+
return runProfileHookAttachCommand(parsed);
|
|
2679
|
+
}
|
|
2680
|
+
function runAttachCommand(parsed) {
|
|
2681
|
+
return runProfileHookAttachCommand(parsed);
|
|
2682
|
+
}
|
|
1978
2683
|
function validateOpenClawHome(openclawHome) {
|
|
1979
2684
|
if (!existsSync(openclawHome)) {
|
|
1980
2685
|
throw new Error(`--openclaw-home directory does not exist: ${openclawHome}`);
|
|
@@ -2024,25 +2729,43 @@ function buildRestartGuidance(restart) {
|
|
|
2024
2729
|
function runDetachCommand(parsed) {
|
|
2025
2730
|
const steps = [];
|
|
2026
2731
|
validateOpenClawHome(parsed.openclawHome);
|
|
2732
|
+
const targetInspection = inspectOpenClawHome(parsed.openclawHome);
|
|
2733
|
+
steps.push(`Detected layout: ${formatOpenClawTargetExplanation(targetInspection)}`);
|
|
2027
2734
|
const activationRoot = resolveCleanupActivationRoot(parsed.openclawHome, parsed.activationRoot);
|
|
2028
2735
|
const extensionDir = removeProfileHookup(parsed.openclawHome, steps);
|
|
2736
|
+
const legacyResidue = removeLegacyProfileResidue(parsed.openclawHome);
|
|
2029
2737
|
const activationData = summarizeKeptActivationData(activationRoot);
|
|
2030
2738
|
const restartGuidance = buildRestartGuidance(parsed.restart);
|
|
2031
2739
|
const nextSteps = [
|
|
2032
2740
|
restartGuidance,
|
|
2033
2741
|
activationRoot === null ? null : `Inspect preserved data: ${buildInstallStatusCommand(activationRoot)}`,
|
|
2034
|
-
`Reattach later: ${
|
|
2742
|
+
`Reattach later: ${buildAttachCommand(parsed.openclawHome, activationRoot)}`
|
|
2035
2743
|
].filter((step) => step !== null);
|
|
2744
|
+
if (legacyResidue.removedNotes.length > 0) {
|
|
2745
|
+
steps.push(`Removed legacy profile notes: ${legacyResidue.removedNotes.map((notePath) => shortenPath(notePath)).join(", ")}`);
|
|
2746
|
+
}
|
|
2747
|
+
if (legacyResidue.updatedAgents.length > 0) {
|
|
2748
|
+
steps.push(`Removed legacy AGENTS.md brain references: ${legacyResidue.updatedAgents.map((agentsPath) => shortenPath(agentsPath)).join(", ")}`);
|
|
2749
|
+
}
|
|
2036
2750
|
steps.push(activationData.activationDataDetail);
|
|
2037
2751
|
steps.push("Detach only removes the OpenClaw profile hook; it does not delete OpenClawBrain data.");
|
|
2038
2752
|
if (parsed.json) {
|
|
2039
2753
|
console.log(JSON.stringify({
|
|
2040
2754
|
command: "detach",
|
|
2041
2755
|
openclawHome: parsed.openclawHome,
|
|
2756
|
+
openclawTarget: {
|
|
2757
|
+
layout: targetInspection.layout,
|
|
2758
|
+
detail: describeOpenClawHomeInspection(targetInspection),
|
|
2759
|
+
profileId: targetInspection.profileId,
|
|
2760
|
+
profileSource: targetInspection.profileSource,
|
|
2761
|
+
configuredProfileIds: targetInspection.configuredProfileIds
|
|
2762
|
+
},
|
|
2042
2763
|
extensionDir,
|
|
2043
2764
|
activationRoot,
|
|
2044
2765
|
dataAction: "kept",
|
|
2045
2766
|
activationDataState: activationData.activationDataState,
|
|
2767
|
+
removedLegacyNotes: legacyResidue.removedNotes,
|
|
2768
|
+
updatedAgents: legacyResidue.updatedAgents,
|
|
2046
2769
|
restartMode: parsed.restart,
|
|
2047
2770
|
restartGuidance,
|
|
2048
2771
|
nextSteps,
|
|
@@ -2055,7 +2778,8 @@ function runDetachCommand(parsed) {
|
|
|
2055
2778
|
console.log(` ✓ ${step}`);
|
|
2056
2779
|
}
|
|
2057
2780
|
console.log("");
|
|
2058
|
-
console.log(`Lifecycle: OpenClaw
|
|
2781
|
+
console.log(`Lifecycle: OpenClaw home ${shortenPath(parsed.openclawHome)} is detached from the brain hook.`);
|
|
2782
|
+
console.log(`Target: ${formatOpenClawTargetExplanation(targetInspection)}`);
|
|
2059
2783
|
if (activationRoot !== null) {
|
|
2060
2784
|
console.log(`Brain data: ${shortenPath(activationRoot)} remains available for inspection or reattach.`);
|
|
2061
2785
|
}
|
|
@@ -2066,15 +2790,18 @@ function runDetachCommand(parsed) {
|
|
|
2066
2790
|
if (activationRoot !== null) {
|
|
2067
2791
|
console.log(`Check: ${buildInstallStatusCommand(activationRoot)}`);
|
|
2068
2792
|
}
|
|
2069
|
-
console.log(`Reattach: ${
|
|
2793
|
+
console.log(`Reattach: ${buildAttachCommand(parsed.openclawHome, activationRoot)}`);
|
|
2070
2794
|
}
|
|
2071
2795
|
return 0;
|
|
2072
2796
|
}
|
|
2073
2797
|
function runUninstallCommand(parsed) {
|
|
2074
2798
|
const steps = [];
|
|
2075
2799
|
validateOpenClawHome(parsed.openclawHome);
|
|
2800
|
+
const targetInspection = inspectOpenClawHome(parsed.openclawHome);
|
|
2801
|
+
steps.push(`Detected layout: ${formatOpenClawTargetExplanation(targetInspection)}`);
|
|
2076
2802
|
const activationRoot = resolveCleanupActivationRoot(parsed.openclawHome, parsed.activationRoot);
|
|
2077
2803
|
const extensionDir = removeProfileHookup(parsed.openclawHome, steps);
|
|
2804
|
+
const legacyResidue = removeLegacyProfileResidue(parsed.openclawHome);
|
|
2078
2805
|
let activationData;
|
|
2079
2806
|
if (parsed.dataMode === "purge") {
|
|
2080
2807
|
if (activationRoot === null) {
|
|
@@ -2103,8 +2830,16 @@ function runUninstallCommand(parsed) {
|
|
|
2103
2830
|
const nextSteps = [
|
|
2104
2831
|
restartGuidance,
|
|
2105
2832
|
parsed.dataMode === "keep" && activationRoot !== null ? `Inspect preserved data: ${buildInstallStatusCommand(activationRoot)}` : null,
|
|
2106
|
-
|
|
2833
|
+
parsed.dataMode === "keep"
|
|
2834
|
+
? `Reattach later: ${buildAttachCommand(parsed.openclawHome, activationRoot)}`
|
|
2835
|
+
: `Reinstall later: ${buildInstallCommand(parsed.openclawHome)}`
|
|
2107
2836
|
].filter((step) => step !== null);
|
|
2837
|
+
if (legacyResidue.removedNotes.length > 0) {
|
|
2838
|
+
steps.push(`Removed legacy profile notes: ${legacyResidue.removedNotes.map((notePath) => shortenPath(notePath)).join(", ")}`);
|
|
2839
|
+
}
|
|
2840
|
+
if (legacyResidue.updatedAgents.length > 0) {
|
|
2841
|
+
steps.push(`Removed legacy AGENTS.md brain references: ${legacyResidue.updatedAgents.map((agentsPath) => shortenPath(agentsPath)).join(", ")}`);
|
|
2842
|
+
}
|
|
2108
2843
|
steps.push(activationData.activationDataDetail);
|
|
2109
2844
|
steps.push(parsed.dataMode === "purge"
|
|
2110
2845
|
? "Uninstall removed the OpenClaw profile hook and activation data."
|
|
@@ -2113,10 +2848,19 @@ function runUninstallCommand(parsed) {
|
|
|
2113
2848
|
console.log(JSON.stringify({
|
|
2114
2849
|
command: "uninstall",
|
|
2115
2850
|
openclawHome: parsed.openclawHome,
|
|
2851
|
+
openclawTarget: {
|
|
2852
|
+
layout: targetInspection.layout,
|
|
2853
|
+
detail: describeOpenClawHomeInspection(targetInspection),
|
|
2854
|
+
profileId: targetInspection.profileId,
|
|
2855
|
+
profileSource: targetInspection.profileSource,
|
|
2856
|
+
configuredProfileIds: targetInspection.configuredProfileIds
|
|
2857
|
+
},
|
|
2116
2858
|
extensionDir,
|
|
2117
2859
|
activationRoot,
|
|
2118
2860
|
dataAction: parsed.dataMode,
|
|
2119
2861
|
activationDataState: activationData.activationDataState,
|
|
2862
|
+
removedLegacyNotes: legacyResidue.removedNotes,
|
|
2863
|
+
updatedAgents: legacyResidue.updatedAgents,
|
|
2120
2864
|
restartMode: parsed.restart,
|
|
2121
2865
|
restartGuidance,
|
|
2122
2866
|
nextSteps,
|
|
@@ -2129,7 +2873,8 @@ function runUninstallCommand(parsed) {
|
|
|
2129
2873
|
console.log(` ✓ ${step}`);
|
|
2130
2874
|
}
|
|
2131
2875
|
console.log("");
|
|
2132
|
-
console.log(`Lifecycle: OpenClaw
|
|
2876
|
+
console.log(`Lifecycle: OpenClaw home ${shortenPath(parsed.openclawHome)} no longer has the brain hook installed.`);
|
|
2877
|
+
console.log(`Target: ${formatOpenClawTargetExplanation(targetInspection)}`);
|
|
2133
2878
|
console.log(`Data mode: ${parsed.dataMode === "purge" ? "purged" : "kept"}`);
|
|
2134
2879
|
if (activationRoot !== null) {
|
|
2135
2880
|
console.log(`Activation: ${parsed.dataMode === "purge" ? shortenPath(activationRoot) : `${shortenPath(activationRoot)} preserved`}`);
|
|
@@ -2138,7 +2883,12 @@ function runUninstallCommand(parsed) {
|
|
|
2138
2883
|
if (parsed.dataMode === "keep" && activationRoot !== null) {
|
|
2139
2884
|
console.log(`Check: ${buildInstallStatusCommand(activationRoot)}`);
|
|
2140
2885
|
}
|
|
2141
|
-
|
|
2886
|
+
if (parsed.dataMode === "keep") {
|
|
2887
|
+
console.log(`Reattach: ${buildAttachCommand(parsed.openclawHome, activationRoot)}`);
|
|
2888
|
+
}
|
|
2889
|
+
else {
|
|
2890
|
+
console.log(`Reinstall: ${buildInstallCommand(parsed.openclawHome)}`);
|
|
2891
|
+
}
|
|
2142
2892
|
}
|
|
2143
2893
|
return 0;
|
|
2144
2894
|
}
|
|
@@ -2782,15 +3532,18 @@ function applyWatchMaterialization(activationRoot, snapshot, lastHandledMaterial
|
|
|
2782
3532
|
};
|
|
2783
3533
|
}
|
|
2784
3534
|
}
|
|
2785
|
-
function resolveWatchTeacherLabelerConfig(input) {
|
|
3535
|
+
function resolveWatchTeacherLabelerConfig(input, activationRoot) {
|
|
2786
3536
|
if (input !== undefined) {
|
|
2787
3537
|
return {
|
|
2788
3538
|
teacherLabeler: input,
|
|
2789
3539
|
warnings: []
|
|
2790
3540
|
};
|
|
2791
3541
|
}
|
|
2792
|
-
const providerConfig =
|
|
2793
|
-
|
|
3542
|
+
const providerConfig = readOpenClawBrainProviderConfigFromSources({
|
|
3543
|
+
env: process.env,
|
|
3544
|
+
activationRoot
|
|
3545
|
+
});
|
|
3546
|
+
const warnings = providerConfig.warnings.filter((warning) => /OPENCLAWBRAIN_TEACHER_|provider defaults/u.test(warning));
|
|
2794
3547
|
if (providerConfig.teacher.provider !== "ollama") {
|
|
2795
3548
|
return {
|
|
2796
3549
|
teacherLabeler: null,
|
|
@@ -2832,11 +3585,11 @@ export async function createWatchCommandRuntime(input) {
|
|
|
2832
3585
|
log(`Watch starting — activation: ${shortenPath(activationRoot)}`);
|
|
2833
3586
|
log(`Scan root: ${shortenPath(scanRoot)}`);
|
|
2834
3587
|
log(`State: cursor=${shortenPath(sessionTailCursorPath)} snapshot=${shortenPath(teacherSnapshotPath)}`);
|
|
2835
|
-
const resolvedTeacherLabeler = resolveWatchTeacherLabelerConfig(input.teacherLabeler);
|
|
3588
|
+
const resolvedTeacherLabeler = resolveWatchTeacherLabelerConfig(input.teacherLabeler, activationRoot);
|
|
2836
3589
|
const teacherLabeler = resolvedTeacherLabeler.teacherLabeler;
|
|
2837
3590
|
for (const warning of resolvedTeacherLabeler.warnings) {
|
|
2838
|
-
startupWarnings.push(`
|
|
2839
|
-
log(`Teacher
|
|
3591
|
+
startupWarnings.push(`teacher_config_warning:${warning}`);
|
|
3592
|
+
log(`Teacher config warning: ${warning}`);
|
|
2840
3593
|
}
|
|
2841
3594
|
if (teacherLabeler?.provider === "ollama") {
|
|
2842
3595
|
log(`Teacher labeler: provider=ollama model=${teacherLabeler.model ?? "qwen3.5:9b"}`);
|
|
@@ -3330,31 +4083,7 @@ export function runOperatorCli(argv = process.argv.slice(2)) {
|
|
|
3330
4083
|
return runUninstallCommand(parsed);
|
|
3331
4084
|
}
|
|
3332
4085
|
if (parsed.command === "attach") {
|
|
3333
|
-
|
|
3334
|
-
mkdirSync(parsed.packRoot, { recursive: true });
|
|
3335
|
-
const result = bootstrapRuntimeAttach({
|
|
3336
|
-
profileSelector: "current_profile",
|
|
3337
|
-
...(parsed.brainAttachmentPolicy != null ? { brainAttachmentPolicy: parsed.brainAttachmentPolicy } : {}),
|
|
3338
|
-
activationRoot: parsed.activationRoot,
|
|
3339
|
-
packRoot: parsed.packRoot,
|
|
3340
|
-
packLabel: parsed.packLabel,
|
|
3341
|
-
workspace: {
|
|
3342
|
-
workspaceId: parsed.workspaceId,
|
|
3343
|
-
snapshotId: `${parsed.workspaceId}@bootstrap-${new Date().toISOString().slice(0, 10)}`,
|
|
3344
|
-
capturedAt: new Date().toISOString(),
|
|
3345
|
-
rootDir: process.cwd(),
|
|
3346
|
-
revision: "cli-bootstrap-v1"
|
|
3347
|
-
},
|
|
3348
|
-
interactionEvents: [],
|
|
3349
|
-
feedbackEvents: []
|
|
3350
|
-
});
|
|
3351
|
-
if (parsed.json) {
|
|
3352
|
-
console.log(JSON.stringify(result, null, 2));
|
|
3353
|
-
}
|
|
3354
|
-
else {
|
|
3355
|
-
console.log(formatBootstrapRuntimeAttachReport(result));
|
|
3356
|
-
}
|
|
3357
|
-
return 0;
|
|
4086
|
+
return runAttachCommand(parsed);
|
|
3358
4087
|
}
|
|
3359
4088
|
if (parsed.command === "scan") {
|
|
3360
4089
|
if (parsed.sessionPath !== null) {
|
|
@@ -3392,6 +4121,7 @@ export function runOperatorCli(argv = process.argv.slice(2)) {
|
|
|
3392
4121
|
// At this point only status/rollback commands remain
|
|
3393
4122
|
const statusOrRollback = parsed;
|
|
3394
4123
|
const activationRoot = requireActivationRoot(statusOrRollback.input, statusOrRollback.openclawHome, statusOrRollback.command);
|
|
4124
|
+
const targetInspection = statusOrRollback.openclawHome === null ? null : inspectOpenClawHome(statusOrRollback.openclawHome);
|
|
3395
4125
|
if (statusOrRollback.command === "rollback") {
|
|
3396
4126
|
const result = rollbackRuntimeAttach({
|
|
3397
4127
|
activationRoot,
|
|
@@ -3417,11 +4147,18 @@ export function runOperatorCli(argv = process.argv.slice(2)) {
|
|
|
3417
4147
|
}
|
|
3418
4148
|
else {
|
|
3419
4149
|
const report = buildOperatorSurfaceReport(operatorInput);
|
|
4150
|
+
const providerConfig = readOpenClawBrainProviderConfig(process.env);
|
|
3420
4151
|
if (statusOrRollback.detailed) {
|
|
3421
|
-
console.log(formatCurrentProfileStatusSummary(status, report
|
|
4152
|
+
console.log(formatCurrentProfileStatusSummary(status, report, targetInspection, {
|
|
4153
|
+
openclawHome: statusOrRollback.openclawHome,
|
|
4154
|
+
providerConfig
|
|
4155
|
+
}));
|
|
3422
4156
|
}
|
|
3423
4157
|
else {
|
|
3424
|
-
console.log(formatHumanFriendlyStatus(status, report
|
|
4158
|
+
console.log(formatHumanFriendlyStatus(status, report, targetInspection, {
|
|
4159
|
+
openclawHome: statusOrRollback.openclawHome,
|
|
4160
|
+
providerConfig
|
|
4161
|
+
}));
|
|
3425
4162
|
}
|
|
3426
4163
|
}
|
|
3427
4164
|
return 0;
|