@openclawbrain/openclaw 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/extension/runtime-guard.js +5 -0
- package/dist/extension/runtime-guard.js.map +1 -1
- package/dist/src/cli.d.ts +7 -1
- package/dist/src/cli.js +1015 -54
- 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 +65 -1
- package/dist/src/index.js +627 -56
- 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/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/runtime-guard.ts +6 -0
- package/package.json +7 -7
package/dist/src/cli.js
CHANGED
|
@@ -5,19 +5,22 @@ 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 { buildNormalizedEventExportFromScannedEvents, bootstrapRuntimeAttach, buildOperatorSurfaceReport, compileRuntimeContext, createAsyncTeacherLiveLoop, createOpenClawLocalSessionTail, createRuntimeEventExportScanner, describeCurrentProfileBrainStatus, formatOperatorRollbackReport, loadWatchTeacherSnapshotState, loadRuntimeEventExportBundle, persistWatchTeacherSnapshot, rollbackRuntimeAttach, resolveOperatorTeacherSnapshotPath, resolveAsyncTeacherLiveLoopSnapshotPath, resolveWatchSessionTailCursorPath, resolveWatchStateRoot, resolveWatchTeacherSnapshotPath, scanLiveEventExport, scanRecordedSession, summarizeLearningPathFromMaterialization, summarizeNormalizedEventExportLabelFlow, writeScannedEventExportBundle } from "./index.js";
|
|
16
|
+
import { DEFAULT_WATCH_POLL_INTERVAL_SECONDS, buildNormalizedEventExportFromScannedEvents, bootstrapRuntimeAttach, buildOperatorSurfaceReport, compileRuntimeContext, createAsyncTeacherLiveLoop, createOpenClawLocalSessionTail, createRuntimeEventExportScanner, describeCurrentProfileBrainStatus, formatOperatorRollbackReport, loadWatchTeacherSnapshotState, loadRuntimeEventExportBundle, persistWatchTeacherSnapshot, rollbackRuntimeAttach, resolveOperatorTeacherSnapshotPath, resolveAsyncTeacherLiveLoopSnapshotPath, resolveWatchSessionTailCursorPath, resolveWatchStateRoot, resolveWatchTeacherSnapshotPath, scanLiveEventExport, scanRecordedSession, summarizeLearningPathFromMaterialization, summarizeNormalizedEventExportLabelFlow, writeScannedEventExportBundle } from "./index.js";
|
|
17
17
|
import { appendLearningUpdateLogs } from "./learning-spine.js";
|
|
18
18
|
import { buildPassiveLearningSessionExportFromOpenClawSessionStore } from "./local-session-passive-learning.js";
|
|
19
19
|
import { discoverOpenClawSessionStores, loadOpenClawSessionIndex, readOpenClawSessionFile } from "./session-store.js";
|
|
20
|
-
import { readOpenClawBrainProviderConfig, readOpenClawBrainProviderConfigFromSources, resolveOpenClawBrainProviderDefaultsPath } from "./provider-config.js";
|
|
20
|
+
import { readOpenClawBrainProviderDefaults, readOpenClawBrainProviderConfig, readOpenClawBrainProviderConfigFromSources, resolveOpenClawBrainProviderDefaultsPath } from "./provider-config.js";
|
|
21
|
+
const OPENCLAWBRAIN_EMBEDDER_BASE_URL_ENV = "OPENCLAWBRAIN_EMBEDDER_BASE_URL";
|
|
22
|
+
const OPENCLAWBRAIN_EMBEDDER_PROVIDER_ENV = "OPENCLAWBRAIN_EMBEDDER_PROVIDER";
|
|
23
|
+
const OPENCLAWBRAIN_EMBEDDER_MODEL_ENV = "OPENCLAWBRAIN_EMBEDDER_MODEL";
|
|
21
24
|
const OPENCLAWBRAIN_INSTALL_SKIP_EMBEDDER_PROVISION_ENV = "OPENCLAWBRAIN_INSTALL_SKIP_EMBEDDER_PROVISION";
|
|
22
25
|
const INSTALL_COMPATIBLE_LOCAL_TEACHER_MODEL_PREFIXES = [
|
|
23
26
|
"qwen3.5:9b",
|
|
@@ -48,6 +51,56 @@ function getCliHomeDir() {
|
|
|
48
51
|
function discoverInstallCandidateOpenClawHomes(homeDir = getCliHomeDir()) {
|
|
49
52
|
return discoverOpenClawHomes(homeDir).map((inspection) => inspection.openclawHome);
|
|
50
53
|
}
|
|
54
|
+
function findInstalledHookReferencesForActivationRoot(input) {
|
|
55
|
+
const resolvedActivationRoot = path.resolve(input.activationRoot);
|
|
56
|
+
const resolvedExcludedHome = input.excludingOpenClawHome === undefined || input.excludingOpenClawHome === null
|
|
57
|
+
? null
|
|
58
|
+
: path.resolve(input.excludingOpenClawHome);
|
|
59
|
+
return discoverOpenClawHomes(input.homeDir ?? getCliHomeDir())
|
|
60
|
+
.filter((inspection) => resolvedExcludedHome === null || path.resolve(inspection.openclawHome) !== resolvedExcludedHome)
|
|
61
|
+
.flatMap((inspection) => {
|
|
62
|
+
const installedActivationRoot = resolveActivationRoot({
|
|
63
|
+
openclawHome: inspection.openclawHome,
|
|
64
|
+
quiet: true
|
|
65
|
+
});
|
|
66
|
+
if (installedActivationRoot.trim().length === 0) {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
return path.resolve(installedActivationRoot) === resolvedActivationRoot
|
|
70
|
+
? [{ openclawHome: inspection.openclawHome, inspection }]
|
|
71
|
+
: [];
|
|
72
|
+
})
|
|
73
|
+
.sort((left, right) => left.openclawHome.localeCompare(right.openclawHome));
|
|
74
|
+
}
|
|
75
|
+
function findOtherInstalledHookReferencesForActivationRoot(input) {
|
|
76
|
+
return findInstalledHookReferencesForActivationRoot(input);
|
|
77
|
+
}
|
|
78
|
+
function resolveWatchProfileRootsForActivationRoot(activationRoot, homeDir = getCliHomeDir()) {
|
|
79
|
+
const attachedProfileRoots = findInstalledHookReferencesForActivationRoot({
|
|
80
|
+
activationRoot,
|
|
81
|
+
homeDir
|
|
82
|
+
}).map((reference) => path.resolve(reference.openclawHome));
|
|
83
|
+
return attachedProfileRoots.length > 0 ? attachedProfileRoots : undefined;
|
|
84
|
+
}
|
|
85
|
+
function assertActivationRootPurgeIsNotShared(input) {
|
|
86
|
+
const sharedReferences = findOtherInstalledHookReferencesForActivationRoot({
|
|
87
|
+
activationRoot: input.activationRoot,
|
|
88
|
+
excludingOpenClawHome: input.openclawHome
|
|
89
|
+
});
|
|
90
|
+
if (sharedReferences.length === 0) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const attachedProfiles = sharedReferences
|
|
94
|
+
.map(({ openclawHome, inspection }) => ` - ${path.resolve(openclawHome)} (${describeOpenClawHomeInspection(inspection)})`)
|
|
95
|
+
.join("\n");
|
|
96
|
+
throw new Error([
|
|
97
|
+
`Refusing to purge activation root ${path.resolve(input.activationRoot)} because another installed OpenClaw profile still points at it.`,
|
|
98
|
+
"Other attached profiles:",
|
|
99
|
+
attachedProfiles,
|
|
100
|
+
"Use uninstall --keep-data or detach on this profile first, then remove the remaining profile hooks before purging shared brain data.",
|
|
101
|
+
"For Eagle dogfood, prefer its own activation root so CormorantAI stays untouched."
|
|
102
|
+
].join("\n"));
|
|
103
|
+
}
|
|
51
104
|
function formatInstallOpenClawHomeSource(source) {
|
|
52
105
|
switch (source) {
|
|
53
106
|
case "explicit":
|
|
@@ -418,6 +471,24 @@ function formatStructuralOps(report) {
|
|
|
418
471
|
? "none"
|
|
419
472
|
: `split:${structuralOps.split},merge:${structuralOps.merge},prune:${structuralOps.prune},connect:${structuralOps.connect}`;
|
|
420
473
|
}
|
|
474
|
+
function formatGraphConnectDiagnostics(diagnostics) {
|
|
475
|
+
if (diagnostics === null) {
|
|
476
|
+
return "none";
|
|
477
|
+
}
|
|
478
|
+
return `budget:${diagnostics.requestedBudget},threshold:${diagnostics.scoreThreshold},pairs:${diagnostics.appliedPairCount}/${diagnostics.candidatePairCount},edges:${diagnostics.createdEdgeCount}`;
|
|
479
|
+
}
|
|
480
|
+
function formatCompactGraphConnectDiagnostics(diagnostics) {
|
|
481
|
+
if (diagnostics === null) {
|
|
482
|
+
return "none";
|
|
483
|
+
}
|
|
484
|
+
return `pairs:${diagnostics.appliedPairCount},edges:${diagnostics.createdEdgeCount}`;
|
|
485
|
+
}
|
|
486
|
+
function formatGraphSummary(report) {
|
|
487
|
+
return (report.graph.latestMaterialization.operatorSummary ??
|
|
488
|
+
report.graph.operatorSummary ??
|
|
489
|
+
report.graph.latestMaterialization.detail ??
|
|
490
|
+
report.graph.detail);
|
|
491
|
+
}
|
|
421
492
|
function formatScannerSurfaces(report) {
|
|
422
493
|
return report.supervision.scanSurfaces.length === 0 ? "none" : report.supervision.scanSurfaces.join("|");
|
|
423
494
|
}
|
|
@@ -506,6 +577,14 @@ const LEARNING_WARNING_MESSAGES = {
|
|
|
506
577
|
teacher_no_artifacts: "teacher produced no artifacts",
|
|
507
578
|
teacher_snapshot_unavailable: "teacher snapshot is unavailable"
|
|
508
579
|
};
|
|
580
|
+
const TEACHER_NO_OP_MESSAGES = {
|
|
581
|
+
none: "the latest processed export produced teacher artifacts",
|
|
582
|
+
duplicate_export: "the latest cycle was a no-op because the export was already seen",
|
|
583
|
+
queue_full: "the latest cycle was a no-op because the teacher queue was full",
|
|
584
|
+
no_teacher_artifacts: "the latest cycle was a no-op because no teacher artifacts were produced",
|
|
585
|
+
empty_scan: "the latest cycle was a no-op because the scanner did not produce any events",
|
|
586
|
+
unavailable: "the latest cycle is not visible from the current operator snapshot"
|
|
587
|
+
};
|
|
509
588
|
function summarizeStatusInstallHook(openclawHome) {
|
|
510
589
|
if (openclawHome === null) {
|
|
511
590
|
return {
|
|
@@ -528,6 +607,15 @@ function summarizeStatusInstallHook(openclawHome) {
|
|
|
528
607
|
detail: `profile hook is not present at ${shortenPath(extensionDir)}`
|
|
529
608
|
};
|
|
530
609
|
}
|
|
610
|
+
function summarizeStatusHookLoad(installHook, status) {
|
|
611
|
+
return {
|
|
612
|
+
installState: installHook.state === "unknown" ? "unverified" : installHook.state,
|
|
613
|
+
loadProof: status.attachment.state === "attached" && status.brainStatus.serveState === "serving_active_pack"
|
|
614
|
+
? "status_probe_ready"
|
|
615
|
+
: "not_ready",
|
|
616
|
+
detail: installHook.detail
|
|
617
|
+
};
|
|
618
|
+
}
|
|
531
619
|
function runOllamaProbe(args, baseUrl) {
|
|
532
620
|
try {
|
|
533
621
|
execFileSync("ollama", [...args], {
|
|
@@ -639,6 +727,162 @@ function summarizeStatusLocalLlm(providerConfig) {
|
|
|
639
727
|
: `teacher labeling is ${providerConfig.teacher.provider}; no local Ollama CLI was detected`
|
|
640
728
|
};
|
|
641
729
|
}
|
|
730
|
+
function summarizeStatusTeacher(report, providerConfig, localLlm) {
|
|
731
|
+
const enabled = providerConfig.teacher.provider === "ollama";
|
|
732
|
+
const latestCycle = report.teacherLoop.lastNoOpReason === "unavailable"
|
|
733
|
+
? "unknown"
|
|
734
|
+
: report.teacherLoop.lastNoOpReason === "none"
|
|
735
|
+
? "teacher_artifact"
|
|
736
|
+
: "no_op";
|
|
737
|
+
if (!enabled) {
|
|
738
|
+
return {
|
|
739
|
+
model: providerConfig.teacher.model,
|
|
740
|
+
enabled,
|
|
741
|
+
healthy: false,
|
|
742
|
+
stale: false,
|
|
743
|
+
idle: false,
|
|
744
|
+
latestCycle,
|
|
745
|
+
detail: `${providerConfig.teacher.model} is not enabled because teacher labeling is ${providerConfig.teacher.provider}`
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
if (!localLlm.detected) {
|
|
749
|
+
return {
|
|
750
|
+
model: providerConfig.teacher.model,
|
|
751
|
+
enabled,
|
|
752
|
+
healthy: false,
|
|
753
|
+
stale: null,
|
|
754
|
+
idle: false,
|
|
755
|
+
latestCycle,
|
|
756
|
+
detail: `${providerConfig.teacher.model} is configured on Ollama, but the local LLM surface is not answering at ${providerConfig.teacherBaseUrl}`
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
if (!report.teacherLoop.available) {
|
|
760
|
+
return {
|
|
761
|
+
model: providerConfig.teacher.model,
|
|
762
|
+
enabled,
|
|
763
|
+
healthy: null,
|
|
764
|
+
stale: null,
|
|
765
|
+
idle: null,
|
|
766
|
+
latestCycle,
|
|
767
|
+
detail: `${providerConfig.teacher.model} is enabled on Ollama, but no watch teacher snapshot is visible yet`
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
const stale = report.teacherLoop.latestFreshness === "stale" || report.teacherLoop.watchState === "stale_snapshot";
|
|
771
|
+
const idle = report.teacherLoop.running === false &&
|
|
772
|
+
(report.teacherLoop.queueDepth ?? 0) === 0 &&
|
|
773
|
+
report.teacherLoop.failureMode === "none";
|
|
774
|
+
const healthy = report.teacherLoop.failureMode === "none" &&
|
|
775
|
+
stale === false &&
|
|
776
|
+
report.teacherLoop.watchState !== "not_visible";
|
|
777
|
+
const cycleDetail = TEACHER_NO_OP_MESSAGES[report.teacherLoop.lastNoOpReason] ?? "the latest teacher cycle detail is unavailable";
|
|
778
|
+
if (report.teacherLoop.failureMode !== "none" && report.teacherLoop.failureMode !== "unavailable") {
|
|
779
|
+
return {
|
|
780
|
+
model: providerConfig.teacher.model,
|
|
781
|
+
enabled,
|
|
782
|
+
healthy: false,
|
|
783
|
+
stale,
|
|
784
|
+
idle,
|
|
785
|
+
latestCycle,
|
|
786
|
+
detail: report.teacherLoop.failureDetail === null
|
|
787
|
+
? `${providerConfig.teacher.model} is enabled, but the watch loop recorded ${report.teacherLoop.failureMode}`
|
|
788
|
+
: `${providerConfig.teacher.model} is enabled, but the watch loop recorded ${report.teacherLoop.failureMode}: ${report.teacherLoop.failureDetail}`
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
return {
|
|
792
|
+
model: providerConfig.teacher.model,
|
|
793
|
+
enabled,
|
|
794
|
+
healthy,
|
|
795
|
+
stale,
|
|
796
|
+
idle,
|
|
797
|
+
latestCycle,
|
|
798
|
+
detail: `${providerConfig.teacher.model} is enabled on Ollama; ${cycleDetail}`
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
function summarizeStatusEmbedder(embeddings) {
|
|
802
|
+
const provisioned = embeddings.provisionedState === "confirmed" || embeddings.provisionedState === "builtin"
|
|
803
|
+
? true
|
|
804
|
+
: embeddings.provisionedState === "not_confirmed" || embeddings.provisionedState === "off"
|
|
805
|
+
? false
|
|
806
|
+
: null;
|
|
807
|
+
const live = embeddings.liveState === "yes" ? true : embeddings.liveState === "no" ? false : null;
|
|
808
|
+
if (embeddings.provider === "off") {
|
|
809
|
+
return {
|
|
810
|
+
model: embeddings.model,
|
|
811
|
+
provisioned,
|
|
812
|
+
live,
|
|
813
|
+
detail: `${embeddings.model} is not provisioned because the embedder provider is off`
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
if (embeddings.provider === "keywords") {
|
|
817
|
+
return {
|
|
818
|
+
model: embeddings.model,
|
|
819
|
+
provisioned,
|
|
820
|
+
live,
|
|
821
|
+
detail: "keyword embeddings are builtin, so there is no Ollama model to provision"
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
if (provisioned === true && live === true) {
|
|
825
|
+
return {
|
|
826
|
+
model: embeddings.model,
|
|
827
|
+
provisioned,
|
|
828
|
+
live,
|
|
829
|
+
detail: `${embeddings.model} is confirmed on Ollama and the active pack stores live numeric embeddings`
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
if (provisioned === true && live === false) {
|
|
833
|
+
return {
|
|
834
|
+
model: embeddings.model,
|
|
835
|
+
provisioned,
|
|
836
|
+
live,
|
|
837
|
+
detail: `${embeddings.model} is confirmed on Ollama, but the active pack still has no live numeric embeddings`
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
if (provisioned === false && live === true) {
|
|
841
|
+
return {
|
|
842
|
+
model: embeddings.model,
|
|
843
|
+
provisioned,
|
|
844
|
+
live,
|
|
845
|
+
detail: `${embeddings.model} is not confirmed on Ollama, but the active pack already carries numeric embeddings from an earlier materialization`
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
return {
|
|
849
|
+
model: embeddings.model,
|
|
850
|
+
provisioned,
|
|
851
|
+
live,
|
|
852
|
+
detail: embeddings.detail
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
function summarizeStatusRouteFn(status, report) {
|
|
856
|
+
const freshness = report.servePath.refreshStatus ?? status.brain.routeFreshness;
|
|
857
|
+
if (!report.routeFn.available) {
|
|
858
|
+
return {
|
|
859
|
+
available: false,
|
|
860
|
+
freshness,
|
|
861
|
+
trainedAt: report.routeFn.trainedAt,
|
|
862
|
+
updatedAt: report.routeFn.updatedAt,
|
|
863
|
+
usedAt: report.routeFn.usedAt,
|
|
864
|
+
detail: report.routeFn.detail
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
let detail = report.routeFn.detail;
|
|
868
|
+
if (report.servePath.usedLearnedRouteFn === true) {
|
|
869
|
+
detail = `current serve proof used the learned route_fn; ${report.routeFn.detail}`;
|
|
870
|
+
}
|
|
871
|
+
else if (report.routeFn.usedAt !== null) {
|
|
872
|
+
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}`;
|
|
873
|
+
}
|
|
874
|
+
else if (report.routeFn.updatedAt !== null) {
|
|
875
|
+
detail = `active route_fn was last updated at ${report.routeFn.updatedAt}, but no learned serve use is visible yet for the current pack`;
|
|
876
|
+
}
|
|
877
|
+
return {
|
|
878
|
+
available: true,
|
|
879
|
+
freshness,
|
|
880
|
+
trainedAt: report.routeFn.trainedAt,
|
|
881
|
+
updatedAt: report.routeFn.updatedAt,
|
|
882
|
+
usedAt: report.routeFn.usedAt,
|
|
883
|
+
detail
|
|
884
|
+
};
|
|
885
|
+
}
|
|
642
886
|
function pushUniqueAlert(target, value) {
|
|
643
887
|
const normalized = value.trim();
|
|
644
888
|
if (normalized.length === 0) {
|
|
@@ -698,11 +942,8 @@ function summarizeStatusAlerts(report, providerConfig, embeddings, localLlm) {
|
|
|
698
942
|
}
|
|
699
943
|
return buckets;
|
|
700
944
|
}
|
|
701
|
-
function summarizeStatusWatchState(
|
|
702
|
-
|
|
703
|
-
return "not_visible";
|
|
704
|
-
}
|
|
705
|
-
return report.teacherLoop.running === true ? "running" : "snapshot_only";
|
|
945
|
+
function summarizeStatusWatchState(status) {
|
|
946
|
+
return status.passiveLearning.watchState;
|
|
706
947
|
}
|
|
707
948
|
function summarizeStatusServeReality(status) {
|
|
708
949
|
if (status.brainStatus.serveState === "serving_active_pack") {
|
|
@@ -710,36 +951,75 @@ function summarizeStatusServeReality(status) {
|
|
|
710
951
|
}
|
|
711
952
|
return status.brainStatus.serveState;
|
|
712
953
|
}
|
|
954
|
+
function summarizeStatusPromotionState(status) {
|
|
955
|
+
if (status.brain.state === "pg_promoted_pack_authoritative") {
|
|
956
|
+
return "promoted";
|
|
957
|
+
}
|
|
958
|
+
if (status.brain.state === "seed_state_authoritative") {
|
|
959
|
+
return status.passiveLearning.firstExportOccurred ? "seed_authoritative" : "awaiting_first_export";
|
|
960
|
+
}
|
|
961
|
+
return status.brain.state;
|
|
962
|
+
}
|
|
713
963
|
function formatStatusAlertLine(values) {
|
|
714
964
|
const normalized = values.map((value) => value.trim()).filter((value) => value.length > 0);
|
|
715
965
|
return normalized.length === 0 ? "none" : formatCompactList(normalized, 2, 64);
|
|
716
966
|
}
|
|
717
|
-
function
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
967
|
+
function formatStatusNullableNumber(value, unknown = "unknown") {
|
|
968
|
+
return value === null ? unknown : String(value);
|
|
969
|
+
}
|
|
970
|
+
function formatStatusNullableYesNo(value) {
|
|
971
|
+
return value === null ? "unknown" : yesNo(value);
|
|
972
|
+
}
|
|
973
|
+
function formatStatusNullableMilliseconds(value) {
|
|
974
|
+
return value === null ? "none" : `${value.toFixed(2)}ms`;
|
|
975
|
+
}
|
|
976
|
+
function formatStatusHotPathTiming(timing) {
|
|
977
|
+
return [
|
|
978
|
+
`hotPath=${formatStatusNullableMilliseconds(timing.totalMs)}`,
|
|
979
|
+
`route=${formatStatusNullableMilliseconds(timing.routeSelectionMs)}`,
|
|
980
|
+
`prompt=${formatStatusNullableMilliseconds(timing.promptAssemblyMs)}`,
|
|
981
|
+
`other=${formatStatusNullableMilliseconds(timing.otherMs)}`,
|
|
982
|
+
`background=${timing.backgroundWorkIncluded ? "included" : "excluded"}`
|
|
983
|
+
].join(" ");
|
|
984
|
+
}
|
|
985
|
+
function formatStatusObservedDeltaTransition(delta) {
|
|
986
|
+
if (delta.latestPackTransition === null) {
|
|
987
|
+
return "none";
|
|
723
988
|
}
|
|
724
|
-
return
|
|
989
|
+
return `${delta.latestPackTransition.kind}:${delta.latestPackTransition.fromPackId ?? "none"}->${delta.latestPackTransition.toPackId}`;
|
|
725
990
|
}
|
|
726
991
|
function buildCompactStatusHeader(status, report, options) {
|
|
727
992
|
const installHook = summarizeStatusInstallHook(options.openclawHome);
|
|
993
|
+
const hookLoad = summarizeStatusHookLoad(installHook, status);
|
|
728
994
|
const embeddings = summarizeStatusEmbeddings(report, options.providerConfig);
|
|
729
995
|
const localLlm = summarizeStatusLocalLlm(options.providerConfig);
|
|
996
|
+
const teacher = summarizeStatusTeacher(report, options.providerConfig, localLlm);
|
|
997
|
+
const embedder = summarizeStatusEmbedder(embeddings);
|
|
998
|
+
const routeFn = summarizeStatusRouteFn(status, report);
|
|
730
999
|
const alerts = summarizeStatusAlerts(report, options.providerConfig, embeddings, localLlm);
|
|
731
|
-
const promoted = status.brain.state === "pg_promoted_pack_authoritative" ? "yes" : "no";
|
|
732
1000
|
const liveModels = embeddings.models.length === 0 ? "none" : embeddings.models.join("|");
|
|
733
1001
|
return [
|
|
734
|
-
`
|
|
735
|
-
`
|
|
1002
|
+
`lifecycle attach=${status.attachment.state} learner=${yesNo(status.passiveLearning.learnerRunning)} watch=${summarizeStatusWatchState(status)} export=${status.passiveLearning.exportState} promote=${summarizeStatusPromotionState(status)} serve=${summarizeStatusServeReality(status)}`,
|
|
1003
|
+
`hook install=${hookLoad.installState} loadProof=${hookLoad.loadProof} detail=${hookLoad.detail}`,
|
|
1004
|
+
`passive firstExport=${yesNo(status.passiveLearning.firstExportOccurred)} backlog=${status.passiveLearning.backlogState} pending=${formatStatusNullableNumber(status.passiveLearning.pendingLive)}/${formatStatusNullableNumber(status.passiveLearning.pendingBackfill)}`,
|
|
1005
|
+
`serving pack=${status.passiveLearning.currentServingPackId ?? "none"} lastExport=${status.passiveLearning.lastExportAt ?? "none"} lastPromotion=${status.passiveLearning.lastPromotionAt ?? "none"}`,
|
|
1006
|
+
`timing ${formatStatusHotPathTiming(status.brainStatus.timing)}`,
|
|
1007
|
+
`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)}`,
|
|
1008
|
+
`changed ${status.passiveLearning.lastObservedDelta.explanation}`,
|
|
736
1009
|
`explain ${status.brain.summary}`,
|
|
1010
|
+
`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)}`,
|
|
1011
|
+
`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}`,
|
|
1012
|
+
`embedder model=${embedder.model} provisioned=${yesNo(embedder.provisioned)} live=${yesNo(embedder.live)} why=${embedder.detail}`,
|
|
1013
|
+
`routeFn available=${yesNo(routeFn.available)} freshness=${routeFn.freshness} trained=${routeFn.trainedAt ?? "none"} updated=${routeFn.updatedAt ?? "none"} used=${routeFn.usedAt ?? "none"} why=${routeFn.detail}`,
|
|
737
1014
|
`embeddings provider=${embeddings.provider} provisioned=${embeddings.provisionedState} live=${embeddings.liveState} stored=${embeddings.embeddedEntryCount ?? "none"}/${embeddings.totalEntryCount ?? "none"} models=${liveModels}`,
|
|
738
1015
|
`localLLM detected=${yesNo(localLlm.detected)} enabled=${yesNo(localLlm.enabled)} provider=${localLlm.provider} model=${localLlm.model}`,
|
|
739
1016
|
`alerts service_risk=${formatStatusAlertLine(alerts.serviceRisk)} degraded_brain=${formatStatusAlertLine(alerts.degradedBrain)} cosmetic_noise=${formatStatusAlertLine(alerts.cosmeticNoise)}`
|
|
740
1017
|
];
|
|
741
1018
|
}
|
|
742
1019
|
function formatCurrentProfileStatusSummary(status, report, targetInspection, options) {
|
|
1020
|
+
const embeddings = summarizeStatusEmbeddings(report, options.providerConfig);
|
|
1021
|
+
const localLlm = summarizeStatusLocalLlm(options.providerConfig);
|
|
1022
|
+
const liveModels = embeddings.models.length === 0 ? "none" : embeddings.models.join("|");
|
|
743
1023
|
const profileIdSuffix = status.profile.profileId === null ? "" : ` id=${status.profile.profileId}`;
|
|
744
1024
|
const targetLine = targetInspection === null
|
|
745
1025
|
? `target activation=${status.host.activationRoot} source=activation_root_only`
|
|
@@ -760,13 +1040,17 @@ function formatCurrentProfileStatusSummary(status, report, targetInspection, opt
|
|
|
760
1040
|
`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
1041
|
`decision ${status.brainStatus.structuralDecision.detail}`,
|
|
762
1042
|
`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"}`,
|
|
1043
|
+
`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}`,
|
|
1044
|
+
`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
1045
|
`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
1046
|
`labels ${formatLabelFlowSummary(report.labelFlow)}`,
|
|
765
|
-
`graph source=${report.graph.runtimePlasticitySource ?? "none"}
|
|
1047
|
+
`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
1048
|
`path ${formatLearningPathSummary(report.learningPath)}`,
|
|
767
1049
|
`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
|
-
`
|
|
1050
|
+
`teacherProof ${formatTeacherLoopSummary(report)}`,
|
|
1051
|
+
`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"}`,
|
|
1052
|
+
`embeddings provider=${embeddings.provider} provisioned=${embeddings.provisionedState} live=${embeddings.liveState} stored=${embeddings.embeddedEntryCount ?? "none"}/${embeddings.totalEntryCount ?? "none"} models=${liveModels}`,
|
|
1053
|
+
`localLLM detected=${yesNo(localLlm.detected)} enabled=${yesNo(localLlm.enabled)} provider=${localLlm.provider} model=${localLlm.model}`,
|
|
770
1054
|
`rollback ready=${yesNo(report.rollback.allowed)} state=${report.rollback.state} previous=${report.rollback.previousPackId ?? "none"}`,
|
|
771
1055
|
`proof lastExport=${status.brain.lastExportAt ?? "none"} lastLearningUpdate=${status.brain.lastLearningUpdateAt ?? "none"} lastPromotion=${status.brain.lastPromotionAt ?? "none"}`,
|
|
772
1056
|
`logs root=${status.brain.logRoot ?? "none"}`,
|
|
@@ -795,6 +1079,15 @@ function formatOpenClawTargetExplanation(inspection) {
|
|
|
795
1079
|
function buildInstallStatusCommand(activationRoot) {
|
|
796
1080
|
return `openclawbrain status --activation-root ${quoteShellArg(activationRoot)}`;
|
|
797
1081
|
}
|
|
1082
|
+
function buildLearnerServiceStatusCommand(activationRoot) {
|
|
1083
|
+
return `openclawbrain daemon status --activation-root ${quoteShellArg(activationRoot)}`;
|
|
1084
|
+
}
|
|
1085
|
+
function buildGatewayRestartCommand(profileId) {
|
|
1086
|
+
return `env -i HOME="$HOME" PATH="$PATH" openclaw --profile ${quoteShellArg(profileId)} gateway restart`;
|
|
1087
|
+
}
|
|
1088
|
+
function buildGatewayStatusCommand(profileId) {
|
|
1089
|
+
return `env -i HOME="$HOME" PATH="$PATH" openclaw --profile ${quoteShellArg(profileId)} gateway status`;
|
|
1090
|
+
}
|
|
798
1091
|
function buildInstallCommand(openclawHome) {
|
|
799
1092
|
return `openclawbrain install --openclaw-home ${quoteShellArg(openclawHome)}`;
|
|
800
1093
|
}
|
|
@@ -978,6 +1271,12 @@ function writeInstallProviderDefaults(parsed) {
|
|
|
978
1271
|
: "Teacher: no compatible local Ollama model detected; watch stays heuristic unless explicitly overridden"
|
|
979
1272
|
};
|
|
980
1273
|
}
|
|
1274
|
+
function shouldWriteProfileHookProviderDefaults(parsed, activationPlan, isInstall) {
|
|
1275
|
+
if (isInstall || activationPlan.action === "bootstrap") {
|
|
1276
|
+
return true;
|
|
1277
|
+
}
|
|
1278
|
+
return !existsSync(resolveOpenClawBrainProviderDefaultsPath(parsed.activationRoot));
|
|
1279
|
+
}
|
|
981
1280
|
function buildInstallBrainFeedbackSummary(input) {
|
|
982
1281
|
const providerDefaultsPath = resolveOpenClawBrainProviderDefaultsPath(input.parsed.activationRoot);
|
|
983
1282
|
const embedderState = input.embedderProvision === null ? "unchanged" : input.embedderProvision.state;
|
|
@@ -985,15 +1284,61 @@ function buildInstallBrainFeedbackSummary(input) {
|
|
|
985
1284
|
const teacherProvider = teacherDefaults?.provider ?? "unknown";
|
|
986
1285
|
const teacherModel = teacherDefaults?.model ?? null;
|
|
987
1286
|
const detectedLocalLlm = teacherDefaults?.detectedLocally ?? null;
|
|
1287
|
+
const profileName = input.targetInspection.profileId;
|
|
1288
|
+
const profileSource = input.targetInspection.profileSource;
|
|
1289
|
+
const casingGuidance = profileName === null
|
|
1290
|
+
? "Exact OpenClaw --profile casing is unresolved here because this target stays on the host-selected current_profile boundary."
|
|
1291
|
+
: `Use the exact OpenClaw profile casing shown here for host-side restart/status commands: ${quoteShellArg(profileName)}.`;
|
|
1292
|
+
const attachment = input.parsed.shared
|
|
1293
|
+
? {
|
|
1294
|
+
policy: "shared",
|
|
1295
|
+
activationRootMode: "shared_root_declared",
|
|
1296
|
+
sameGatewayProof: "not_checked_in",
|
|
1297
|
+
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."
|
|
1298
|
+
}
|
|
1299
|
+
: {
|
|
1300
|
+
policy: "dedicated",
|
|
1301
|
+
activationRootMode: "dedicated_per_profile",
|
|
1302
|
+
sameGatewayProof: "not_applicable",
|
|
1303
|
+
detail: "Dedicated activation root for this profile/home boundary."
|
|
1304
|
+
};
|
|
1305
|
+
const restart = profileName === null
|
|
1306
|
+
? {
|
|
1307
|
+
exactProfile: false,
|
|
1308
|
+
profile: null,
|
|
1309
|
+
profileSource,
|
|
1310
|
+
guidance: `Operator-owned restart step: this install did not infer an exact --profile token from ${shortenPath(input.targetInspection.openclawHome)}. ` +
|
|
1311
|
+
"If immediate load matters, restart the host-selected current_profile from OpenClaw itself; otherwise the next natural launch will pick up the hook.",
|
|
1312
|
+
restartCommand: null,
|
|
1313
|
+
gatewayStatusCommand: null
|
|
1314
|
+
}
|
|
1315
|
+
: {
|
|
1316
|
+
exactProfile: true,
|
|
1317
|
+
profile: profileName,
|
|
1318
|
+
profileSource,
|
|
1319
|
+
guidance: `Operator-owned restart step: if immediate load matters and profile ${quoteShellArg(profileName)} is already running, run ${buildGatewayRestartCommand(profileName)}. ` +
|
|
1320
|
+
`If it is stopped, the next launch of profile ${quoteShellArg(profileName)} will pick up the hook. ${casingGuidance}`,
|
|
1321
|
+
restartCommand: buildGatewayRestartCommand(profileName),
|
|
1322
|
+
gatewayStatusCommand: buildGatewayStatusCommand(profileName)
|
|
1323
|
+
};
|
|
988
1324
|
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
|
-
:
|
|
1325
|
+
? `hook written, activation root ready, seed/current-profile attach bootstrapped, learner service ${input.learnerService.state}, provider defaults ${input.providerDefaults === null ? "kept" : "written"}`
|
|
1326
|
+
: `hook written, activation root kept, active pack ${input.activationPlan.activePackId ?? "unknown"} preserved, learner service ${input.learnerService.state}${input.providerDefaults === null ? "" : ", provider defaults written"}`;
|
|
1327
|
+
const notYetProved = input.learnerService.state === "deferred"
|
|
1328
|
+
? `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`
|
|
1329
|
+
: input.activationPlan.action === "bootstrap"
|
|
1330
|
+
? `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`
|
|
1331
|
+
: `Passive learning is wired for this activation root, but this ${input.parsed.command} run does not itself prove live startup/load after restart`;
|
|
994
1332
|
return {
|
|
995
1333
|
hookPath: input.extensionDir,
|
|
996
1334
|
providerDefaultsPath,
|
|
1335
|
+
profile: {
|
|
1336
|
+
exactProfileName: profileName,
|
|
1337
|
+
profileSource,
|
|
1338
|
+
casingGuidance
|
|
1339
|
+
},
|
|
1340
|
+
attachment,
|
|
1341
|
+
restart,
|
|
997
1342
|
embedder: {
|
|
998
1343
|
provider: "ollama",
|
|
999
1344
|
model: DEFAULT_OLLAMA_EMBEDDING_MODEL,
|
|
@@ -1004,6 +1349,14 @@ function buildInstallBrainFeedbackSummary(input) {
|
|
|
1004
1349
|
model: teacherModel,
|
|
1005
1350
|
detectedLocalLlm
|
|
1006
1351
|
},
|
|
1352
|
+
learnerService: {
|
|
1353
|
+
state: input.learnerService.state,
|
|
1354
|
+
detail: input.learnerService.detail,
|
|
1355
|
+
plistPath: input.learnerService.plistPath,
|
|
1356
|
+
logPath: input.learnerService.logPath,
|
|
1357
|
+
configuredActivationRoot: input.learnerService.configuredActivationRoot,
|
|
1358
|
+
matchesRequestedActivationRoot: input.learnerService.matchesRequestedActivationRoot
|
|
1359
|
+
},
|
|
1007
1360
|
startup: {
|
|
1008
1361
|
token: "BRAIN_NOT_YET_LOADED",
|
|
1009
1362
|
proof: "restart_required"
|
|
@@ -1012,19 +1365,28 @@ function buildInstallBrainFeedbackSummary(input) {
|
|
|
1012
1365
|
notYetProved,
|
|
1013
1366
|
lines: [
|
|
1014
1367
|
`target ${formatOpenClawTargetLine(input.targetInspection)} source=${formatInstallOpenClawHomeSource(input.parsed.openclawHomeSource)}`,
|
|
1368
|
+
profileName === null
|
|
1369
|
+
? "profile exactName=unresolved selector=current_profile casing=not_available"
|
|
1370
|
+
: `profile exactName=${quoteShellArg(profileName)} source=${profileSource} casing=preserved`,
|
|
1015
1371
|
`hook written=${shortenPath(input.extensionDir)}`,
|
|
1016
1372
|
`activation root=${shortenPath(input.parsed.activationRoot)} source=${formatInstallActivationRootSource(input.parsed.activationRootSource)}`,
|
|
1373
|
+
`attachment policy=${attachment.policy} rootMode=${attachment.activationRootMode} sameGatewayProof=${attachment.sameGatewayProof} detail=${attachment.detail}`,
|
|
1017
1374
|
`defaults provider-defaults=${shortenPath(providerDefaultsPath)} state=${input.providerDefaults === null ? "unchanged" : "written"}`,
|
|
1018
1375
|
`embedder provider=ollama model=${DEFAULT_OLLAMA_EMBEDDING_MODEL} state=${embedderState}`,
|
|
1019
1376
|
`teacher provider=${teacherProvider} model=${teacherModel ?? "none"} localLLM=${detectedLocalLlm === null ? "unknown" : yesNo(detectedLocalLlm)}`,
|
|
1377
|
+
`learner state=${input.learnerService.state} detail=${input.learnerService.detail}`,
|
|
1378
|
+
`restart operator=manual exactProfile=${yesNo(restart.exactProfile)} command=${restart.restartCommand ?? "unavailable"}`,
|
|
1020
1379
|
"startup BRAIN_NOT_YET_LOADED proof=restart_required",
|
|
1021
1380
|
`provedNow ${provedNow}`,
|
|
1022
1381
|
`notYet ${notYetProved}`
|
|
1023
1382
|
]
|
|
1024
1383
|
};
|
|
1025
1384
|
}
|
|
1026
|
-
function buildInstallReloadGuidance() {
|
|
1027
|
-
|
|
1385
|
+
function buildInstallReloadGuidance(input) {
|
|
1386
|
+
if (input.targetInspection.profileId === null) {
|
|
1387
|
+
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.`;
|
|
1388
|
+
}
|
|
1389
|
+
return `Restart now if immediate load matters: ${buildGatewayRestartCommand(input.targetInspection.profileId)}`;
|
|
1028
1390
|
}
|
|
1029
1391
|
const LEGACY_PROFILE_NOTE_FILENAMES = ["BRAIN.md", "brain.md"];
|
|
1030
1392
|
const LEGACY_BRAIN_AGENTS_LINE = "5. Read `BRAIN.md` — your learning brain context";
|
|
@@ -1108,7 +1470,7 @@ function buildCleanupRestartGuidance(restart) {
|
|
|
1108
1470
|
}
|
|
1109
1471
|
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
1472
|
}
|
|
1111
|
-
function buildStatusNextStep(status, report) {
|
|
1473
|
+
function buildStatusNextStep(status, report, options) {
|
|
1112
1474
|
const activationRootArg = quoteShellArg(status.host.activationRoot);
|
|
1113
1475
|
if (status.brainStatus.activationState === "broken_install") {
|
|
1114
1476
|
return "Repair or replace the activation root before trusting serve-path status again.";
|
|
@@ -1119,9 +1481,18 @@ function buildStatusNextStep(status, report) {
|
|
|
1119
1481
|
if (status.brainStatus.status === "fail") {
|
|
1120
1482
|
return `Run \`openclawbrain status --activation-root ${activationRootArg} --detailed\` before changing lifecycle state so the serve-path failure is explicit.`;
|
|
1121
1483
|
}
|
|
1484
|
+
if (options.openclawHome !== null && options.installHook.state === "not_installed") {
|
|
1485
|
+
return `Run \`${buildInstallCommand(options.openclawHome)}\` before expecting this OpenClaw home to load the brain hook.`;
|
|
1486
|
+
}
|
|
1122
1487
|
if (status.brainStatus.awaitingFirstExport) {
|
|
1123
1488
|
return `Let the attached OpenClaw profile emit a real export, then rerun \`openclawbrain status --activation-root ${activationRootArg}\`.`;
|
|
1124
1489
|
}
|
|
1490
|
+
if (options.openclawHome === null) {
|
|
1491
|
+
return `Pin \`--openclaw-home <path>\` when you need exact hook-install truth; activation-root-only status only proves this root's serve-path state.`;
|
|
1492
|
+
}
|
|
1493
|
+
if (options.installHook.state === "installed" && status.brainStatus.serveState === "serving_active_pack") {
|
|
1494
|
+
return "Check the OpenClaw startup log for the `[openclawbrain] BRAIN LOADED` breadcrumb when you need live hook-load proof.";
|
|
1495
|
+
}
|
|
1125
1496
|
if (report.learning.warningStates.includes("principal_live_backlog") ||
|
|
1126
1497
|
report.learning.warningStates.includes("active_pack_behind_latest_principal")) {
|
|
1127
1498
|
return "A newer principal correction is still pending promotion; keep the current pack conservative until learner promotion lands.";
|
|
@@ -1139,7 +1510,10 @@ function formatHumanFriendlyStatus(status, report, targetInspection, options) {
|
|
|
1139
1510
|
`target ${formatOpenClawTargetLine(targetInspection)}`,
|
|
1140
1511
|
`preflight ${formatOpenClawTargetExplanation(targetInspection)}`
|
|
1141
1512
|
]),
|
|
1142
|
-
`next ${buildStatusNextStep(status, report
|
|
1513
|
+
`next ${buildStatusNextStep(status, report, {
|
|
1514
|
+
openclawHome: options.openclawHome,
|
|
1515
|
+
installHook: summarizeStatusInstallHook(options.openclawHome)
|
|
1516
|
+
})}`
|
|
1143
1517
|
];
|
|
1144
1518
|
return lines.join("\n");
|
|
1145
1519
|
}
|
|
@@ -2139,7 +2513,7 @@ function buildExtensionIndexTs(activationRoot) {
|
|
|
2139
2513
|
function buildExtensionPackageJson() {
|
|
2140
2514
|
const packageMetadata = readOpenClawPackageMetadata();
|
|
2141
2515
|
return JSON.stringify({
|
|
2142
|
-
name: "openclawbrain
|
|
2516
|
+
name: "openclawbrain",
|
|
2143
2517
|
version: packageMetadata.version,
|
|
2144
2518
|
private: true,
|
|
2145
2519
|
type: "module",
|
|
@@ -2242,6 +2616,56 @@ function buildHistoryEntry(record, slot, isActive) {
|
|
|
2242
2616
|
current: isActive
|
|
2243
2617
|
};
|
|
2244
2618
|
}
|
|
2619
|
+
function ensureLifecycleLearnerService(activationRoot) {
|
|
2620
|
+
const outcome = ensureManagedLearnerServiceForActivationRoot(activationRoot);
|
|
2621
|
+
return {
|
|
2622
|
+
state: outcome.state,
|
|
2623
|
+
detail: outcome.detail,
|
|
2624
|
+
plistPath: outcome.inspection.plistPath,
|
|
2625
|
+
logPath: outcome.inspection.logPath,
|
|
2626
|
+
configuredActivationRoot: outcome.inspection.configuredActivationRoot,
|
|
2627
|
+
matchesRequestedActivationRoot: outcome.inspection.matchesRequestedActivationRoot
|
|
2628
|
+
};
|
|
2629
|
+
}
|
|
2630
|
+
function resolveCleanupLearnerServiceOutcome(activationRoot, openclawHome) {
|
|
2631
|
+
if (activationRoot === null) {
|
|
2632
|
+
return {
|
|
2633
|
+
state: "unresolved",
|
|
2634
|
+
detail: "Learner service preservation is unresolved because the activation root could not be resolved from the installed profile hook.",
|
|
2635
|
+
plistPath: null,
|
|
2636
|
+
logPath: null,
|
|
2637
|
+
configuredActivationRoot: null,
|
|
2638
|
+
matchesRequestedActivationRoot: null
|
|
2639
|
+
};
|
|
2640
|
+
}
|
|
2641
|
+
const remainingProfiles = findOtherInstalledHookReferencesForActivationRoot({
|
|
2642
|
+
activationRoot,
|
|
2643
|
+
excludingOpenClawHome: openclawHome
|
|
2644
|
+
});
|
|
2645
|
+
if (remainingProfiles.length > 0) {
|
|
2646
|
+
const inspection = inspectManagedLearnerService(activationRoot);
|
|
2647
|
+
const attachedProfiles = remainingProfiles
|
|
2648
|
+
.map(({ openclawHome: profileHome }) => shortenPath(path.resolve(profileHome)))
|
|
2649
|
+
.join(", ");
|
|
2650
|
+
return {
|
|
2651
|
+
state: "preserved",
|
|
2652
|
+
detail: `Preserved the background learner service for ${path.resolve(activationRoot)} because other attached OpenClaw profiles still share this activation root: ${attachedProfiles}.`,
|
|
2653
|
+
plistPath: inspection.plistPath,
|
|
2654
|
+
logPath: inspection.logPath,
|
|
2655
|
+
configuredActivationRoot: inspection.configuredActivationRoot,
|
|
2656
|
+
matchesRequestedActivationRoot: inspection.matchesRequestedActivationRoot
|
|
2657
|
+
};
|
|
2658
|
+
}
|
|
2659
|
+
const outcome = removeManagedLearnerServiceForActivationRoot(activationRoot);
|
|
2660
|
+
return {
|
|
2661
|
+
state: outcome.state,
|
|
2662
|
+
detail: outcome.detail,
|
|
2663
|
+
plistPath: outcome.inspection.plistPath,
|
|
2664
|
+
logPath: outcome.inspection.logPath,
|
|
2665
|
+
configuredActivationRoot: outcome.inspection.configuredActivationRoot,
|
|
2666
|
+
matchesRequestedActivationRoot: outcome.inspection.matchesRequestedActivationRoot
|
|
2667
|
+
};
|
|
2668
|
+
}
|
|
2245
2669
|
function formatInspectionFindings(findings) {
|
|
2246
2670
|
return findings.join("; ");
|
|
2247
2671
|
}
|
|
@@ -2438,11 +2862,11 @@ function runProfileHookAttachCommand(parsed) {
|
|
|
2438
2862
|
}
|
|
2439
2863
|
steps.push(activationPlan.inspectionStep);
|
|
2440
2864
|
// 5. Persist install-written local provider defaults so watch/learning surfaces do not depend on gateway env wiring.
|
|
2441
|
-
const providerDefaults =
|
|
2865
|
+
const providerDefaults = shouldWriteProfileHookProviderDefaults(parsed, activationPlan, isInstall)
|
|
2442
2866
|
? writeInstallProviderDefaults(parsed)
|
|
2443
2867
|
: null;
|
|
2444
2868
|
if (providerDefaults === null) {
|
|
2445
|
-
steps.push("
|
|
2869
|
+
steps.push("Preserved existing provider-defaults.json because explicit attach is reusing existing activation data.");
|
|
2446
2870
|
}
|
|
2447
2871
|
else {
|
|
2448
2872
|
steps.push(providerDefaults.detail);
|
|
@@ -2528,16 +2952,36 @@ function runProfileHookAttachCommand(parsed) {
|
|
|
2528
2952
|
const manifestPath = path.join(extensionDir, "openclaw.plugin.json");
|
|
2529
2953
|
writeFileSync(manifestPath, buildExtensionPluginManifest(), "utf8");
|
|
2530
2954
|
steps.push(`Wrote manifest: ${manifestPath}`);
|
|
2531
|
-
const
|
|
2955
|
+
const learnerService = ensureLifecycleLearnerService(parsed.activationRoot);
|
|
2956
|
+
steps.push(learnerService.detail);
|
|
2957
|
+
const brainFeedback = buildInstallBrainFeedbackSummary({
|
|
2958
|
+
parsed,
|
|
2959
|
+
targetInspection,
|
|
2960
|
+
extensionDir,
|
|
2961
|
+
activationPlan,
|
|
2962
|
+
learnerService,
|
|
2963
|
+
embedderProvision,
|
|
2964
|
+
providerDefaults
|
|
2965
|
+
});
|
|
2966
|
+
const restartGuidance = buildInstallReloadGuidance({
|
|
2967
|
+
targetInspection
|
|
2968
|
+
});
|
|
2532
2969
|
const nextSteps = [
|
|
2533
2970
|
restartGuidance,
|
|
2971
|
+
brainFeedback.restart.gatewayStatusCommand === null
|
|
2972
|
+
? null
|
|
2973
|
+
: `Confirm gateway after restart: ${brainFeedback.restart.gatewayStatusCommand}`,
|
|
2534
2974
|
`Check status: ${buildInstallStatusCommand(parsed.activationRoot)}`,
|
|
2975
|
+
`Check learner service: ${buildLearnerServiceStatusCommand(parsed.activationRoot)}`,
|
|
2535
2976
|
embedderProvision !== null && embedderProvision.state === "skipped"
|
|
2536
2977
|
? `Provision default embedder later: ${buildInstallEmbedderProvisionCommand(embedderProvision.baseUrl, embedderProvision.model)}`
|
|
2537
2978
|
: null
|
|
2538
2979
|
].filter((step) => step !== null);
|
|
2539
2980
|
const preflightSummary = [
|
|
2540
2981
|
`Hook: installed at ${shortenPath(extensionDir)}`,
|
|
2982
|
+
parsed.shared
|
|
2983
|
+
? "Attachment policy: shared activation root declared; same-gateway many-profile load/serve proof is still not checked in"
|
|
2984
|
+
: "Attachment policy: dedicated activation root for this profile/home boundary",
|
|
2541
2985
|
activationPlan.action === "bootstrap"
|
|
2542
2986
|
? "Attachment: seed/current-profile attach created; restart plus status will prove later serve-path use"
|
|
2543
2987
|
: `Attachment: existing active pack ${activationPlan.activePackId} kept in place; restart plus status will prove later serve-path use`,
|
|
@@ -2546,6 +2990,7 @@ function runProfileHookAttachCommand(parsed) {
|
|
|
2546
2990
|
: embedderProvision.state === "ensured"
|
|
2547
2991
|
? `Embedder: default Ollama model ${embedderProvision.model} was ensured before bootstrap`
|
|
2548
2992
|
: `Embedder: default Ollama model ${embedderProvision.model} was intentionally skipped`,
|
|
2993
|
+
`Learner: background service ${learnerService.state} for the exact activation root/profile boundary`,
|
|
2549
2994
|
`Serve path: install alone does not prove serving; restart the profile and run ${buildInstallStatusCommand(parsed.activationRoot)}`
|
|
2550
2995
|
];
|
|
2551
2996
|
const lifecycleSummary = [
|
|
@@ -2554,7 +2999,11 @@ function runProfileHookAttachCommand(parsed) {
|
|
|
2554
2999
|
: "Lifecycle mode: attach (explicit reattach/manual profile hookup)",
|
|
2555
3000
|
`OpenClaw target: ${shortenPath(parsed.openclawHome)} (${formatInstallOpenClawHomeSource(parsed.openclawHomeSource)})`,
|
|
2556
3001
|
`Detected layout: ${formatOpenClawTargetExplanation(targetInspection)}`,
|
|
3002
|
+
brainFeedback.profile.exactProfileName === null
|
|
3003
|
+
? "Profile token: current_profile only; this install did not infer an exact --profile token"
|
|
3004
|
+
: `Profile token: use exact OpenClaw profile casing ${quoteShellArg(brainFeedback.profile.exactProfileName)} for host-side restart/status commands`,
|
|
2557
3005
|
`Activation root: ${shortenPath(parsed.activationRoot)} (${formatInstallActivationRootSource(parsed.activationRootSource)})`,
|
|
3006
|
+
`Attachment policy: ${brainFeedback.attachment.policy} (${brainFeedback.attachment.detail})`,
|
|
2558
3007
|
`Workspace ID: ${parsed.workspaceId} (${formatInstallWorkspaceIdSource(parsed.workspaceIdSource)})`,
|
|
2559
3008
|
embedderProvision === null
|
|
2560
3009
|
? "Embedder: unchanged because no bootstrap was needed"
|
|
@@ -2563,6 +3012,7 @@ function runProfileHookAttachCommand(parsed) {
|
|
|
2563
3012
|
: `Embedder: skipped default Ollama model ${embedderProvision.model} via ${parsed.skipEmbedderProvisionSource === "flag" ? "--skip-embedder-provision" : OPENCLAWBRAIN_INSTALL_SKIP_EMBEDDER_PROVISION_ENV}`,
|
|
2564
3013
|
...(providerDefaults === null ? [] : [`${providerDefaults.lifecycleSummary} (${shortenPath(providerDefaults.path)})`]),
|
|
2565
3014
|
`Profile hook: installed at ${shortenPath(extensionDir)}`,
|
|
3015
|
+
`Learner service: ${learnerService.state} for ${shortenPath(parsed.activationRoot)}`,
|
|
2566
3016
|
activationPlan.resolution === "new_root"
|
|
2567
3017
|
? `Activation data: initialized at ${shortenPath(parsed.activationRoot)}`
|
|
2568
3018
|
: activationPlan.resolution === "missing_pointers"
|
|
@@ -2580,14 +3030,6 @@ function runProfileHookAttachCommand(parsed) {
|
|
|
2580
3030
|
? `Install: kept healthy active pack ${activationPlan.activePackId} in place`
|
|
2581
3031
|
: `Attach: rewired the profile hook to healthy active pack ${activationPlan.activePackId}`
|
|
2582
3032
|
];
|
|
2583
|
-
const brainFeedback = buildInstallBrainFeedbackSummary({
|
|
2584
|
-
parsed,
|
|
2585
|
-
targetInspection,
|
|
2586
|
-
extensionDir,
|
|
2587
|
-
activationPlan,
|
|
2588
|
-
embedderProvision,
|
|
2589
|
-
providerDefaults
|
|
2590
|
-
});
|
|
2591
3033
|
// 9. Print summary
|
|
2592
3034
|
if (parsed.json) {
|
|
2593
3035
|
console.log(JSON.stringify({
|
|
@@ -2642,11 +3084,16 @@ function runProfileHookAttachCommand(parsed) {
|
|
|
2642
3084
|
teacherBaseUrl: providerDefaults.defaults.teacherBaseUrl ?? null,
|
|
2643
3085
|
embedderBaseUrl: providerDefaults.defaults.embedderBaseUrl ?? null
|
|
2644
3086
|
},
|
|
3087
|
+
learnerService,
|
|
2645
3088
|
brainFeedback: {
|
|
2646
3089
|
hookPath: brainFeedback.hookPath,
|
|
2647
3090
|
providerDefaultsPath: brainFeedback.providerDefaultsPath,
|
|
3091
|
+
profile: brainFeedback.profile,
|
|
3092
|
+
attachment: brainFeedback.attachment,
|
|
3093
|
+
restart: brainFeedback.restart,
|
|
2648
3094
|
embedder: brainFeedback.embedder,
|
|
2649
3095
|
teacher: brainFeedback.teacher,
|
|
3096
|
+
learnerService: brainFeedback.learnerService,
|
|
2650
3097
|
startup: brainFeedback.startup,
|
|
2651
3098
|
provedNow: brainFeedback.provedNow,
|
|
2652
3099
|
notYetProved: brainFeedback.notYetProved,
|
|
@@ -2666,8 +3113,12 @@ function runProfileHookAttachCommand(parsed) {
|
|
|
2666
3113
|
for (const line of brainFeedback.lines) {
|
|
2667
3114
|
console.log(` ${line}`);
|
|
2668
3115
|
}
|
|
2669
|
-
console.log(`
|
|
3116
|
+
console.log(`Restart: ${restartGuidance}`);
|
|
3117
|
+
if (brainFeedback.restart.gatewayStatusCommand !== null) {
|
|
3118
|
+
console.log(`Gateway: Confirm OpenClaw after restart: ${brainFeedback.restart.gatewayStatusCommand}`);
|
|
3119
|
+
}
|
|
2670
3120
|
console.log(`Check: ${buildInstallStatusCommand(parsed.activationRoot)}`);
|
|
3121
|
+
console.log(`Learner: ${buildLearnerServiceStatusCommand(parsed.activationRoot)}`);
|
|
2671
3122
|
if (embedderProvision !== null && embedderProvision.state === "skipped") {
|
|
2672
3123
|
console.log(`Embedder: ${buildInstallEmbedderProvisionCommand(embedderProvision.baseUrl, embedderProvision.model)}`);
|
|
2673
3124
|
}
|
|
@@ -2689,6 +3140,85 @@ function validateOpenClawHome(openclawHome) {
|
|
|
2689
3140
|
throw new Error(`openclaw.json not found in ${openclawHome}`);
|
|
2690
3141
|
}
|
|
2691
3142
|
}
|
|
3143
|
+
function readJsonObjectRecord(value) {
|
|
3144
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
3145
|
+
return null;
|
|
3146
|
+
}
|
|
3147
|
+
return value;
|
|
3148
|
+
}
|
|
3149
|
+
function readOpenClawJsonConfig(openclawHome) {
|
|
3150
|
+
const openclawJsonPath = path.join(openclawHome, "openclaw.json");
|
|
3151
|
+
let parsed;
|
|
3152
|
+
try {
|
|
3153
|
+
parsed = JSON.parse(readFileSync(openclawJsonPath, "utf8"));
|
|
3154
|
+
}
|
|
3155
|
+
catch (error) {
|
|
3156
|
+
throw new Error(`Failed to read ${openclawJsonPath}: ${toErrorMessage(error)}`);
|
|
3157
|
+
}
|
|
3158
|
+
const config = readJsonObjectRecord(parsed);
|
|
3159
|
+
if (config === null) {
|
|
3160
|
+
throw new Error(`Failed to read ${openclawJsonPath}: openclaw.json must contain a top-level object`);
|
|
3161
|
+
}
|
|
3162
|
+
return {
|
|
3163
|
+
path: openclawJsonPath,
|
|
3164
|
+
config
|
|
3165
|
+
};
|
|
3166
|
+
}
|
|
3167
|
+
function scrubOpenClawBrainPluginConfig(openclawHome) {
|
|
3168
|
+
const { path: openclawJsonPath, config } = readOpenClawJsonConfig(openclawHome);
|
|
3169
|
+
const plugins = readJsonObjectRecord(config.plugins);
|
|
3170
|
+
if (plugins === null) {
|
|
3171
|
+
return {
|
|
3172
|
+
path: openclawJsonPath,
|
|
3173
|
+
changed: false,
|
|
3174
|
+
detail: `No stale openclawbrain plugin config found in ${openclawJsonPath}`
|
|
3175
|
+
};
|
|
3176
|
+
}
|
|
3177
|
+
const changes = [];
|
|
3178
|
+
let changed = false;
|
|
3179
|
+
if (Array.isArray(plugins.allow)) {
|
|
3180
|
+
const filteredAllow = plugins.allow.filter((entry) => entry !== "openclawbrain");
|
|
3181
|
+
if (filteredAllow.length !== plugins.allow.length) {
|
|
3182
|
+
changed = true;
|
|
3183
|
+
changes.push("removed plugins.allow entry");
|
|
3184
|
+
if (filteredAllow.length > 0) {
|
|
3185
|
+
plugins.allow = filteredAllow;
|
|
3186
|
+
}
|
|
3187
|
+
else {
|
|
3188
|
+
delete plugins.allow;
|
|
3189
|
+
}
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
const entries = readJsonObjectRecord(plugins.entries);
|
|
3193
|
+
if (entries !== null && Object.prototype.hasOwnProperty.call(entries, "openclawbrain")) {
|
|
3194
|
+
delete entries.openclawbrain;
|
|
3195
|
+
changed = true;
|
|
3196
|
+
changes.push("removed plugins.entries.openclawbrain");
|
|
3197
|
+
}
|
|
3198
|
+
if (entries !== null && Object.keys(entries).length === 0 && Object.prototype.hasOwnProperty.call(plugins, "entries")) {
|
|
3199
|
+
delete plugins.entries;
|
|
3200
|
+
changed = true;
|
|
3201
|
+
changes.push("removed empty plugins.entries container");
|
|
3202
|
+
}
|
|
3203
|
+
if (Object.keys(plugins).length === 0 && Object.prototype.hasOwnProperty.call(config, "plugins")) {
|
|
3204
|
+
delete config.plugins;
|
|
3205
|
+
changed = true;
|
|
3206
|
+
changes.push("removed empty plugins container");
|
|
3207
|
+
}
|
|
3208
|
+
if (changed) {
|
|
3209
|
+
writeFileSync(openclawJsonPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
3210
|
+
return {
|
|
3211
|
+
path: openclawJsonPath,
|
|
3212
|
+
changed: true,
|
|
3213
|
+
detail: `Scrubbed stale openclawbrain plugin config in ${openclawJsonPath}: ${changes.join(", ")}`
|
|
3214
|
+
};
|
|
3215
|
+
}
|
|
3216
|
+
return {
|
|
3217
|
+
path: openclawJsonPath,
|
|
3218
|
+
changed: false,
|
|
3219
|
+
detail: `No stale openclawbrain plugin config found in ${openclawJsonPath}`
|
|
3220
|
+
};
|
|
3221
|
+
}
|
|
2692
3222
|
function resolveCleanupActivationRoot(openclawHome, explicitActivationRoot) {
|
|
2693
3223
|
if (explicitActivationRoot !== null) {
|
|
2694
3224
|
return path.resolve(explicitActivationRoot);
|
|
@@ -2732,6 +3262,8 @@ function runDetachCommand(parsed) {
|
|
|
2732
3262
|
const targetInspection = inspectOpenClawHome(parsed.openclawHome);
|
|
2733
3263
|
steps.push(`Detected layout: ${formatOpenClawTargetExplanation(targetInspection)}`);
|
|
2734
3264
|
const activationRoot = resolveCleanupActivationRoot(parsed.openclawHome, parsed.activationRoot);
|
|
3265
|
+
const learnerService = resolveCleanupLearnerServiceOutcome(activationRoot, parsed.openclawHome);
|
|
3266
|
+
const pluginConfigCleanup = scrubOpenClawBrainPluginConfig(parsed.openclawHome);
|
|
2735
3267
|
const extensionDir = removeProfileHookup(parsed.openclawHome, steps);
|
|
2736
3268
|
const legacyResidue = removeLegacyProfileResidue(parsed.openclawHome);
|
|
2737
3269
|
const activationData = summarizeKeptActivationData(activationRoot);
|
|
@@ -2739,14 +3271,17 @@ function runDetachCommand(parsed) {
|
|
|
2739
3271
|
const nextSteps = [
|
|
2740
3272
|
restartGuidance,
|
|
2741
3273
|
activationRoot === null ? null : `Inspect preserved data: ${buildInstallStatusCommand(activationRoot)}`,
|
|
3274
|
+
activationRoot === null ? null : `Inspect learner service: ${buildLearnerServiceStatusCommand(activationRoot)}`,
|
|
2742
3275
|
`Reattach later: ${buildAttachCommand(parsed.openclawHome, activationRoot)}`
|
|
2743
3276
|
].filter((step) => step !== null);
|
|
3277
|
+
steps.push(pluginConfigCleanup.detail);
|
|
2744
3278
|
if (legacyResidue.removedNotes.length > 0) {
|
|
2745
3279
|
steps.push(`Removed legacy profile notes: ${legacyResidue.removedNotes.map((notePath) => shortenPath(notePath)).join(", ")}`);
|
|
2746
3280
|
}
|
|
2747
3281
|
if (legacyResidue.updatedAgents.length > 0) {
|
|
2748
3282
|
steps.push(`Removed legacy AGENTS.md brain references: ${legacyResidue.updatedAgents.map((agentsPath) => shortenPath(agentsPath)).join(", ")}`);
|
|
2749
3283
|
}
|
|
3284
|
+
steps.push(learnerService.detail);
|
|
2750
3285
|
steps.push(activationData.activationDataDetail);
|
|
2751
3286
|
steps.push("Detach only removes the OpenClaw profile hook; it does not delete OpenClawBrain data.");
|
|
2752
3287
|
if (parsed.json) {
|
|
@@ -2764,6 +3299,8 @@ function runDetachCommand(parsed) {
|
|
|
2764
3299
|
activationRoot,
|
|
2765
3300
|
dataAction: "kept",
|
|
2766
3301
|
activationDataState: activationData.activationDataState,
|
|
3302
|
+
pluginConfigCleanup,
|
|
3303
|
+
learnerService,
|
|
2767
3304
|
removedLegacyNotes: legacyResidue.removedNotes,
|
|
2768
3305
|
updatedAgents: legacyResidue.updatedAgents,
|
|
2769
3306
|
restartMode: parsed.restart,
|
|
@@ -2786,9 +3323,12 @@ function runDetachCommand(parsed) {
|
|
|
2786
3323
|
else {
|
|
2787
3324
|
console.log("Brain data: preserved, but the activation root could not be resolved from the removed hook.");
|
|
2788
3325
|
}
|
|
3326
|
+
console.log(`Config: ${pluginConfigCleanup.detail}`);
|
|
3327
|
+
console.log(`Learner: ${learnerService.detail}`);
|
|
2789
3328
|
console.log(`Next: ${restartGuidance}`);
|
|
2790
3329
|
if (activationRoot !== null) {
|
|
2791
3330
|
console.log(`Check: ${buildInstallStatusCommand(activationRoot)}`);
|
|
3331
|
+
console.log(`Service: ${buildLearnerServiceStatusCommand(activationRoot)}`);
|
|
2792
3332
|
}
|
|
2793
3333
|
console.log(`Reattach: ${buildAttachCommand(parsed.openclawHome, activationRoot)}`);
|
|
2794
3334
|
}
|
|
@@ -2800,6 +3340,20 @@ function runUninstallCommand(parsed) {
|
|
|
2800
3340
|
const targetInspection = inspectOpenClawHome(parsed.openclawHome);
|
|
2801
3341
|
steps.push(`Detected layout: ${formatOpenClawTargetExplanation(targetInspection)}`);
|
|
2802
3342
|
const activationRoot = resolveCleanupActivationRoot(parsed.openclawHome, parsed.activationRoot);
|
|
3343
|
+
if (parsed.dataMode === "purge" && activationRoot !== null) {
|
|
3344
|
+
assertActivationRootPurgeIsNotShared({
|
|
3345
|
+
activationRoot,
|
|
3346
|
+
openclawHome: parsed.openclawHome
|
|
3347
|
+
});
|
|
3348
|
+
}
|
|
3349
|
+
const learnerService = resolveCleanupLearnerServiceOutcome(activationRoot, parsed.openclawHome);
|
|
3350
|
+
const pluginConfigCleanup = scrubOpenClawBrainPluginConfig(parsed.openclawHome);
|
|
3351
|
+
if (parsed.dataMode === "purge" &&
|
|
3352
|
+
activationRoot !== null &&
|
|
3353
|
+
learnerService.state === "preserved" &&
|
|
3354
|
+
learnerService.matchesRequestedActivationRoot !== false) {
|
|
3355
|
+
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}`);
|
|
3356
|
+
}
|
|
2803
3357
|
const extensionDir = removeProfileHookup(parsed.openclawHome, steps);
|
|
2804
3358
|
const legacyResidue = removeLegacyProfileResidue(parsed.openclawHome);
|
|
2805
3359
|
let activationData;
|
|
@@ -2830,16 +3384,19 @@ function runUninstallCommand(parsed) {
|
|
|
2830
3384
|
const nextSteps = [
|
|
2831
3385
|
restartGuidance,
|
|
2832
3386
|
parsed.dataMode === "keep" && activationRoot !== null ? `Inspect preserved data: ${buildInstallStatusCommand(activationRoot)}` : null,
|
|
3387
|
+
activationRoot === null ? null : `Inspect learner service: ${buildLearnerServiceStatusCommand(activationRoot)}`,
|
|
2833
3388
|
parsed.dataMode === "keep"
|
|
2834
3389
|
? `Reattach later: ${buildAttachCommand(parsed.openclawHome, activationRoot)}`
|
|
2835
3390
|
: `Reinstall later: ${buildInstallCommand(parsed.openclawHome)}`
|
|
2836
3391
|
].filter((step) => step !== null);
|
|
3392
|
+
steps.push(pluginConfigCleanup.detail);
|
|
2837
3393
|
if (legacyResidue.removedNotes.length > 0) {
|
|
2838
3394
|
steps.push(`Removed legacy profile notes: ${legacyResidue.removedNotes.map((notePath) => shortenPath(notePath)).join(", ")}`);
|
|
2839
3395
|
}
|
|
2840
3396
|
if (legacyResidue.updatedAgents.length > 0) {
|
|
2841
3397
|
steps.push(`Removed legacy AGENTS.md brain references: ${legacyResidue.updatedAgents.map((agentsPath) => shortenPath(agentsPath)).join(", ")}`);
|
|
2842
3398
|
}
|
|
3399
|
+
steps.push(learnerService.detail);
|
|
2843
3400
|
steps.push(activationData.activationDataDetail);
|
|
2844
3401
|
steps.push(parsed.dataMode === "purge"
|
|
2845
3402
|
? "Uninstall removed the OpenClaw profile hook and activation data."
|
|
@@ -2859,6 +3416,8 @@ function runUninstallCommand(parsed) {
|
|
|
2859
3416
|
activationRoot,
|
|
2860
3417
|
dataAction: parsed.dataMode,
|
|
2861
3418
|
activationDataState: activationData.activationDataState,
|
|
3419
|
+
pluginConfigCleanup,
|
|
3420
|
+
learnerService,
|
|
2862
3421
|
removedLegacyNotes: legacyResidue.removedNotes,
|
|
2863
3422
|
updatedAgents: legacyResidue.updatedAgents,
|
|
2864
3423
|
restartMode: parsed.restart,
|
|
@@ -2879,10 +3438,15 @@ function runUninstallCommand(parsed) {
|
|
|
2879
3438
|
if (activationRoot !== null) {
|
|
2880
3439
|
console.log(`Activation: ${parsed.dataMode === "purge" ? shortenPath(activationRoot) : `${shortenPath(activationRoot)} preserved`}`);
|
|
2881
3440
|
}
|
|
3441
|
+
console.log(`Config: ${pluginConfigCleanup.detail}`);
|
|
3442
|
+
console.log(`Learner: ${learnerService.detail}`);
|
|
2882
3443
|
console.log(`Next: ${restartGuidance}`);
|
|
2883
3444
|
if (parsed.dataMode === "keep" && activationRoot !== null) {
|
|
2884
3445
|
console.log(`Check: ${buildInstallStatusCommand(activationRoot)}`);
|
|
2885
3446
|
}
|
|
3447
|
+
if (activationRoot !== null) {
|
|
3448
|
+
console.log(`Service: ${buildLearnerServiceStatusCommand(activationRoot)}`);
|
|
3449
|
+
}
|
|
2886
3450
|
if (parsed.dataMode === "keep") {
|
|
2887
3451
|
console.log(`Reattach: ${buildAttachCommand(parsed.openclawHome, activationRoot)}`);
|
|
2888
3452
|
}
|
|
@@ -3454,13 +4018,62 @@ function exportLocalSessionTailChangesToScanRoot(input) {
|
|
|
3454
4018
|
warnings
|
|
3455
4019
|
};
|
|
3456
4020
|
}
|
|
3457
|
-
function
|
|
3458
|
-
|
|
4021
|
+
function summarizeVectorEmbeddingState(vectors) {
|
|
4022
|
+
if (vectors === null || vectors === undefined) {
|
|
4023
|
+
return {
|
|
4024
|
+
vectorEntryCount: null,
|
|
4025
|
+
numericEmbeddingEntryCount: null,
|
|
4026
|
+
embeddingModels: []
|
|
4027
|
+
};
|
|
4028
|
+
}
|
|
4029
|
+
const embeddingModels = [...new Set(vectors.entries.flatMap((entry) => (entry.embedding === undefined ? [] : [entry.embedding.model])))].sort((left, right) => left.localeCompare(right));
|
|
4030
|
+
return {
|
|
4031
|
+
vectorEntryCount: vectors.entries.length,
|
|
4032
|
+
numericEmbeddingEntryCount: vectors.entries.filter((entry) => entry.embedding !== undefined).length,
|
|
4033
|
+
embeddingModels
|
|
4034
|
+
};
|
|
4035
|
+
}
|
|
4036
|
+
function buildWatchEmbedTracePoint(input) {
|
|
4037
|
+
const summary = summarizeVectorEmbeddingState(input.vectors);
|
|
4038
|
+
return {
|
|
4039
|
+
slot: input.slot,
|
|
4040
|
+
packId: input.packId,
|
|
4041
|
+
runtimeEmbedderPresent: input.embedder !== null,
|
|
4042
|
+
runtimeEmbedderModel: input.embedder?.model ?? null,
|
|
4043
|
+
vectorEntryCount: summary.vectorEntryCount,
|
|
4044
|
+
numericEmbeddingEntryCount: summary.numericEmbeddingEntryCount,
|
|
4045
|
+
embeddingModels: summary.embeddingModels,
|
|
4046
|
+
error: input.error ?? null
|
|
4047
|
+
};
|
|
4048
|
+
}
|
|
4049
|
+
function buildWatchEmbedTracePointFromPack(input) {
|
|
4050
|
+
return buildWatchEmbedTracePoint({
|
|
4051
|
+
slot: input.slot,
|
|
4052
|
+
packId: input.pack?.manifest.packId ?? null,
|
|
4053
|
+
embedder: input.embedder,
|
|
4054
|
+
vectors: input.pack?.vectors,
|
|
4055
|
+
error: input.error ?? null
|
|
4056
|
+
});
|
|
4057
|
+
}
|
|
4058
|
+
function formatWatchEmbedTracePoint(label, point) {
|
|
4059
|
+
const models = point.embeddingModels.length === 0 ? "none" : point.embeddingModels.join("|");
|
|
4060
|
+
const slot = point.slot ?? "build";
|
|
4061
|
+
const packId = point.packId ?? "unknown";
|
|
4062
|
+
const embedderState = point.runtimeEmbedderPresent ? `present:${point.runtimeEmbedderModel ?? "unknown"}` : "null";
|
|
4063
|
+
const counts = point.vectorEntryCount === null || point.numericEmbeddingEntryCount === null
|
|
4064
|
+
? "vectors=unknown numeric=unknown"
|
|
4065
|
+
: `vectors=${point.vectorEntryCount} numeric=${point.numericEmbeddingEntryCount}`;
|
|
4066
|
+
const error = point.error === null ? "" : ` error=${point.error}`;
|
|
4067
|
+
return `embed-trace ${label} slot=${slot} pack=${packId} runtimeEmbedder=${embedderState} ${counts} models=${models}${error}`;
|
|
4068
|
+
}
|
|
4069
|
+
async function applyWatchMaterialization(activationRoot, snapshot, lastHandledMaterializationPackId, embedder, log) {
|
|
4070
|
+
let materialization = snapshot?.learner?.lastMaterialization ?? null;
|
|
3459
4071
|
if (materialization === null) {
|
|
3460
4072
|
return {
|
|
3461
4073
|
lastHandledMaterializationPackId,
|
|
3462
4074
|
logLine: null,
|
|
3463
4075
|
materializedPackId: null,
|
|
4076
|
+
embedInstrumentation: null,
|
|
3464
4077
|
failure: null
|
|
3465
4078
|
};
|
|
3466
4079
|
}
|
|
@@ -3472,10 +4085,38 @@ function applyWatchMaterialization(activationRoot, snapshot, lastHandledMaterial
|
|
|
3472
4085
|
lastHandledMaterializationPackId,
|
|
3473
4086
|
logLine: null,
|
|
3474
4087
|
materializedPackId: packId,
|
|
4088
|
+
embedInstrumentation: null,
|
|
3475
4089
|
failure: null
|
|
3476
4090
|
};
|
|
3477
4091
|
}
|
|
4092
|
+
if (embedder !== null) {
|
|
4093
|
+
materialization = {
|
|
4094
|
+
...materialization,
|
|
4095
|
+
candidate: await reindexCandidatePackBuildResultWithEmbedder(materialization.candidate, embedder)
|
|
4096
|
+
};
|
|
4097
|
+
if (snapshot?.learner !== undefined && snapshot.learner !== null) {
|
|
4098
|
+
snapshot.learner.lastMaterialization = materialization;
|
|
4099
|
+
}
|
|
4100
|
+
}
|
|
3478
4101
|
const shortPackId = packId.length > 16 ? packId.slice(0, 16) : packId;
|
|
4102
|
+
const observedAt = new Date().toISOString();
|
|
4103
|
+
const beforeCandidateMaterialization = buildWatchEmbedTracePoint({
|
|
4104
|
+
slot: null,
|
|
4105
|
+
packId,
|
|
4106
|
+
embedder,
|
|
4107
|
+
vectors: materialization?.candidate?.payloads?.vectors
|
|
4108
|
+
});
|
|
4109
|
+
let embedInstrumentation = {
|
|
4110
|
+
observedAt,
|
|
4111
|
+
candidatePackId: packId,
|
|
4112
|
+
promotionAllowed: null,
|
|
4113
|
+
promotionFindings: [],
|
|
4114
|
+
beforeCandidateMaterialization,
|
|
4115
|
+
afterCandidateMaterialization: null,
|
|
4116
|
+
afterStage: null,
|
|
4117
|
+
afterPromote: null
|
|
4118
|
+
};
|
|
4119
|
+
log?.(formatWatchEmbedTracePoint("before_materialize", beforeCandidateMaterialization));
|
|
3479
4120
|
try {
|
|
3480
4121
|
const candidateRootDir = path.resolve(activationRoot, "packs", packId);
|
|
3481
4122
|
mkdirSync(candidateRootDir, { recursive: true });
|
|
@@ -3487,27 +4128,81 @@ function applyWatchMaterialization(activationRoot, snapshot, lastHandledMaterial
|
|
|
3487
4128
|
activeBeforePack = null;
|
|
3488
4129
|
}
|
|
3489
4130
|
const candidateDescriptor = materializeAlwaysOnLearningCandidatePack(candidateRootDir, materialization);
|
|
4131
|
+
embedInstrumentation = {
|
|
4132
|
+
...embedInstrumentation,
|
|
4133
|
+
afterCandidateMaterialization: buildWatchEmbedTracePointFromPack({
|
|
4134
|
+
slot: "candidate",
|
|
4135
|
+
pack: candidateDescriptor,
|
|
4136
|
+
embedder
|
|
4137
|
+
})
|
|
4138
|
+
};
|
|
4139
|
+
if (embedInstrumentation.afterCandidateMaterialization !== null) {
|
|
4140
|
+
log?.(formatWatchEmbedTracePoint("after_materialize", embedInstrumentation.afterCandidateMaterialization));
|
|
4141
|
+
}
|
|
3490
4142
|
appendLearningUpdateLogs({
|
|
3491
4143
|
activationRoot,
|
|
3492
4144
|
materialization,
|
|
3493
4145
|
activeBeforePack,
|
|
3494
4146
|
candidateDescriptor
|
|
3495
4147
|
});
|
|
3496
|
-
const now =
|
|
4148
|
+
const now = observedAt;
|
|
3497
4149
|
stageCandidatePack(activationRoot, candidateRootDir, {
|
|
3498
4150
|
updatedAt: now,
|
|
3499
4151
|
reason: `watch_stage:${materialization.reason}:${materialization.lane}`
|
|
3500
4152
|
});
|
|
3501
4153
|
const inspection = inspectActivationState(activationRoot, now);
|
|
4154
|
+
let stagedPack = null;
|
|
4155
|
+
let stagedPackError = null;
|
|
4156
|
+
try {
|
|
4157
|
+
stagedPack = loadPackFromActivation(activationRoot, "candidate", { requireActivationReady: true });
|
|
4158
|
+
}
|
|
4159
|
+
catch (error) {
|
|
4160
|
+
stagedPackError = formatWatchError(error);
|
|
4161
|
+
}
|
|
4162
|
+
embedInstrumentation = {
|
|
4163
|
+
...embedInstrumentation,
|
|
4164
|
+
promotionAllowed: inspection.promotion.allowed,
|
|
4165
|
+
promotionFindings: [...inspection.promotion.findings],
|
|
4166
|
+
afterStage: buildWatchEmbedTracePointFromPack({
|
|
4167
|
+
slot: "candidate",
|
|
4168
|
+
pack: stagedPack,
|
|
4169
|
+
embedder,
|
|
4170
|
+
error: stagedPackError
|
|
4171
|
+
})
|
|
4172
|
+
};
|
|
4173
|
+
if (embedInstrumentation.afterStage !== null) {
|
|
4174
|
+
log?.(formatWatchEmbedTracePoint("after_stage", embedInstrumentation.afterStage));
|
|
4175
|
+
}
|
|
3502
4176
|
if (inspection.promotion.allowed) {
|
|
3503
4177
|
promoteCandidatePack(activationRoot, {
|
|
3504
4178
|
updatedAt: now,
|
|
3505
4179
|
reason: `watch_promote:${materialization.reason}:${materialization.lane}`
|
|
3506
4180
|
});
|
|
4181
|
+
let promotedPack = null;
|
|
4182
|
+
let promotedPackError = null;
|
|
4183
|
+
try {
|
|
4184
|
+
promotedPack = loadPackFromActivation(activationRoot, "active", { requireActivationReady: true });
|
|
4185
|
+
}
|
|
4186
|
+
catch (error) {
|
|
4187
|
+
promotedPackError = formatWatchError(error);
|
|
4188
|
+
}
|
|
4189
|
+
embedInstrumentation = {
|
|
4190
|
+
...embedInstrumentation,
|
|
4191
|
+
afterPromote: buildWatchEmbedTracePointFromPack({
|
|
4192
|
+
slot: "active",
|
|
4193
|
+
pack: promotedPack,
|
|
4194
|
+
embedder,
|
|
4195
|
+
error: promotedPackError
|
|
4196
|
+
})
|
|
4197
|
+
};
|
|
4198
|
+
if (embedInstrumentation.afterPromote !== null) {
|
|
4199
|
+
log?.(formatWatchEmbedTracePoint("after_promote", embedInstrumentation.afterPromote));
|
|
4200
|
+
}
|
|
3507
4201
|
return {
|
|
3508
4202
|
lastHandledMaterializationPackId: packId,
|
|
3509
4203
|
materializedPackId: packId,
|
|
3510
4204
|
logLine: `Promoted ${shortPackId} → active`,
|
|
4205
|
+
embedInstrumentation,
|
|
3511
4206
|
failure: null
|
|
3512
4207
|
};
|
|
3513
4208
|
}
|
|
@@ -3515,15 +4210,28 @@ function applyWatchMaterialization(activationRoot, snapshot, lastHandledMaterial
|
|
|
3515
4210
|
lastHandledMaterializationPackId: packId,
|
|
3516
4211
|
materializedPackId: packId,
|
|
3517
4212
|
logLine: `Staged ${shortPackId} (promotion blocked: ${inspection.promotion.findings.join(", ")})`,
|
|
4213
|
+
embedInstrumentation,
|
|
3518
4214
|
failure: null
|
|
3519
4215
|
};
|
|
3520
4216
|
}
|
|
3521
4217
|
catch (error) {
|
|
3522
4218
|
const message = error instanceof Error ? error.message : String(error);
|
|
4219
|
+
embedInstrumentation = {
|
|
4220
|
+
...embedInstrumentation,
|
|
4221
|
+
afterCandidateMaterialization: embedInstrumentation.afterCandidateMaterialization ??
|
|
4222
|
+
buildWatchEmbedTracePoint({
|
|
4223
|
+
slot: "candidate",
|
|
4224
|
+
packId,
|
|
4225
|
+
embedder,
|
|
4226
|
+
vectors: null,
|
|
4227
|
+
error: message
|
|
4228
|
+
})
|
|
4229
|
+
};
|
|
3523
4230
|
return {
|
|
3524
4231
|
lastHandledMaterializationPackId,
|
|
3525
4232
|
materializedPackId: packId,
|
|
3526
4233
|
logLine: `Promotion failed for ${shortPackId}: ${message}`,
|
|
4234
|
+
embedInstrumentation,
|
|
3527
4235
|
failure: {
|
|
3528
4236
|
mode: "materialization_failed",
|
|
3529
4237
|
detail: message,
|
|
@@ -3569,12 +4277,178 @@ function resolveWatchTeacherLabelerConfig(input, activationRoot) {
|
|
|
3569
4277
|
warnings
|
|
3570
4278
|
};
|
|
3571
4279
|
}
|
|
4280
|
+
function resolveWatchEmbedderConfig(input, activationRoot) {
|
|
4281
|
+
if (input !== undefined) {
|
|
4282
|
+
return {
|
|
4283
|
+
embedder: input,
|
|
4284
|
+
warnings: []
|
|
4285
|
+
};
|
|
4286
|
+
}
|
|
4287
|
+
const defaultsResult = readOpenClawBrainProviderDefaults(activationRoot);
|
|
4288
|
+
const providerConfig = readOpenClawBrainProviderConfigFromSources({
|
|
4289
|
+
env: process.env,
|
|
4290
|
+
activationRoot,
|
|
4291
|
+
defaults: defaultsResult.defaults
|
|
4292
|
+
});
|
|
4293
|
+
const warnings = [...new Set([
|
|
4294
|
+
...defaultsResult.warnings.filter((warning) => /OPENCLAWBRAIN_EMBEDDER_|provider defaults/u.test(warning)),
|
|
4295
|
+
...providerConfig.warnings.filter((warning) => /OPENCLAWBRAIN_EMBEDDER_|provider defaults/u.test(warning))
|
|
4296
|
+
])];
|
|
4297
|
+
const explicitEnv = typeof process.env[OPENCLAWBRAIN_EMBEDDER_PROVIDER_ENV] === "string" ||
|
|
4298
|
+
typeof process.env[OPENCLAWBRAIN_EMBEDDER_MODEL_ENV] === "string" ||
|
|
4299
|
+
typeof process.env[OPENCLAWBRAIN_EMBEDDER_BASE_URL_ENV] === "string";
|
|
4300
|
+
// Legacy install-written provider-defaults.json files can predate embedder fields entirely.
|
|
4301
|
+
// If a persisted defaults file exists, treat that activation root as explicitly configured and
|
|
4302
|
+
// let provider-config resolution fill in the embedder fallback instead of silently dropping to null.
|
|
4303
|
+
const explicitDefaults = defaultsResult.defaults !== null;
|
|
4304
|
+
if (!explicitEnv && !explicitDefaults) {
|
|
4305
|
+
return {
|
|
4306
|
+
embedder: null,
|
|
4307
|
+
warnings
|
|
4308
|
+
};
|
|
4309
|
+
}
|
|
4310
|
+
if (providerConfig.embedder.provider !== "ollama") {
|
|
4311
|
+
return {
|
|
4312
|
+
embedder: null,
|
|
4313
|
+
warnings
|
|
4314
|
+
};
|
|
4315
|
+
}
|
|
4316
|
+
return {
|
|
4317
|
+
embedder: createOllamaEmbedder({
|
|
4318
|
+
baseUrl: providerConfig.embedderBaseUrl,
|
|
4319
|
+
model: providerConfig.embedder.model
|
|
4320
|
+
}),
|
|
4321
|
+
warnings
|
|
4322
|
+
};
|
|
4323
|
+
}
|
|
4324
|
+
function summarizeWatchLatestUserMessage(localPoll) {
|
|
4325
|
+
let latest = null;
|
|
4326
|
+
for (const change of localPoll.changes) {
|
|
4327
|
+
if (change.lastUserMessageAt === null || change.lastUserMessageText === null) {
|
|
4328
|
+
continue;
|
|
4329
|
+
}
|
|
4330
|
+
const candidate = {
|
|
4331
|
+
at: change.lastUserMessageAt,
|
|
4332
|
+
text: change.lastUserMessageText,
|
|
4333
|
+
sessionId: change.sessionId
|
|
4334
|
+
};
|
|
4335
|
+
if (latest === null || Date.parse(candidate.at) >= Date.parse(latest.at)) {
|
|
4336
|
+
latest = candidate;
|
|
4337
|
+
}
|
|
4338
|
+
}
|
|
4339
|
+
return latest;
|
|
4340
|
+
}
|
|
4341
|
+
function summarizeWatchPackTransition(input) {
|
|
4342
|
+
const beforeActivePackId = input.before?.active?.packId ?? input.before?.pointers.active?.packId ?? null;
|
|
4343
|
+
const afterActivePackId = input.after?.active?.packId ?? input.after?.pointers.active?.packId ?? null;
|
|
4344
|
+
if (afterActivePackId !== null && beforeActivePackId !== afterActivePackId) {
|
|
4345
|
+
return {
|
|
4346
|
+
kind: "promoted_active",
|
|
4347
|
+
fromPackId: beforeActivePackId,
|
|
4348
|
+
toPackId: afterActivePackId
|
|
4349
|
+
};
|
|
4350
|
+
}
|
|
4351
|
+
const beforeCandidatePackId = input.before?.candidate?.packId ?? input.before?.pointers.candidate?.packId ?? null;
|
|
4352
|
+
const afterCandidatePackId = input.after?.candidate?.packId ?? input.after?.pointers.candidate?.packId ?? null;
|
|
4353
|
+
if (afterCandidatePackId !== null && beforeCandidatePackId !== afterCandidatePackId) {
|
|
4354
|
+
return {
|
|
4355
|
+
kind: "staged_candidate",
|
|
4356
|
+
fromPackId: beforeCandidatePackId,
|
|
4357
|
+
toPackId: afterCandidatePackId
|
|
4358
|
+
};
|
|
4359
|
+
}
|
|
4360
|
+
return null;
|
|
4361
|
+
}
|
|
4362
|
+
function truncateWatchMessage(text, maxLength = 96) {
|
|
4363
|
+
const normalized = text.replace(/\s+/gu, " ").trim();
|
|
4364
|
+
if (normalized.length <= maxLength) {
|
|
4365
|
+
return normalized;
|
|
4366
|
+
}
|
|
4367
|
+
return `${normalized.slice(0, maxLength - 1)}…`;
|
|
4368
|
+
}
|
|
4369
|
+
function buildWatchLastObservedDelta(input) {
|
|
4370
|
+
const exported = input.exported.exportedBundleCount > 0 ||
|
|
4371
|
+
input.exported.exportedEventCount > 0;
|
|
4372
|
+
const labeled = (input.snapshotAfter.diagnostics.emittedArtifactCount ?? 0) >
|
|
4373
|
+
(input.snapshotBefore.diagnostics.emittedArtifactCount ?? 0);
|
|
4374
|
+
const latestPackTransition = summarizeWatchPackTransition({
|
|
4375
|
+
before: input.beforeInspection,
|
|
4376
|
+
after: input.afterInspection
|
|
4377
|
+
});
|
|
4378
|
+
const promoted = latestPackTransition?.kind === "promoted_active";
|
|
4379
|
+
const afterActivePackId = input.afterInspection?.active?.packId ?? input.afterInspection?.pointers.active?.packId ?? null;
|
|
4380
|
+
const served = promoted && afterActivePackId === latestPackTransition?.toPackId && input.afterInspection?.active?.activationReady === true;
|
|
4381
|
+
const latestUserMessage = summarizeWatchLatestUserMessage(input.localPoll);
|
|
4382
|
+
const selectedBackfillOnly = !exported && input.scanResult.selected.length > 0;
|
|
4383
|
+
const cycleDidNothing = !exported && !labeled && !promoted && !served;
|
|
4384
|
+
let explanation;
|
|
4385
|
+
if (latestUserMessage === null) {
|
|
4386
|
+
if (selectedBackfillOnly) {
|
|
4387
|
+
explanation =
|
|
4388
|
+
"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.";
|
|
4389
|
+
}
|
|
4390
|
+
else if (cycleDidNothing) {
|
|
4391
|
+
explanation = "No new local user message or learner-visible export was observed in this cycle, so nothing changed.";
|
|
4392
|
+
}
|
|
4393
|
+
else if (promoted && latestPackTransition !== null) {
|
|
4394
|
+
explanation =
|
|
4395
|
+
`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.`;
|
|
4396
|
+
}
|
|
4397
|
+
else {
|
|
4398
|
+
explanation =
|
|
4399
|
+
"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.";
|
|
4400
|
+
}
|
|
4401
|
+
}
|
|
4402
|
+
else {
|
|
4403
|
+
const quotedMessage = `"${truncateWatchMessage(latestUserMessage.text)}"`;
|
|
4404
|
+
if (exported && labeled && promoted && served && latestPackTransition !== null) {
|
|
4405
|
+
explanation =
|
|
4406
|
+
`Latest user message ${quotedMessage} was exported, labeled, promoted into pack ${latestPackTransition.toPackId}, and is now served from the active pack.`;
|
|
4407
|
+
}
|
|
4408
|
+
else if (exported && labeled && !promoted) {
|
|
4409
|
+
explanation =
|
|
4410
|
+
`Latest user message ${quotedMessage} was exported and labeled, but it has not been promoted into the serving pack yet.`;
|
|
4411
|
+
}
|
|
4412
|
+
else if (exported && !labeled && !promoted) {
|
|
4413
|
+
explanation =
|
|
4414
|
+
`Latest user message ${quotedMessage} was exported, but it did not add a new teacher label or change the serving pack in this cycle.`;
|
|
4415
|
+
}
|
|
4416
|
+
else if (exported && !labeled && promoted && latestPackTransition !== null) {
|
|
4417
|
+
explanation =
|
|
4418
|
+
`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.`;
|
|
4419
|
+
}
|
|
4420
|
+
else if (!exported && labeled) {
|
|
4421
|
+
explanation =
|
|
4422
|
+
`Latest user message ${quotedMessage} was already in stored exports; this cycle only labeled or replayed it, without a new local export.`;
|
|
4423
|
+
}
|
|
4424
|
+
else if (cycleDidNothing) {
|
|
4425
|
+
explanation = `Latest user message ${quotedMessage} did not produce a new export, label, or serving-pack change in this cycle.`;
|
|
4426
|
+
}
|
|
4427
|
+
else {
|
|
4428
|
+
explanation =
|
|
4429
|
+
`Latest user message ${quotedMessage} changed learner state this cycle, but the local artifacts do not prove a clean export-to-serve handoff yet.`;
|
|
4430
|
+
}
|
|
4431
|
+
}
|
|
4432
|
+
return {
|
|
4433
|
+
available: true,
|
|
4434
|
+
observedAt: input.observedAt,
|
|
4435
|
+
exported,
|
|
4436
|
+
labeled,
|
|
4437
|
+
promoted,
|
|
4438
|
+
served,
|
|
4439
|
+
latestPackTransition,
|
|
4440
|
+
explanation
|
|
4441
|
+
};
|
|
4442
|
+
}
|
|
3572
4443
|
export async function createWatchCommandRuntime(input) {
|
|
3573
4444
|
const activationRoot = path.resolve(input.activationRoot);
|
|
3574
4445
|
const bootstrapObservedAt = new Date().toISOString();
|
|
3575
4446
|
const scanRoot = input.scanRoot !== undefined && input.scanRoot !== null
|
|
3576
4447
|
? path.resolve(input.scanRoot)
|
|
3577
4448
|
: path.resolve(activationRoot, "event-exports");
|
|
4449
|
+
const pollIntervalSeconds = Number.isInteger(input.pollIntervalSeconds) && (input.pollIntervalSeconds ?? 0) > 0
|
|
4450
|
+
? input.pollIntervalSeconds
|
|
4451
|
+
: DEFAULT_WATCH_POLL_INTERVAL_SECONDS;
|
|
3578
4452
|
const sessionTailCursorPath = resolveWatchSessionTailCursorPath(activationRoot);
|
|
3579
4453
|
const teacherSnapshotPath = resolveWatchTeacherSnapshotPath(activationRoot);
|
|
3580
4454
|
const restoredTeacherState = loadWatchTeacherSnapshotState(teacherSnapshotPath);
|
|
@@ -3586,14 +4460,25 @@ export async function createWatchCommandRuntime(input) {
|
|
|
3586
4460
|
log(`Scan root: ${shortenPath(scanRoot)}`);
|
|
3587
4461
|
log(`State: cursor=${shortenPath(sessionTailCursorPath)} snapshot=${shortenPath(teacherSnapshotPath)}`);
|
|
3588
4462
|
const resolvedTeacherLabeler = resolveWatchTeacherLabelerConfig(input.teacherLabeler, activationRoot);
|
|
4463
|
+
const resolvedEmbedder = resolveWatchEmbedderConfig(input.embedder, activationRoot);
|
|
3589
4464
|
const teacherLabeler = resolvedTeacherLabeler.teacherLabeler;
|
|
3590
4465
|
for (const warning of resolvedTeacherLabeler.warnings) {
|
|
3591
4466
|
startupWarnings.push(`teacher_config_warning:${warning}`);
|
|
3592
4467
|
log(`Teacher config warning: ${warning}`);
|
|
3593
4468
|
}
|
|
4469
|
+
for (const warning of resolvedEmbedder.warnings) {
|
|
4470
|
+
startupWarnings.push(`embedder_config_warning:${warning}`);
|
|
4471
|
+
log(`Embedder config warning: ${warning}`);
|
|
4472
|
+
}
|
|
3594
4473
|
if (teacherLabeler?.provider === "ollama") {
|
|
3595
4474
|
log(`Teacher labeler: provider=ollama model=${teacherLabeler.model ?? "qwen3.5:9b"}`);
|
|
3596
4475
|
}
|
|
4476
|
+
if (resolvedEmbedder.embedder !== null) {
|
|
4477
|
+
log(`Embedder: provider=ollama model=${resolvedEmbedder.embedder.model}`);
|
|
4478
|
+
}
|
|
4479
|
+
else {
|
|
4480
|
+
log("Embedder: numeric pack materialization is not configured; watch will keep keyword/weight vectors only.");
|
|
4481
|
+
}
|
|
3597
4482
|
const scanner = createRuntimeEventExportScanner({ scanRoot });
|
|
3598
4483
|
let lastServeTimeFallbackReason = null;
|
|
3599
4484
|
const baseTeacherLoopInput = {
|
|
@@ -3630,10 +4515,23 @@ export async function createWatchCommandRuntime(input) {
|
|
|
3630
4515
|
};
|
|
3631
4516
|
let teacherLoop;
|
|
3632
4517
|
let lastHandledMaterializationPackId = restoredTeacherState.lastHandledMaterializationPackId;
|
|
4518
|
+
let lastEmbedInstrumentation = restoredTeacherState.embedInstrumentation;
|
|
4519
|
+
let restoredLastObservedDelta = restoredTeacherState.lastObservedDelta;
|
|
3633
4520
|
if (restoredTeacherState.error !== null) {
|
|
3634
4521
|
const message = restoredTeacherState.error;
|
|
3635
4522
|
startupWarnings.push(`teacher_snapshot_reset:${message}`);
|
|
3636
4523
|
lastHandledMaterializationPackId = null;
|
|
4524
|
+
lastEmbedInstrumentation = null;
|
|
4525
|
+
restoredLastObservedDelta = {
|
|
4526
|
+
available: true,
|
|
4527
|
+
observedAt: bootstrapObservedAt,
|
|
4528
|
+
exported: false,
|
|
4529
|
+
labeled: false,
|
|
4530
|
+
promoted: false,
|
|
4531
|
+
served: false,
|
|
4532
|
+
latestPackTransition: null,
|
|
4533
|
+
explanation: "Watch reset an unreadable teacher snapshot, so no prior last-turn delta can be trusted."
|
|
4534
|
+
};
|
|
3637
4535
|
log(`Teacher snapshot reset: ${message}`);
|
|
3638
4536
|
teacherLoop = createAsyncTeacherLiveLoop(baseTeacherLoopInput);
|
|
3639
4537
|
}
|
|
@@ -3648,6 +4546,17 @@ export async function createWatchCommandRuntime(input) {
|
|
|
3648
4546
|
const message = formatWatchError(error);
|
|
3649
4547
|
startupWarnings.push(`teacher_snapshot_reset:${message}`);
|
|
3650
4548
|
lastHandledMaterializationPackId = null;
|
|
4549
|
+
lastEmbedInstrumentation = null;
|
|
4550
|
+
restoredLastObservedDelta = {
|
|
4551
|
+
available: true,
|
|
4552
|
+
observedAt: bootstrapObservedAt,
|
|
4553
|
+
exported: false,
|
|
4554
|
+
labeled: false,
|
|
4555
|
+
promoted: false,
|
|
4556
|
+
served: false,
|
|
4557
|
+
latestPackTransition: null,
|
|
4558
|
+
explanation: "Watch reset an unusable teacher snapshot, so no prior last-turn delta can be trusted."
|
|
4559
|
+
};
|
|
3651
4560
|
log(`Teacher snapshot reset: ${message}`);
|
|
3652
4561
|
teacherLoop = createAsyncTeacherLiveLoop(baseTeacherLoopInput);
|
|
3653
4562
|
}
|
|
@@ -3656,11 +4565,19 @@ export async function createWatchCommandRuntime(input) {
|
|
|
3656
4565
|
const restoredSeenExportCount = restoredTeacherState.snapshot.state?.seenExportDigests.length ?? 0;
|
|
3657
4566
|
log(`Restored teacher snapshot: seen=${restoredSeenExportCount} artifacts=${restoredTeacherState.snapshot.teacher.artifactCount}`);
|
|
3658
4567
|
}
|
|
4568
|
+
const resolvedProfileRoots = input.profileRoots === undefined
|
|
4569
|
+
? resolveWatchProfileRootsForActivationRoot(activationRoot)
|
|
4570
|
+
: [...new Set(input.profileRoots.map((root) => path.resolve(root)))];
|
|
4571
|
+
if (input.profileRoots === undefined && resolvedProfileRoots !== undefined) {
|
|
4572
|
+
log(`Session tail scope: attached OpenClaw home${resolvedProfileRoots.length === 1 ? "" : "s"} ${resolvedProfileRoots
|
|
4573
|
+
.map((root) => shortenPath(root))
|
|
4574
|
+
.join(", ")}`);
|
|
4575
|
+
}
|
|
3659
4576
|
let restoredCursor = loadWatchSessionTailCursor(sessionTailCursorPath);
|
|
3660
4577
|
let localSessionTail;
|
|
3661
4578
|
try {
|
|
3662
4579
|
localSessionTail = createOpenClawLocalSessionTail({
|
|
3663
|
-
...(
|
|
4580
|
+
...(resolvedProfileRoots === undefined ? {} : { profileRoots: resolvedProfileRoots }),
|
|
3664
4581
|
cursor: restoredCursor,
|
|
3665
4582
|
emitExistingOnFirstPoll: restoredCursor.length === 0
|
|
3666
4583
|
});
|
|
@@ -3670,7 +4587,7 @@ export async function createWatchCommandRuntime(input) {
|
|
|
3670
4587
|
log(`Session tail cursor reset: ${message}`);
|
|
3671
4588
|
restoredCursor = [];
|
|
3672
4589
|
localSessionTail = createOpenClawLocalSessionTail({
|
|
3673
|
-
...(
|
|
4590
|
+
...(resolvedProfileRoots === undefined ? {} : { profileRoots: resolvedProfileRoots }),
|
|
3674
4591
|
emitExistingOnFirstPoll: true
|
|
3675
4592
|
});
|
|
3676
4593
|
persistWatchSessionTailCursor(sessionTailCursorPath, []);
|
|
@@ -3691,8 +4608,11 @@ export async function createWatchCommandRuntime(input) {
|
|
|
3691
4608
|
log(`Replayed ${replayState.replayedBundleCount} stored export bundle${replayState.replayedBundleCount === 1 ? "" : "s"} (${replayState.replayedEventCount} event${replayState.replayedEventCount === 1 ? "" : "s"})`);
|
|
3692
4609
|
}
|
|
3693
4610
|
let bootstrapSnapshot = teacherLoop.snapshot();
|
|
3694
|
-
const replayPromotion = applyWatchMaterialization(activationRoot, bootstrapSnapshot, lastHandledMaterializationPackId);
|
|
4611
|
+
const replayPromotion = await applyWatchMaterialization(activationRoot, bootstrapSnapshot, lastHandledMaterializationPackId, resolvedEmbedder.embedder, log);
|
|
3695
4612
|
lastHandledMaterializationPackId = replayPromotion.lastHandledMaterializationPackId;
|
|
4613
|
+
if (replayPromotion.embedInstrumentation !== null) {
|
|
4614
|
+
lastEmbedInstrumentation = replayPromotion.embedInstrumentation;
|
|
4615
|
+
}
|
|
3696
4616
|
if (replayPromotion.logLine !== null) {
|
|
3697
4617
|
log(replayPromotion.logLine);
|
|
3698
4618
|
bootstrapSnapshot = teacherLoop.snapshot();
|
|
@@ -3700,6 +4620,7 @@ export async function createWatchCommandRuntime(input) {
|
|
|
3700
4620
|
const bootstrapCursor = localSessionTail.snapshot();
|
|
3701
4621
|
persistWatchTeacherSnapshot(teacherSnapshotPath, {
|
|
3702
4622
|
lastRunAt: bootstrapObservedAt,
|
|
4623
|
+
pollIntervalSeconds,
|
|
3703
4624
|
scanRoot,
|
|
3704
4625
|
sessionTailCursorPath,
|
|
3705
4626
|
sessionTailCursorUpdatedAt: bootstrapObservedAt,
|
|
@@ -3715,26 +4636,44 @@ export async function createWatchCommandRuntime(input) {
|
|
|
3715
4636
|
lastTeacherError: null,
|
|
3716
4637
|
localSessionTailNoopReason: null,
|
|
3717
4638
|
lastHandledMaterializationPackId,
|
|
4639
|
+
lastObservedDelta: restoredLastObservedDelta.available
|
|
4640
|
+
? restoredLastObservedDelta
|
|
4641
|
+
: {
|
|
4642
|
+
available: true,
|
|
4643
|
+
observedAt: bootstrapObservedAt,
|
|
4644
|
+
exported: false,
|
|
4645
|
+
labeled: false,
|
|
4646
|
+
promoted: false,
|
|
4647
|
+
served: false,
|
|
4648
|
+
latestPackTransition: null,
|
|
4649
|
+
explanation: "Watch bootstrapped its state, but no new local user-message delta has been observed yet."
|
|
4650
|
+
},
|
|
4651
|
+
embedInstrumentation: lastEmbedInstrumentation,
|
|
3718
4652
|
failure: replayPromotion.failure,
|
|
3719
4653
|
snapshot: bootstrapSnapshot
|
|
3720
4654
|
});
|
|
3721
4655
|
return {
|
|
3722
4656
|
activationRoot,
|
|
3723
4657
|
scanRoot,
|
|
4658
|
+
pollIntervalSeconds,
|
|
3724
4659
|
sessionTailCursorPath,
|
|
3725
4660
|
teacherSnapshotPath,
|
|
3726
4661
|
startupWarnings,
|
|
3727
4662
|
lastTeacherError: null,
|
|
3728
4663
|
replayState,
|
|
3729
4664
|
lastHandledMaterializationPackId,
|
|
4665
|
+
lastEmbedInstrumentation,
|
|
3730
4666
|
scanner,
|
|
3731
4667
|
teacherLoop,
|
|
3732
|
-
localSessionTail
|
|
4668
|
+
localSessionTail,
|
|
4669
|
+
embedder: resolvedEmbedder.embedder
|
|
3733
4670
|
};
|
|
3734
4671
|
}
|
|
3735
4672
|
export async function runWatchCommandPass(runtime, options = {}) {
|
|
3736
4673
|
const log = options.log ?? watchLog;
|
|
3737
4674
|
const observedAt = options.observedAt ?? new Date().toISOString();
|
|
4675
|
+
const snapshotBefore = runtime.teacherLoop.snapshot();
|
|
4676
|
+
const beforeInspection = inspectActivationState(runtime.activationRoot, observedAt);
|
|
3738
4677
|
const localPoll = runtime.localSessionTail.pollOnce({
|
|
3739
4678
|
observedAt
|
|
3740
4679
|
});
|
|
@@ -3768,9 +4707,12 @@ export async function runWatchCommandPass(runtime, options = {}) {
|
|
|
3768
4707
|
const ingestResult = await runtime.teacherLoop.ingestRuntimeEventExportScannerScan(scanResult);
|
|
3769
4708
|
runtime.lastTeacherError = null;
|
|
3770
4709
|
snapshot = ingestResult.snapshot;
|
|
3771
|
-
const promotion = applyWatchMaterialization(runtime.activationRoot, snapshot, runtime.lastHandledMaterializationPackId);
|
|
4710
|
+
const promotion = await applyWatchMaterialization(runtime.activationRoot, snapshot, runtime.lastHandledMaterializationPackId, runtime.embedder, log);
|
|
3772
4711
|
runtime.lastHandledMaterializationPackId = promotion.lastHandledMaterializationPackId;
|
|
3773
4712
|
materializedPackId = promotion.materializedPackId;
|
|
4713
|
+
if (promotion.embedInstrumentation !== null) {
|
|
4714
|
+
runtime.lastEmbedInstrumentation = promotion.embedInstrumentation;
|
|
4715
|
+
}
|
|
3774
4716
|
failure = promotion.failure;
|
|
3775
4717
|
if (promotion.logLine !== null) {
|
|
3776
4718
|
log(promotion.logLine);
|
|
@@ -3802,8 +4744,20 @@ export async function runWatchCommandPass(runtime, options = {}) {
|
|
|
3802
4744
|
snapshot = runtime.teacherLoop.snapshot();
|
|
3803
4745
|
}
|
|
3804
4746
|
}
|
|
4747
|
+
const afterInspection = inspectActivationState(runtime.activationRoot, observedAt);
|
|
4748
|
+
const lastObservedDelta = buildWatchLastObservedDelta({
|
|
4749
|
+
observedAt,
|
|
4750
|
+
localPoll,
|
|
4751
|
+
exported,
|
|
4752
|
+
scanResult,
|
|
4753
|
+
snapshotBefore,
|
|
4754
|
+
snapshotAfter: snapshot,
|
|
4755
|
+
beforeInspection,
|
|
4756
|
+
afterInspection
|
|
4757
|
+
});
|
|
3805
4758
|
persistWatchTeacherSnapshot(runtime.teacherSnapshotPath, {
|
|
3806
4759
|
lastRunAt: observedAt,
|
|
4760
|
+
pollIntervalSeconds: runtime.pollIntervalSeconds,
|
|
3807
4761
|
scanRoot: runtime.scanRoot,
|
|
3808
4762
|
sessionTailCursorPath: runtime.sessionTailCursorPath,
|
|
3809
4763
|
sessionTailCursorUpdatedAt: observedAt,
|
|
@@ -3819,6 +4773,8 @@ export async function runWatchCommandPass(runtime, options = {}) {
|
|
|
3819
4773
|
lastTeacherError: runtime.lastTeacherError,
|
|
3820
4774
|
localSessionTailNoopReason: localPoll.noopReason,
|
|
3821
4775
|
lastHandledMaterializationPackId: runtime.lastHandledMaterializationPackId,
|
|
4776
|
+
lastObservedDelta,
|
|
4777
|
+
embedInstrumentation: runtime.lastEmbedInstrumentation,
|
|
3822
4778
|
failure,
|
|
3823
4779
|
snapshot
|
|
3824
4780
|
});
|
|
@@ -3839,6 +4795,7 @@ export async function runWatchCommandPass(runtime, options = {}) {
|
|
|
3839
4795
|
scannerProcessedBundles: persistedScannerCheckpoint.processedExportDigests.length,
|
|
3840
4796
|
scannerLiveAfter: persistedScannerCheckpoint.live.after?.exportDigest ?? null,
|
|
3841
4797
|
materialized: materializedPackId,
|
|
4798
|
+
lastObservedDelta,
|
|
3842
4799
|
diagnostics: snapshot.diagnostics ?? null,
|
|
3843
4800
|
localSessionTailNoopReason: localPoll.noopReason
|
|
3844
4801
|
}));
|
|
@@ -3856,6 +4813,7 @@ async function runWatchCommand(parsed) {
|
|
|
3856
4813
|
const runtime = await createWatchCommandRuntime({
|
|
3857
4814
|
activationRoot: parsed.activationRoot,
|
|
3858
4815
|
scanRoot: parsed.scanRoot,
|
|
4816
|
+
pollIntervalSeconds: parsed.interval,
|
|
3859
4817
|
log: watchLog
|
|
3860
4818
|
});
|
|
3861
4819
|
watchLog(`Interval: ${parsed.interval}s`);
|
|
@@ -4147,7 +5105,10 @@ export function runOperatorCli(argv = process.argv.slice(2)) {
|
|
|
4147
5105
|
}
|
|
4148
5106
|
else {
|
|
4149
5107
|
const report = buildOperatorSurfaceReport(operatorInput);
|
|
4150
|
-
const providerConfig =
|
|
5108
|
+
const providerConfig = readOpenClawBrainProviderConfigFromSources({
|
|
5109
|
+
env: process.env,
|
|
5110
|
+
activationRoot
|
|
5111
|
+
});
|
|
4151
5112
|
if (statusOrRollback.detailed) {
|
|
4152
5113
|
console.log(formatCurrentProfileStatusSummary(status, report, targetInspection, {
|
|
4153
5114
|
openclawHome: statusOrRollback.openclawHome,
|