@openclawbrain/openclaw 0.3.1 → 0.3.3
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/dist/extension/index.js +17 -1
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/runtime-guard.js +5 -0
- package/dist/extension/runtime-guard.js.map +1 -1
- package/dist/src/attachment-truth.d.ts +34 -0
- package/dist/src/attachment-truth.js +215 -0
- package/dist/src/attachment-truth.js.map +1 -0
- package/dist/src/cli.d.ts +7 -1
- package/dist/src/cli.js +1414 -67
- package/dist/src/cli.js.map +1 -1
- package/dist/src/daemon.d.ts +42 -1
- package/dist/src/daemon.js +360 -50
- package/dist/src/daemon.js.map +1 -1
- package/dist/src/index.d.ts +89 -1
- package/dist/src/index.js +764 -105
- package/dist/src/index.js.map +1 -1
- package/dist/src/learning-spine.d.ts +3 -1
- package/dist/src/learning-spine.js +1 -0
- package/dist/src/learning-spine.js.map +1 -1
- package/dist/src/local-session-passive-learning.js +6 -1
- package/dist/src/local-session-passive-learning.js.map +1 -1
- package/dist/src/openclaw-hook-truth.d.ts +25 -0
- package/dist/src/openclaw-hook-truth.js +154 -0
- package/dist/src/openclaw-hook-truth.js.map +1 -0
- package/dist/src/semantic-metadata.d.ts +4 -0
- package/dist/src/semantic-metadata.js +41 -0
- package/dist/src/semantic-metadata.js.map +1 -0
- package/dist/src/session-tail.d.ts +2 -0
- package/dist/src/session-tail.js +54 -14
- package/dist/src/session-tail.js.map +1 -1
- package/dist/src/shadow-extension-proof.js +4 -0
- package/dist/src/shadow-extension-proof.js.map +1 -1
- package/extension/index.ts +19 -1
- package/extension/runtime-guard.ts +6 -0
- package/package.json +7 -7
package/dist/src/cli.js
CHANGED
|
@@ -5,19 +5,23 @@ 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";
|
|
9
|
-
import { parseDaemonArgs, runDaemonCommand } from "./daemon.js";
|
|
8
|
+
import { DEFAULT_OLLAMA_EMBEDDING_MODEL, createOllamaEmbedder } from "@openclawbrain/compiler";
|
|
9
|
+
import { ensureManagedLearnerServiceForActivationRoot, inspectManagedLearnerService, removeManagedLearnerServiceForActivationRoot, parseDaemonArgs, runDaemonCommand } from "./daemon.js";
|
|
10
10
|
import { exportBrain, importBrain } from "./import-export.js";
|
|
11
11
|
import { buildNormalizedEventExport } from "@openclawbrain/contracts";
|
|
12
|
-
import { buildTeacherSupervisionArtifactsFromNormalizedEventExport, createAlwaysOnLearningRuntimeState, describeAlwaysOnLearningRuntimeState, drainAlwaysOnLearningRuntime, loadOrInitBaseline, materializeAlwaysOnLearningCandidatePack, persistBaseline } from "@openclawbrain/learner";
|
|
12
|
+
import { buildTeacherSupervisionArtifactsFromNormalizedEventExport, createAlwaysOnLearningRuntimeState, describeAlwaysOnLearningRuntimeState, drainAlwaysOnLearningRuntime, loadOrInitBaseline, reindexCandidatePackBuildResultWithEmbedder, materializeAlwaysOnLearningCandidatePack, persistBaseline } from "@openclawbrain/learner";
|
|
13
13
|
import { inspectActivationState, loadPackFromActivation, promoteCandidatePack, readLearningSpineLogEntries, stageCandidatePack } from "@openclawbrain/pack-format";
|
|
14
14
|
import { resolveActivationRoot } from "./resolve-activation-root.js";
|
|
15
15
|
import { describeOpenClawHomeInspection, discoverOpenClawHomes, formatOpenClawHomeLayout, formatOpenClawHomeProfileSource, inspectOpenClawHome } from "./openclaw-home-layout.js";
|
|
16
|
-
import {
|
|
16
|
+
import { inspectOpenClawBrainHookStatus, inspectOpenClawBrainPluginAllowlist } from "./openclaw-hook-truth.js";
|
|
17
|
+
import { DEFAULT_WATCH_POLL_INTERVAL_SECONDS, buildNormalizedEventExportFromScannedEvents, bootstrapRuntimeAttach, buildOperatorSurfaceReport, clearOpenClawProfileRuntimeLoadProof, compileRuntimeContext, createAsyncTeacherLiveLoop, createOpenClawLocalSessionTail, createRuntimeEventExportScanner, describeCurrentProfileBrainStatus, formatOperatorRollbackReport, listOpenClawProfileRuntimeLoadProofs, loadRuntimeEventExportBundle, loadWatchTeacherSnapshotState, persistWatchTeacherSnapshot, rollbackRuntimeAttach, resolveAttachmentRuntimeLoadProofsPath, resolveOperatorTeacherSnapshotPath, resolveAsyncTeacherLiveLoopSnapshotPath, resolveWatchSessionTailCursorPath, resolveWatchStateRoot, resolveWatchTeacherSnapshotPath, scanLiveEventExport, scanRecordedSession, summarizeLearningPathFromMaterialization, summarizeNormalizedEventExportLabelFlow, writeScannedEventExportBundle } from "./index.js";
|
|
17
18
|
import { appendLearningUpdateLogs } from "./learning-spine.js";
|
|
18
19
|
import { buildPassiveLearningSessionExportFromOpenClawSessionStore } from "./local-session-passive-learning.js";
|
|
19
20
|
import { discoverOpenClawSessionStores, loadOpenClawSessionIndex, readOpenClawSessionFile } from "./session-store.js";
|
|
20
|
-
import { readOpenClawBrainProviderConfig, readOpenClawBrainProviderConfigFromSources, resolveOpenClawBrainProviderDefaultsPath } from "./provider-config.js";
|
|
21
|
+
import { readOpenClawBrainProviderDefaults, readOpenClawBrainProviderConfig, readOpenClawBrainProviderConfigFromSources, resolveOpenClawBrainProviderDefaultsPath } from "./provider-config.js";
|
|
22
|
+
const OPENCLAWBRAIN_EMBEDDER_BASE_URL_ENV = "OPENCLAWBRAIN_EMBEDDER_BASE_URL";
|
|
23
|
+
const OPENCLAWBRAIN_EMBEDDER_PROVIDER_ENV = "OPENCLAWBRAIN_EMBEDDER_PROVIDER";
|
|
24
|
+
const OPENCLAWBRAIN_EMBEDDER_MODEL_ENV = "OPENCLAWBRAIN_EMBEDDER_MODEL";
|
|
21
25
|
const OPENCLAWBRAIN_INSTALL_SKIP_EMBEDDER_PROVISION_ENV = "OPENCLAWBRAIN_INSTALL_SKIP_EMBEDDER_PROVISION";
|
|
22
26
|
const INSTALL_COMPATIBLE_LOCAL_TEACHER_MODEL_PREFIXES = [
|
|
23
27
|
"qwen3.5:9b",
|
|
@@ -35,6 +39,15 @@ function normalizeOptionalCliString(value) {
|
|
|
35
39
|
const trimmed = value.trim();
|
|
36
40
|
return trimmed.length > 0 ? trimmed : null;
|
|
37
41
|
}
|
|
42
|
+
function canonicalizeExistingCliPath(filePath) {
|
|
43
|
+
const resolvedPath = path.resolve(filePath);
|
|
44
|
+
try {
|
|
45
|
+
return realpathSync(resolvedPath);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return resolvedPath;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
38
51
|
function readTruthyEnvFlag(name, env = process.env) {
|
|
39
52
|
const value = normalizeOptionalCliString(env[name]);
|
|
40
53
|
if (value === null) {
|
|
@@ -48,6 +61,132 @@ function getCliHomeDir() {
|
|
|
48
61
|
function discoverInstallCandidateOpenClawHomes(homeDir = getCliHomeDir()) {
|
|
49
62
|
return discoverOpenClawHomes(homeDir).map((inspection) => inspection.openclawHome);
|
|
50
63
|
}
|
|
64
|
+
function summarizeSharedActivationRootReferenceProof(reference) {
|
|
65
|
+
switch (reference.installState) {
|
|
66
|
+
case "installed":
|
|
67
|
+
return "hook installed and loadable";
|
|
68
|
+
case "blocked_by_allowlist":
|
|
69
|
+
return "hook files exist but OpenClaw will not load them because plugins.allow blocks openclawbrain";
|
|
70
|
+
case "not_installed":
|
|
71
|
+
default:
|
|
72
|
+
return "hook files are incomplete, so serve-path attachment is not proven";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function partitionSharedActivationRootHookReferences(references) {
|
|
76
|
+
return references.reduce((result, reference) => {
|
|
77
|
+
if (reference.serveAttachmentState === "attached") {
|
|
78
|
+
result.attached.push(reference);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
result.halfAttached.push(reference);
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
}, {
|
|
85
|
+
attached: [],
|
|
86
|
+
halfAttached: []
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function formatSharedActivationRootReferenceList(references, options = {}) {
|
|
90
|
+
return references
|
|
91
|
+
.map((reference) => {
|
|
92
|
+
const parts = [path.resolve(reference.openclawHome)];
|
|
93
|
+
if (options.includeInspection) {
|
|
94
|
+
parts.push(describeOpenClawHomeInspection(reference.inspection));
|
|
95
|
+
}
|
|
96
|
+
if (options.includeProof) {
|
|
97
|
+
parts.push(summarizeSharedActivationRootReferenceProof(reference));
|
|
98
|
+
}
|
|
99
|
+
return ` - ${parts.join(" | ")}`;
|
|
100
|
+
})
|
|
101
|
+
.join("\n");
|
|
102
|
+
}
|
|
103
|
+
function findInstalledHookReferencesForActivationRoot(input) {
|
|
104
|
+
const resolvedActivationRoot = path.resolve(input.activationRoot);
|
|
105
|
+
const resolvedExcludedHome = input.excludingOpenClawHome === undefined || input.excludingOpenClawHome === null
|
|
106
|
+
? null
|
|
107
|
+
: path.resolve(input.excludingOpenClawHome);
|
|
108
|
+
return discoverOpenClawHomes(input.homeDir ?? getCliHomeDir())
|
|
109
|
+
.filter((inspection) => resolvedExcludedHome === null || path.resolve(inspection.openclawHome) !== resolvedExcludedHome)
|
|
110
|
+
.flatMap((inspection) => {
|
|
111
|
+
const installedActivationRoot = resolveActivationRoot({
|
|
112
|
+
openclawHome: inspection.openclawHome,
|
|
113
|
+
quiet: true
|
|
114
|
+
});
|
|
115
|
+
if (installedActivationRoot.trim().length === 0) {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
const installHook = summarizeStatusInstallHook(inspection.openclawHome);
|
|
119
|
+
const reference = {
|
|
120
|
+
openclawHome: inspection.openclawHome,
|
|
121
|
+
inspection,
|
|
122
|
+
installState: installHook.state === "installed" || installHook.state === "blocked_by_allowlist"
|
|
123
|
+
? installHook.state
|
|
124
|
+
: "not_installed",
|
|
125
|
+
serveAttachmentState: installHook.state === "installed" ? "attached" : "half_attached",
|
|
126
|
+
hookDetail: installHook.detail
|
|
127
|
+
};
|
|
128
|
+
return path.resolve(installedActivationRoot) === resolvedActivationRoot
|
|
129
|
+
? [reference]
|
|
130
|
+
: [];
|
|
131
|
+
})
|
|
132
|
+
.sort((left, right) => left.openclawHome.localeCompare(right.openclawHome));
|
|
133
|
+
}
|
|
134
|
+
function findOtherInstalledHookReferencesForActivationRoot(input) {
|
|
135
|
+
return findInstalledHookReferencesForActivationRoot(input);
|
|
136
|
+
}
|
|
137
|
+
function resolveWatchProfileRootsForActivationRoot(activationRoot, homeDir = getCliHomeDir()) {
|
|
138
|
+
const references = findInstalledHookReferencesForActivationRoot({
|
|
139
|
+
activationRoot,
|
|
140
|
+
homeDir
|
|
141
|
+
});
|
|
142
|
+
const partitioned = partitionSharedActivationRootHookReferences(references);
|
|
143
|
+
const attachedProfileRoots = partitioned.attached.map((reference) => path.resolve(reference.openclawHome));
|
|
144
|
+
return {
|
|
145
|
+
attachedProfileRoots: attachedProfileRoots.length > 0 || references.length > 0
|
|
146
|
+
? attachedProfileRoots
|
|
147
|
+
: undefined,
|
|
148
|
+
halfAttachedReferences: partitioned.halfAttached
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function assertActivationRootPurgeIsNotShared(input) {
|
|
152
|
+
const sharedReferences = findOtherInstalledHookReferencesForActivationRoot({
|
|
153
|
+
activationRoot: input.activationRoot,
|
|
154
|
+
excludingOpenClawHome: input.openclawHome
|
|
155
|
+
});
|
|
156
|
+
if (sharedReferences.length === 0) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const partitioned = partitionSharedActivationRootHookReferences(sharedReferences);
|
|
160
|
+
const attachedProfiles = formatSharedActivationRootReferenceList(partitioned.attached, {
|
|
161
|
+
includeInspection: true,
|
|
162
|
+
includeProof: true
|
|
163
|
+
});
|
|
164
|
+
const halfAttachedProfiles = formatSharedActivationRootReferenceList(partitioned.halfAttached, {
|
|
165
|
+
includeInspection: true,
|
|
166
|
+
includeProof: true
|
|
167
|
+
});
|
|
168
|
+
throw new Error(partitioned.attached.length > 0
|
|
169
|
+
? [
|
|
170
|
+
`Refusing to purge activation root ${path.resolve(input.activationRoot)} because another installed OpenClaw profile still points at it.`,
|
|
171
|
+
"Other attached profiles:",
|
|
172
|
+
attachedProfiles,
|
|
173
|
+
...(partitioned.halfAttached.length === 0
|
|
174
|
+
? []
|
|
175
|
+
: [
|
|
176
|
+
"Other half-attached profiles:",
|
|
177
|
+
halfAttachedProfiles
|
|
178
|
+
]),
|
|
179
|
+
"Use uninstall --keep-data or detach on this profile first, then remove or repair the remaining profile hooks before purging shared brain data.",
|
|
180
|
+
"For Eagle dogfood, prefer its own activation root so CormorantAI stays untouched."
|
|
181
|
+
].join("\n")
|
|
182
|
+
: [
|
|
183
|
+
`Refusing to purge activation root ${path.resolve(input.activationRoot)} because another OpenClaw profile still points at it, but its hook is not loadable yet.`,
|
|
184
|
+
"Other half-attached profiles:",
|
|
185
|
+
halfAttachedProfiles,
|
|
186
|
+
"Repair or remove those half-attached profile hooks before purging shared brain data so status stays explicit instead of drifting into a missing-root surprise.",
|
|
187
|
+
"For Eagle dogfood, prefer its own activation root so CormorantAI stays untouched."
|
|
188
|
+
].join("\n"));
|
|
189
|
+
}
|
|
51
190
|
function formatInstallOpenClawHomeSource(source) {
|
|
52
191
|
switch (source) {
|
|
53
192
|
case "explicit":
|
|
@@ -418,6 +557,24 @@ function formatStructuralOps(report) {
|
|
|
418
557
|
? "none"
|
|
419
558
|
: `split:${structuralOps.split},merge:${structuralOps.merge},prune:${structuralOps.prune},connect:${structuralOps.connect}`;
|
|
420
559
|
}
|
|
560
|
+
function formatGraphConnectDiagnostics(diagnostics) {
|
|
561
|
+
if (diagnostics === null) {
|
|
562
|
+
return "none";
|
|
563
|
+
}
|
|
564
|
+
return `budget:${diagnostics.requestedBudget},threshold:${diagnostics.scoreThreshold},pairs:${diagnostics.appliedPairCount}/${diagnostics.candidatePairCount},edges:${diagnostics.createdEdgeCount}`;
|
|
565
|
+
}
|
|
566
|
+
function formatCompactGraphConnectDiagnostics(diagnostics) {
|
|
567
|
+
if (diagnostics === null) {
|
|
568
|
+
return "none";
|
|
569
|
+
}
|
|
570
|
+
return `pairs:${diagnostics.appliedPairCount},edges:${diagnostics.createdEdgeCount}`;
|
|
571
|
+
}
|
|
572
|
+
function formatGraphSummary(report) {
|
|
573
|
+
return (report.graph.latestMaterialization.operatorSummary ??
|
|
574
|
+
report.graph.operatorSummary ??
|
|
575
|
+
report.graph.latestMaterialization.detail ??
|
|
576
|
+
report.graph.detail);
|
|
577
|
+
}
|
|
421
578
|
function formatScannerSurfaces(report) {
|
|
422
579
|
return report.supervision.scanSurfaces.length === 0 ? "none" : report.supervision.scanSurfaces.join("|");
|
|
423
580
|
}
|
|
@@ -470,6 +627,8 @@ function formatCompactList(values, maxItems = 2, maxLength = 64) {
|
|
|
470
627
|
return values.length > maxItems ? `${visible.join("|")}+${values.length - maxItems}more` : visible.join("|");
|
|
471
628
|
}
|
|
472
629
|
const SERVICE_RISK_FINDING_CODES = new Set([
|
|
630
|
+
"hook_desynced",
|
|
631
|
+
"current_profile_not_attached",
|
|
473
632
|
"activation_broken_install",
|
|
474
633
|
"activation_stale_incomplete",
|
|
475
634
|
"active_missing",
|
|
@@ -480,6 +639,7 @@ const SERVICE_RISK_FINDING_CODES = new Set([
|
|
|
480
639
|
"serve_path_route_evidence_missing"
|
|
481
640
|
]);
|
|
482
641
|
const DEGRADED_BRAIN_FINDING_CODES = new Set([
|
|
642
|
+
"attachment_scope_partial",
|
|
483
643
|
"bootstrap_waiting_for_first_export",
|
|
484
644
|
"serve_path_unprobed",
|
|
485
645
|
"brain_context_kernel_only",
|
|
@@ -506,26 +666,189 @@ const LEARNING_WARNING_MESSAGES = {
|
|
|
506
666
|
teacher_no_artifacts: "teacher produced no artifacts",
|
|
507
667
|
teacher_snapshot_unavailable: "teacher snapshot is unavailable"
|
|
508
668
|
};
|
|
669
|
+
const TEACHER_NO_OP_MESSAGES = {
|
|
670
|
+
none: "the latest processed export produced teacher artifacts",
|
|
671
|
+
duplicate_export: "the latest cycle was a no-op because the export was already seen",
|
|
672
|
+
queue_full: "the latest cycle was a no-op because the teacher queue was full",
|
|
673
|
+
no_teacher_artifacts: "the latest cycle was a no-op because no teacher artifacts were produced",
|
|
674
|
+
empty_scan: "the latest cycle was a no-op because the scanner did not produce any events",
|
|
675
|
+
unavailable: "the latest cycle is not visible from the current operator snapshot"
|
|
676
|
+
};
|
|
509
677
|
function summarizeStatusInstallHook(openclawHome) {
|
|
678
|
+
const hook = inspectOpenClawBrainHookStatus(openclawHome);
|
|
679
|
+
return {
|
|
680
|
+
state: hook.installState === "unverified" ? "unknown" : hook.installState,
|
|
681
|
+
detail: hook.detail
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
function summarizeStatusHookLoad(installHook, status) {
|
|
685
|
+
return {
|
|
686
|
+
installState: installHook.state === "unknown" ? "unverified" : installHook.state,
|
|
687
|
+
loadProof: status.hook.loadProof,
|
|
688
|
+
detail: status.hook.detail
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
function summarizeStatusConfigLoad(openclawHome) {
|
|
510
692
|
if (openclawHome === null) {
|
|
511
693
|
return {
|
|
512
|
-
state: "
|
|
513
|
-
detail: "
|
|
694
|
+
state: "unverified",
|
|
695
|
+
detail: "plugin allowlist state is unknown from activation-root-only status; pin --openclaw-home to prove config load state"
|
|
514
696
|
};
|
|
515
697
|
}
|
|
516
|
-
const
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
698
|
+
const allowlist = inspectOpenClawBrainPluginAllowlist(openclawHome);
|
|
699
|
+
if (allowlist.state === "blocked") {
|
|
700
|
+
return {
|
|
701
|
+
state: "blocked",
|
|
702
|
+
detail: allowlist.detail
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
if (allowlist.state === "invalid") {
|
|
521
706
|
return {
|
|
522
|
-
state: "
|
|
523
|
-
detail:
|
|
707
|
+
state: "invalid",
|
|
708
|
+
detail: allowlist.detail
|
|
524
709
|
};
|
|
525
710
|
}
|
|
526
711
|
return {
|
|
527
|
-
state: "
|
|
528
|
-
detail:
|
|
712
|
+
state: "allows_load",
|
|
713
|
+
detail: allowlist.detail
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
function summarizeStatusHookFilesState(installHook) {
|
|
717
|
+
if (installHook.state === "installed" || installHook.state === "blocked_by_allowlist") {
|
|
718
|
+
return "present";
|
|
719
|
+
}
|
|
720
|
+
if (installHook.state === "not_installed") {
|
|
721
|
+
return "missing";
|
|
722
|
+
}
|
|
723
|
+
return "unverified";
|
|
724
|
+
}
|
|
725
|
+
function summarizeStatusAttachmentWatcher(status) {
|
|
726
|
+
if (status.passiveLearning.watchState === "watching") {
|
|
727
|
+
return "alive";
|
|
728
|
+
}
|
|
729
|
+
if (status.passiveLearning.watchState === "stale_snapshot") {
|
|
730
|
+
return "stale";
|
|
731
|
+
}
|
|
732
|
+
return "not_visible";
|
|
733
|
+
}
|
|
734
|
+
function formatAttachedProfileIdentity(reference) {
|
|
735
|
+
const profileLabel = reference.inspection.profileId ?? "current_profile";
|
|
736
|
+
return `${profileLabel}@${shortenPath(path.resolve(reference.openclawHome))}`;
|
|
737
|
+
}
|
|
738
|
+
function buildStatusAttachedProfileTruth(input) {
|
|
739
|
+
const resolvedOpenClawHome = canonicalizeExistingCliPath(input.reference.openclawHome);
|
|
740
|
+
const installHook = summarizeStatusInstallHook(resolvedOpenClawHome);
|
|
741
|
+
const configLoad = summarizeStatusConfigLoad(resolvedOpenClawHome);
|
|
742
|
+
const runtimeProof = input.runtimeProofByHome.get(resolvedOpenClawHome);
|
|
743
|
+
const currentOpenClawHome = input.currentOpenClawHome === null ? null : canonicalizeExistingCliPath(input.currentOpenClawHome);
|
|
744
|
+
const hookFiles = summarizeStatusHookFilesState(installHook);
|
|
745
|
+
return {
|
|
746
|
+
label: formatAttachedProfileIdentity(input.reference),
|
|
747
|
+
openclawHome: resolvedOpenClawHome,
|
|
748
|
+
current: currentOpenClawHome !== null && currentOpenClawHome === resolvedOpenClawHome,
|
|
749
|
+
hookFiles,
|
|
750
|
+
configLoad: configLoad.state,
|
|
751
|
+
runtimeLoad: input.runtimeProofError !== null
|
|
752
|
+
? "proof_error"
|
|
753
|
+
: hookFiles !== "present"
|
|
754
|
+
? "not_proven"
|
|
755
|
+
: runtimeProof !== undefined
|
|
756
|
+
? "proven"
|
|
757
|
+
: "not_proven",
|
|
758
|
+
runtimeLoadedAt: runtimeProof?.loadedAt ?? null
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
function buildCurrentStatusAttachmentTruthDetail(input) {
|
|
762
|
+
const attachedProfileCount = input.attachedProfiles.length;
|
|
763
|
+
const attachedProfilesDetail = attachedProfileCount === 0
|
|
764
|
+
? "no attached profiles were discovered for this activation root"
|
|
765
|
+
: `attached profile set has ${attachedProfileCount} discoverable ${attachedProfileCount === 1 ? "profile" : "profiles"}`;
|
|
766
|
+
if (input.openclawHome === null) {
|
|
767
|
+
return ("current-profile hook/config/runtime load truth is unverified without --openclaw-home; " +
|
|
768
|
+
`${attachedProfilesDetail}`);
|
|
769
|
+
}
|
|
770
|
+
if (input.runtimeProofError !== null) {
|
|
771
|
+
return (`current profile ${input.currentProfileLabel} could not prove runtime load because ${shortenPath(input.runtimeProofPath)} is unreadable: ${input.runtimeProofError}; ` +
|
|
772
|
+
`${attachedProfilesDetail}`);
|
|
773
|
+
}
|
|
774
|
+
const hookDetail = input.hookFiles === "present"
|
|
775
|
+
? "hook files are present"
|
|
776
|
+
: input.hookFiles === "missing"
|
|
777
|
+
? "hook files are missing"
|
|
778
|
+
: "hook files are unverified";
|
|
779
|
+
const configDetail = input.configLoad === "allows_load"
|
|
780
|
+
? "config allows load"
|
|
781
|
+
: input.configLoad === "blocked"
|
|
782
|
+
? "config blocks load"
|
|
783
|
+
: input.configLoad === "invalid"
|
|
784
|
+
? "config is invalid for load proof"
|
|
785
|
+
: "config load is unverified";
|
|
786
|
+
const runtimeDetail = input.runtimeLoad === "proven"
|
|
787
|
+
? "runtime load is proven"
|
|
788
|
+
: input.runtimeLoad === "not_proven"
|
|
789
|
+
? "runtime load is not yet proven"
|
|
790
|
+
: input.runtimeLoad === "proof_error"
|
|
791
|
+
? "runtime load proof is broken"
|
|
792
|
+
: "runtime load is unverified";
|
|
793
|
+
const watcherDetail = input.watcher === "alive"
|
|
794
|
+
? "watcher is alive"
|
|
795
|
+
: input.watcher === "stale"
|
|
796
|
+
? "watcher visibility is stale"
|
|
797
|
+
: "watcher is not visible";
|
|
798
|
+
return `current profile ${input.currentProfileLabel}: ${hookDetail}, ${configDetail}, ${runtimeDetail}, ${watcherDetail}; ${attachedProfilesDetail}`;
|
|
799
|
+
}
|
|
800
|
+
function summarizeStatusAttachmentTruth(input) {
|
|
801
|
+
const runtimeProofs = listOpenClawProfileRuntimeLoadProofs(input.activationRoot);
|
|
802
|
+
const runtimeProofByHome = new Map();
|
|
803
|
+
for (const proof of runtimeProofs.proofs?.profiles ?? []) {
|
|
804
|
+
runtimeProofByHome.set(canonicalizeExistingCliPath(proof.openclawHome), {
|
|
805
|
+
loadedAt: proof.loadedAt
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
const attachedProfiles = findInstalledHookReferencesForActivationRoot({
|
|
809
|
+
activationRoot: input.activationRoot
|
|
810
|
+
}).map((reference) => buildStatusAttachedProfileTruth({
|
|
811
|
+
reference,
|
|
812
|
+
currentOpenClawHome: input.openclawHome,
|
|
813
|
+
runtimeProofByHome,
|
|
814
|
+
runtimeProofError: runtimeProofs.error
|
|
815
|
+
}));
|
|
816
|
+
const currentInspection = input.openclawHome === null ? null : inspectOpenClawHome(input.openclawHome);
|
|
817
|
+
const currentProfileLabel = currentInspection?.profileId ?? "current_profile";
|
|
818
|
+
const installHook = summarizeStatusInstallHook(input.openclawHome);
|
|
819
|
+
const configLoad = summarizeStatusConfigLoad(input.openclawHome);
|
|
820
|
+
const currentRuntimeProof = input.openclawHome === null ? undefined : runtimeProofByHome.get(canonicalizeExistingCliPath(input.openclawHome));
|
|
821
|
+
const hookFiles = summarizeStatusHookFilesState(installHook);
|
|
822
|
+
const watcher = summarizeStatusAttachmentWatcher(input.status);
|
|
823
|
+
const runtimeLoad = input.openclawHome === null
|
|
824
|
+
? "unverified"
|
|
825
|
+
: runtimeProofs.error !== null
|
|
826
|
+
? "proof_error"
|
|
827
|
+
: hookFiles !== "present"
|
|
828
|
+
? "not_proven"
|
|
829
|
+
: currentRuntimeProof !== undefined
|
|
830
|
+
? "proven"
|
|
831
|
+
: "not_proven";
|
|
832
|
+
return {
|
|
833
|
+
currentProfileLabel,
|
|
834
|
+
hookFiles,
|
|
835
|
+
configLoad: configLoad.state,
|
|
836
|
+
runtimeLoad,
|
|
837
|
+
watcher,
|
|
838
|
+
attachedProfiles,
|
|
839
|
+
runtimeProofPath: runtimeProofs.path,
|
|
840
|
+
runtimeProofError: runtimeProofs.error,
|
|
841
|
+
detail: buildCurrentStatusAttachmentTruthDetail({
|
|
842
|
+
openclawHome: input.openclawHome,
|
|
843
|
+
currentProfileLabel,
|
|
844
|
+
hookFiles,
|
|
845
|
+
configLoad: configLoad.state,
|
|
846
|
+
runtimeLoad,
|
|
847
|
+
watcher,
|
|
848
|
+
attachedProfiles,
|
|
849
|
+
runtimeProofPath: runtimeProofs.path,
|
|
850
|
+
runtimeProofError: runtimeProofs.error
|
|
851
|
+
})
|
|
529
852
|
};
|
|
530
853
|
}
|
|
531
854
|
function runOllamaProbe(args, baseUrl) {
|
|
@@ -639,6 +962,162 @@ function summarizeStatusLocalLlm(providerConfig) {
|
|
|
639
962
|
: `teacher labeling is ${providerConfig.teacher.provider}; no local Ollama CLI was detected`
|
|
640
963
|
};
|
|
641
964
|
}
|
|
965
|
+
function summarizeStatusTeacher(report, providerConfig, localLlm) {
|
|
966
|
+
const enabled = providerConfig.teacher.provider === "ollama";
|
|
967
|
+
const latestCycle = report.teacherLoop.lastNoOpReason === "unavailable"
|
|
968
|
+
? "unknown"
|
|
969
|
+
: report.teacherLoop.lastNoOpReason === "none"
|
|
970
|
+
? "teacher_artifact"
|
|
971
|
+
: "no_op";
|
|
972
|
+
if (!enabled) {
|
|
973
|
+
return {
|
|
974
|
+
model: providerConfig.teacher.model,
|
|
975
|
+
enabled,
|
|
976
|
+
healthy: false,
|
|
977
|
+
stale: false,
|
|
978
|
+
idle: false,
|
|
979
|
+
latestCycle,
|
|
980
|
+
detail: `${providerConfig.teacher.model} is not enabled because teacher labeling is ${providerConfig.teacher.provider}`
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
if (!localLlm.detected) {
|
|
984
|
+
return {
|
|
985
|
+
model: providerConfig.teacher.model,
|
|
986
|
+
enabled,
|
|
987
|
+
healthy: false,
|
|
988
|
+
stale: null,
|
|
989
|
+
idle: false,
|
|
990
|
+
latestCycle,
|
|
991
|
+
detail: `${providerConfig.teacher.model} is configured on Ollama, but the local LLM surface is not answering at ${providerConfig.teacherBaseUrl}`
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
if (!report.teacherLoop.available) {
|
|
995
|
+
return {
|
|
996
|
+
model: providerConfig.teacher.model,
|
|
997
|
+
enabled,
|
|
998
|
+
healthy: null,
|
|
999
|
+
stale: null,
|
|
1000
|
+
idle: null,
|
|
1001
|
+
latestCycle,
|
|
1002
|
+
detail: `${providerConfig.teacher.model} is enabled on Ollama, but no watch teacher snapshot is visible yet`
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
const stale = report.teacherLoop.latestFreshness === "stale" || report.teacherLoop.watchState === "stale_snapshot";
|
|
1006
|
+
const idle = report.teacherLoop.running === false &&
|
|
1007
|
+
(report.teacherLoop.queueDepth ?? 0) === 0 &&
|
|
1008
|
+
report.teacherLoop.failureMode === "none";
|
|
1009
|
+
const healthy = report.teacherLoop.failureMode === "none" &&
|
|
1010
|
+
stale === false &&
|
|
1011
|
+
report.teacherLoop.watchState !== "not_visible";
|
|
1012
|
+
const cycleDetail = TEACHER_NO_OP_MESSAGES[report.teacherLoop.lastNoOpReason] ?? "the latest teacher cycle detail is unavailable";
|
|
1013
|
+
if (report.teacherLoop.failureMode !== "none" && report.teacherLoop.failureMode !== "unavailable") {
|
|
1014
|
+
return {
|
|
1015
|
+
model: providerConfig.teacher.model,
|
|
1016
|
+
enabled,
|
|
1017
|
+
healthy: false,
|
|
1018
|
+
stale,
|
|
1019
|
+
idle,
|
|
1020
|
+
latestCycle,
|
|
1021
|
+
detail: report.teacherLoop.failureDetail === null
|
|
1022
|
+
? `${providerConfig.teacher.model} is enabled, but the watch loop recorded ${report.teacherLoop.failureMode}`
|
|
1023
|
+
: `${providerConfig.teacher.model} is enabled, but the watch loop recorded ${report.teacherLoop.failureMode}: ${report.teacherLoop.failureDetail}`
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
return {
|
|
1027
|
+
model: providerConfig.teacher.model,
|
|
1028
|
+
enabled,
|
|
1029
|
+
healthy,
|
|
1030
|
+
stale,
|
|
1031
|
+
idle,
|
|
1032
|
+
latestCycle,
|
|
1033
|
+
detail: `${providerConfig.teacher.model} is enabled on Ollama; ${cycleDetail}`
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
function summarizeStatusEmbedder(embeddings) {
|
|
1037
|
+
const provisioned = embeddings.provisionedState === "confirmed" || embeddings.provisionedState === "builtin"
|
|
1038
|
+
? true
|
|
1039
|
+
: embeddings.provisionedState === "not_confirmed" || embeddings.provisionedState === "off"
|
|
1040
|
+
? false
|
|
1041
|
+
: null;
|
|
1042
|
+
const live = embeddings.liveState === "yes" ? true : embeddings.liveState === "no" ? false : null;
|
|
1043
|
+
if (embeddings.provider === "off") {
|
|
1044
|
+
return {
|
|
1045
|
+
model: embeddings.model,
|
|
1046
|
+
provisioned,
|
|
1047
|
+
live,
|
|
1048
|
+
detail: `${embeddings.model} is not provisioned because the embedder provider is off`
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
if (embeddings.provider === "keywords") {
|
|
1052
|
+
return {
|
|
1053
|
+
model: embeddings.model,
|
|
1054
|
+
provisioned,
|
|
1055
|
+
live,
|
|
1056
|
+
detail: "keyword embeddings are builtin, so there is no Ollama model to provision"
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
if (provisioned === true && live === true) {
|
|
1060
|
+
return {
|
|
1061
|
+
model: embeddings.model,
|
|
1062
|
+
provisioned,
|
|
1063
|
+
live,
|
|
1064
|
+
detail: `${embeddings.model} is confirmed on Ollama and the active pack stores live numeric embeddings`
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
if (provisioned === true && live === false) {
|
|
1068
|
+
return {
|
|
1069
|
+
model: embeddings.model,
|
|
1070
|
+
provisioned,
|
|
1071
|
+
live,
|
|
1072
|
+
detail: `${embeddings.model} is confirmed on Ollama, but the active pack still has no live numeric embeddings`
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
if (provisioned === false && live === true) {
|
|
1076
|
+
return {
|
|
1077
|
+
model: embeddings.model,
|
|
1078
|
+
provisioned,
|
|
1079
|
+
live,
|
|
1080
|
+
detail: `${embeddings.model} is not confirmed on Ollama, but the active pack already carries numeric embeddings from an earlier materialization`
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
return {
|
|
1084
|
+
model: embeddings.model,
|
|
1085
|
+
provisioned,
|
|
1086
|
+
live,
|
|
1087
|
+
detail: embeddings.detail
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
function summarizeStatusRouteFn(status, report) {
|
|
1091
|
+
const freshness = report.servePath.refreshStatus ?? status.brain.routeFreshness;
|
|
1092
|
+
if (!report.routeFn.available) {
|
|
1093
|
+
return {
|
|
1094
|
+
available: false,
|
|
1095
|
+
freshness,
|
|
1096
|
+
trainedAt: report.routeFn.trainedAt,
|
|
1097
|
+
updatedAt: report.routeFn.updatedAt,
|
|
1098
|
+
usedAt: report.routeFn.usedAt,
|
|
1099
|
+
detail: report.routeFn.detail
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
let detail = report.routeFn.detail;
|
|
1103
|
+
if (report.servePath.usedLearnedRouteFn === true) {
|
|
1104
|
+
detail = `current serve proof used the learned route_fn; ${report.routeFn.detail}`;
|
|
1105
|
+
}
|
|
1106
|
+
else if (report.routeFn.usedAt !== null) {
|
|
1107
|
+
detail = `current serve proof did not use the learned route_fn, but the active route_fn last served a learned turn at ${report.routeFn.usedAt}`;
|
|
1108
|
+
}
|
|
1109
|
+
else if (report.routeFn.updatedAt !== null) {
|
|
1110
|
+
detail = `active route_fn was last updated at ${report.routeFn.updatedAt}, but no learned serve use is visible yet for the current pack`;
|
|
1111
|
+
}
|
|
1112
|
+
return {
|
|
1113
|
+
available: true,
|
|
1114
|
+
freshness,
|
|
1115
|
+
trainedAt: report.routeFn.trainedAt,
|
|
1116
|
+
updatedAt: report.routeFn.updatedAt,
|
|
1117
|
+
usedAt: report.routeFn.usedAt,
|
|
1118
|
+
detail
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
642
1121
|
function pushUniqueAlert(target, value) {
|
|
643
1122
|
const normalized = value.trim();
|
|
644
1123
|
if (normalized.length === 0) {
|
|
@@ -698,11 +1177,8 @@ function summarizeStatusAlerts(report, providerConfig, embeddings, localLlm) {
|
|
|
698
1177
|
}
|
|
699
1178
|
return buckets;
|
|
700
1179
|
}
|
|
701
|
-
function summarizeStatusWatchState(
|
|
702
|
-
|
|
703
|
-
return "not_visible";
|
|
704
|
-
}
|
|
705
|
-
return report.teacherLoop.running === true ? "running" : "snapshot_only";
|
|
1180
|
+
function summarizeStatusWatchState(status) {
|
|
1181
|
+
return status.passiveLearning.watchState;
|
|
706
1182
|
}
|
|
707
1183
|
function summarizeStatusServeReality(status) {
|
|
708
1184
|
if (status.brainStatus.serveState === "serving_active_pack") {
|
|
@@ -710,48 +1186,128 @@ function summarizeStatusServeReality(status) {
|
|
|
710
1186
|
}
|
|
711
1187
|
return status.brainStatus.serveState;
|
|
712
1188
|
}
|
|
1189
|
+
function summarizeStatusPromotionState(status) {
|
|
1190
|
+
if (status.brain.state === "pg_promoted_pack_authoritative") {
|
|
1191
|
+
return "promoted";
|
|
1192
|
+
}
|
|
1193
|
+
if (status.brain.state === "seed_state_authoritative") {
|
|
1194
|
+
return status.passiveLearning.firstExportOccurred ? "seed_authoritative" : "awaiting_first_export";
|
|
1195
|
+
}
|
|
1196
|
+
return status.brain.state;
|
|
1197
|
+
}
|
|
713
1198
|
function formatStatusAlertLine(values) {
|
|
714
1199
|
const normalized = values.map((value) => value.trim()).filter((value) => value.length > 0);
|
|
715
1200
|
return normalized.length === 0 ? "none" : formatCompactList(normalized, 2, 64);
|
|
716
1201
|
}
|
|
717
|
-
function
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
1202
|
+
function formatStatusNullableNumber(value, unknown = "unknown") {
|
|
1203
|
+
return value === null ? unknown : String(value);
|
|
1204
|
+
}
|
|
1205
|
+
function formatStatusNullableYesNo(value) {
|
|
1206
|
+
return value === null ? "unknown" : yesNo(value);
|
|
1207
|
+
}
|
|
1208
|
+
function formatStatusNullableMilliseconds(value) {
|
|
1209
|
+
return value === null ? "none" : `${value.toFixed(2)}ms`;
|
|
1210
|
+
}
|
|
1211
|
+
function formatStatusHotPathTiming(timing) {
|
|
1212
|
+
return [
|
|
1213
|
+
`hotPath=${formatStatusNullableMilliseconds(timing.totalMs)}`,
|
|
1214
|
+
`route=${formatStatusNullableMilliseconds(timing.routeSelectionMs)}`,
|
|
1215
|
+
`prompt=${formatStatusNullableMilliseconds(timing.promptAssemblyMs)}`,
|
|
1216
|
+
`other=${formatStatusNullableMilliseconds(timing.otherMs)}`,
|
|
1217
|
+
`background=${timing.backgroundWorkIncluded ? "included" : "excluded"}`
|
|
1218
|
+
].join(" ");
|
|
1219
|
+
}
|
|
1220
|
+
function formatStatusObservedDeltaTransition(delta) {
|
|
1221
|
+
if (delta.latestPackTransition === null) {
|
|
1222
|
+
return "none";
|
|
723
1223
|
}
|
|
724
|
-
return
|
|
1224
|
+
return `${delta.latestPackTransition.kind}:${delta.latestPackTransition.fromPackId ?? "none"}->${delta.latestPackTransition.toPackId}`;
|
|
1225
|
+
}
|
|
1226
|
+
function formatAttachedProfileTruthCompact(entry) {
|
|
1227
|
+
const currentPrefix = entry.current ? "*" : "";
|
|
1228
|
+
return (`${currentPrefix}${entry.label}` +
|
|
1229
|
+
`[hook=${entry.hookFiles} config=${entry.configLoad} runtime=${entry.runtimeLoad}` +
|
|
1230
|
+
`${entry.runtimeLoadedAt === null ? "" : `@${entry.runtimeLoadedAt}`}]`);
|
|
1231
|
+
}
|
|
1232
|
+
function formatAttachedProfileTruthCompactList(entries) {
|
|
1233
|
+
return entries.length === 0
|
|
1234
|
+
? "none"
|
|
1235
|
+
: formatCompactList(entries.map((entry) => formatAttachedProfileTruthCompact(entry)), 2, 80);
|
|
1236
|
+
}
|
|
1237
|
+
function formatAttachedProfileTruthDetailedList(entries) {
|
|
1238
|
+
return entries.length === 0
|
|
1239
|
+
? "none"
|
|
1240
|
+
: entries
|
|
1241
|
+
.map((entry) => `${entry.current ? "*" : ""}${entry.label}` +
|
|
1242
|
+
`[hook=${entry.hookFiles} config=${entry.configLoad} runtime=${entry.runtimeLoad} loadedAt=${entry.runtimeLoadedAt ?? "none"}]`)
|
|
1243
|
+
.join(" ");
|
|
1244
|
+
}
|
|
1245
|
+
function summarizeDisplayedStatus(status, installHook) {
|
|
1246
|
+
return installHook.state === "blocked_by_allowlist" ? "fail" : status.brainStatus.status;
|
|
725
1247
|
}
|
|
726
1248
|
function buildCompactStatusHeader(status, report, options) {
|
|
727
1249
|
const installHook = summarizeStatusInstallHook(options.openclawHome);
|
|
1250
|
+
const hookLoad = summarizeStatusHookLoad(installHook, status);
|
|
728
1251
|
const embeddings = summarizeStatusEmbeddings(report, options.providerConfig);
|
|
729
1252
|
const localLlm = summarizeStatusLocalLlm(options.providerConfig);
|
|
1253
|
+
const teacher = summarizeStatusTeacher(report, options.providerConfig, localLlm);
|
|
1254
|
+
const embedder = summarizeStatusEmbedder(embeddings);
|
|
1255
|
+
const routeFn = summarizeStatusRouteFn(status, report);
|
|
730
1256
|
const alerts = summarizeStatusAlerts(report, options.providerConfig, embeddings, localLlm);
|
|
731
|
-
const promoted = status.brain.state === "pg_promoted_pack_authoritative" ? "yes" : "no";
|
|
732
1257
|
const liveModels = embeddings.models.length === 0 ? "none" : embeddings.models.join("|");
|
|
1258
|
+
const attachmentTruth = summarizeStatusAttachmentTruth({
|
|
1259
|
+
activationRoot: status.host.activationRoot,
|
|
1260
|
+
openclawHome: options.openclawHome,
|
|
1261
|
+
status
|
|
1262
|
+
});
|
|
733
1263
|
return [
|
|
734
|
-
`
|
|
735
|
-
`
|
|
1264
|
+
`lifecycle attach=${status.attachment.state} learner=${yesNo(status.passiveLearning.learnerRunning)} watch=${summarizeStatusWatchState(status)} export=${status.passiveLearning.exportState} promote=${summarizeStatusPromotionState(status)} serve=${summarizeStatusServeReality(status)}`,
|
|
1265
|
+
`hook install=${hookLoad.installState} loadProof=${hookLoad.loadProof} detail=${hookLoad.detail}`,
|
|
1266
|
+
`attachTruth current=${attachmentTruth.currentProfileLabel} hook=${attachmentTruth.hookFiles} config=${attachmentTruth.configLoad} runtime=${attachmentTruth.runtimeLoad} watcher=${attachmentTruth.watcher} attachedSet=${formatAttachedProfileTruthCompactList(attachmentTruth.attachedProfiles)} why=${attachmentTruth.detail}`,
|
|
1267
|
+
`passive firstExport=${yesNo(status.passiveLearning.firstExportOccurred)} backlog=${status.passiveLearning.backlogState} pending=${formatStatusNullableNumber(status.passiveLearning.pendingLive)}/${formatStatusNullableNumber(status.passiveLearning.pendingBackfill)}`,
|
|
1268
|
+
`serving pack=${status.passiveLearning.currentServingPackId ?? "none"} lastExport=${status.passiveLearning.lastExportAt ?? "none"} lastPromotion=${status.passiveLearning.lastPromotionAt ?? "none"}`,
|
|
1269
|
+
`timing ${formatStatusHotPathTiming(status.brainStatus.timing)}`,
|
|
1270
|
+
`delta observed=${status.passiveLearning.lastObservedDelta.observedAt ?? "none"} exported=${formatStatusNullableYesNo(status.passiveLearning.lastObservedDelta.exported)} labeled=${formatStatusNullableYesNo(status.passiveLearning.lastObservedDelta.labeled)} promoted=${formatStatusNullableYesNo(status.passiveLearning.lastObservedDelta.promoted)} served=${formatStatusNullableYesNo(status.passiveLearning.lastObservedDelta.served)} transition=${formatStatusObservedDeltaTransition(status.passiveLearning.lastObservedDelta)}`,
|
|
1271
|
+
`changed ${status.passiveLearning.lastObservedDelta.explanation}`,
|
|
736
1272
|
`explain ${status.brain.summary}`,
|
|
1273
|
+
`graph blocks=${report.graph.blockCount ?? "none"} strongest=${report.graph.strongestBlockId ?? "none"} latest=${report.graph.latestMaterialization.packId ?? "none"} latestChanged=${yesNo(report.graph.latestMaterialization.changed)} connect=${formatCompactGraphConnectDiagnostics(report.graph.latestMaterialization.connectDiagnostics ?? report.graph.connectDiagnostics)}`,
|
|
1274
|
+
`teacher model=${teacher.model} enabled=${yesNo(teacher.enabled)} healthy=${yesNo(teacher.healthy)} stale=${yesNo(teacher.stale)} idle=${yesNo(teacher.idle)} cycle=${teacher.latestCycle} why=${teacher.detail}`,
|
|
1275
|
+
`embedder model=${embedder.model} provisioned=${yesNo(embedder.provisioned)} live=${yesNo(embedder.live)} why=${embedder.detail}`,
|
|
1276
|
+
`routeFn available=${yesNo(routeFn.available)} freshness=${routeFn.freshness} trained=${routeFn.trainedAt ?? "none"} updated=${routeFn.updatedAt ?? "none"} used=${routeFn.usedAt ?? "none"} why=${routeFn.detail}`,
|
|
737
1277
|
`embeddings provider=${embeddings.provider} provisioned=${embeddings.provisionedState} live=${embeddings.liveState} stored=${embeddings.embeddedEntryCount ?? "none"}/${embeddings.totalEntryCount ?? "none"} models=${liveModels}`,
|
|
738
1278
|
`localLLM detected=${yesNo(localLlm.detected)} enabled=${yesNo(localLlm.enabled)} provider=${localLlm.provider} model=${localLlm.model}`,
|
|
739
1279
|
`alerts service_risk=${formatStatusAlertLine(alerts.serviceRisk)} degraded_brain=${formatStatusAlertLine(alerts.degradedBrain)} cosmetic_noise=${formatStatusAlertLine(alerts.cosmeticNoise)}`
|
|
740
1280
|
];
|
|
741
1281
|
}
|
|
742
1282
|
function formatCurrentProfileStatusSummary(status, report, targetInspection, options) {
|
|
1283
|
+
const installHook = summarizeStatusInstallHook(options.openclawHome);
|
|
1284
|
+
const displayedStatus = summarizeDisplayedStatus(status, installHook);
|
|
1285
|
+
const embeddings = summarizeStatusEmbeddings(report, options.providerConfig);
|
|
1286
|
+
const localLlm = summarizeStatusLocalLlm(options.providerConfig);
|
|
1287
|
+
const liveModels = embeddings.models.length === 0 ? "none" : embeddings.models.join("|");
|
|
1288
|
+
const attachmentTruth = summarizeStatusAttachmentTruth({
|
|
1289
|
+
activationRoot: status.host.activationRoot,
|
|
1290
|
+
openclawHome: options.openclawHome,
|
|
1291
|
+
status
|
|
1292
|
+
});
|
|
743
1293
|
const profileIdSuffix = status.profile.profileId === null ? "" : ` id=${status.profile.profileId}`;
|
|
744
1294
|
const targetLine = targetInspection === null
|
|
745
1295
|
? `target activation=${status.host.activationRoot} source=activation_root_only`
|
|
746
1296
|
: `target activation=${status.host.activationRoot} ${formatOpenClawTargetLine(targetInspection)} hook=${shortenPath(path.join(targetInspection.openclawHome, "extensions", "openclawbrain", "index.ts"))}`;
|
|
747
1297
|
return [
|
|
748
|
-
`STATUS ${
|
|
1298
|
+
`STATUS ${displayedStatus}`,
|
|
749
1299
|
...buildCompactStatusHeader(status, report, options),
|
|
750
1300
|
`answer ${status.brain.summary}`,
|
|
751
1301
|
targetLine,
|
|
752
1302
|
...(targetInspection === null ? [] : [`preflight ${formatOpenClawTargetExplanation(targetInspection)}`]),
|
|
1303
|
+
`next ${buildStatusNextStep(status, report, {
|
|
1304
|
+
openclawHome: options.openclawHome,
|
|
1305
|
+
installHook
|
|
1306
|
+
})}`,
|
|
753
1307
|
`host runtime=${status.host.runtimeOwner} activation=${status.host.activationRoot}`,
|
|
754
1308
|
`profile selector=${status.profile.selector}${profileIdSuffix} attachment=${status.attachment.state} policy=${status.attachment.policyMode}`,
|
|
1309
|
+
`attachTruth current=${attachmentTruth.currentProfileLabel} hook=${attachmentTruth.hookFiles} config=${attachmentTruth.configLoad} runtime=${attachmentTruth.runtimeLoad} watcher=${attachmentTruth.watcher} detail=${attachmentTruth.detail}`,
|
|
1310
|
+
`attachedSet ${formatAttachedProfileTruthDetailedList(attachmentTruth.attachedProfiles)} proofPath=${shortenPath(attachmentTruth.runtimeProofPath)} proofError=${attachmentTruth.runtimeProofError ?? "none"}`,
|
|
755
1311
|
`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)}`,
|
|
756
1312
|
`activation state=${status.brainStatus.activationState} detail=${status.brain.detail}`,
|
|
757
1313
|
`brain pack=${status.brain.activePackId ?? "none"} state=${status.brain.state} init=${status.brain.initMode ?? "unknown"} routeFreshness=${status.brain.routeFreshness} lastPromotion=${status.brain.lastPromotionAt ?? "none"} router=${status.brain.routerIdentity ?? "none"}`,
|
|
@@ -760,13 +1316,17 @@ function formatCurrentProfileStatusSummary(status, report, targetInspection, opt
|
|
|
760
1316
|
`budget requested=${report.servePath.requestedBudgetStrategy ?? "none"} resolved=${report.servePath.resolvedBudgetStrategy ?? "none"} maxBlocks=${report.servePath.resolvedMaxContextBlocks ?? "none"} source=${report.servePath.structuralBudgetSource ?? "none"} origin=${status.brainStatus.structuralDecision.origin} basis=${status.brainStatus.structuralDecision.basis}`,
|
|
761
1317
|
`decision ${status.brainStatus.structuralDecision.detail}`,
|
|
762
1318
|
`principal latest=${formatPrincipalLatest(report)} pending=${report.principal.pendingCount ?? report.learning.pendingPrincipalCount ?? "none"} checkpoint=${formatPrincipalCheckpointFrontier(report)} downstream=${yesNo(report.principal.servingDownstreamOfLatestCorrection)} lag=${report.learning.principalLagToPromotion.sequenceLag ?? "none"}`,
|
|
1319
|
+
`passive learner=${yesNo(status.passiveLearning.learnerRunning)} firstExport=${yesNo(status.passiveLearning.firstExportOccurred)} watch=${status.passiveLearning.watchState} export=${status.passiveLearning.exportState} backlog=${status.passiveLearning.backlogState} pending=${formatStatusNullableNumber(status.passiveLearning.pendingLive)}/${formatStatusNullableNumber(status.passiveLearning.pendingBackfill)} detail=${status.passiveLearning.detail}`,
|
|
1320
|
+
`delta observed=${status.passiveLearning.lastObservedDelta.observedAt ?? "none"} exported=${formatStatusNullableYesNo(status.passiveLearning.lastObservedDelta.exported)} labeled=${formatStatusNullableYesNo(status.passiveLearning.lastObservedDelta.labeled)} promoted=${formatStatusNullableYesNo(status.passiveLearning.lastObservedDelta.promoted)} served=${formatStatusNullableYesNo(status.passiveLearning.lastObservedDelta.served)} transition=${formatStatusObservedDeltaTransition(status.passiveLearning.lastObservedDelta)} detail=${status.passiveLearning.lastObservedDelta.explanation}`,
|
|
763
1321
|
`scanner flowing=${yesNo(report.supervision.flowing)} scan=${report.supervision.scanPolicy ?? "none"} surfaces=${formatScannerSurfaces(report)} labels=${report.supervision.humanLabelCount ?? "none"}/${report.supervision.selfLabelCount ?? "none"} attributable=${report.supervision.attributedEventCount ?? "none"}/${report.supervision.totalEventCount ?? "none"} digests=${report.supervision.selectionDigestCount ?? "none"}`,
|
|
764
1322
|
`labels ${formatLabelFlowSummary(report.labelFlow)}`,
|
|
765
|
-
`graph source=${report.graph.runtimePlasticitySource ?? "none"}
|
|
1323
|
+
`graph source=${report.graph.runtimePlasticitySource ?? "none"} blocks=${report.graph.blockCount ?? "none"} strongest=${report.graph.strongestBlockId ?? "none"} ops=${formatStructuralOps(report)} latest=${report.graph.latestMaterialization.packId ?? "none"} latestChanged=${yesNo(report.graph.latestMaterialization.changed)} connect=${formatGraphConnectDiagnostics(report.graph.latestMaterialization.connectDiagnostics ?? report.graph.connectDiagnostics)} summary=${formatGraphSummary(report)}`,
|
|
766
1324
|
`path ${formatLearningPathSummary(report.learningPath)}`,
|
|
767
1325
|
`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}`,
|
|
768
|
-
`
|
|
769
|
-
`
|
|
1326
|
+
`teacherProof ${formatTeacherLoopSummary(report)}`,
|
|
1327
|
+
`watch cadence=${report.teacherLoop.learningCadence} scan=${report.teacherLoop.scanPolicy} heartbeat=${report.teacherLoop.lastHeartbeatAt ?? "none"} interval=${report.teacherLoop.pollIntervalSeconds ?? "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"}`,
|
|
1328
|
+
`embeddings provider=${embeddings.provider} provisioned=${embeddings.provisionedState} live=${embeddings.liveState} stored=${embeddings.embeddedEntryCount ?? "none"}/${embeddings.totalEntryCount ?? "none"} models=${liveModels}`,
|
|
1329
|
+
`localLLM detected=${yesNo(localLlm.detected)} enabled=${yesNo(localLlm.enabled)} provider=${localLlm.provider} model=${localLlm.model}`,
|
|
770
1330
|
`rollback ready=${yesNo(report.rollback.allowed)} state=${report.rollback.state} previous=${report.rollback.previousPackId ?? "none"}`,
|
|
771
1331
|
`proof lastExport=${status.brain.lastExportAt ?? "none"} lastLearningUpdate=${status.brain.lastLearningUpdateAt ?? "none"} lastPromotion=${status.brain.lastPromotionAt ?? "none"}`,
|
|
772
1332
|
`logs root=${status.brain.logRoot ?? "none"}`,
|
|
@@ -795,6 +1355,15 @@ function formatOpenClawTargetExplanation(inspection) {
|
|
|
795
1355
|
function buildInstallStatusCommand(activationRoot) {
|
|
796
1356
|
return `openclawbrain status --activation-root ${quoteShellArg(activationRoot)}`;
|
|
797
1357
|
}
|
|
1358
|
+
function buildLearnerServiceStatusCommand(activationRoot) {
|
|
1359
|
+
return `openclawbrain daemon status --activation-root ${quoteShellArg(activationRoot)}`;
|
|
1360
|
+
}
|
|
1361
|
+
function buildGatewayRestartCommand(profileId) {
|
|
1362
|
+
return `env -i HOME="$HOME" PATH="$PATH" openclaw --profile ${quoteShellArg(profileId)} gateway restart`;
|
|
1363
|
+
}
|
|
1364
|
+
function buildGatewayStatusCommand(profileId) {
|
|
1365
|
+
return `env -i HOME="$HOME" PATH="$PATH" openclaw --profile ${quoteShellArg(profileId)} gateway status`;
|
|
1366
|
+
}
|
|
798
1367
|
function buildInstallCommand(openclawHome) {
|
|
799
1368
|
return `openclawbrain install --openclaw-home ${quoteShellArg(openclawHome)}`;
|
|
800
1369
|
}
|
|
@@ -978,6 +1547,12 @@ function writeInstallProviderDefaults(parsed) {
|
|
|
978
1547
|
: "Teacher: no compatible local Ollama model detected; watch stays heuristic unless explicitly overridden"
|
|
979
1548
|
};
|
|
980
1549
|
}
|
|
1550
|
+
function shouldWriteProfileHookProviderDefaults(parsed, activationPlan, isInstall) {
|
|
1551
|
+
if (isInstall || activationPlan.action === "bootstrap") {
|
|
1552
|
+
return true;
|
|
1553
|
+
}
|
|
1554
|
+
return !existsSync(resolveOpenClawBrainProviderDefaultsPath(parsed.activationRoot));
|
|
1555
|
+
}
|
|
981
1556
|
function buildInstallBrainFeedbackSummary(input) {
|
|
982
1557
|
const providerDefaultsPath = resolveOpenClawBrainProviderDefaultsPath(input.parsed.activationRoot);
|
|
983
1558
|
const embedderState = input.embedderProvision === null ? "unchanged" : input.embedderProvision.state;
|
|
@@ -985,15 +1560,61 @@ function buildInstallBrainFeedbackSummary(input) {
|
|
|
985
1560
|
const teacherProvider = teacherDefaults?.provider ?? "unknown";
|
|
986
1561
|
const teacherModel = teacherDefaults?.model ?? null;
|
|
987
1562
|
const detectedLocalLlm = teacherDefaults?.detectedLocally ?? null;
|
|
1563
|
+
const profileName = input.targetInspection.profileId;
|
|
1564
|
+
const profileSource = input.targetInspection.profileSource;
|
|
1565
|
+
const casingGuidance = profileName === null
|
|
1566
|
+
? "Exact OpenClaw --profile casing is unresolved here because this target stays on the host-selected current_profile boundary."
|
|
1567
|
+
: `Use the exact OpenClaw profile casing shown here for host-side restart/status commands: ${quoteShellArg(profileName)}.`;
|
|
1568
|
+
const attachment = input.parsed.shared
|
|
1569
|
+
? {
|
|
1570
|
+
policy: "shared",
|
|
1571
|
+
activationRootMode: "shared_root_declared",
|
|
1572
|
+
sameGatewayProof: "not_checked_in",
|
|
1573
|
+
detail: "Shared activation root declared. Other profiles may point at this same root, but same-gateway many-profile load/serve proof is not checked in."
|
|
1574
|
+
}
|
|
1575
|
+
: {
|
|
1576
|
+
policy: "dedicated",
|
|
1577
|
+
activationRootMode: "dedicated_per_profile",
|
|
1578
|
+
sameGatewayProof: "not_applicable",
|
|
1579
|
+
detail: "Dedicated activation root for this profile/home boundary."
|
|
1580
|
+
};
|
|
1581
|
+
const restart = profileName === null
|
|
1582
|
+
? {
|
|
1583
|
+
exactProfile: false,
|
|
1584
|
+
profile: null,
|
|
1585
|
+
profileSource,
|
|
1586
|
+
guidance: `Operator-owned restart step: this install did not infer an exact --profile token from ${shortenPath(input.targetInspection.openclawHome)}. ` +
|
|
1587
|
+
"If immediate load matters, restart the host-selected current_profile from OpenClaw itself; otherwise the next natural launch will pick up the hook.",
|
|
1588
|
+
restartCommand: null,
|
|
1589
|
+
gatewayStatusCommand: null
|
|
1590
|
+
}
|
|
1591
|
+
: {
|
|
1592
|
+
exactProfile: true,
|
|
1593
|
+
profile: profileName,
|
|
1594
|
+
profileSource,
|
|
1595
|
+
guidance: `Operator-owned restart step: if immediate load matters and profile ${quoteShellArg(profileName)} is already running, run ${buildGatewayRestartCommand(profileName)}. ` +
|
|
1596
|
+
`If it is stopped, the next launch of profile ${quoteShellArg(profileName)} will pick up the hook. ${casingGuidance}`,
|
|
1597
|
+
restartCommand: buildGatewayRestartCommand(profileName),
|
|
1598
|
+
gatewayStatusCommand: buildGatewayStatusCommand(profileName)
|
|
1599
|
+
};
|
|
988
1600
|
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.
|
|
992
|
-
? `OpenClaw has not reloaded this hook yet; restart plus status still must prove
|
|
993
|
-
:
|
|
1601
|
+
? `hook written, activation root ready, seed/current-profile attach bootstrapped, learner service ${input.learnerService.state}, provider defaults ${input.providerDefaults === null ? "kept" : "written"}`
|
|
1602
|
+
: `hook written, activation root kept, active pack ${input.activationPlan.activePackId ?? "unknown"} preserved, learner service ${input.learnerService.state}${input.providerDefaults === null ? "" : ", provider defaults written"}`;
|
|
1603
|
+
const notYetProved = input.learnerService.state === "deferred"
|
|
1604
|
+
? `OpenClaw has not reloaded this hook yet, and passive learner auto-start was deferred; restart plus status still must prove serve-path load, while learner-service start remains a separate operator check`
|
|
1605
|
+
: input.activationPlan.action === "bootstrap"
|
|
1606
|
+
? `Passive learning is wired for this activation root, but OpenClaw has not reloaded the hook yet; restart plus status still must prove live startup/load and the first exported turn`
|
|
1607
|
+
: `Passive learning is wired for this activation root, but this ${input.parsed.command} run does not itself prove live startup/load after restart`;
|
|
994
1608
|
return {
|
|
995
1609
|
hookPath: input.extensionDir,
|
|
996
1610
|
providerDefaultsPath,
|
|
1611
|
+
profile: {
|
|
1612
|
+
exactProfileName: profileName,
|
|
1613
|
+
profileSource,
|
|
1614
|
+
casingGuidance
|
|
1615
|
+
},
|
|
1616
|
+
attachment,
|
|
1617
|
+
restart,
|
|
997
1618
|
embedder: {
|
|
998
1619
|
provider: "ollama",
|
|
999
1620
|
model: DEFAULT_OLLAMA_EMBEDDING_MODEL,
|
|
@@ -1004,6 +1625,14 @@ function buildInstallBrainFeedbackSummary(input) {
|
|
|
1004
1625
|
model: teacherModel,
|
|
1005
1626
|
detectedLocalLlm
|
|
1006
1627
|
},
|
|
1628
|
+
learnerService: {
|
|
1629
|
+
state: input.learnerService.state,
|
|
1630
|
+
detail: input.learnerService.detail,
|
|
1631
|
+
plistPath: input.learnerService.plistPath,
|
|
1632
|
+
logPath: input.learnerService.logPath,
|
|
1633
|
+
configuredActivationRoot: input.learnerService.configuredActivationRoot,
|
|
1634
|
+
matchesRequestedActivationRoot: input.learnerService.matchesRequestedActivationRoot
|
|
1635
|
+
},
|
|
1007
1636
|
startup: {
|
|
1008
1637
|
token: "BRAIN_NOT_YET_LOADED",
|
|
1009
1638
|
proof: "restart_required"
|
|
@@ -1012,19 +1641,28 @@ function buildInstallBrainFeedbackSummary(input) {
|
|
|
1012
1641
|
notYetProved,
|
|
1013
1642
|
lines: [
|
|
1014
1643
|
`target ${formatOpenClawTargetLine(input.targetInspection)} source=${formatInstallOpenClawHomeSource(input.parsed.openclawHomeSource)}`,
|
|
1644
|
+
profileName === null
|
|
1645
|
+
? "profile exactName=unresolved selector=current_profile casing=not_available"
|
|
1646
|
+
: `profile exactName=${quoteShellArg(profileName)} source=${profileSource} casing=preserved`,
|
|
1015
1647
|
`hook written=${shortenPath(input.extensionDir)}`,
|
|
1016
1648
|
`activation root=${shortenPath(input.parsed.activationRoot)} source=${formatInstallActivationRootSource(input.parsed.activationRootSource)}`,
|
|
1649
|
+
`attachment policy=${attachment.policy} rootMode=${attachment.activationRootMode} sameGatewayProof=${attachment.sameGatewayProof} detail=${attachment.detail}`,
|
|
1017
1650
|
`defaults provider-defaults=${shortenPath(providerDefaultsPath)} state=${input.providerDefaults === null ? "unchanged" : "written"}`,
|
|
1018
1651
|
`embedder provider=ollama model=${DEFAULT_OLLAMA_EMBEDDING_MODEL} state=${embedderState}`,
|
|
1019
1652
|
`teacher provider=${teacherProvider} model=${teacherModel ?? "none"} localLLM=${detectedLocalLlm === null ? "unknown" : yesNo(detectedLocalLlm)}`,
|
|
1653
|
+
`learner state=${input.learnerService.state} detail=${input.learnerService.detail}`,
|
|
1654
|
+
`restart operator=manual exactProfile=${yesNo(restart.exactProfile)} command=${restart.restartCommand ?? "unavailable"}`,
|
|
1020
1655
|
"startup BRAIN_NOT_YET_LOADED proof=restart_required",
|
|
1021
1656
|
`provedNow ${provedNow}`,
|
|
1022
1657
|
`notYet ${notYetProved}`
|
|
1023
1658
|
]
|
|
1024
1659
|
};
|
|
1025
1660
|
}
|
|
1026
|
-
function buildInstallReloadGuidance() {
|
|
1027
|
-
|
|
1661
|
+
function buildInstallReloadGuidance(input) {
|
|
1662
|
+
if (input.targetInspection.profileId === null) {
|
|
1663
|
+
return `Restart later from OpenClaw for the host-selected current_profile behind ${shortenPath(input.targetInspection.openclawHome)} if immediate load matters; this install did not infer an exact --profile token.`;
|
|
1664
|
+
}
|
|
1665
|
+
return `Restart now if immediate load matters: ${buildGatewayRestartCommand(input.targetInspection.profileId)}`;
|
|
1028
1666
|
}
|
|
1029
1667
|
const LEGACY_PROFILE_NOTE_FILENAMES = ["BRAIN.md", "brain.md"];
|
|
1030
1668
|
const LEGACY_BRAIN_AGENTS_LINE = "5. Read `BRAIN.md` — your learning brain context";
|
|
@@ -1108,8 +1746,21 @@ function buildCleanupRestartGuidance(restart) {
|
|
|
1108
1746
|
}
|
|
1109
1747
|
return "If this OpenClaw profile is currently running, restart it before expecting the new hook state to take effect. If it is stopped, the next launch will pick it up.";
|
|
1110
1748
|
}
|
|
1111
|
-
function buildStatusNextStep(status, report) {
|
|
1749
|
+
function buildStatusNextStep(status, report, options) {
|
|
1112
1750
|
const activationRootArg = quoteShellArg(status.host.activationRoot);
|
|
1751
|
+
const attachmentTruth = summarizeStatusAttachmentTruth({
|
|
1752
|
+
activationRoot: status.host.activationRoot,
|
|
1753
|
+
openclawHome: options.openclawHome,
|
|
1754
|
+
status
|
|
1755
|
+
});
|
|
1756
|
+
if (options.installHook.state === "blocked_by_allowlist") {
|
|
1757
|
+
if (options.openclawHome === null) {
|
|
1758
|
+
return "Repair the OpenClaw plugin allowlist mismatch before trusting serve-path status again.";
|
|
1759
|
+
}
|
|
1760
|
+
return ("Repair the OpenClaw plugin allowlist mismatch " +
|
|
1761
|
+
`(rerun ${buildInstallCommand(options.openclawHome)} or ${buildAttachCommand(options.openclawHome, status.host.activationRoot)}) ` +
|
|
1762
|
+
"before trusting serve-path status again.");
|
|
1763
|
+
}
|
|
1113
1764
|
if (status.brainStatus.activationState === "broken_install") {
|
|
1114
1765
|
return "Repair or replace the activation root before trusting serve-path status again.";
|
|
1115
1766
|
}
|
|
@@ -1119,9 +1770,27 @@ function buildStatusNextStep(status, report) {
|
|
|
1119
1770
|
if (status.brainStatus.status === "fail") {
|
|
1120
1771
|
return `Run \`openclawbrain status --activation-root ${activationRootArg} --detailed\` before changing lifecycle state so the serve-path failure is explicit.`;
|
|
1121
1772
|
}
|
|
1773
|
+
if (options.openclawHome !== null && options.installHook.state === "not_installed") {
|
|
1774
|
+
return `Run \`${buildInstallCommand(options.openclawHome)}\` before expecting this OpenClaw home to load the brain hook.`;
|
|
1775
|
+
}
|
|
1776
|
+
if (options.openclawHome !== null &&
|
|
1777
|
+
attachmentTruth.hookFiles === "present" &&
|
|
1778
|
+
attachmentTruth.configLoad === "allows_load" &&
|
|
1779
|
+
attachmentTruth.runtimeLoad === "not_proven") {
|
|
1780
|
+
return "Restart the exact OpenClaw profile or wait for its next launch, then rerun status until runtime load becomes proven instead of assumed from on-disk state.";
|
|
1781
|
+
}
|
|
1122
1782
|
if (status.brainStatus.awaitingFirstExport) {
|
|
1123
1783
|
return `Let the attached OpenClaw profile emit a real export, then rerun \`openclawbrain status --activation-root ${activationRootArg}\`.`;
|
|
1124
1784
|
}
|
|
1785
|
+
if (options.openclawHome === null) {
|
|
1786
|
+
return `Pin \`--openclaw-home <path>\` when you need exact hook-install truth; activation-root-only status only proves this root's serve-path state.`;
|
|
1787
|
+
}
|
|
1788
|
+
if (attachmentTruth.runtimeLoad === "proof_error") {
|
|
1789
|
+
return "Repair the runtime-load proof file before trusting attach truth again; status now knows the exact file that broke.";
|
|
1790
|
+
}
|
|
1791
|
+
if (options.installHook.state === "installed" && status.brainStatus.serveState === "serving_active_pack") {
|
|
1792
|
+
return "Check the OpenClaw startup log for the `[openclawbrain] BRAIN LOADED` breadcrumb when you need live hook-load proof.";
|
|
1793
|
+
}
|
|
1125
1794
|
if (report.learning.warningStates.includes("principal_live_backlog") ||
|
|
1126
1795
|
report.learning.warningStates.includes("active_pack_behind_latest_principal")) {
|
|
1127
1796
|
return "A newer principal correction is still pending promotion; keep the current pack conservative until learner promotion lands.";
|
|
@@ -1132,14 +1801,19 @@ function buildStatusNextStep(status, report) {
|
|
|
1132
1801
|
return `Use \`openclawbrain status --activation-root ${activationRootArg} --detailed\` when you need the full lifecycle, serve-path, and backlog proof.`;
|
|
1133
1802
|
}
|
|
1134
1803
|
function formatHumanFriendlyStatus(status, report, targetInspection, options) {
|
|
1804
|
+
const installHook = summarizeStatusInstallHook(options.openclawHome);
|
|
1805
|
+
const displayedStatus = summarizeDisplayedStatus(status, installHook);
|
|
1135
1806
|
const lines = [
|
|
1136
|
-
`STATUS ${
|
|
1807
|
+
`STATUS ${displayedStatus}`,
|
|
1137
1808
|
...buildCompactStatusHeader(status, report, options),
|
|
1138
1809
|
...(targetInspection === null ? [] : [
|
|
1139
1810
|
`target ${formatOpenClawTargetLine(targetInspection)}`,
|
|
1140
1811
|
`preflight ${formatOpenClawTargetExplanation(targetInspection)}`
|
|
1141
1812
|
]),
|
|
1142
|
-
`next ${buildStatusNextStep(status, report
|
|
1813
|
+
`next ${buildStatusNextStep(status, report, {
|
|
1814
|
+
openclawHome: options.openclawHome,
|
|
1815
|
+
installHook
|
|
1816
|
+
})}`
|
|
1143
1817
|
];
|
|
1144
1818
|
return lines.join("\n");
|
|
1145
1819
|
}
|
|
@@ -2139,7 +2813,7 @@ function buildExtensionIndexTs(activationRoot) {
|
|
|
2139
2813
|
function buildExtensionPackageJson() {
|
|
2140
2814
|
const packageMetadata = readOpenClawPackageMetadata();
|
|
2141
2815
|
return JSON.stringify({
|
|
2142
|
-
name: "openclawbrain
|
|
2816
|
+
name: "openclawbrain",
|
|
2143
2817
|
version: packageMetadata.version,
|
|
2144
2818
|
private: true,
|
|
2145
2819
|
type: "module",
|
|
@@ -2242,6 +2916,67 @@ function buildHistoryEntry(record, slot, isActive) {
|
|
|
2242
2916
|
current: isActive
|
|
2243
2917
|
};
|
|
2244
2918
|
}
|
|
2919
|
+
function ensureLifecycleLearnerService(activationRoot) {
|
|
2920
|
+
const outcome = ensureManagedLearnerServiceForActivationRoot(activationRoot);
|
|
2921
|
+
return {
|
|
2922
|
+
state: outcome.state,
|
|
2923
|
+
detail: outcome.detail,
|
|
2924
|
+
plistPath: outcome.inspection.plistPath,
|
|
2925
|
+
logPath: outcome.inspection.logPath,
|
|
2926
|
+
configuredActivationRoot: outcome.inspection.configuredActivationRoot,
|
|
2927
|
+
matchesRequestedActivationRoot: outcome.inspection.matchesRequestedActivationRoot
|
|
2928
|
+
};
|
|
2929
|
+
}
|
|
2930
|
+
function resolveCleanupLearnerServiceOutcome(activationRoot, openclawHome) {
|
|
2931
|
+
if (activationRoot === null) {
|
|
2932
|
+
return {
|
|
2933
|
+
state: "unresolved",
|
|
2934
|
+
detail: "Learner service preservation is unresolved because the activation root could not be resolved from the installed profile hook.",
|
|
2935
|
+
plistPath: null,
|
|
2936
|
+
logPath: null,
|
|
2937
|
+
configuredActivationRoot: null,
|
|
2938
|
+
matchesRequestedActivationRoot: null
|
|
2939
|
+
};
|
|
2940
|
+
}
|
|
2941
|
+
const remainingProfiles = findOtherInstalledHookReferencesForActivationRoot({
|
|
2942
|
+
activationRoot,
|
|
2943
|
+
excludingOpenClawHome: openclawHome
|
|
2944
|
+
});
|
|
2945
|
+
const partitioned = partitionSharedActivationRootHookReferences(remainingProfiles);
|
|
2946
|
+
if (partitioned.attached.length > 0) {
|
|
2947
|
+
const inspection = inspectManagedLearnerService(activationRoot);
|
|
2948
|
+
const attachedProfiles = partitioned.attached
|
|
2949
|
+
.map(({ openclawHome: profileHome }) => shortenPath(path.resolve(profileHome)))
|
|
2950
|
+
.join(", ");
|
|
2951
|
+
const halfAttachedNote = partitioned.halfAttached.length === 0
|
|
2952
|
+
? ""
|
|
2953
|
+
: ` Half-attached hooks still point at this root but were not counted as attached: ${partitioned.halfAttached
|
|
2954
|
+
.map((reference) => `${shortenPath(path.resolve(reference.openclawHome))} (${summarizeSharedActivationRootReferenceProof(reference)})`)
|
|
2955
|
+
.join(", ")}.`;
|
|
2956
|
+
return {
|
|
2957
|
+
state: "preserved",
|
|
2958
|
+
detail: `Preserved the background learner service for ${path.resolve(activationRoot)} because other attached OpenClaw profiles still share this activation root: ${attachedProfiles}.${halfAttachedNote}`,
|
|
2959
|
+
plistPath: inspection.plistPath,
|
|
2960
|
+
logPath: inspection.logPath,
|
|
2961
|
+
configuredActivationRoot: inspection.configuredActivationRoot,
|
|
2962
|
+
matchesRequestedActivationRoot: inspection.matchesRequestedActivationRoot
|
|
2963
|
+
};
|
|
2964
|
+
}
|
|
2965
|
+
const outcome = removeManagedLearnerServiceForActivationRoot(activationRoot);
|
|
2966
|
+
const halfAttachedNote = partitioned.halfAttached.length === 0
|
|
2967
|
+
? ""
|
|
2968
|
+
: ` Ignored half-attached OpenClaw profile hooks that still point at this activation root because they do not prove serve-path attachment: ${partitioned.halfAttached
|
|
2969
|
+
.map((reference) => `${shortenPath(path.resolve(reference.openclawHome))} (${summarizeSharedActivationRootReferenceProof(reference)})`)
|
|
2970
|
+
.join(", ")}.`;
|
|
2971
|
+
return {
|
|
2972
|
+
state: outcome.state,
|
|
2973
|
+
detail: `${outcome.detail}${halfAttachedNote}`,
|
|
2974
|
+
plistPath: outcome.inspection.plistPath,
|
|
2975
|
+
logPath: outcome.inspection.logPath,
|
|
2976
|
+
configuredActivationRoot: outcome.inspection.configuredActivationRoot,
|
|
2977
|
+
matchesRequestedActivationRoot: outcome.inspection.matchesRequestedActivationRoot
|
|
2978
|
+
};
|
|
2979
|
+
}
|
|
2245
2980
|
function formatInspectionFindings(findings) {
|
|
2246
2981
|
return findings.join("; ");
|
|
2247
2982
|
}
|
|
@@ -2438,11 +3173,11 @@ function runProfileHookAttachCommand(parsed) {
|
|
|
2438
3173
|
}
|
|
2439
3174
|
steps.push(activationPlan.inspectionStep);
|
|
2440
3175
|
// 5. Persist install-written local provider defaults so watch/learning surfaces do not depend on gateway env wiring.
|
|
2441
|
-
const providerDefaults =
|
|
3176
|
+
const providerDefaults = shouldWriteProfileHookProviderDefaults(parsed, activationPlan, isInstall)
|
|
2442
3177
|
? writeInstallProviderDefaults(parsed)
|
|
2443
3178
|
: null;
|
|
2444
3179
|
if (providerDefaults === null) {
|
|
2445
|
-
steps.push("
|
|
3180
|
+
steps.push("Preserved existing provider-defaults.json because explicit attach is reusing existing activation data.");
|
|
2446
3181
|
}
|
|
2447
3182
|
else {
|
|
2448
3183
|
steps.push(providerDefaults.detail);
|
|
@@ -2528,16 +3263,38 @@ function runProfileHookAttachCommand(parsed) {
|
|
|
2528
3263
|
const manifestPath = path.join(extensionDir, "openclaw.plugin.json");
|
|
2529
3264
|
writeFileSync(manifestPath, buildExtensionPluginManifest(), "utf8");
|
|
2530
3265
|
steps.push(`Wrote manifest: ${manifestPath}`);
|
|
2531
|
-
const
|
|
3266
|
+
const pluginConfigRepair = ensureOpenClawBrainPluginConfig(parsed.openclawHome);
|
|
3267
|
+
steps.push(pluginConfigRepair.detail);
|
|
3268
|
+
const learnerService = ensureLifecycleLearnerService(parsed.activationRoot);
|
|
3269
|
+
steps.push(learnerService.detail);
|
|
3270
|
+
const brainFeedback = buildInstallBrainFeedbackSummary({
|
|
3271
|
+
parsed,
|
|
3272
|
+
targetInspection,
|
|
3273
|
+
extensionDir,
|
|
3274
|
+
activationPlan,
|
|
3275
|
+
learnerService,
|
|
3276
|
+
embedderProvision,
|
|
3277
|
+
providerDefaults
|
|
3278
|
+
});
|
|
3279
|
+
const restartGuidance = buildInstallReloadGuidance({
|
|
3280
|
+
targetInspection
|
|
3281
|
+
});
|
|
2532
3282
|
const nextSteps = [
|
|
2533
3283
|
restartGuidance,
|
|
3284
|
+
brainFeedback.restart.gatewayStatusCommand === null
|
|
3285
|
+
? null
|
|
3286
|
+
: `Confirm gateway after restart: ${brainFeedback.restart.gatewayStatusCommand}`,
|
|
2534
3287
|
`Check status: ${buildInstallStatusCommand(parsed.activationRoot)}`,
|
|
3288
|
+
`Check learner service: ${buildLearnerServiceStatusCommand(parsed.activationRoot)}`,
|
|
2535
3289
|
embedderProvision !== null && embedderProvision.state === "skipped"
|
|
2536
3290
|
? `Provision default embedder later: ${buildInstallEmbedderProvisionCommand(embedderProvision.baseUrl, embedderProvision.model)}`
|
|
2537
3291
|
: null
|
|
2538
3292
|
].filter((step) => step !== null);
|
|
2539
3293
|
const preflightSummary = [
|
|
2540
3294
|
`Hook: installed at ${shortenPath(extensionDir)}`,
|
|
3295
|
+
parsed.shared
|
|
3296
|
+
? "Attachment policy: shared activation root declared; same-gateway many-profile load/serve proof is still not checked in"
|
|
3297
|
+
: "Attachment policy: dedicated activation root for this profile/home boundary",
|
|
2541
3298
|
activationPlan.action === "bootstrap"
|
|
2542
3299
|
? "Attachment: seed/current-profile attach created; restart plus status will prove later serve-path use"
|
|
2543
3300
|
: `Attachment: existing active pack ${activationPlan.activePackId} kept in place; restart plus status will prove later serve-path use`,
|
|
@@ -2546,6 +3303,7 @@ function runProfileHookAttachCommand(parsed) {
|
|
|
2546
3303
|
: embedderProvision.state === "ensured"
|
|
2547
3304
|
? `Embedder: default Ollama model ${embedderProvision.model} was ensured before bootstrap`
|
|
2548
3305
|
: `Embedder: default Ollama model ${embedderProvision.model} was intentionally skipped`,
|
|
3306
|
+
`Learner: background service ${learnerService.state} for the exact activation root/profile boundary`,
|
|
2549
3307
|
`Serve path: install alone does not prove serving; restart the profile and run ${buildInstallStatusCommand(parsed.activationRoot)}`
|
|
2550
3308
|
];
|
|
2551
3309
|
const lifecycleSummary = [
|
|
@@ -2554,7 +3312,11 @@ function runProfileHookAttachCommand(parsed) {
|
|
|
2554
3312
|
: "Lifecycle mode: attach (explicit reattach/manual profile hookup)",
|
|
2555
3313
|
`OpenClaw target: ${shortenPath(parsed.openclawHome)} (${formatInstallOpenClawHomeSource(parsed.openclawHomeSource)})`,
|
|
2556
3314
|
`Detected layout: ${formatOpenClawTargetExplanation(targetInspection)}`,
|
|
3315
|
+
brainFeedback.profile.exactProfileName === null
|
|
3316
|
+
? "Profile token: current_profile only; this install did not infer an exact --profile token"
|
|
3317
|
+
: `Profile token: use exact OpenClaw profile casing ${quoteShellArg(brainFeedback.profile.exactProfileName)} for host-side restart/status commands`,
|
|
2557
3318
|
`Activation root: ${shortenPath(parsed.activationRoot)} (${formatInstallActivationRootSource(parsed.activationRootSource)})`,
|
|
3319
|
+
`Attachment policy: ${brainFeedback.attachment.policy} (${brainFeedback.attachment.detail})`,
|
|
2558
3320
|
`Workspace ID: ${parsed.workspaceId} (${formatInstallWorkspaceIdSource(parsed.workspaceIdSource)})`,
|
|
2559
3321
|
embedderProvision === null
|
|
2560
3322
|
? "Embedder: unchanged because no bootstrap was needed"
|
|
@@ -2563,6 +3325,7 @@ function runProfileHookAttachCommand(parsed) {
|
|
|
2563
3325
|
: `Embedder: skipped default Ollama model ${embedderProvision.model} via ${parsed.skipEmbedderProvisionSource === "flag" ? "--skip-embedder-provision" : OPENCLAWBRAIN_INSTALL_SKIP_EMBEDDER_PROVISION_ENV}`,
|
|
2564
3326
|
...(providerDefaults === null ? [] : [`${providerDefaults.lifecycleSummary} (${shortenPath(providerDefaults.path)})`]),
|
|
2565
3327
|
`Profile hook: installed at ${shortenPath(extensionDir)}`,
|
|
3328
|
+
`Learner service: ${learnerService.state} for ${shortenPath(parsed.activationRoot)}`,
|
|
2566
3329
|
activationPlan.resolution === "new_root"
|
|
2567
3330
|
? `Activation data: initialized at ${shortenPath(parsed.activationRoot)}`
|
|
2568
3331
|
: activationPlan.resolution === "missing_pointers"
|
|
@@ -2580,14 +3343,6 @@ function runProfileHookAttachCommand(parsed) {
|
|
|
2580
3343
|
? `Install: kept healthy active pack ${activationPlan.activePackId} in place`
|
|
2581
3344
|
: `Attach: rewired the profile hook to healthy active pack ${activationPlan.activePackId}`
|
|
2582
3345
|
];
|
|
2583
|
-
const brainFeedback = buildInstallBrainFeedbackSummary({
|
|
2584
|
-
parsed,
|
|
2585
|
-
targetInspection,
|
|
2586
|
-
extensionDir,
|
|
2587
|
-
activationPlan,
|
|
2588
|
-
embedderProvision,
|
|
2589
|
-
providerDefaults
|
|
2590
|
-
});
|
|
2591
3346
|
// 9. Print summary
|
|
2592
3347
|
if (parsed.json) {
|
|
2593
3348
|
console.log(JSON.stringify({
|
|
@@ -2642,11 +3397,17 @@ function runProfileHookAttachCommand(parsed) {
|
|
|
2642
3397
|
teacherBaseUrl: providerDefaults.defaults.teacherBaseUrl ?? null,
|
|
2643
3398
|
embedderBaseUrl: providerDefaults.defaults.embedderBaseUrl ?? null
|
|
2644
3399
|
},
|
|
3400
|
+
pluginConfigRepair,
|
|
3401
|
+
learnerService,
|
|
2645
3402
|
brainFeedback: {
|
|
2646
3403
|
hookPath: brainFeedback.hookPath,
|
|
2647
3404
|
providerDefaultsPath: brainFeedback.providerDefaultsPath,
|
|
3405
|
+
profile: brainFeedback.profile,
|
|
3406
|
+
attachment: brainFeedback.attachment,
|
|
3407
|
+
restart: brainFeedback.restart,
|
|
2648
3408
|
embedder: brainFeedback.embedder,
|
|
2649
3409
|
teacher: brainFeedback.teacher,
|
|
3410
|
+
learnerService: brainFeedback.learnerService,
|
|
2650
3411
|
startup: brainFeedback.startup,
|
|
2651
3412
|
provedNow: brainFeedback.provedNow,
|
|
2652
3413
|
notYetProved: brainFeedback.notYetProved,
|
|
@@ -2666,8 +3427,12 @@ function runProfileHookAttachCommand(parsed) {
|
|
|
2666
3427
|
for (const line of brainFeedback.lines) {
|
|
2667
3428
|
console.log(` ${line}`);
|
|
2668
3429
|
}
|
|
2669
|
-
console.log(`
|
|
3430
|
+
console.log(`Restart: ${restartGuidance}`);
|
|
3431
|
+
if (brainFeedback.restart.gatewayStatusCommand !== null) {
|
|
3432
|
+
console.log(`Gateway: Confirm OpenClaw after restart: ${brainFeedback.restart.gatewayStatusCommand}`);
|
|
3433
|
+
}
|
|
2670
3434
|
console.log(`Check: ${buildInstallStatusCommand(parsed.activationRoot)}`);
|
|
3435
|
+
console.log(`Learner: ${buildLearnerServiceStatusCommand(parsed.activationRoot)}`);
|
|
2671
3436
|
if (embedderProvision !== null && embedderProvision.state === "skipped") {
|
|
2672
3437
|
console.log(`Embedder: ${buildInstallEmbedderProvisionCommand(embedderProvision.baseUrl, embedderProvision.model)}`);
|
|
2673
3438
|
}
|
|
@@ -2689,6 +3454,125 @@ function validateOpenClawHome(openclawHome) {
|
|
|
2689
3454
|
throw new Error(`openclaw.json not found in ${openclawHome}`);
|
|
2690
3455
|
}
|
|
2691
3456
|
}
|
|
3457
|
+
function readJsonObjectRecord(value) {
|
|
3458
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
3459
|
+
return null;
|
|
3460
|
+
}
|
|
3461
|
+
return value;
|
|
3462
|
+
}
|
|
3463
|
+
function readOpenClawJsonConfig(openclawHome) {
|
|
3464
|
+
const openclawJsonPath = path.join(openclawHome, "openclaw.json");
|
|
3465
|
+
let parsed;
|
|
3466
|
+
try {
|
|
3467
|
+
parsed = JSON.parse(readFileSync(openclawJsonPath, "utf8"));
|
|
3468
|
+
}
|
|
3469
|
+
catch (error) {
|
|
3470
|
+
throw new Error(`Failed to read ${openclawJsonPath}: ${toErrorMessage(error)}`);
|
|
3471
|
+
}
|
|
3472
|
+
const config = readJsonObjectRecord(parsed);
|
|
3473
|
+
if (config === null) {
|
|
3474
|
+
throw new Error(`Failed to read ${openclawJsonPath}: openclaw.json must contain a top-level object`);
|
|
3475
|
+
}
|
|
3476
|
+
return {
|
|
3477
|
+
path: openclawJsonPath,
|
|
3478
|
+
config
|
|
3479
|
+
};
|
|
3480
|
+
}
|
|
3481
|
+
function ensureOpenClawBrainPluginConfig(openclawHome) {
|
|
3482
|
+
const { path: openclawJsonPath, config } = readOpenClawJsonConfig(openclawHome);
|
|
3483
|
+
const plugins = readJsonObjectRecord(config.plugins);
|
|
3484
|
+
if (plugins === null) {
|
|
3485
|
+
return {
|
|
3486
|
+
path: openclawJsonPath,
|
|
3487
|
+
changed: false,
|
|
3488
|
+
detail: `Left ${shortenPath(openclawJsonPath)} unchanged because plugins.allow is not configured`
|
|
3489
|
+
};
|
|
3490
|
+
}
|
|
3491
|
+
if (!Object.prototype.hasOwnProperty.call(plugins, "allow")) {
|
|
3492
|
+
return {
|
|
3493
|
+
path: openclawJsonPath,
|
|
3494
|
+
changed: false,
|
|
3495
|
+
detail: `Left ${shortenPath(openclawJsonPath)} unchanged because plugins.allow is not configured`
|
|
3496
|
+
};
|
|
3497
|
+
}
|
|
3498
|
+
if (!Array.isArray(plugins.allow)) {
|
|
3499
|
+
return {
|
|
3500
|
+
path: openclawJsonPath,
|
|
3501
|
+
changed: false,
|
|
3502
|
+
detail: `Left ${shortenPath(openclawJsonPath)} unchanged because plugins.allow is not an array`
|
|
3503
|
+
};
|
|
3504
|
+
}
|
|
3505
|
+
if (plugins.allow.includes("openclawbrain")) {
|
|
3506
|
+
return {
|
|
3507
|
+
path: openclawJsonPath,
|
|
3508
|
+
changed: false,
|
|
3509
|
+
detail: `Verified ${shortenPath(openclawJsonPath)} plugins.allow already includes openclawbrain`
|
|
3510
|
+
};
|
|
3511
|
+
}
|
|
3512
|
+
plugins.allow = [...plugins.allow, "openclawbrain"];
|
|
3513
|
+
config.plugins = plugins;
|
|
3514
|
+
writeFileSync(openclawJsonPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
3515
|
+
return {
|
|
3516
|
+
path: openclawJsonPath,
|
|
3517
|
+
changed: true,
|
|
3518
|
+
detail: `Repaired ${shortenPath(openclawJsonPath)} plugins.allow by adding openclawbrain`
|
|
3519
|
+
};
|
|
3520
|
+
}
|
|
3521
|
+
function scrubOpenClawBrainPluginConfig(openclawHome) {
|
|
3522
|
+
const { path: openclawJsonPath, config } = readOpenClawJsonConfig(openclawHome);
|
|
3523
|
+
const plugins = readJsonObjectRecord(config.plugins);
|
|
3524
|
+
if (plugins === null) {
|
|
3525
|
+
return {
|
|
3526
|
+
path: openclawJsonPath,
|
|
3527
|
+
changed: false,
|
|
3528
|
+
detail: `No stale openclawbrain plugin config found in ${openclawJsonPath}`
|
|
3529
|
+
};
|
|
3530
|
+
}
|
|
3531
|
+
const changes = [];
|
|
3532
|
+
let changed = false;
|
|
3533
|
+
if (Array.isArray(plugins.allow)) {
|
|
3534
|
+
const filteredAllow = plugins.allow.filter((entry) => entry !== "openclawbrain");
|
|
3535
|
+
if (filteredAllow.length !== plugins.allow.length) {
|
|
3536
|
+
changed = true;
|
|
3537
|
+
changes.push("removed plugins.allow entry");
|
|
3538
|
+
if (filteredAllow.length > 0) {
|
|
3539
|
+
plugins.allow = filteredAllow;
|
|
3540
|
+
}
|
|
3541
|
+
else {
|
|
3542
|
+
delete plugins.allow;
|
|
3543
|
+
}
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3546
|
+
const entries = readJsonObjectRecord(plugins.entries);
|
|
3547
|
+
if (entries !== null && Object.prototype.hasOwnProperty.call(entries, "openclawbrain")) {
|
|
3548
|
+
delete entries.openclawbrain;
|
|
3549
|
+
changed = true;
|
|
3550
|
+
changes.push("removed plugins.entries.openclawbrain");
|
|
3551
|
+
}
|
|
3552
|
+
if (entries !== null && Object.keys(entries).length === 0 && Object.prototype.hasOwnProperty.call(plugins, "entries")) {
|
|
3553
|
+
delete plugins.entries;
|
|
3554
|
+
changed = true;
|
|
3555
|
+
changes.push("removed empty plugins.entries container");
|
|
3556
|
+
}
|
|
3557
|
+
if (Object.keys(plugins).length === 0 && Object.prototype.hasOwnProperty.call(config, "plugins")) {
|
|
3558
|
+
delete config.plugins;
|
|
3559
|
+
changed = true;
|
|
3560
|
+
changes.push("removed empty plugins container");
|
|
3561
|
+
}
|
|
3562
|
+
if (changed) {
|
|
3563
|
+
writeFileSync(openclawJsonPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
3564
|
+
return {
|
|
3565
|
+
path: openclawJsonPath,
|
|
3566
|
+
changed: true,
|
|
3567
|
+
detail: `Scrubbed stale openclawbrain plugin config in ${openclawJsonPath}: ${changes.join(", ")}`
|
|
3568
|
+
};
|
|
3569
|
+
}
|
|
3570
|
+
return {
|
|
3571
|
+
path: openclawJsonPath,
|
|
3572
|
+
changed: false,
|
|
3573
|
+
detail: `No stale openclawbrain plugin config found in ${openclawJsonPath}`
|
|
3574
|
+
};
|
|
3575
|
+
}
|
|
2692
3576
|
function resolveCleanupActivationRoot(openclawHome, explicitActivationRoot) {
|
|
2693
3577
|
if (explicitActivationRoot !== null) {
|
|
2694
3578
|
return path.resolve(explicitActivationRoot);
|
|
@@ -2726,12 +3610,32 @@ function summarizeKeptActivationData(activationRoot) {
|
|
|
2726
3610
|
function buildRestartGuidance(restart) {
|
|
2727
3611
|
return buildCleanupRestartGuidance(restart);
|
|
2728
3612
|
}
|
|
3613
|
+
function clearCleanupRuntimeLoadProof(activationRoot, openclawHome, steps) {
|
|
3614
|
+
if (activationRoot === null) {
|
|
3615
|
+
return;
|
|
3616
|
+
}
|
|
3617
|
+
try {
|
|
3618
|
+
const cleared = clearOpenClawProfileRuntimeLoadProof({
|
|
3619
|
+
activationRoot,
|
|
3620
|
+
openclawHome
|
|
3621
|
+
});
|
|
3622
|
+
if (cleared) {
|
|
3623
|
+
steps.push(`Cleared runtime-load proof for ${shortenPath(openclawHome)} from ${shortenPath(resolveAttachmentRuntimeLoadProofsPath(activationRoot))}`);
|
|
3624
|
+
}
|
|
3625
|
+
}
|
|
3626
|
+
catch (error) {
|
|
3627
|
+
steps.push(`Runtime-load proof cleanup failed open at ${shortenPath(resolveAttachmentRuntimeLoadProofsPath(activationRoot))}: ${toErrorMessage(error)}`);
|
|
3628
|
+
}
|
|
3629
|
+
}
|
|
2729
3630
|
function runDetachCommand(parsed) {
|
|
2730
3631
|
const steps = [];
|
|
2731
3632
|
validateOpenClawHome(parsed.openclawHome);
|
|
2732
3633
|
const targetInspection = inspectOpenClawHome(parsed.openclawHome);
|
|
2733
3634
|
steps.push(`Detected layout: ${formatOpenClawTargetExplanation(targetInspection)}`);
|
|
2734
3635
|
const activationRoot = resolveCleanupActivationRoot(parsed.openclawHome, parsed.activationRoot);
|
|
3636
|
+
clearCleanupRuntimeLoadProof(activationRoot, parsed.openclawHome, steps);
|
|
3637
|
+
const learnerService = resolveCleanupLearnerServiceOutcome(activationRoot, parsed.openclawHome);
|
|
3638
|
+
const pluginConfigCleanup = scrubOpenClawBrainPluginConfig(parsed.openclawHome);
|
|
2735
3639
|
const extensionDir = removeProfileHookup(parsed.openclawHome, steps);
|
|
2736
3640
|
const legacyResidue = removeLegacyProfileResidue(parsed.openclawHome);
|
|
2737
3641
|
const activationData = summarizeKeptActivationData(activationRoot);
|
|
@@ -2739,14 +3643,17 @@ function runDetachCommand(parsed) {
|
|
|
2739
3643
|
const nextSteps = [
|
|
2740
3644
|
restartGuidance,
|
|
2741
3645
|
activationRoot === null ? null : `Inspect preserved data: ${buildInstallStatusCommand(activationRoot)}`,
|
|
3646
|
+
activationRoot === null ? null : `Inspect learner service: ${buildLearnerServiceStatusCommand(activationRoot)}`,
|
|
2742
3647
|
`Reattach later: ${buildAttachCommand(parsed.openclawHome, activationRoot)}`
|
|
2743
3648
|
].filter((step) => step !== null);
|
|
3649
|
+
steps.push(pluginConfigCleanup.detail);
|
|
2744
3650
|
if (legacyResidue.removedNotes.length > 0) {
|
|
2745
3651
|
steps.push(`Removed legacy profile notes: ${legacyResidue.removedNotes.map((notePath) => shortenPath(notePath)).join(", ")}`);
|
|
2746
3652
|
}
|
|
2747
3653
|
if (legacyResidue.updatedAgents.length > 0) {
|
|
2748
3654
|
steps.push(`Removed legacy AGENTS.md brain references: ${legacyResidue.updatedAgents.map((agentsPath) => shortenPath(agentsPath)).join(", ")}`);
|
|
2749
3655
|
}
|
|
3656
|
+
steps.push(learnerService.detail);
|
|
2750
3657
|
steps.push(activationData.activationDataDetail);
|
|
2751
3658
|
steps.push("Detach only removes the OpenClaw profile hook; it does not delete OpenClawBrain data.");
|
|
2752
3659
|
if (parsed.json) {
|
|
@@ -2764,6 +3671,8 @@ function runDetachCommand(parsed) {
|
|
|
2764
3671
|
activationRoot,
|
|
2765
3672
|
dataAction: "kept",
|
|
2766
3673
|
activationDataState: activationData.activationDataState,
|
|
3674
|
+
pluginConfigCleanup,
|
|
3675
|
+
learnerService,
|
|
2767
3676
|
removedLegacyNotes: legacyResidue.removedNotes,
|
|
2768
3677
|
updatedAgents: legacyResidue.updatedAgents,
|
|
2769
3678
|
restartMode: parsed.restart,
|
|
@@ -2786,9 +3695,12 @@ function runDetachCommand(parsed) {
|
|
|
2786
3695
|
else {
|
|
2787
3696
|
console.log("Brain data: preserved, but the activation root could not be resolved from the removed hook.");
|
|
2788
3697
|
}
|
|
3698
|
+
console.log(`Config: ${pluginConfigCleanup.detail}`);
|
|
3699
|
+
console.log(`Learner: ${learnerService.detail}`);
|
|
2789
3700
|
console.log(`Next: ${restartGuidance}`);
|
|
2790
3701
|
if (activationRoot !== null) {
|
|
2791
3702
|
console.log(`Check: ${buildInstallStatusCommand(activationRoot)}`);
|
|
3703
|
+
console.log(`Service: ${buildLearnerServiceStatusCommand(activationRoot)}`);
|
|
2792
3704
|
}
|
|
2793
3705
|
console.log(`Reattach: ${buildAttachCommand(parsed.openclawHome, activationRoot)}`);
|
|
2794
3706
|
}
|
|
@@ -2800,6 +3712,21 @@ function runUninstallCommand(parsed) {
|
|
|
2800
3712
|
const targetInspection = inspectOpenClawHome(parsed.openclawHome);
|
|
2801
3713
|
steps.push(`Detected layout: ${formatOpenClawTargetExplanation(targetInspection)}`);
|
|
2802
3714
|
const activationRoot = resolveCleanupActivationRoot(parsed.openclawHome, parsed.activationRoot);
|
|
3715
|
+
clearCleanupRuntimeLoadProof(activationRoot, parsed.openclawHome, steps);
|
|
3716
|
+
if (parsed.dataMode === "purge" && activationRoot !== null) {
|
|
3717
|
+
assertActivationRootPurgeIsNotShared({
|
|
3718
|
+
activationRoot,
|
|
3719
|
+
openclawHome: parsed.openclawHome
|
|
3720
|
+
});
|
|
3721
|
+
}
|
|
3722
|
+
const learnerService = resolveCleanupLearnerServiceOutcome(activationRoot, parsed.openclawHome);
|
|
3723
|
+
const pluginConfigCleanup = scrubOpenClawBrainPluginConfig(parsed.openclawHome);
|
|
3724
|
+
if (parsed.dataMode === "purge" &&
|
|
3725
|
+
activationRoot !== null &&
|
|
3726
|
+
learnerService.state === "preserved" &&
|
|
3727
|
+
learnerService.matchesRequestedActivationRoot !== false) {
|
|
3728
|
+
throw new Error(`Refusing to purge activation root ${path.resolve(activationRoot)} because the background learner service for this exact root could not be removed. ${learnerService.detail}`);
|
|
3729
|
+
}
|
|
2803
3730
|
const extensionDir = removeProfileHookup(parsed.openclawHome, steps);
|
|
2804
3731
|
const legacyResidue = removeLegacyProfileResidue(parsed.openclawHome);
|
|
2805
3732
|
let activationData;
|
|
@@ -2830,16 +3757,19 @@ function runUninstallCommand(parsed) {
|
|
|
2830
3757
|
const nextSteps = [
|
|
2831
3758
|
restartGuidance,
|
|
2832
3759
|
parsed.dataMode === "keep" && activationRoot !== null ? `Inspect preserved data: ${buildInstallStatusCommand(activationRoot)}` : null,
|
|
3760
|
+
activationRoot === null ? null : `Inspect learner service: ${buildLearnerServiceStatusCommand(activationRoot)}`,
|
|
2833
3761
|
parsed.dataMode === "keep"
|
|
2834
3762
|
? `Reattach later: ${buildAttachCommand(parsed.openclawHome, activationRoot)}`
|
|
2835
3763
|
: `Reinstall later: ${buildInstallCommand(parsed.openclawHome)}`
|
|
2836
3764
|
].filter((step) => step !== null);
|
|
3765
|
+
steps.push(pluginConfigCleanup.detail);
|
|
2837
3766
|
if (legacyResidue.removedNotes.length > 0) {
|
|
2838
3767
|
steps.push(`Removed legacy profile notes: ${legacyResidue.removedNotes.map((notePath) => shortenPath(notePath)).join(", ")}`);
|
|
2839
3768
|
}
|
|
2840
3769
|
if (legacyResidue.updatedAgents.length > 0) {
|
|
2841
3770
|
steps.push(`Removed legacy AGENTS.md brain references: ${legacyResidue.updatedAgents.map((agentsPath) => shortenPath(agentsPath)).join(", ")}`);
|
|
2842
3771
|
}
|
|
3772
|
+
steps.push(learnerService.detail);
|
|
2843
3773
|
steps.push(activationData.activationDataDetail);
|
|
2844
3774
|
steps.push(parsed.dataMode === "purge"
|
|
2845
3775
|
? "Uninstall removed the OpenClaw profile hook and activation data."
|
|
@@ -2859,6 +3789,8 @@ function runUninstallCommand(parsed) {
|
|
|
2859
3789
|
activationRoot,
|
|
2860
3790
|
dataAction: parsed.dataMode,
|
|
2861
3791
|
activationDataState: activationData.activationDataState,
|
|
3792
|
+
pluginConfigCleanup,
|
|
3793
|
+
learnerService,
|
|
2862
3794
|
removedLegacyNotes: legacyResidue.removedNotes,
|
|
2863
3795
|
updatedAgents: legacyResidue.updatedAgents,
|
|
2864
3796
|
restartMode: parsed.restart,
|
|
@@ -2879,10 +3811,15 @@ function runUninstallCommand(parsed) {
|
|
|
2879
3811
|
if (activationRoot !== null) {
|
|
2880
3812
|
console.log(`Activation: ${parsed.dataMode === "purge" ? shortenPath(activationRoot) : `${shortenPath(activationRoot)} preserved`}`);
|
|
2881
3813
|
}
|
|
3814
|
+
console.log(`Config: ${pluginConfigCleanup.detail}`);
|
|
3815
|
+
console.log(`Learner: ${learnerService.detail}`);
|
|
2882
3816
|
console.log(`Next: ${restartGuidance}`);
|
|
2883
3817
|
if (parsed.dataMode === "keep" && activationRoot !== null) {
|
|
2884
3818
|
console.log(`Check: ${buildInstallStatusCommand(activationRoot)}`);
|
|
2885
3819
|
}
|
|
3820
|
+
if (activationRoot !== null) {
|
|
3821
|
+
console.log(`Service: ${buildLearnerServiceStatusCommand(activationRoot)}`);
|
|
3822
|
+
}
|
|
2886
3823
|
if (parsed.dataMode === "keep") {
|
|
2887
3824
|
console.log(`Reattach: ${buildAttachCommand(parsed.openclawHome, activationRoot)}`);
|
|
2888
3825
|
}
|
|
@@ -3454,13 +4391,62 @@ function exportLocalSessionTailChangesToScanRoot(input) {
|
|
|
3454
4391
|
warnings
|
|
3455
4392
|
};
|
|
3456
4393
|
}
|
|
3457
|
-
function
|
|
3458
|
-
|
|
4394
|
+
function summarizeVectorEmbeddingState(vectors) {
|
|
4395
|
+
if (vectors === null || vectors === undefined) {
|
|
4396
|
+
return {
|
|
4397
|
+
vectorEntryCount: null,
|
|
4398
|
+
numericEmbeddingEntryCount: null,
|
|
4399
|
+
embeddingModels: []
|
|
4400
|
+
};
|
|
4401
|
+
}
|
|
4402
|
+
const embeddingModels = [...new Set(vectors.entries.flatMap((entry) => (entry.embedding === undefined ? [] : [entry.embedding.model])))].sort((left, right) => left.localeCompare(right));
|
|
4403
|
+
return {
|
|
4404
|
+
vectorEntryCount: vectors.entries.length,
|
|
4405
|
+
numericEmbeddingEntryCount: vectors.entries.filter((entry) => entry.embedding !== undefined).length,
|
|
4406
|
+
embeddingModels
|
|
4407
|
+
};
|
|
4408
|
+
}
|
|
4409
|
+
function buildWatchEmbedTracePoint(input) {
|
|
4410
|
+
const summary = summarizeVectorEmbeddingState(input.vectors);
|
|
4411
|
+
return {
|
|
4412
|
+
slot: input.slot,
|
|
4413
|
+
packId: input.packId,
|
|
4414
|
+
runtimeEmbedderPresent: input.embedder !== null,
|
|
4415
|
+
runtimeEmbedderModel: input.embedder?.model ?? null,
|
|
4416
|
+
vectorEntryCount: summary.vectorEntryCount,
|
|
4417
|
+
numericEmbeddingEntryCount: summary.numericEmbeddingEntryCount,
|
|
4418
|
+
embeddingModels: summary.embeddingModels,
|
|
4419
|
+
error: input.error ?? null
|
|
4420
|
+
};
|
|
4421
|
+
}
|
|
4422
|
+
function buildWatchEmbedTracePointFromPack(input) {
|
|
4423
|
+
return buildWatchEmbedTracePoint({
|
|
4424
|
+
slot: input.slot,
|
|
4425
|
+
packId: input.pack?.manifest.packId ?? null,
|
|
4426
|
+
embedder: input.embedder,
|
|
4427
|
+
vectors: input.pack?.vectors,
|
|
4428
|
+
error: input.error ?? null
|
|
4429
|
+
});
|
|
4430
|
+
}
|
|
4431
|
+
function formatWatchEmbedTracePoint(label, point) {
|
|
4432
|
+
const models = point.embeddingModels.length === 0 ? "none" : point.embeddingModels.join("|");
|
|
4433
|
+
const slot = point.slot ?? "build";
|
|
4434
|
+
const packId = point.packId ?? "unknown";
|
|
4435
|
+
const embedderState = point.runtimeEmbedderPresent ? `present:${point.runtimeEmbedderModel ?? "unknown"}` : "null";
|
|
4436
|
+
const counts = point.vectorEntryCount === null || point.numericEmbeddingEntryCount === null
|
|
4437
|
+
? "vectors=unknown numeric=unknown"
|
|
4438
|
+
: `vectors=${point.vectorEntryCount} numeric=${point.numericEmbeddingEntryCount}`;
|
|
4439
|
+
const error = point.error === null ? "" : ` error=${point.error}`;
|
|
4440
|
+
return `embed-trace ${label} slot=${slot} pack=${packId} runtimeEmbedder=${embedderState} ${counts} models=${models}${error}`;
|
|
4441
|
+
}
|
|
4442
|
+
async function applyWatchMaterialization(activationRoot, snapshot, lastHandledMaterializationPackId, embedder, log) {
|
|
4443
|
+
let materialization = snapshot?.learner?.lastMaterialization ?? null;
|
|
3459
4444
|
if (materialization === null) {
|
|
3460
4445
|
return {
|
|
3461
4446
|
lastHandledMaterializationPackId,
|
|
3462
4447
|
logLine: null,
|
|
3463
4448
|
materializedPackId: null,
|
|
4449
|
+
embedInstrumentation: null,
|
|
3464
4450
|
failure: null
|
|
3465
4451
|
};
|
|
3466
4452
|
}
|
|
@@ -3472,10 +4458,38 @@ function applyWatchMaterialization(activationRoot, snapshot, lastHandledMaterial
|
|
|
3472
4458
|
lastHandledMaterializationPackId,
|
|
3473
4459
|
logLine: null,
|
|
3474
4460
|
materializedPackId: packId,
|
|
4461
|
+
embedInstrumentation: null,
|
|
3475
4462
|
failure: null
|
|
3476
4463
|
};
|
|
3477
4464
|
}
|
|
4465
|
+
if (embedder !== null) {
|
|
4466
|
+
materialization = {
|
|
4467
|
+
...materialization,
|
|
4468
|
+
candidate: await reindexCandidatePackBuildResultWithEmbedder(materialization.candidate, embedder)
|
|
4469
|
+
};
|
|
4470
|
+
if (snapshot?.learner !== undefined && snapshot.learner !== null) {
|
|
4471
|
+
snapshot.learner.lastMaterialization = materialization;
|
|
4472
|
+
}
|
|
4473
|
+
}
|
|
3478
4474
|
const shortPackId = packId.length > 16 ? packId.slice(0, 16) : packId;
|
|
4475
|
+
const observedAt = new Date().toISOString();
|
|
4476
|
+
const beforeCandidateMaterialization = buildWatchEmbedTracePoint({
|
|
4477
|
+
slot: null,
|
|
4478
|
+
packId,
|
|
4479
|
+
embedder,
|
|
4480
|
+
vectors: materialization?.candidate?.payloads?.vectors
|
|
4481
|
+
});
|
|
4482
|
+
let embedInstrumentation = {
|
|
4483
|
+
observedAt,
|
|
4484
|
+
candidatePackId: packId,
|
|
4485
|
+
promotionAllowed: null,
|
|
4486
|
+
promotionFindings: [],
|
|
4487
|
+
beforeCandidateMaterialization,
|
|
4488
|
+
afterCandidateMaterialization: null,
|
|
4489
|
+
afterStage: null,
|
|
4490
|
+
afterPromote: null
|
|
4491
|
+
};
|
|
4492
|
+
log?.(formatWatchEmbedTracePoint("before_materialize", beforeCandidateMaterialization));
|
|
3479
4493
|
try {
|
|
3480
4494
|
const candidateRootDir = path.resolve(activationRoot, "packs", packId);
|
|
3481
4495
|
mkdirSync(candidateRootDir, { recursive: true });
|
|
@@ -3487,27 +4501,81 @@ function applyWatchMaterialization(activationRoot, snapshot, lastHandledMaterial
|
|
|
3487
4501
|
activeBeforePack = null;
|
|
3488
4502
|
}
|
|
3489
4503
|
const candidateDescriptor = materializeAlwaysOnLearningCandidatePack(candidateRootDir, materialization);
|
|
4504
|
+
embedInstrumentation = {
|
|
4505
|
+
...embedInstrumentation,
|
|
4506
|
+
afterCandidateMaterialization: buildWatchEmbedTracePointFromPack({
|
|
4507
|
+
slot: "candidate",
|
|
4508
|
+
pack: candidateDescriptor,
|
|
4509
|
+
embedder
|
|
4510
|
+
})
|
|
4511
|
+
};
|
|
4512
|
+
if (embedInstrumentation.afterCandidateMaterialization !== null) {
|
|
4513
|
+
log?.(formatWatchEmbedTracePoint("after_materialize", embedInstrumentation.afterCandidateMaterialization));
|
|
4514
|
+
}
|
|
3490
4515
|
appendLearningUpdateLogs({
|
|
3491
4516
|
activationRoot,
|
|
3492
4517
|
materialization,
|
|
3493
4518
|
activeBeforePack,
|
|
3494
4519
|
candidateDescriptor
|
|
3495
4520
|
});
|
|
3496
|
-
const now =
|
|
4521
|
+
const now = observedAt;
|
|
3497
4522
|
stageCandidatePack(activationRoot, candidateRootDir, {
|
|
3498
4523
|
updatedAt: now,
|
|
3499
4524
|
reason: `watch_stage:${materialization.reason}:${materialization.lane}`
|
|
3500
4525
|
});
|
|
3501
4526
|
const inspection = inspectActivationState(activationRoot, now);
|
|
4527
|
+
let stagedPack = null;
|
|
4528
|
+
let stagedPackError = null;
|
|
4529
|
+
try {
|
|
4530
|
+
stagedPack = loadPackFromActivation(activationRoot, "candidate", { requireActivationReady: true });
|
|
4531
|
+
}
|
|
4532
|
+
catch (error) {
|
|
4533
|
+
stagedPackError = formatWatchError(error);
|
|
4534
|
+
}
|
|
4535
|
+
embedInstrumentation = {
|
|
4536
|
+
...embedInstrumentation,
|
|
4537
|
+
promotionAllowed: inspection.promotion.allowed,
|
|
4538
|
+
promotionFindings: [...inspection.promotion.findings],
|
|
4539
|
+
afterStage: buildWatchEmbedTracePointFromPack({
|
|
4540
|
+
slot: "candidate",
|
|
4541
|
+
pack: stagedPack,
|
|
4542
|
+
embedder,
|
|
4543
|
+
error: stagedPackError
|
|
4544
|
+
})
|
|
4545
|
+
};
|
|
4546
|
+
if (embedInstrumentation.afterStage !== null) {
|
|
4547
|
+
log?.(formatWatchEmbedTracePoint("after_stage", embedInstrumentation.afterStage));
|
|
4548
|
+
}
|
|
3502
4549
|
if (inspection.promotion.allowed) {
|
|
3503
4550
|
promoteCandidatePack(activationRoot, {
|
|
3504
4551
|
updatedAt: now,
|
|
3505
4552
|
reason: `watch_promote:${materialization.reason}:${materialization.lane}`
|
|
3506
4553
|
});
|
|
4554
|
+
let promotedPack = null;
|
|
4555
|
+
let promotedPackError = null;
|
|
4556
|
+
try {
|
|
4557
|
+
promotedPack = loadPackFromActivation(activationRoot, "active", { requireActivationReady: true });
|
|
4558
|
+
}
|
|
4559
|
+
catch (error) {
|
|
4560
|
+
promotedPackError = formatWatchError(error);
|
|
4561
|
+
}
|
|
4562
|
+
embedInstrumentation = {
|
|
4563
|
+
...embedInstrumentation,
|
|
4564
|
+
afterPromote: buildWatchEmbedTracePointFromPack({
|
|
4565
|
+
slot: "active",
|
|
4566
|
+
pack: promotedPack,
|
|
4567
|
+
embedder,
|
|
4568
|
+
error: promotedPackError
|
|
4569
|
+
})
|
|
4570
|
+
};
|
|
4571
|
+
if (embedInstrumentation.afterPromote !== null) {
|
|
4572
|
+
log?.(formatWatchEmbedTracePoint("after_promote", embedInstrumentation.afterPromote));
|
|
4573
|
+
}
|
|
3507
4574
|
return {
|
|
3508
4575
|
lastHandledMaterializationPackId: packId,
|
|
3509
4576
|
materializedPackId: packId,
|
|
3510
4577
|
logLine: `Promoted ${shortPackId} → active`,
|
|
4578
|
+
embedInstrumentation,
|
|
3511
4579
|
failure: null
|
|
3512
4580
|
};
|
|
3513
4581
|
}
|
|
@@ -3515,15 +4583,28 @@ function applyWatchMaterialization(activationRoot, snapshot, lastHandledMaterial
|
|
|
3515
4583
|
lastHandledMaterializationPackId: packId,
|
|
3516
4584
|
materializedPackId: packId,
|
|
3517
4585
|
logLine: `Staged ${shortPackId} (promotion blocked: ${inspection.promotion.findings.join(", ")})`,
|
|
4586
|
+
embedInstrumentation,
|
|
3518
4587
|
failure: null
|
|
3519
4588
|
};
|
|
3520
4589
|
}
|
|
3521
4590
|
catch (error) {
|
|
3522
4591
|
const message = error instanceof Error ? error.message : String(error);
|
|
4592
|
+
embedInstrumentation = {
|
|
4593
|
+
...embedInstrumentation,
|
|
4594
|
+
afterCandidateMaterialization: embedInstrumentation.afterCandidateMaterialization ??
|
|
4595
|
+
buildWatchEmbedTracePoint({
|
|
4596
|
+
slot: "candidate",
|
|
4597
|
+
packId,
|
|
4598
|
+
embedder,
|
|
4599
|
+
vectors: null,
|
|
4600
|
+
error: message
|
|
4601
|
+
})
|
|
4602
|
+
};
|
|
3523
4603
|
return {
|
|
3524
4604
|
lastHandledMaterializationPackId,
|
|
3525
4605
|
materializedPackId: packId,
|
|
3526
4606
|
logLine: `Promotion failed for ${shortPackId}: ${message}`,
|
|
4607
|
+
embedInstrumentation,
|
|
3527
4608
|
failure: {
|
|
3528
4609
|
mode: "materialization_failed",
|
|
3529
4610
|
detail: message,
|
|
@@ -3569,12 +4650,178 @@ function resolveWatchTeacherLabelerConfig(input, activationRoot) {
|
|
|
3569
4650
|
warnings
|
|
3570
4651
|
};
|
|
3571
4652
|
}
|
|
4653
|
+
function resolveWatchEmbedderConfig(input, activationRoot) {
|
|
4654
|
+
if (input !== undefined) {
|
|
4655
|
+
return {
|
|
4656
|
+
embedder: input,
|
|
4657
|
+
warnings: []
|
|
4658
|
+
};
|
|
4659
|
+
}
|
|
4660
|
+
const defaultsResult = readOpenClawBrainProviderDefaults(activationRoot);
|
|
4661
|
+
const providerConfig = readOpenClawBrainProviderConfigFromSources({
|
|
4662
|
+
env: process.env,
|
|
4663
|
+
activationRoot,
|
|
4664
|
+
defaults: defaultsResult.defaults
|
|
4665
|
+
});
|
|
4666
|
+
const warnings = [...new Set([
|
|
4667
|
+
...defaultsResult.warnings.filter((warning) => /OPENCLAWBRAIN_EMBEDDER_|provider defaults/u.test(warning)),
|
|
4668
|
+
...providerConfig.warnings.filter((warning) => /OPENCLAWBRAIN_EMBEDDER_|provider defaults/u.test(warning))
|
|
4669
|
+
])];
|
|
4670
|
+
const explicitEnv = typeof process.env[OPENCLAWBRAIN_EMBEDDER_PROVIDER_ENV] === "string" ||
|
|
4671
|
+
typeof process.env[OPENCLAWBRAIN_EMBEDDER_MODEL_ENV] === "string" ||
|
|
4672
|
+
typeof process.env[OPENCLAWBRAIN_EMBEDDER_BASE_URL_ENV] === "string";
|
|
4673
|
+
// Legacy install-written provider-defaults.json files can predate embedder fields entirely.
|
|
4674
|
+
// If a persisted defaults file exists, treat that activation root as explicitly configured and
|
|
4675
|
+
// let provider-config resolution fill in the embedder fallback instead of silently dropping to null.
|
|
4676
|
+
const explicitDefaults = defaultsResult.defaults !== null;
|
|
4677
|
+
if (!explicitEnv && !explicitDefaults) {
|
|
4678
|
+
return {
|
|
4679
|
+
embedder: null,
|
|
4680
|
+
warnings
|
|
4681
|
+
};
|
|
4682
|
+
}
|
|
4683
|
+
if (providerConfig.embedder.provider !== "ollama") {
|
|
4684
|
+
return {
|
|
4685
|
+
embedder: null,
|
|
4686
|
+
warnings
|
|
4687
|
+
};
|
|
4688
|
+
}
|
|
4689
|
+
return {
|
|
4690
|
+
embedder: createOllamaEmbedder({
|
|
4691
|
+
baseUrl: providerConfig.embedderBaseUrl,
|
|
4692
|
+
model: providerConfig.embedder.model
|
|
4693
|
+
}),
|
|
4694
|
+
warnings
|
|
4695
|
+
};
|
|
4696
|
+
}
|
|
4697
|
+
function summarizeWatchLatestUserMessage(localPoll) {
|
|
4698
|
+
let latest = null;
|
|
4699
|
+
for (const change of localPoll.changes) {
|
|
4700
|
+
if (change.lastUserMessageAt === null || change.lastUserMessageText === null) {
|
|
4701
|
+
continue;
|
|
4702
|
+
}
|
|
4703
|
+
const candidate = {
|
|
4704
|
+
at: change.lastUserMessageAt,
|
|
4705
|
+
text: change.lastUserMessageText,
|
|
4706
|
+
sessionId: change.sessionId
|
|
4707
|
+
};
|
|
4708
|
+
if (latest === null || Date.parse(candidate.at) >= Date.parse(latest.at)) {
|
|
4709
|
+
latest = candidate;
|
|
4710
|
+
}
|
|
4711
|
+
}
|
|
4712
|
+
return latest;
|
|
4713
|
+
}
|
|
4714
|
+
function summarizeWatchPackTransition(input) {
|
|
4715
|
+
const beforeActivePackId = input.before?.active?.packId ?? input.before?.pointers.active?.packId ?? null;
|
|
4716
|
+
const afterActivePackId = input.after?.active?.packId ?? input.after?.pointers.active?.packId ?? null;
|
|
4717
|
+
if (afterActivePackId !== null && beforeActivePackId !== afterActivePackId) {
|
|
4718
|
+
return {
|
|
4719
|
+
kind: "promoted_active",
|
|
4720
|
+
fromPackId: beforeActivePackId,
|
|
4721
|
+
toPackId: afterActivePackId
|
|
4722
|
+
};
|
|
4723
|
+
}
|
|
4724
|
+
const beforeCandidatePackId = input.before?.candidate?.packId ?? input.before?.pointers.candidate?.packId ?? null;
|
|
4725
|
+
const afterCandidatePackId = input.after?.candidate?.packId ?? input.after?.pointers.candidate?.packId ?? null;
|
|
4726
|
+
if (afterCandidatePackId !== null && beforeCandidatePackId !== afterCandidatePackId) {
|
|
4727
|
+
return {
|
|
4728
|
+
kind: "staged_candidate",
|
|
4729
|
+
fromPackId: beforeCandidatePackId,
|
|
4730
|
+
toPackId: afterCandidatePackId
|
|
4731
|
+
};
|
|
4732
|
+
}
|
|
4733
|
+
return null;
|
|
4734
|
+
}
|
|
4735
|
+
function truncateWatchMessage(text, maxLength = 96) {
|
|
4736
|
+
const normalized = text.replace(/\s+/gu, " ").trim();
|
|
4737
|
+
if (normalized.length <= maxLength) {
|
|
4738
|
+
return normalized;
|
|
4739
|
+
}
|
|
4740
|
+
return `${normalized.slice(0, maxLength - 1)}…`;
|
|
4741
|
+
}
|
|
4742
|
+
function buildWatchLastObservedDelta(input) {
|
|
4743
|
+
const exported = input.exported.exportedBundleCount > 0 ||
|
|
4744
|
+
input.exported.exportedEventCount > 0;
|
|
4745
|
+
const labeled = (input.snapshotAfter.diagnostics.emittedArtifactCount ?? 0) >
|
|
4746
|
+
(input.snapshotBefore.diagnostics.emittedArtifactCount ?? 0);
|
|
4747
|
+
const latestPackTransition = summarizeWatchPackTransition({
|
|
4748
|
+
before: input.beforeInspection,
|
|
4749
|
+
after: input.afterInspection
|
|
4750
|
+
});
|
|
4751
|
+
const promoted = latestPackTransition?.kind === "promoted_active";
|
|
4752
|
+
const afterActivePackId = input.afterInspection?.active?.packId ?? input.afterInspection?.pointers.active?.packId ?? null;
|
|
4753
|
+
const served = promoted && afterActivePackId === latestPackTransition?.toPackId && input.afterInspection?.active?.activationReady === true;
|
|
4754
|
+
const latestUserMessage = summarizeWatchLatestUserMessage(input.localPoll);
|
|
4755
|
+
const selectedBackfillOnly = !exported && input.scanResult.selected.length > 0;
|
|
4756
|
+
const cycleDidNothing = !exported && !labeled && !promoted && !served;
|
|
4757
|
+
let explanation;
|
|
4758
|
+
if (latestUserMessage === null) {
|
|
4759
|
+
if (selectedBackfillOnly) {
|
|
4760
|
+
explanation =
|
|
4761
|
+
"No new local user message was exported in this cycle; the learner only revisited stored exports, so this pass does not prove a new last-turn change.";
|
|
4762
|
+
}
|
|
4763
|
+
else if (cycleDidNothing) {
|
|
4764
|
+
explanation = "No new local user message or learner-visible export was observed in this cycle, so nothing changed.";
|
|
4765
|
+
}
|
|
4766
|
+
else if (promoted && latestPackTransition !== null) {
|
|
4767
|
+
explanation =
|
|
4768
|
+
`No new local user message was exported in this cycle; pack ${latestPackTransition.toPackId} moved into ${latestPackTransition.kind === "promoted_active" ? "active serving" : "the candidate slot"} from previously accumulated learner state.`;
|
|
4769
|
+
}
|
|
4770
|
+
else {
|
|
4771
|
+
explanation =
|
|
4772
|
+
"This cycle observed learner activity, but it did not include a new local user message, so the latest last-turn delta cannot be attributed to a fresh user turn.";
|
|
4773
|
+
}
|
|
4774
|
+
}
|
|
4775
|
+
else {
|
|
4776
|
+
const quotedMessage = `"${truncateWatchMessage(latestUserMessage.text)}"`;
|
|
4777
|
+
if (exported && labeled && promoted && served && latestPackTransition !== null) {
|
|
4778
|
+
explanation =
|
|
4779
|
+
`Latest user message ${quotedMessage} was exported, labeled, promoted into pack ${latestPackTransition.toPackId}, and is now served from the active pack.`;
|
|
4780
|
+
}
|
|
4781
|
+
else if (exported && labeled && !promoted) {
|
|
4782
|
+
explanation =
|
|
4783
|
+
`Latest user message ${quotedMessage} was exported and labeled, but it has not been promoted into the serving pack yet.`;
|
|
4784
|
+
}
|
|
4785
|
+
else if (exported && !labeled && !promoted) {
|
|
4786
|
+
explanation =
|
|
4787
|
+
`Latest user message ${quotedMessage} was exported, but it did not add a new teacher label or change the serving pack in this cycle.`;
|
|
4788
|
+
}
|
|
4789
|
+
else if (exported && !labeled && promoted && latestPackTransition !== null) {
|
|
4790
|
+
explanation =
|
|
4791
|
+
`Latest user message ${quotedMessage} was exported, but this cycle's promotion to pack ${latestPackTransition.toPackId} is not backed by a new teacher label from that message alone.`;
|
|
4792
|
+
}
|
|
4793
|
+
else if (!exported && labeled) {
|
|
4794
|
+
explanation =
|
|
4795
|
+
`Latest user message ${quotedMessage} was already in stored exports; this cycle only labeled or replayed it, without a new local export.`;
|
|
4796
|
+
}
|
|
4797
|
+
else if (cycleDidNothing) {
|
|
4798
|
+
explanation = `Latest user message ${quotedMessage} did not produce a new export, label, or serving-pack change in this cycle.`;
|
|
4799
|
+
}
|
|
4800
|
+
else {
|
|
4801
|
+
explanation =
|
|
4802
|
+
`Latest user message ${quotedMessage} changed learner state this cycle, but the local artifacts do not prove a clean export-to-serve handoff yet.`;
|
|
4803
|
+
}
|
|
4804
|
+
}
|
|
4805
|
+
return {
|
|
4806
|
+
available: true,
|
|
4807
|
+
observedAt: input.observedAt,
|
|
4808
|
+
exported,
|
|
4809
|
+
labeled,
|
|
4810
|
+
promoted,
|
|
4811
|
+
served,
|
|
4812
|
+
latestPackTransition,
|
|
4813
|
+
explanation
|
|
4814
|
+
};
|
|
4815
|
+
}
|
|
3572
4816
|
export async function createWatchCommandRuntime(input) {
|
|
3573
4817
|
const activationRoot = path.resolve(input.activationRoot);
|
|
3574
4818
|
const bootstrapObservedAt = new Date().toISOString();
|
|
3575
4819
|
const scanRoot = input.scanRoot !== undefined && input.scanRoot !== null
|
|
3576
4820
|
? path.resolve(input.scanRoot)
|
|
3577
4821
|
: path.resolve(activationRoot, "event-exports");
|
|
4822
|
+
const pollIntervalSeconds = Number.isInteger(input.pollIntervalSeconds) && (input.pollIntervalSeconds ?? 0) > 0
|
|
4823
|
+
? input.pollIntervalSeconds
|
|
4824
|
+
: DEFAULT_WATCH_POLL_INTERVAL_SECONDS;
|
|
3578
4825
|
const sessionTailCursorPath = resolveWatchSessionTailCursorPath(activationRoot);
|
|
3579
4826
|
const teacherSnapshotPath = resolveWatchTeacherSnapshotPath(activationRoot);
|
|
3580
4827
|
const restoredTeacherState = loadWatchTeacherSnapshotState(teacherSnapshotPath);
|
|
@@ -3586,14 +4833,25 @@ export async function createWatchCommandRuntime(input) {
|
|
|
3586
4833
|
log(`Scan root: ${shortenPath(scanRoot)}`);
|
|
3587
4834
|
log(`State: cursor=${shortenPath(sessionTailCursorPath)} snapshot=${shortenPath(teacherSnapshotPath)}`);
|
|
3588
4835
|
const resolvedTeacherLabeler = resolveWatchTeacherLabelerConfig(input.teacherLabeler, activationRoot);
|
|
4836
|
+
const resolvedEmbedder = resolveWatchEmbedderConfig(input.embedder, activationRoot);
|
|
3589
4837
|
const teacherLabeler = resolvedTeacherLabeler.teacherLabeler;
|
|
3590
4838
|
for (const warning of resolvedTeacherLabeler.warnings) {
|
|
3591
4839
|
startupWarnings.push(`teacher_config_warning:${warning}`);
|
|
3592
4840
|
log(`Teacher config warning: ${warning}`);
|
|
3593
4841
|
}
|
|
4842
|
+
for (const warning of resolvedEmbedder.warnings) {
|
|
4843
|
+
startupWarnings.push(`embedder_config_warning:${warning}`);
|
|
4844
|
+
log(`Embedder config warning: ${warning}`);
|
|
4845
|
+
}
|
|
3594
4846
|
if (teacherLabeler?.provider === "ollama") {
|
|
3595
4847
|
log(`Teacher labeler: provider=ollama model=${teacherLabeler.model ?? "qwen3.5:9b"}`);
|
|
3596
4848
|
}
|
|
4849
|
+
if (resolvedEmbedder.embedder !== null) {
|
|
4850
|
+
log(`Embedder: provider=ollama model=${resolvedEmbedder.embedder.model}`);
|
|
4851
|
+
}
|
|
4852
|
+
else {
|
|
4853
|
+
log("Embedder: numeric pack materialization is not configured; watch will keep keyword/weight vectors only.");
|
|
4854
|
+
}
|
|
3597
4855
|
const scanner = createRuntimeEventExportScanner({ scanRoot });
|
|
3598
4856
|
let lastServeTimeFallbackReason = null;
|
|
3599
4857
|
const baseTeacherLoopInput = {
|
|
@@ -3630,10 +4888,23 @@ export async function createWatchCommandRuntime(input) {
|
|
|
3630
4888
|
};
|
|
3631
4889
|
let teacherLoop;
|
|
3632
4890
|
let lastHandledMaterializationPackId = restoredTeacherState.lastHandledMaterializationPackId;
|
|
4891
|
+
let lastEmbedInstrumentation = restoredTeacherState.embedInstrumentation;
|
|
4892
|
+
let restoredLastObservedDelta = restoredTeacherState.lastObservedDelta;
|
|
3633
4893
|
if (restoredTeacherState.error !== null) {
|
|
3634
4894
|
const message = restoredTeacherState.error;
|
|
3635
4895
|
startupWarnings.push(`teacher_snapshot_reset:${message}`);
|
|
3636
4896
|
lastHandledMaterializationPackId = null;
|
|
4897
|
+
lastEmbedInstrumentation = null;
|
|
4898
|
+
restoredLastObservedDelta = {
|
|
4899
|
+
available: true,
|
|
4900
|
+
observedAt: bootstrapObservedAt,
|
|
4901
|
+
exported: false,
|
|
4902
|
+
labeled: false,
|
|
4903
|
+
promoted: false,
|
|
4904
|
+
served: false,
|
|
4905
|
+
latestPackTransition: null,
|
|
4906
|
+
explanation: "Watch reset an unreadable teacher snapshot, so no prior last-turn delta can be trusted."
|
|
4907
|
+
};
|
|
3637
4908
|
log(`Teacher snapshot reset: ${message}`);
|
|
3638
4909
|
teacherLoop = createAsyncTeacherLiveLoop(baseTeacherLoopInput);
|
|
3639
4910
|
}
|
|
@@ -3648,6 +4919,17 @@ export async function createWatchCommandRuntime(input) {
|
|
|
3648
4919
|
const message = formatWatchError(error);
|
|
3649
4920
|
startupWarnings.push(`teacher_snapshot_reset:${message}`);
|
|
3650
4921
|
lastHandledMaterializationPackId = null;
|
|
4922
|
+
lastEmbedInstrumentation = null;
|
|
4923
|
+
restoredLastObservedDelta = {
|
|
4924
|
+
available: true,
|
|
4925
|
+
observedAt: bootstrapObservedAt,
|
|
4926
|
+
exported: false,
|
|
4927
|
+
labeled: false,
|
|
4928
|
+
promoted: false,
|
|
4929
|
+
served: false,
|
|
4930
|
+
latestPackTransition: null,
|
|
4931
|
+
explanation: "Watch reset an unusable teacher snapshot, so no prior last-turn delta can be trusted."
|
|
4932
|
+
};
|
|
3651
4933
|
log(`Teacher snapshot reset: ${message}`);
|
|
3652
4934
|
teacherLoop = createAsyncTeacherLiveLoop(baseTeacherLoopInput);
|
|
3653
4935
|
}
|
|
@@ -3656,11 +4938,28 @@ export async function createWatchCommandRuntime(input) {
|
|
|
3656
4938
|
const restoredSeenExportCount = restoredTeacherState.snapshot.state?.seenExportDigests.length ?? 0;
|
|
3657
4939
|
log(`Restored teacher snapshot: seen=${restoredSeenExportCount} artifacts=${restoredTeacherState.snapshot.teacher.artifactCount}`);
|
|
3658
4940
|
}
|
|
4941
|
+
const resolvedWatchProfileScope = input.profileRoots === undefined
|
|
4942
|
+
? resolveWatchProfileRootsForActivationRoot(activationRoot)
|
|
4943
|
+
: {
|
|
4944
|
+
attachedProfileRoots: [...new Set(input.profileRoots.map((root) => path.resolve(root)))],
|
|
4945
|
+
halfAttachedReferences: []
|
|
4946
|
+
};
|
|
4947
|
+
const resolvedProfileRoots = resolvedWatchProfileScope.attachedProfileRoots;
|
|
4948
|
+
if (input.profileRoots === undefined && resolvedProfileRoots !== undefined && resolvedProfileRoots.length > 0) {
|
|
4949
|
+
log(`Session tail scope: attached OpenClaw home${resolvedProfileRoots.length === 1 ? "" : "s"} ${resolvedProfileRoots
|
|
4950
|
+
.map((root) => shortenPath(root))
|
|
4951
|
+
.join(", ")}`);
|
|
4952
|
+
}
|
|
4953
|
+
if (input.profileRoots === undefined && resolvedWatchProfileScope.halfAttachedReferences.length > 0) {
|
|
4954
|
+
log(`Session tail scope skipped half-attached OpenClaw home${resolvedWatchProfileScope.halfAttachedReferences.length === 1 ? "" : "s"} ${resolvedWatchProfileScope.halfAttachedReferences
|
|
4955
|
+
.map((reference) => `${shortenPath(path.resolve(reference.openclawHome))} (${summarizeSharedActivationRootReferenceProof(reference)})`)
|
|
4956
|
+
.join(", ")}`);
|
|
4957
|
+
}
|
|
3659
4958
|
let restoredCursor = loadWatchSessionTailCursor(sessionTailCursorPath);
|
|
3660
4959
|
let localSessionTail;
|
|
3661
4960
|
try {
|
|
3662
4961
|
localSessionTail = createOpenClawLocalSessionTail({
|
|
3663
|
-
...(
|
|
4962
|
+
...(resolvedProfileRoots === undefined ? {} : { profileRoots: resolvedProfileRoots }),
|
|
3664
4963
|
cursor: restoredCursor,
|
|
3665
4964
|
emitExistingOnFirstPoll: restoredCursor.length === 0
|
|
3666
4965
|
});
|
|
@@ -3670,7 +4969,7 @@ export async function createWatchCommandRuntime(input) {
|
|
|
3670
4969
|
log(`Session tail cursor reset: ${message}`);
|
|
3671
4970
|
restoredCursor = [];
|
|
3672
4971
|
localSessionTail = createOpenClawLocalSessionTail({
|
|
3673
|
-
...(
|
|
4972
|
+
...(resolvedProfileRoots === undefined ? {} : { profileRoots: resolvedProfileRoots }),
|
|
3674
4973
|
emitExistingOnFirstPoll: true
|
|
3675
4974
|
});
|
|
3676
4975
|
persistWatchSessionTailCursor(sessionTailCursorPath, []);
|
|
@@ -3691,8 +4990,11 @@ export async function createWatchCommandRuntime(input) {
|
|
|
3691
4990
|
log(`Replayed ${replayState.replayedBundleCount} stored export bundle${replayState.replayedBundleCount === 1 ? "" : "s"} (${replayState.replayedEventCount} event${replayState.replayedEventCount === 1 ? "" : "s"})`);
|
|
3692
4991
|
}
|
|
3693
4992
|
let bootstrapSnapshot = teacherLoop.snapshot();
|
|
3694
|
-
const replayPromotion = applyWatchMaterialization(activationRoot, bootstrapSnapshot, lastHandledMaterializationPackId);
|
|
4993
|
+
const replayPromotion = await applyWatchMaterialization(activationRoot, bootstrapSnapshot, lastHandledMaterializationPackId, resolvedEmbedder.embedder, log);
|
|
3695
4994
|
lastHandledMaterializationPackId = replayPromotion.lastHandledMaterializationPackId;
|
|
4995
|
+
if (replayPromotion.embedInstrumentation !== null) {
|
|
4996
|
+
lastEmbedInstrumentation = replayPromotion.embedInstrumentation;
|
|
4997
|
+
}
|
|
3696
4998
|
if (replayPromotion.logLine !== null) {
|
|
3697
4999
|
log(replayPromotion.logLine);
|
|
3698
5000
|
bootstrapSnapshot = teacherLoop.snapshot();
|
|
@@ -3700,6 +5002,7 @@ export async function createWatchCommandRuntime(input) {
|
|
|
3700
5002
|
const bootstrapCursor = localSessionTail.snapshot();
|
|
3701
5003
|
persistWatchTeacherSnapshot(teacherSnapshotPath, {
|
|
3702
5004
|
lastRunAt: bootstrapObservedAt,
|
|
5005
|
+
pollIntervalSeconds,
|
|
3703
5006
|
scanRoot,
|
|
3704
5007
|
sessionTailCursorPath,
|
|
3705
5008
|
sessionTailCursorUpdatedAt: bootstrapObservedAt,
|
|
@@ -3715,26 +5018,44 @@ export async function createWatchCommandRuntime(input) {
|
|
|
3715
5018
|
lastTeacherError: null,
|
|
3716
5019
|
localSessionTailNoopReason: null,
|
|
3717
5020
|
lastHandledMaterializationPackId,
|
|
5021
|
+
lastObservedDelta: restoredLastObservedDelta.available
|
|
5022
|
+
? restoredLastObservedDelta
|
|
5023
|
+
: {
|
|
5024
|
+
available: true,
|
|
5025
|
+
observedAt: bootstrapObservedAt,
|
|
5026
|
+
exported: false,
|
|
5027
|
+
labeled: false,
|
|
5028
|
+
promoted: false,
|
|
5029
|
+
served: false,
|
|
5030
|
+
latestPackTransition: null,
|
|
5031
|
+
explanation: "Watch bootstrapped its state, but no new local user-message delta has been observed yet."
|
|
5032
|
+
},
|
|
5033
|
+
embedInstrumentation: lastEmbedInstrumentation,
|
|
3718
5034
|
failure: replayPromotion.failure,
|
|
3719
5035
|
snapshot: bootstrapSnapshot
|
|
3720
5036
|
});
|
|
3721
5037
|
return {
|
|
3722
5038
|
activationRoot,
|
|
3723
5039
|
scanRoot,
|
|
5040
|
+
pollIntervalSeconds,
|
|
3724
5041
|
sessionTailCursorPath,
|
|
3725
5042
|
teacherSnapshotPath,
|
|
3726
5043
|
startupWarnings,
|
|
3727
5044
|
lastTeacherError: null,
|
|
3728
5045
|
replayState,
|
|
3729
5046
|
lastHandledMaterializationPackId,
|
|
5047
|
+
lastEmbedInstrumentation,
|
|
3730
5048
|
scanner,
|
|
3731
5049
|
teacherLoop,
|
|
3732
|
-
localSessionTail
|
|
5050
|
+
localSessionTail,
|
|
5051
|
+
embedder: resolvedEmbedder.embedder
|
|
3733
5052
|
};
|
|
3734
5053
|
}
|
|
3735
5054
|
export async function runWatchCommandPass(runtime, options = {}) {
|
|
3736
5055
|
const log = options.log ?? watchLog;
|
|
3737
5056
|
const observedAt = options.observedAt ?? new Date().toISOString();
|
|
5057
|
+
const snapshotBefore = runtime.teacherLoop.snapshot();
|
|
5058
|
+
const beforeInspection = inspectActivationState(runtime.activationRoot, observedAt);
|
|
3738
5059
|
const localPoll = runtime.localSessionTail.pollOnce({
|
|
3739
5060
|
observedAt
|
|
3740
5061
|
});
|
|
@@ -3768,9 +5089,12 @@ export async function runWatchCommandPass(runtime, options = {}) {
|
|
|
3768
5089
|
const ingestResult = await runtime.teacherLoop.ingestRuntimeEventExportScannerScan(scanResult);
|
|
3769
5090
|
runtime.lastTeacherError = null;
|
|
3770
5091
|
snapshot = ingestResult.snapshot;
|
|
3771
|
-
const promotion = applyWatchMaterialization(runtime.activationRoot, snapshot, runtime.lastHandledMaterializationPackId);
|
|
5092
|
+
const promotion = await applyWatchMaterialization(runtime.activationRoot, snapshot, runtime.lastHandledMaterializationPackId, runtime.embedder, log);
|
|
3772
5093
|
runtime.lastHandledMaterializationPackId = promotion.lastHandledMaterializationPackId;
|
|
3773
5094
|
materializedPackId = promotion.materializedPackId;
|
|
5095
|
+
if (promotion.embedInstrumentation !== null) {
|
|
5096
|
+
runtime.lastEmbedInstrumentation = promotion.embedInstrumentation;
|
|
5097
|
+
}
|
|
3774
5098
|
failure = promotion.failure;
|
|
3775
5099
|
if (promotion.logLine !== null) {
|
|
3776
5100
|
log(promotion.logLine);
|
|
@@ -3802,8 +5126,20 @@ export async function runWatchCommandPass(runtime, options = {}) {
|
|
|
3802
5126
|
snapshot = runtime.teacherLoop.snapshot();
|
|
3803
5127
|
}
|
|
3804
5128
|
}
|
|
5129
|
+
const afterInspection = inspectActivationState(runtime.activationRoot, observedAt);
|
|
5130
|
+
const lastObservedDelta = buildWatchLastObservedDelta({
|
|
5131
|
+
observedAt,
|
|
5132
|
+
localPoll,
|
|
5133
|
+
exported,
|
|
5134
|
+
scanResult,
|
|
5135
|
+
snapshotBefore,
|
|
5136
|
+
snapshotAfter: snapshot,
|
|
5137
|
+
beforeInspection,
|
|
5138
|
+
afterInspection
|
|
5139
|
+
});
|
|
3805
5140
|
persistWatchTeacherSnapshot(runtime.teacherSnapshotPath, {
|
|
3806
5141
|
lastRunAt: observedAt,
|
|
5142
|
+
pollIntervalSeconds: runtime.pollIntervalSeconds,
|
|
3807
5143
|
scanRoot: runtime.scanRoot,
|
|
3808
5144
|
sessionTailCursorPath: runtime.sessionTailCursorPath,
|
|
3809
5145
|
sessionTailCursorUpdatedAt: observedAt,
|
|
@@ -3819,6 +5155,8 @@ export async function runWatchCommandPass(runtime, options = {}) {
|
|
|
3819
5155
|
lastTeacherError: runtime.lastTeacherError,
|
|
3820
5156
|
localSessionTailNoopReason: localPoll.noopReason,
|
|
3821
5157
|
lastHandledMaterializationPackId: runtime.lastHandledMaterializationPackId,
|
|
5158
|
+
lastObservedDelta,
|
|
5159
|
+
embedInstrumentation: runtime.lastEmbedInstrumentation,
|
|
3822
5160
|
failure,
|
|
3823
5161
|
snapshot
|
|
3824
5162
|
});
|
|
@@ -3839,6 +5177,7 @@ export async function runWatchCommandPass(runtime, options = {}) {
|
|
|
3839
5177
|
scannerProcessedBundles: persistedScannerCheckpoint.processedExportDigests.length,
|
|
3840
5178
|
scannerLiveAfter: persistedScannerCheckpoint.live.after?.exportDigest ?? null,
|
|
3841
5179
|
materialized: materializedPackId,
|
|
5180
|
+
lastObservedDelta,
|
|
3842
5181
|
diagnostics: snapshot.diagnostics ?? null,
|
|
3843
5182
|
localSessionTailNoopReason: localPoll.noopReason
|
|
3844
5183
|
}));
|
|
@@ -3856,6 +5195,7 @@ async function runWatchCommand(parsed) {
|
|
|
3856
5195
|
const runtime = await createWatchCommandRuntime({
|
|
3857
5196
|
activationRoot: parsed.activationRoot,
|
|
3858
5197
|
scanRoot: parsed.scanRoot,
|
|
5198
|
+
pollIntervalSeconds: parsed.interval,
|
|
3859
5199
|
log: watchLog
|
|
3860
5200
|
});
|
|
3861
5201
|
watchLog(`Interval: ${parsed.interval}s`);
|
|
@@ -4139,6 +5479,10 @@ export function runOperatorCli(argv = process.argv.slice(2)) {
|
|
|
4139
5479
|
const operatorInput = {
|
|
4140
5480
|
...statusOrRollback.input,
|
|
4141
5481
|
activationRoot,
|
|
5482
|
+
openclawHome: statusOrRollback.openclawHome,
|
|
5483
|
+
...(targetInspection?.profileId === null || targetInspection?.profileId === undefined
|
|
5484
|
+
? {}
|
|
5485
|
+
: { profileId: targetInspection.profileId }),
|
|
4142
5486
|
teacherSnapshotPath: resolveOperatorTeacherSnapshotPath(activationRoot, statusOrRollback.input.teacherSnapshotPath)
|
|
4143
5487
|
};
|
|
4144
5488
|
const status = describeCurrentProfileBrainStatus(operatorInput);
|
|
@@ -4147,7 +5491,10 @@ export function runOperatorCli(argv = process.argv.slice(2)) {
|
|
|
4147
5491
|
}
|
|
4148
5492
|
else {
|
|
4149
5493
|
const report = buildOperatorSurfaceReport(operatorInput);
|
|
4150
|
-
const providerConfig =
|
|
5494
|
+
const providerConfig = readOpenClawBrainProviderConfigFromSources({
|
|
5495
|
+
env: process.env,
|
|
5496
|
+
activationRoot
|
|
5497
|
+
});
|
|
4151
5498
|
if (statusOrRollback.detailed) {
|
|
4152
5499
|
console.log(formatCurrentProfileStatusSummary(status, report, targetInspection, {
|
|
4153
5500
|
openclawHome: statusOrRollback.openclawHome,
|