@openclawbrain/openclaw 0.3.2 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -1
- package/dist/extension/index.js +17 -1
- package/dist/extension/index.js.map +1 -1
- package/dist/src/attachment-truth.d.ts +34 -0
- package/dist/src/attachment-truth.js +215 -0
- package/dist/src/attachment-truth.js.map +1 -0
- package/dist/src/cli.js +428 -42
- package/dist/src/cli.js.map +1 -1
- package/dist/src/index.d.ts +25 -1
- package/dist/src/index.js +138 -50
- package/dist/src/index.js.map +1 -1
- package/dist/src/openclaw-hook-truth.d.ts +25 -0
- package/dist/src/openclaw-hook-truth.js +154 -0
- package/dist/src/openclaw-hook-truth.js.map +1 -0
- package/extension/index.ts +19 -1
- package/package.json +7 -7
package/dist/src/cli.js
CHANGED
|
@@ -13,7 +13,8 @@ import { buildTeacherSupervisionArtifactsFromNormalizedEventExport, createAlways
|
|
|
13
13
|
import { inspectActivationState, loadPackFromActivation, promoteCandidatePack, readLearningSpineLogEntries, stageCandidatePack } from "@openclawbrain/pack-format";
|
|
14
14
|
import { resolveActivationRoot } from "./resolve-activation-root.js";
|
|
15
15
|
import { describeOpenClawHomeInspection, discoverOpenClawHomes, formatOpenClawHomeLayout, formatOpenClawHomeProfileSource, inspectOpenClawHome } from "./openclaw-home-layout.js";
|
|
16
|
-
import {
|
|
16
|
+
import { inspectOpenClawBrainHookStatus, inspectOpenClawBrainPluginAllowlist } from "./openclaw-hook-truth.js";
|
|
17
|
+
import { DEFAULT_WATCH_POLL_INTERVAL_SECONDS, buildNormalizedEventExportFromScannedEvents, bootstrapRuntimeAttach, buildOperatorSurfaceReport, clearOpenClawProfileRuntimeLoadProof, compileRuntimeContext, createAsyncTeacherLiveLoop, createOpenClawLocalSessionTail, createRuntimeEventExportScanner, describeCurrentProfileBrainStatus, formatOperatorRollbackReport, listOpenClawProfileRuntimeLoadProofs, loadRuntimeEventExportBundle, loadWatchTeacherSnapshotState, persistWatchTeacherSnapshot, rollbackRuntimeAttach, resolveAttachmentRuntimeLoadProofsPath, resolveOperatorTeacherSnapshotPath, resolveAsyncTeacherLiveLoopSnapshotPath, resolveWatchSessionTailCursorPath, resolveWatchStateRoot, resolveWatchTeacherSnapshotPath, scanLiveEventExport, scanRecordedSession, summarizeLearningPathFromMaterialization, summarizeNormalizedEventExportLabelFlow, writeScannedEventExportBundle } from "./index.js";
|
|
17
18
|
import { appendLearningUpdateLogs } from "./learning-spine.js";
|
|
18
19
|
import { buildPassiveLearningSessionExportFromOpenClawSessionStore } from "./local-session-passive-learning.js";
|
|
19
20
|
import { discoverOpenClawSessionStores, loadOpenClawSessionIndex, readOpenClawSessionFile } from "./session-store.js";
|
|
@@ -38,6 +39,15 @@ function normalizeOptionalCliString(value) {
|
|
|
38
39
|
const trimmed = value.trim();
|
|
39
40
|
return trimmed.length > 0 ? trimmed : null;
|
|
40
41
|
}
|
|
42
|
+
function canonicalizeExistingCliPath(filePath) {
|
|
43
|
+
const resolvedPath = path.resolve(filePath);
|
|
44
|
+
try {
|
|
45
|
+
return realpathSync(resolvedPath);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return resolvedPath;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
41
51
|
function readTruthyEnvFlag(name, env = process.env) {
|
|
42
52
|
const value = normalizeOptionalCliString(env[name]);
|
|
43
53
|
if (value === null) {
|
|
@@ -51,6 +61,45 @@ function getCliHomeDir() {
|
|
|
51
61
|
function discoverInstallCandidateOpenClawHomes(homeDir = getCliHomeDir()) {
|
|
52
62
|
return discoverOpenClawHomes(homeDir).map((inspection) => inspection.openclawHome);
|
|
53
63
|
}
|
|
64
|
+
function summarizeSharedActivationRootReferenceProof(reference) {
|
|
65
|
+
switch (reference.installState) {
|
|
66
|
+
case "installed":
|
|
67
|
+
return "hook installed and loadable";
|
|
68
|
+
case "blocked_by_allowlist":
|
|
69
|
+
return "hook files exist but OpenClaw will not load them because plugins.allow blocks openclawbrain";
|
|
70
|
+
case "not_installed":
|
|
71
|
+
default:
|
|
72
|
+
return "hook files are incomplete, so serve-path attachment is not proven";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function partitionSharedActivationRootHookReferences(references) {
|
|
76
|
+
return references.reduce((result, reference) => {
|
|
77
|
+
if (reference.serveAttachmentState === "attached") {
|
|
78
|
+
result.attached.push(reference);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
result.halfAttached.push(reference);
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
}, {
|
|
85
|
+
attached: [],
|
|
86
|
+
halfAttached: []
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function formatSharedActivationRootReferenceList(references, options = {}) {
|
|
90
|
+
return references
|
|
91
|
+
.map((reference) => {
|
|
92
|
+
const parts = [path.resolve(reference.openclawHome)];
|
|
93
|
+
if (options.includeInspection) {
|
|
94
|
+
parts.push(describeOpenClawHomeInspection(reference.inspection));
|
|
95
|
+
}
|
|
96
|
+
if (options.includeProof) {
|
|
97
|
+
parts.push(summarizeSharedActivationRootReferenceProof(reference));
|
|
98
|
+
}
|
|
99
|
+
return ` - ${parts.join(" | ")}`;
|
|
100
|
+
})
|
|
101
|
+
.join("\n");
|
|
102
|
+
}
|
|
54
103
|
function findInstalledHookReferencesForActivationRoot(input) {
|
|
55
104
|
const resolvedActivationRoot = path.resolve(input.activationRoot);
|
|
56
105
|
const resolvedExcludedHome = input.excludingOpenClawHome === undefined || input.excludingOpenClawHome === null
|
|
@@ -66,8 +115,18 @@ function findInstalledHookReferencesForActivationRoot(input) {
|
|
|
66
115
|
if (installedActivationRoot.trim().length === 0) {
|
|
67
116
|
return [];
|
|
68
117
|
}
|
|
118
|
+
const installHook = summarizeStatusInstallHook(inspection.openclawHome);
|
|
119
|
+
const reference = {
|
|
120
|
+
openclawHome: inspection.openclawHome,
|
|
121
|
+
inspection,
|
|
122
|
+
installState: installHook.state === "installed" || installHook.state === "blocked_by_allowlist"
|
|
123
|
+
? installHook.state
|
|
124
|
+
: "not_installed",
|
|
125
|
+
serveAttachmentState: installHook.state === "installed" ? "attached" : "half_attached",
|
|
126
|
+
hookDetail: installHook.detail
|
|
127
|
+
};
|
|
69
128
|
return path.resolve(installedActivationRoot) === resolvedActivationRoot
|
|
70
|
-
? [
|
|
129
|
+
? [reference]
|
|
71
130
|
: [];
|
|
72
131
|
})
|
|
73
132
|
.sort((left, right) => left.openclawHome.localeCompare(right.openclawHome));
|
|
@@ -76,11 +135,18 @@ function findOtherInstalledHookReferencesForActivationRoot(input) {
|
|
|
76
135
|
return findInstalledHookReferencesForActivationRoot(input);
|
|
77
136
|
}
|
|
78
137
|
function resolveWatchProfileRootsForActivationRoot(activationRoot, homeDir = getCliHomeDir()) {
|
|
79
|
-
const
|
|
138
|
+
const references = findInstalledHookReferencesForActivationRoot({
|
|
80
139
|
activationRoot,
|
|
81
140
|
homeDir
|
|
82
|
-
})
|
|
83
|
-
|
|
141
|
+
});
|
|
142
|
+
const partitioned = partitionSharedActivationRootHookReferences(references);
|
|
143
|
+
const attachedProfileRoots = partitioned.attached.map((reference) => path.resolve(reference.openclawHome));
|
|
144
|
+
return {
|
|
145
|
+
attachedProfileRoots: attachedProfileRoots.length > 0 || references.length > 0
|
|
146
|
+
? attachedProfileRoots
|
|
147
|
+
: undefined,
|
|
148
|
+
halfAttachedReferences: partitioned.halfAttached
|
|
149
|
+
};
|
|
84
150
|
}
|
|
85
151
|
function assertActivationRootPurgeIsNotShared(input) {
|
|
86
152
|
const sharedReferences = findOtherInstalledHookReferencesForActivationRoot({
|
|
@@ -90,16 +156,36 @@ function assertActivationRootPurgeIsNotShared(input) {
|
|
|
90
156
|
if (sharedReferences.length === 0) {
|
|
91
157
|
return;
|
|
92
158
|
}
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
159
|
+
const partitioned = partitionSharedActivationRootHookReferences(sharedReferences);
|
|
160
|
+
const attachedProfiles = formatSharedActivationRootReferenceList(partitioned.attached, {
|
|
161
|
+
includeInspection: true,
|
|
162
|
+
includeProof: true
|
|
163
|
+
});
|
|
164
|
+
const halfAttachedProfiles = formatSharedActivationRootReferenceList(partitioned.halfAttached, {
|
|
165
|
+
includeInspection: true,
|
|
166
|
+
includeProof: true
|
|
167
|
+
});
|
|
168
|
+
throw new Error(partitioned.attached.length > 0
|
|
169
|
+
? [
|
|
170
|
+
`Refusing to purge activation root ${path.resolve(input.activationRoot)} because another installed OpenClaw profile still points at it.`,
|
|
171
|
+
"Other attached profiles:",
|
|
172
|
+
attachedProfiles,
|
|
173
|
+
...(partitioned.halfAttached.length === 0
|
|
174
|
+
? []
|
|
175
|
+
: [
|
|
176
|
+
"Other half-attached profiles:",
|
|
177
|
+
halfAttachedProfiles
|
|
178
|
+
]),
|
|
179
|
+
"Use uninstall --keep-data or detach on this profile first, then remove or repair the remaining profile hooks before purging shared brain data.",
|
|
180
|
+
"For Eagle dogfood, prefer its own activation root so CormorantAI stays untouched."
|
|
181
|
+
].join("\n")
|
|
182
|
+
: [
|
|
183
|
+
`Refusing to purge activation root ${path.resolve(input.activationRoot)} because another OpenClaw profile still points at it, but its hook is not loadable yet.`,
|
|
184
|
+
"Other half-attached profiles:",
|
|
185
|
+
halfAttachedProfiles,
|
|
186
|
+
"Repair or remove those half-attached profile hooks before purging shared brain data so status stays explicit instead of drifting into a missing-root surprise.",
|
|
187
|
+
"For Eagle dogfood, prefer its own activation root so CormorantAI stays untouched."
|
|
188
|
+
].join("\n"));
|
|
103
189
|
}
|
|
104
190
|
function formatInstallOpenClawHomeSource(source) {
|
|
105
191
|
switch (source) {
|
|
@@ -541,6 +627,8 @@ function formatCompactList(values, maxItems = 2, maxLength = 64) {
|
|
|
541
627
|
return values.length > maxItems ? `${visible.join("|")}+${values.length - maxItems}more` : visible.join("|");
|
|
542
628
|
}
|
|
543
629
|
const SERVICE_RISK_FINDING_CODES = new Set([
|
|
630
|
+
"hook_desynced",
|
|
631
|
+
"current_profile_not_attached",
|
|
544
632
|
"activation_broken_install",
|
|
545
633
|
"activation_stale_incomplete",
|
|
546
634
|
"active_missing",
|
|
@@ -551,6 +639,7 @@ const SERVICE_RISK_FINDING_CODES = new Set([
|
|
|
551
639
|
"serve_path_route_evidence_missing"
|
|
552
640
|
]);
|
|
553
641
|
const DEGRADED_BRAIN_FINDING_CODES = new Set([
|
|
642
|
+
"attachment_scope_partial",
|
|
554
643
|
"bootstrap_waiting_for_first_export",
|
|
555
644
|
"serve_path_unprobed",
|
|
556
645
|
"brain_context_kernel_only",
|
|
@@ -586,34 +675,180 @@ const TEACHER_NO_OP_MESSAGES = {
|
|
|
586
675
|
unavailable: "the latest cycle is not visible from the current operator snapshot"
|
|
587
676
|
};
|
|
588
677
|
function summarizeStatusInstallHook(openclawHome) {
|
|
678
|
+
const hook = inspectOpenClawBrainHookStatus(openclawHome);
|
|
679
|
+
return {
|
|
680
|
+
state: hook.installState === "unverified" ? "unknown" : hook.installState,
|
|
681
|
+
detail: hook.detail
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
function summarizeStatusHookLoad(installHook, status) {
|
|
685
|
+
return {
|
|
686
|
+
installState: installHook.state === "unknown" ? "unverified" : installHook.state,
|
|
687
|
+
loadProof: status.hook.loadProof,
|
|
688
|
+
detail: status.hook.detail
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
function summarizeStatusConfigLoad(openclawHome) {
|
|
589
692
|
if (openclawHome === null) {
|
|
590
693
|
return {
|
|
591
|
-
state: "
|
|
592
|
-
detail: "
|
|
694
|
+
state: "unverified",
|
|
695
|
+
detail: "plugin allowlist state is unknown from activation-root-only status; pin --openclaw-home to prove config load state"
|
|
593
696
|
};
|
|
594
697
|
}
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
const runtimeGuardPath = path.join(extensionDir, "runtime-guard.js");
|
|
598
|
-
const manifestPath = path.join(extensionDir, "openclaw.plugin.json");
|
|
599
|
-
if (existsSync(indexPath) && existsSync(runtimeGuardPath) && existsSync(manifestPath)) {
|
|
698
|
+
const allowlist = inspectOpenClawBrainPluginAllowlist(openclawHome);
|
|
699
|
+
if (allowlist.state === "blocked") {
|
|
600
700
|
return {
|
|
601
|
-
state: "
|
|
602
|
-
detail:
|
|
701
|
+
state: "blocked",
|
|
702
|
+
detail: allowlist.detail
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
if (allowlist.state === "invalid") {
|
|
706
|
+
return {
|
|
707
|
+
state: "invalid",
|
|
708
|
+
detail: allowlist.detail
|
|
603
709
|
};
|
|
604
710
|
}
|
|
605
711
|
return {
|
|
606
|
-
state: "
|
|
607
|
-
detail:
|
|
712
|
+
state: "allows_load",
|
|
713
|
+
detail: allowlist.detail
|
|
608
714
|
};
|
|
609
715
|
}
|
|
610
|
-
function
|
|
716
|
+
function summarizeStatusHookFilesState(installHook) {
|
|
717
|
+
if (installHook.state === "installed" || installHook.state === "blocked_by_allowlist") {
|
|
718
|
+
return "present";
|
|
719
|
+
}
|
|
720
|
+
if (installHook.state === "not_installed") {
|
|
721
|
+
return "missing";
|
|
722
|
+
}
|
|
723
|
+
return "unverified";
|
|
724
|
+
}
|
|
725
|
+
function summarizeStatusAttachmentWatcher(status) {
|
|
726
|
+
if (status.passiveLearning.watchState === "watching") {
|
|
727
|
+
return "alive";
|
|
728
|
+
}
|
|
729
|
+
if (status.passiveLearning.watchState === "stale_snapshot") {
|
|
730
|
+
return "stale";
|
|
731
|
+
}
|
|
732
|
+
return "not_visible";
|
|
733
|
+
}
|
|
734
|
+
function formatAttachedProfileIdentity(reference) {
|
|
735
|
+
const profileLabel = reference.inspection.profileId ?? "current_profile";
|
|
736
|
+
return `${profileLabel}@${shortenPath(path.resolve(reference.openclawHome))}`;
|
|
737
|
+
}
|
|
738
|
+
function buildStatusAttachedProfileTruth(input) {
|
|
739
|
+
const resolvedOpenClawHome = canonicalizeExistingCliPath(input.reference.openclawHome);
|
|
740
|
+
const installHook = summarizeStatusInstallHook(resolvedOpenClawHome);
|
|
741
|
+
const configLoad = summarizeStatusConfigLoad(resolvedOpenClawHome);
|
|
742
|
+
const runtimeProof = input.runtimeProofByHome.get(resolvedOpenClawHome);
|
|
743
|
+
const currentOpenClawHome = input.currentOpenClawHome === null ? null : canonicalizeExistingCliPath(input.currentOpenClawHome);
|
|
744
|
+
const hookFiles = summarizeStatusHookFilesState(installHook);
|
|
611
745
|
return {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
746
|
+
label: formatAttachedProfileIdentity(input.reference),
|
|
747
|
+
openclawHome: resolvedOpenClawHome,
|
|
748
|
+
current: currentOpenClawHome !== null && currentOpenClawHome === resolvedOpenClawHome,
|
|
749
|
+
hookFiles,
|
|
750
|
+
configLoad: configLoad.state,
|
|
751
|
+
runtimeLoad: input.runtimeProofError !== null
|
|
752
|
+
? "proof_error"
|
|
753
|
+
: hookFiles !== "present"
|
|
754
|
+
? "not_proven"
|
|
755
|
+
: runtimeProof !== undefined
|
|
756
|
+
? "proven"
|
|
757
|
+
: "not_proven",
|
|
758
|
+
runtimeLoadedAt: runtimeProof?.loadedAt ?? null
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
function buildCurrentStatusAttachmentTruthDetail(input) {
|
|
762
|
+
const attachedProfileCount = input.attachedProfiles.length;
|
|
763
|
+
const attachedProfilesDetail = attachedProfileCount === 0
|
|
764
|
+
? "no attached profiles were discovered for this activation root"
|
|
765
|
+
: `attached profile set has ${attachedProfileCount} discoverable ${attachedProfileCount === 1 ? "profile" : "profiles"}`;
|
|
766
|
+
if (input.openclawHome === null) {
|
|
767
|
+
return ("current-profile hook/config/runtime load truth is unverified without --openclaw-home; " +
|
|
768
|
+
`${attachedProfilesDetail}`);
|
|
769
|
+
}
|
|
770
|
+
if (input.runtimeProofError !== null) {
|
|
771
|
+
return (`current profile ${input.currentProfileLabel} could not prove runtime load because ${shortenPath(input.runtimeProofPath)} is unreadable: ${input.runtimeProofError}; ` +
|
|
772
|
+
`${attachedProfilesDetail}`);
|
|
773
|
+
}
|
|
774
|
+
const hookDetail = input.hookFiles === "present"
|
|
775
|
+
? "hook files are present"
|
|
776
|
+
: input.hookFiles === "missing"
|
|
777
|
+
? "hook files are missing"
|
|
778
|
+
: "hook files are unverified";
|
|
779
|
+
const configDetail = input.configLoad === "allows_load"
|
|
780
|
+
? "config allows load"
|
|
781
|
+
: input.configLoad === "blocked"
|
|
782
|
+
? "config blocks load"
|
|
783
|
+
: input.configLoad === "invalid"
|
|
784
|
+
? "config is invalid for load proof"
|
|
785
|
+
: "config load is unverified";
|
|
786
|
+
const runtimeDetail = input.runtimeLoad === "proven"
|
|
787
|
+
? "runtime load is proven"
|
|
788
|
+
: input.runtimeLoad === "not_proven"
|
|
789
|
+
? "runtime load is not yet proven"
|
|
790
|
+
: input.runtimeLoad === "proof_error"
|
|
791
|
+
? "runtime load proof is broken"
|
|
792
|
+
: "runtime load is unverified";
|
|
793
|
+
const watcherDetail = input.watcher === "alive"
|
|
794
|
+
? "watcher is alive"
|
|
795
|
+
: input.watcher === "stale"
|
|
796
|
+
? "watcher visibility is stale"
|
|
797
|
+
: "watcher is not visible";
|
|
798
|
+
return `current profile ${input.currentProfileLabel}: ${hookDetail}, ${configDetail}, ${runtimeDetail}, ${watcherDetail}; ${attachedProfilesDetail}`;
|
|
799
|
+
}
|
|
800
|
+
function summarizeStatusAttachmentTruth(input) {
|
|
801
|
+
const runtimeProofs = listOpenClawProfileRuntimeLoadProofs(input.activationRoot);
|
|
802
|
+
const runtimeProofByHome = new Map();
|
|
803
|
+
for (const proof of runtimeProofs.proofs?.profiles ?? []) {
|
|
804
|
+
runtimeProofByHome.set(canonicalizeExistingCliPath(proof.openclawHome), {
|
|
805
|
+
loadedAt: proof.loadedAt
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
const attachedProfiles = findInstalledHookReferencesForActivationRoot({
|
|
809
|
+
activationRoot: input.activationRoot
|
|
810
|
+
}).map((reference) => buildStatusAttachedProfileTruth({
|
|
811
|
+
reference,
|
|
812
|
+
currentOpenClawHome: input.openclawHome,
|
|
813
|
+
runtimeProofByHome,
|
|
814
|
+
runtimeProofError: runtimeProofs.error
|
|
815
|
+
}));
|
|
816
|
+
const currentInspection = input.openclawHome === null ? null : inspectOpenClawHome(input.openclawHome);
|
|
817
|
+
const currentProfileLabel = currentInspection?.profileId ?? "current_profile";
|
|
818
|
+
const installHook = summarizeStatusInstallHook(input.openclawHome);
|
|
819
|
+
const configLoad = summarizeStatusConfigLoad(input.openclawHome);
|
|
820
|
+
const currentRuntimeProof = input.openclawHome === null ? undefined : runtimeProofByHome.get(canonicalizeExistingCliPath(input.openclawHome));
|
|
821
|
+
const hookFiles = summarizeStatusHookFilesState(installHook);
|
|
822
|
+
const watcher = summarizeStatusAttachmentWatcher(input.status);
|
|
823
|
+
const runtimeLoad = input.openclawHome === null
|
|
824
|
+
? "unverified"
|
|
825
|
+
: runtimeProofs.error !== null
|
|
826
|
+
? "proof_error"
|
|
827
|
+
: hookFiles !== "present"
|
|
828
|
+
? "not_proven"
|
|
829
|
+
: currentRuntimeProof !== undefined
|
|
830
|
+
? "proven"
|
|
831
|
+
: "not_proven";
|
|
832
|
+
return {
|
|
833
|
+
currentProfileLabel,
|
|
834
|
+
hookFiles,
|
|
835
|
+
configLoad: configLoad.state,
|
|
836
|
+
runtimeLoad,
|
|
837
|
+
watcher,
|
|
838
|
+
attachedProfiles,
|
|
839
|
+
runtimeProofPath: runtimeProofs.path,
|
|
840
|
+
runtimeProofError: runtimeProofs.error,
|
|
841
|
+
detail: buildCurrentStatusAttachmentTruthDetail({
|
|
842
|
+
openclawHome: input.openclawHome,
|
|
843
|
+
currentProfileLabel,
|
|
844
|
+
hookFiles,
|
|
845
|
+
configLoad: configLoad.state,
|
|
846
|
+
runtimeLoad,
|
|
847
|
+
watcher,
|
|
848
|
+
attachedProfiles,
|
|
849
|
+
runtimeProofPath: runtimeProofs.path,
|
|
850
|
+
runtimeProofError: runtimeProofs.error
|
|
851
|
+
})
|
|
617
852
|
};
|
|
618
853
|
}
|
|
619
854
|
function runOllamaProbe(args, baseUrl) {
|
|
@@ -988,6 +1223,28 @@ function formatStatusObservedDeltaTransition(delta) {
|
|
|
988
1223
|
}
|
|
989
1224
|
return `${delta.latestPackTransition.kind}:${delta.latestPackTransition.fromPackId ?? "none"}->${delta.latestPackTransition.toPackId}`;
|
|
990
1225
|
}
|
|
1226
|
+
function formatAttachedProfileTruthCompact(entry) {
|
|
1227
|
+
const currentPrefix = entry.current ? "*" : "";
|
|
1228
|
+
return (`${currentPrefix}${entry.label}` +
|
|
1229
|
+
`[hook=${entry.hookFiles} config=${entry.configLoad} runtime=${entry.runtimeLoad}` +
|
|
1230
|
+
`${entry.runtimeLoadedAt === null ? "" : `@${entry.runtimeLoadedAt}`}]`);
|
|
1231
|
+
}
|
|
1232
|
+
function formatAttachedProfileTruthCompactList(entries) {
|
|
1233
|
+
return entries.length === 0
|
|
1234
|
+
? "none"
|
|
1235
|
+
: formatCompactList(entries.map((entry) => formatAttachedProfileTruthCompact(entry)), 2, 80);
|
|
1236
|
+
}
|
|
1237
|
+
function formatAttachedProfileTruthDetailedList(entries) {
|
|
1238
|
+
return entries.length === 0
|
|
1239
|
+
? "none"
|
|
1240
|
+
: entries
|
|
1241
|
+
.map((entry) => `${entry.current ? "*" : ""}${entry.label}` +
|
|
1242
|
+
`[hook=${entry.hookFiles} config=${entry.configLoad} runtime=${entry.runtimeLoad} loadedAt=${entry.runtimeLoadedAt ?? "none"}]`)
|
|
1243
|
+
.join(" ");
|
|
1244
|
+
}
|
|
1245
|
+
function summarizeDisplayedStatus(status, installHook) {
|
|
1246
|
+
return installHook.state === "blocked_by_allowlist" ? "fail" : status.brainStatus.status;
|
|
1247
|
+
}
|
|
991
1248
|
function buildCompactStatusHeader(status, report, options) {
|
|
992
1249
|
const installHook = summarizeStatusInstallHook(options.openclawHome);
|
|
993
1250
|
const hookLoad = summarizeStatusHookLoad(installHook, status);
|
|
@@ -998,9 +1255,15 @@ function buildCompactStatusHeader(status, report, options) {
|
|
|
998
1255
|
const routeFn = summarizeStatusRouteFn(status, report);
|
|
999
1256
|
const alerts = summarizeStatusAlerts(report, options.providerConfig, embeddings, localLlm);
|
|
1000
1257
|
const liveModels = embeddings.models.length === 0 ? "none" : embeddings.models.join("|");
|
|
1258
|
+
const attachmentTruth = summarizeStatusAttachmentTruth({
|
|
1259
|
+
activationRoot: status.host.activationRoot,
|
|
1260
|
+
openclawHome: options.openclawHome,
|
|
1261
|
+
status
|
|
1262
|
+
});
|
|
1001
1263
|
return [
|
|
1002
1264
|
`lifecycle attach=${status.attachment.state} learner=${yesNo(status.passiveLearning.learnerRunning)} watch=${summarizeStatusWatchState(status)} export=${status.passiveLearning.exportState} promote=${summarizeStatusPromotionState(status)} serve=${summarizeStatusServeReality(status)}`,
|
|
1003
1265
|
`hook install=${hookLoad.installState} loadProof=${hookLoad.loadProof} detail=${hookLoad.detail}`,
|
|
1266
|
+
`attachTruth current=${attachmentTruth.currentProfileLabel} hook=${attachmentTruth.hookFiles} config=${attachmentTruth.configLoad} runtime=${attachmentTruth.runtimeLoad} watcher=${attachmentTruth.watcher} attachedSet=${formatAttachedProfileTruthCompactList(attachmentTruth.attachedProfiles)} why=${attachmentTruth.detail}`,
|
|
1004
1267
|
`passive firstExport=${yesNo(status.passiveLearning.firstExportOccurred)} backlog=${status.passiveLearning.backlogState} pending=${formatStatusNullableNumber(status.passiveLearning.pendingLive)}/${formatStatusNullableNumber(status.passiveLearning.pendingBackfill)}`,
|
|
1005
1268
|
`serving pack=${status.passiveLearning.currentServingPackId ?? "none"} lastExport=${status.passiveLearning.lastExportAt ?? "none"} lastPromotion=${status.passiveLearning.lastPromotionAt ?? "none"}`,
|
|
1006
1269
|
`timing ${formatStatusHotPathTiming(status.brainStatus.timing)}`,
|
|
@@ -1017,21 +1280,34 @@ function buildCompactStatusHeader(status, report, options) {
|
|
|
1017
1280
|
];
|
|
1018
1281
|
}
|
|
1019
1282
|
function formatCurrentProfileStatusSummary(status, report, targetInspection, options) {
|
|
1283
|
+
const installHook = summarizeStatusInstallHook(options.openclawHome);
|
|
1284
|
+
const displayedStatus = summarizeDisplayedStatus(status, installHook);
|
|
1020
1285
|
const embeddings = summarizeStatusEmbeddings(report, options.providerConfig);
|
|
1021
1286
|
const localLlm = summarizeStatusLocalLlm(options.providerConfig);
|
|
1022
1287
|
const liveModels = embeddings.models.length === 0 ? "none" : embeddings.models.join("|");
|
|
1288
|
+
const attachmentTruth = summarizeStatusAttachmentTruth({
|
|
1289
|
+
activationRoot: status.host.activationRoot,
|
|
1290
|
+
openclawHome: options.openclawHome,
|
|
1291
|
+
status
|
|
1292
|
+
});
|
|
1023
1293
|
const profileIdSuffix = status.profile.profileId === null ? "" : ` id=${status.profile.profileId}`;
|
|
1024
1294
|
const targetLine = targetInspection === null
|
|
1025
1295
|
? `target activation=${status.host.activationRoot} source=activation_root_only`
|
|
1026
1296
|
: `target activation=${status.host.activationRoot} ${formatOpenClawTargetLine(targetInspection)} hook=${shortenPath(path.join(targetInspection.openclawHome, "extensions", "openclawbrain", "index.ts"))}`;
|
|
1027
1297
|
return [
|
|
1028
|
-
`STATUS ${
|
|
1298
|
+
`STATUS ${displayedStatus}`,
|
|
1029
1299
|
...buildCompactStatusHeader(status, report, options),
|
|
1030
1300
|
`answer ${status.brain.summary}`,
|
|
1031
1301
|
targetLine,
|
|
1032
1302
|
...(targetInspection === null ? [] : [`preflight ${formatOpenClawTargetExplanation(targetInspection)}`]),
|
|
1303
|
+
`next ${buildStatusNextStep(status, report, {
|
|
1304
|
+
openclawHome: options.openclawHome,
|
|
1305
|
+
installHook
|
|
1306
|
+
})}`,
|
|
1033
1307
|
`host runtime=${status.host.runtimeOwner} activation=${status.host.activationRoot}`,
|
|
1034
1308
|
`profile selector=${status.profile.selector}${profileIdSuffix} attachment=${status.attachment.state} policy=${status.attachment.policyMode}`,
|
|
1309
|
+
`attachTruth current=${attachmentTruth.currentProfileLabel} hook=${attachmentTruth.hookFiles} config=${attachmentTruth.configLoad} runtime=${attachmentTruth.runtimeLoad} watcher=${attachmentTruth.watcher} detail=${attachmentTruth.detail}`,
|
|
1310
|
+
`attachedSet ${formatAttachedProfileTruthDetailedList(attachmentTruth.attachedProfiles)} proofPath=${shortenPath(attachmentTruth.runtimeProofPath)} proofError=${attachmentTruth.runtimeProofError ?? "none"}`,
|
|
1035
1311
|
`manyProfile surface=${report.manyProfile.operatorSurface} policy=${report.manyProfile.declaredAttachmentPolicy} intent=${report.manyProfile.sameGatewayIntent} checkedProof=${report.manyProfile.checkedInProofTopology} sameGatewayProof=${yesNo(report.manyProfile.sameGatewayProof)} sharedWriteProof=${yesNo(report.manyProfile.sharedWriteSafetyProof)}`,
|
|
1036
1312
|
`activation state=${status.brainStatus.activationState} detail=${status.brain.detail}`,
|
|
1037
1313
|
`brain pack=${status.brain.activePackId ?? "none"} state=${status.brain.state} init=${status.brain.initMode ?? "unknown"} routeFreshness=${status.brain.routeFreshness} lastPromotion=${status.brain.lastPromotionAt ?? "none"} router=${status.brain.routerIdentity ?? "none"}`,
|
|
@@ -1472,6 +1748,19 @@ function buildCleanupRestartGuidance(restart) {
|
|
|
1472
1748
|
}
|
|
1473
1749
|
function buildStatusNextStep(status, report, options) {
|
|
1474
1750
|
const activationRootArg = quoteShellArg(status.host.activationRoot);
|
|
1751
|
+
const attachmentTruth = summarizeStatusAttachmentTruth({
|
|
1752
|
+
activationRoot: status.host.activationRoot,
|
|
1753
|
+
openclawHome: options.openclawHome,
|
|
1754
|
+
status
|
|
1755
|
+
});
|
|
1756
|
+
if (options.installHook.state === "blocked_by_allowlist") {
|
|
1757
|
+
if (options.openclawHome === null) {
|
|
1758
|
+
return "Repair the OpenClaw plugin allowlist mismatch before trusting serve-path status again.";
|
|
1759
|
+
}
|
|
1760
|
+
return ("Repair the OpenClaw plugin allowlist mismatch " +
|
|
1761
|
+
`(rerun ${buildInstallCommand(options.openclawHome)} or ${buildAttachCommand(options.openclawHome, status.host.activationRoot)}) ` +
|
|
1762
|
+
"before trusting serve-path status again.");
|
|
1763
|
+
}
|
|
1475
1764
|
if (status.brainStatus.activationState === "broken_install") {
|
|
1476
1765
|
return "Repair or replace the activation root before trusting serve-path status again.";
|
|
1477
1766
|
}
|
|
@@ -1484,12 +1773,21 @@ function buildStatusNextStep(status, report, options) {
|
|
|
1484
1773
|
if (options.openclawHome !== null && options.installHook.state === "not_installed") {
|
|
1485
1774
|
return `Run \`${buildInstallCommand(options.openclawHome)}\` before expecting this OpenClaw home to load the brain hook.`;
|
|
1486
1775
|
}
|
|
1776
|
+
if (options.openclawHome !== null &&
|
|
1777
|
+
attachmentTruth.hookFiles === "present" &&
|
|
1778
|
+
attachmentTruth.configLoad === "allows_load" &&
|
|
1779
|
+
attachmentTruth.runtimeLoad === "not_proven") {
|
|
1780
|
+
return "Restart the exact OpenClaw profile or wait for its next launch, then rerun status until runtime load becomes proven instead of assumed from on-disk state.";
|
|
1781
|
+
}
|
|
1487
1782
|
if (status.brainStatus.awaitingFirstExport) {
|
|
1488
1783
|
return `Let the attached OpenClaw profile emit a real export, then rerun \`openclawbrain status --activation-root ${activationRootArg}\`.`;
|
|
1489
1784
|
}
|
|
1490
1785
|
if (options.openclawHome === null) {
|
|
1491
1786
|
return `Pin \`--openclaw-home <path>\` when you need exact hook-install truth; activation-root-only status only proves this root's serve-path state.`;
|
|
1492
1787
|
}
|
|
1788
|
+
if (attachmentTruth.runtimeLoad === "proof_error") {
|
|
1789
|
+
return "Repair the runtime-load proof file before trusting attach truth again; status now knows the exact file that broke.";
|
|
1790
|
+
}
|
|
1493
1791
|
if (options.installHook.state === "installed" && status.brainStatus.serveState === "serving_active_pack") {
|
|
1494
1792
|
return "Check the OpenClaw startup log for the `[openclawbrain] BRAIN LOADED` breadcrumb when you need live hook-load proof.";
|
|
1495
1793
|
}
|
|
@@ -1503,8 +1801,10 @@ function buildStatusNextStep(status, report, options) {
|
|
|
1503
1801
|
return `Use \`openclawbrain status --activation-root ${activationRootArg} --detailed\` when you need the full lifecycle, serve-path, and backlog proof.`;
|
|
1504
1802
|
}
|
|
1505
1803
|
function formatHumanFriendlyStatus(status, report, targetInspection, options) {
|
|
1804
|
+
const installHook = summarizeStatusInstallHook(options.openclawHome);
|
|
1805
|
+
const displayedStatus = summarizeDisplayedStatus(status, installHook);
|
|
1506
1806
|
const lines = [
|
|
1507
|
-
`STATUS ${
|
|
1807
|
+
`STATUS ${displayedStatus}`,
|
|
1508
1808
|
...buildCompactStatusHeader(status, report, options),
|
|
1509
1809
|
...(targetInspection === null ? [] : [
|
|
1510
1810
|
`target ${formatOpenClawTargetLine(targetInspection)}`,
|
|
@@ -1512,7 +1812,7 @@ function formatHumanFriendlyStatus(status, report, targetInspection, options) {
|
|
|
1512
1812
|
]),
|
|
1513
1813
|
`next ${buildStatusNextStep(status, report, {
|
|
1514
1814
|
openclawHome: options.openclawHome,
|
|
1515
|
-
installHook
|
|
1815
|
+
installHook
|
|
1516
1816
|
})}`
|
|
1517
1817
|
];
|
|
1518
1818
|
return lines.join("\n");
|
|
@@ -2642,14 +2942,20 @@ function resolveCleanupLearnerServiceOutcome(activationRoot, openclawHome) {
|
|
|
2642
2942
|
activationRoot,
|
|
2643
2943
|
excludingOpenClawHome: openclawHome
|
|
2644
2944
|
});
|
|
2645
|
-
|
|
2945
|
+
const partitioned = partitionSharedActivationRootHookReferences(remainingProfiles);
|
|
2946
|
+
if (partitioned.attached.length > 0) {
|
|
2646
2947
|
const inspection = inspectManagedLearnerService(activationRoot);
|
|
2647
|
-
const attachedProfiles =
|
|
2948
|
+
const attachedProfiles = partitioned.attached
|
|
2648
2949
|
.map(({ openclawHome: profileHome }) => shortenPath(path.resolve(profileHome)))
|
|
2649
2950
|
.join(", ");
|
|
2951
|
+
const halfAttachedNote = partitioned.halfAttached.length === 0
|
|
2952
|
+
? ""
|
|
2953
|
+
: ` Half-attached hooks still point at this root but were not counted as attached: ${partitioned.halfAttached
|
|
2954
|
+
.map((reference) => `${shortenPath(path.resolve(reference.openclawHome))} (${summarizeSharedActivationRootReferenceProof(reference)})`)
|
|
2955
|
+
.join(", ")}.`;
|
|
2650
2956
|
return {
|
|
2651
2957
|
state: "preserved",
|
|
2652
|
-
detail: `Preserved the background learner service for ${path.resolve(activationRoot)} because other attached OpenClaw profiles still share this activation root: ${attachedProfiles}
|
|
2958
|
+
detail: `Preserved the background learner service for ${path.resolve(activationRoot)} because other attached OpenClaw profiles still share this activation root: ${attachedProfiles}.${halfAttachedNote}`,
|
|
2653
2959
|
plistPath: inspection.plistPath,
|
|
2654
2960
|
logPath: inspection.logPath,
|
|
2655
2961
|
configuredActivationRoot: inspection.configuredActivationRoot,
|
|
@@ -2657,9 +2963,14 @@ function resolveCleanupLearnerServiceOutcome(activationRoot, openclawHome) {
|
|
|
2657
2963
|
};
|
|
2658
2964
|
}
|
|
2659
2965
|
const outcome = removeManagedLearnerServiceForActivationRoot(activationRoot);
|
|
2966
|
+
const halfAttachedNote = partitioned.halfAttached.length === 0
|
|
2967
|
+
? ""
|
|
2968
|
+
: ` Ignored half-attached OpenClaw profile hooks that still point at this activation root because they do not prove serve-path attachment: ${partitioned.halfAttached
|
|
2969
|
+
.map((reference) => `${shortenPath(path.resolve(reference.openclawHome))} (${summarizeSharedActivationRootReferenceProof(reference)})`)
|
|
2970
|
+
.join(", ")}.`;
|
|
2660
2971
|
return {
|
|
2661
2972
|
state: outcome.state,
|
|
2662
|
-
detail: outcome.detail
|
|
2973
|
+
detail: `${outcome.detail}${halfAttachedNote}`,
|
|
2663
2974
|
plistPath: outcome.inspection.plistPath,
|
|
2664
2975
|
logPath: outcome.inspection.logPath,
|
|
2665
2976
|
configuredActivationRoot: outcome.inspection.configuredActivationRoot,
|
|
@@ -2952,6 +3263,8 @@ function runProfileHookAttachCommand(parsed) {
|
|
|
2952
3263
|
const manifestPath = path.join(extensionDir, "openclaw.plugin.json");
|
|
2953
3264
|
writeFileSync(manifestPath, buildExtensionPluginManifest(), "utf8");
|
|
2954
3265
|
steps.push(`Wrote manifest: ${manifestPath}`);
|
|
3266
|
+
const pluginConfigRepair = ensureOpenClawBrainPluginConfig(parsed.openclawHome);
|
|
3267
|
+
steps.push(pluginConfigRepair.detail);
|
|
2955
3268
|
const learnerService = ensureLifecycleLearnerService(parsed.activationRoot);
|
|
2956
3269
|
steps.push(learnerService.detail);
|
|
2957
3270
|
const brainFeedback = buildInstallBrainFeedbackSummary({
|
|
@@ -3084,6 +3397,7 @@ function runProfileHookAttachCommand(parsed) {
|
|
|
3084
3397
|
teacherBaseUrl: providerDefaults.defaults.teacherBaseUrl ?? null,
|
|
3085
3398
|
embedderBaseUrl: providerDefaults.defaults.embedderBaseUrl ?? null
|
|
3086
3399
|
},
|
|
3400
|
+
pluginConfigRepair,
|
|
3087
3401
|
learnerService,
|
|
3088
3402
|
brainFeedback: {
|
|
3089
3403
|
hookPath: brainFeedback.hookPath,
|
|
@@ -3164,6 +3478,46 @@ function readOpenClawJsonConfig(openclawHome) {
|
|
|
3164
3478
|
config
|
|
3165
3479
|
};
|
|
3166
3480
|
}
|
|
3481
|
+
function ensureOpenClawBrainPluginConfig(openclawHome) {
|
|
3482
|
+
const { path: openclawJsonPath, config } = readOpenClawJsonConfig(openclawHome);
|
|
3483
|
+
const plugins = readJsonObjectRecord(config.plugins);
|
|
3484
|
+
if (plugins === null) {
|
|
3485
|
+
return {
|
|
3486
|
+
path: openclawJsonPath,
|
|
3487
|
+
changed: false,
|
|
3488
|
+
detail: `Left ${shortenPath(openclawJsonPath)} unchanged because plugins.allow is not configured`
|
|
3489
|
+
};
|
|
3490
|
+
}
|
|
3491
|
+
if (!Object.prototype.hasOwnProperty.call(plugins, "allow")) {
|
|
3492
|
+
return {
|
|
3493
|
+
path: openclawJsonPath,
|
|
3494
|
+
changed: false,
|
|
3495
|
+
detail: `Left ${shortenPath(openclawJsonPath)} unchanged because plugins.allow is not configured`
|
|
3496
|
+
};
|
|
3497
|
+
}
|
|
3498
|
+
if (!Array.isArray(plugins.allow)) {
|
|
3499
|
+
return {
|
|
3500
|
+
path: openclawJsonPath,
|
|
3501
|
+
changed: false,
|
|
3502
|
+
detail: `Left ${shortenPath(openclawJsonPath)} unchanged because plugins.allow is not an array`
|
|
3503
|
+
};
|
|
3504
|
+
}
|
|
3505
|
+
if (plugins.allow.includes("openclawbrain")) {
|
|
3506
|
+
return {
|
|
3507
|
+
path: openclawJsonPath,
|
|
3508
|
+
changed: false,
|
|
3509
|
+
detail: `Verified ${shortenPath(openclawJsonPath)} plugins.allow already includes openclawbrain`
|
|
3510
|
+
};
|
|
3511
|
+
}
|
|
3512
|
+
plugins.allow = [...plugins.allow, "openclawbrain"];
|
|
3513
|
+
config.plugins = plugins;
|
|
3514
|
+
writeFileSync(openclawJsonPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
3515
|
+
return {
|
|
3516
|
+
path: openclawJsonPath,
|
|
3517
|
+
changed: true,
|
|
3518
|
+
detail: `Repaired ${shortenPath(openclawJsonPath)} plugins.allow by adding openclawbrain`
|
|
3519
|
+
};
|
|
3520
|
+
}
|
|
3167
3521
|
function scrubOpenClawBrainPluginConfig(openclawHome) {
|
|
3168
3522
|
const { path: openclawJsonPath, config } = readOpenClawJsonConfig(openclawHome);
|
|
3169
3523
|
const plugins = readJsonObjectRecord(config.plugins);
|
|
@@ -3256,12 +3610,30 @@ function summarizeKeptActivationData(activationRoot) {
|
|
|
3256
3610
|
function buildRestartGuidance(restart) {
|
|
3257
3611
|
return buildCleanupRestartGuidance(restart);
|
|
3258
3612
|
}
|
|
3613
|
+
function clearCleanupRuntimeLoadProof(activationRoot, openclawHome, steps) {
|
|
3614
|
+
if (activationRoot === null) {
|
|
3615
|
+
return;
|
|
3616
|
+
}
|
|
3617
|
+
try {
|
|
3618
|
+
const cleared = clearOpenClawProfileRuntimeLoadProof({
|
|
3619
|
+
activationRoot,
|
|
3620
|
+
openclawHome
|
|
3621
|
+
});
|
|
3622
|
+
if (cleared) {
|
|
3623
|
+
steps.push(`Cleared runtime-load proof for ${shortenPath(openclawHome)} from ${shortenPath(resolveAttachmentRuntimeLoadProofsPath(activationRoot))}`);
|
|
3624
|
+
}
|
|
3625
|
+
}
|
|
3626
|
+
catch (error) {
|
|
3627
|
+
steps.push(`Runtime-load proof cleanup failed open at ${shortenPath(resolveAttachmentRuntimeLoadProofsPath(activationRoot))}: ${toErrorMessage(error)}`);
|
|
3628
|
+
}
|
|
3629
|
+
}
|
|
3259
3630
|
function runDetachCommand(parsed) {
|
|
3260
3631
|
const steps = [];
|
|
3261
3632
|
validateOpenClawHome(parsed.openclawHome);
|
|
3262
3633
|
const targetInspection = inspectOpenClawHome(parsed.openclawHome);
|
|
3263
3634
|
steps.push(`Detected layout: ${formatOpenClawTargetExplanation(targetInspection)}`);
|
|
3264
3635
|
const activationRoot = resolveCleanupActivationRoot(parsed.openclawHome, parsed.activationRoot);
|
|
3636
|
+
clearCleanupRuntimeLoadProof(activationRoot, parsed.openclawHome, steps);
|
|
3265
3637
|
const learnerService = resolveCleanupLearnerServiceOutcome(activationRoot, parsed.openclawHome);
|
|
3266
3638
|
const pluginConfigCleanup = scrubOpenClawBrainPluginConfig(parsed.openclawHome);
|
|
3267
3639
|
const extensionDir = removeProfileHookup(parsed.openclawHome, steps);
|
|
@@ -3340,6 +3712,7 @@ function runUninstallCommand(parsed) {
|
|
|
3340
3712
|
const targetInspection = inspectOpenClawHome(parsed.openclawHome);
|
|
3341
3713
|
steps.push(`Detected layout: ${formatOpenClawTargetExplanation(targetInspection)}`);
|
|
3342
3714
|
const activationRoot = resolveCleanupActivationRoot(parsed.openclawHome, parsed.activationRoot);
|
|
3715
|
+
clearCleanupRuntimeLoadProof(activationRoot, parsed.openclawHome, steps);
|
|
3343
3716
|
if (parsed.dataMode === "purge" && activationRoot !== null) {
|
|
3344
3717
|
assertActivationRootPurgeIsNotShared({
|
|
3345
3718
|
activationRoot,
|
|
@@ -4565,14 +4938,23 @@ export async function createWatchCommandRuntime(input) {
|
|
|
4565
4938
|
const restoredSeenExportCount = restoredTeacherState.snapshot.state?.seenExportDigests.length ?? 0;
|
|
4566
4939
|
log(`Restored teacher snapshot: seen=${restoredSeenExportCount} artifacts=${restoredTeacherState.snapshot.teacher.artifactCount}`);
|
|
4567
4940
|
}
|
|
4568
|
-
const
|
|
4941
|
+
const resolvedWatchProfileScope = input.profileRoots === undefined
|
|
4569
4942
|
? resolveWatchProfileRootsForActivationRoot(activationRoot)
|
|
4570
|
-
:
|
|
4571
|
-
|
|
4943
|
+
: {
|
|
4944
|
+
attachedProfileRoots: [...new Set(input.profileRoots.map((root) => path.resolve(root)))],
|
|
4945
|
+
halfAttachedReferences: []
|
|
4946
|
+
};
|
|
4947
|
+
const resolvedProfileRoots = resolvedWatchProfileScope.attachedProfileRoots;
|
|
4948
|
+
if (input.profileRoots === undefined && resolvedProfileRoots !== undefined && resolvedProfileRoots.length > 0) {
|
|
4572
4949
|
log(`Session tail scope: attached OpenClaw home${resolvedProfileRoots.length === 1 ? "" : "s"} ${resolvedProfileRoots
|
|
4573
4950
|
.map((root) => shortenPath(root))
|
|
4574
4951
|
.join(", ")}`);
|
|
4575
4952
|
}
|
|
4953
|
+
if (input.profileRoots === undefined && resolvedWatchProfileScope.halfAttachedReferences.length > 0) {
|
|
4954
|
+
log(`Session tail scope skipped half-attached OpenClaw home${resolvedWatchProfileScope.halfAttachedReferences.length === 1 ? "" : "s"} ${resolvedWatchProfileScope.halfAttachedReferences
|
|
4955
|
+
.map((reference) => `${shortenPath(path.resolve(reference.openclawHome))} (${summarizeSharedActivationRootReferenceProof(reference)})`)
|
|
4956
|
+
.join(", ")}`);
|
|
4957
|
+
}
|
|
4576
4958
|
let restoredCursor = loadWatchSessionTailCursor(sessionTailCursorPath);
|
|
4577
4959
|
let localSessionTail;
|
|
4578
4960
|
try {
|
|
@@ -5097,6 +5479,10 @@ export function runOperatorCli(argv = process.argv.slice(2)) {
|
|
|
5097
5479
|
const operatorInput = {
|
|
5098
5480
|
...statusOrRollback.input,
|
|
5099
5481
|
activationRoot,
|
|
5482
|
+
openclawHome: statusOrRollback.openclawHome,
|
|
5483
|
+
...(targetInspection?.profileId === null || targetInspection?.profileId === undefined
|
|
5484
|
+
? {}
|
|
5485
|
+
: { profileId: targetInspection.profileId }),
|
|
5100
5486
|
teacherSnapshotPath: resolveOperatorTeacherSnapshotPath(activationRoot, statusOrRollback.input.teacherSnapshotPath)
|
|
5101
5487
|
};
|
|
5102
5488
|
const status = describeCurrentProfileBrainStatus(operatorInput);
|