@openclawbrain/cli 0.4.13 → 0.4.15

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.
@@ -0,0 +1,217 @@
1
+ const CHANGE_REASON_LABELS = {
2
+ install_identity: "plugin install identity changed",
3
+ install_layout: "authoritative install layout changed",
4
+ hook_path: "hook path changed",
5
+ hook_state: "hook install state changed",
6
+ loadability: "hook loadability changed",
7
+ activation_root: "pinned activation root changed",
8
+ loader_source: "loader source changed",
9
+ runtime_guard_source: "runtime-guard source changed",
10
+ plugins_config: "OpenClaw plugins config changed",
11
+ };
12
+
13
+ function sameValue(left, right) {
14
+ return left === right;
15
+ }
16
+
17
+ function installIdentityOf(fingerprint) {
18
+ if (fingerprint?.selectedInstall === null || fingerprint?.selectedInstall === undefined) {
19
+ return null;
20
+ }
21
+ return JSON.stringify({
22
+ extensionDir: fingerprint.selectedInstall.extensionDir ?? null,
23
+ manifestId: fingerprint.selectedInstall.manifestId ?? null,
24
+ installId: fingerprint.selectedInstall.installId ?? null,
25
+ packageName: fingerprint.selectedInstall.packageName ?? null,
26
+ });
27
+ }
28
+
29
+ export function planOpenClawBrainConvergePluginAction(fingerprint) {
30
+ const selectedInstall = fingerprint?.selectedInstall ?? null;
31
+ if (selectedInstall === null) {
32
+ return {
33
+ action: "install",
34
+ packageSpec: "@openclawbrain/openclaw",
35
+ pluginId: "openclawbrain",
36
+ reason: "no authoritative OpenClawBrain plugin install was discovered for this OpenClaw home",
37
+ };
38
+ }
39
+ if (selectedInstall.installLayout !== "native_package_plugin") {
40
+ return {
41
+ action: "install",
42
+ packageSpec: "@openclawbrain/openclaw",
43
+ pluginId: "openclawbrain",
44
+ reason: "the authoritative install is still a generated shadow extension, so converge must install the split-package plugin lane",
45
+ };
46
+ }
47
+ return {
48
+ action: "update",
49
+ packageSpec: "@openclawbrain/openclaw",
50
+ pluginId: "openclawbrain",
51
+ reason: "refresh the existing split-package plugin install so install/upgrade/repair stays on one converge path",
52
+ };
53
+ }
54
+
55
+ export function diffOpenClawBrainConvergeRuntimeFingerprint(before, after) {
56
+ const reasons = [];
57
+ if (!sameValue(installIdentityOf(before), installIdentityOf(after))) {
58
+ reasons.push("install_identity");
59
+ }
60
+ if (!sameValue(before?.installLayout ?? null, after?.installLayout ?? null)) {
61
+ reasons.push("install_layout");
62
+ }
63
+ if (!sameValue(before?.hookPath ?? null, after?.hookPath ?? null)) {
64
+ reasons.push("hook_path");
65
+ }
66
+ if (!sameValue(before?.hookState ?? null, after?.hookState ?? null)) {
67
+ reasons.push("hook_state");
68
+ }
69
+ if (!sameValue(before?.loadability ?? null, after?.loadability ?? null)) {
70
+ reasons.push("loadability");
71
+ }
72
+ if (!sameValue(before?.activationRoot ?? null, after?.activationRoot ?? null)) {
73
+ reasons.push("activation_root");
74
+ }
75
+ if (!sameValue(before?.loaderSource ?? null, after?.loaderSource ?? null)) {
76
+ reasons.push("loader_source");
77
+ }
78
+ if (!sameValue(before?.runtimeGuardSource ?? null, after?.runtimeGuardSource ?? null)) {
79
+ reasons.push("runtime_guard_source");
80
+ }
81
+ if (!sameValue(before?.pluginsConfig ?? null, after?.pluginsConfig ?? null)) {
82
+ reasons.push("plugins_config");
83
+ }
84
+ return {
85
+ changed: reasons.length > 0,
86
+ reasons,
87
+ };
88
+ }
89
+
90
+ export function describeOpenClawBrainConvergeChangeReasons(reasons) {
91
+ const normalized = reasons
92
+ .map((reason) => CHANGE_REASON_LABELS[reason] ?? reason)
93
+ .filter((reason, index, values) => values.indexOf(reason) === index);
94
+ return normalized.length === 0 ? "no runtime-affecting install changes detected" : normalized.join("; ");
95
+ }
96
+
97
+ export function buildOpenClawBrainConvergeRestartPlan(input) {
98
+ const changeReasons = input.changeReasons ?? [];
99
+ if (changeReasons.length === 0) {
100
+ return {
101
+ required: false,
102
+ automatic: false,
103
+ reason: "no_runtime_affecting_changes",
104
+ detail: "Skipped gateway restart because converge did not change plugin files, hook wiring, or the pinned activation root.",
105
+ };
106
+ }
107
+ if (input.profileName === null) {
108
+ return {
109
+ required: true,
110
+ automatic: false,
111
+ reason: "exact_profile_unresolved",
112
+ detail: "Restart is still required because converge changed runtime-affecting install state, but install could not infer an exact OpenClaw profile token for automatic restart.",
113
+ };
114
+ }
115
+ return {
116
+ required: true,
117
+ automatic: true,
118
+ reason: "runtime_affecting_changes_detected",
119
+ detail: `Restart is required because converge changed runtime-affecting install state for profile ${input.profileName}.`,
120
+ };
121
+ }
122
+
123
+ export function classifyOpenClawBrainConvergeVerification(input) {
124
+ const warnings = [];
125
+ const blockingReasons = [];
126
+ const installLayout = input.installLayout ?? null;
127
+ const installState = input.installState ?? "unknown";
128
+ const loadability = input.loadability ?? "unverified";
129
+ const displayedStatus = input.displayedStatus ?? "unknown";
130
+ const runtimeLoad = input.runtimeLoad ?? "unverified";
131
+ const loadProof = input.loadProof ?? "unverified";
132
+ const serveState = input.serveState ?? "unknown";
133
+ if (installLayout !== "native_package_plugin") {
134
+ blockingReasons.push("split-package native package plugin is not authoritative after converge");
135
+ }
136
+ if (installState !== "installed") {
137
+ blockingReasons.push(`hook install state is ${installState}`);
138
+ }
139
+ if (loadability !== "loadable") {
140
+ blockingReasons.push(`hook loadability is ${loadability}`);
141
+ }
142
+ if (displayedStatus === "fail") {
143
+ blockingReasons.push("status still reports fail");
144
+ }
145
+ const runtimeTruthAlreadyProven = displayedStatus === "ok"
146
+ && runtimeLoad === "proven"
147
+ && loadProof === "status_probe_ready";
148
+ if (input.restartRequired === true && input.restartPerformed !== true && !runtimeTruthAlreadyProven) {
149
+ blockingReasons.push("restart is still required before runtime load can be trusted");
150
+ }
151
+ if (blockingReasons.length > 0) {
152
+ return {
153
+ state: "failed",
154
+ manualActionRequired: true,
155
+ blockingReasons,
156
+ warnings,
157
+ };
158
+ }
159
+ if (input.restartRequired === true && input.restartPerformed !== true && runtimeTruthAlreadyProven) {
160
+ warnings.push("automatic restart was not performed because install could not infer an exact OpenClaw profile token, but current status already proves runtime load");
161
+ }
162
+ if (displayedStatus !== "ok") {
163
+ warnings.push(`status is ${displayedStatus}`);
164
+ }
165
+ if (runtimeLoad !== "proven") {
166
+ warnings.push(`runtime load is ${runtimeLoad}`);
167
+ }
168
+ if (loadProof !== "status_probe_ready") {
169
+ warnings.push(`loadProof is ${loadProof}`);
170
+ }
171
+ if (serveState !== "serving_active_pack") {
172
+ warnings.push(`serve state is ${serveState}`);
173
+ }
174
+ if (input.routeFnAvailable !== true) {
175
+ warnings.push("route_fn availability is not yet proven");
176
+ }
177
+ if (input.awaitingFirstExport === true) {
178
+ warnings.push("the attached profile has not emitted its first export yet");
179
+ }
180
+ return {
181
+ state: warnings.length > 0 ? "warning" : "healthy",
182
+ manualActionRequired: false,
183
+ blockingReasons,
184
+ warnings,
185
+ };
186
+ }
187
+
188
+ export function finalizeOpenClawBrainConvergeResult(input) {
189
+ const warnings = [...(input.warnings ?? []), ...(input.verification?.warnings ?? [])];
190
+ const uniqueWarnings = warnings.filter((warning, index, values) => values.indexOf(warning) === index);
191
+ if (input.stepFailure !== null && input.stepFailure !== undefined) {
192
+ return {
193
+ verdict: "failed",
194
+ why: input.stepFailure,
195
+ warnings: uniqueWarnings,
196
+ };
197
+ }
198
+ if (input.verification?.state === "failed") {
199
+ return {
200
+ verdict: input.verification.manualActionRequired ? "manual_action_required" : "failed",
201
+ why: input.verification.blockingReasons.join("; "),
202
+ warnings: uniqueWarnings,
203
+ };
204
+ }
205
+ if (input.verification?.state === "warning") {
206
+ return {
207
+ verdict: "converged_with_warnings",
208
+ why: input.verification.warnings.join("; "),
209
+ warnings: uniqueWarnings,
210
+ };
211
+ }
212
+ return {
213
+ verdict: "converged",
214
+ why: "plugin install, hook wiring, restart behavior, and status verification converged on the expected split-package state",
215
+ warnings: uniqueWarnings,
216
+ };
217
+ }
@@ -1,4 +1,4 @@
1
- import { type AlwaysOnLearningMaterializationJobV1 } from "./local-learner.js";
1
+ import { type AlwaysOnLearningMaterializationJobV1 } from "@openclawbrain/learner";
2
2
  import { type BrainServeHotPathTimingV1, type NormalizedEventExportV1, type RouteMode, type RuntimeCompileResponseV1 } from "@openclawbrain/contracts";
3
3
  import { type LearningSpinePgRouteUpdateLogEntryV1, type LearningSpineServeRouteBreadcrumbsV1, type LearningSpineServeRouteDecisionLogEntryV1, type PackDescriptor } from "@openclawbrain/pack-format";
4
4
  type CompileFailureLike = {
@@ -22,6 +22,7 @@ interface RuntimeTurnLike {
22
22
  createdAt?: string | null;
23
23
  sequenceStart?: number | null;
24
24
  maxContextBlocks?: number;
25
+ maxContextChars?: number;
25
26
  budgetStrategy?: "fixed_v1" | "empirical_v1";
26
27
  mode?: RouteMode;
27
28
  runtimeHints?: readonly string[];
@@ -1,6 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { rankContextBlocks } from "@openclawbrain/compiler";
3
- import { describeSparseFeedbackEventDispositions } from "./local-learner.js";
3
+ import { describeSparseFeedbackEventDispositions } from "@openclawbrain/learner";
4
4
  import { CONTRACT_IDS } from "@openclawbrain/contracts";
5
5
  import { appendLearningSpineLogEntry, buildLearningSpineLogId, loadActivationPointers, loadPack, loadPackFromActivation, readLearningSpineLogEntries } from "@openclawbrain/pack-format";
6
6
  function noteValue(notes, prefix) {
@@ -10,6 +10,7 @@ function noteValue(notes, prefix) {
10
10
  function roundNumber(value) {
11
11
  return Math.round(value * 10_000) / 10_000;
12
12
  }
13
+ const MAX_PERSISTED_ROUTE_DECISION_CANDIDATE_SET_IDS = 256;
13
14
  function softmax(values) {
14
15
  if (values.length === 0) {
15
16
  return [];
@@ -22,6 +23,48 @@ function softmax(values) {
22
23
  }
23
24
  return numerators.map((value) => roundNumber(value / denominator));
24
25
  }
26
+ function isPlainObject(value) {
27
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
28
+ return false;
29
+ }
30
+ const prototype = Object.getPrototypeOf(value);
31
+ return prototype === Object.prototype || prototype === null;
32
+ }
33
+ function compactStructuralSignalValue(value, depth = 0) {
34
+ if (value === null || typeof value === "boolean") {
35
+ return value;
36
+ }
37
+ if (typeof value === "number") {
38
+ return Number.isFinite(value) ? roundNumber(value) : undefined;
39
+ }
40
+ if (typeof value === "string") {
41
+ return value.length <= 256 ? value : `${value.slice(0, 256)}...`;
42
+ }
43
+ if (depth >= 3) {
44
+ return undefined;
45
+ }
46
+ if (Array.isArray(value)) {
47
+ const compacted = value
48
+ .slice(0, 32)
49
+ .map((entry) => compactStructuralSignalValue(entry, depth + 1))
50
+ .filter((entry) => entry !== undefined);
51
+ return compacted;
52
+ }
53
+ if (!isPlainObject(value)) {
54
+ return undefined;
55
+ }
56
+ const compactedEntries = Object.entries(value)
57
+ .slice(0, 32)
58
+ .flatMap(([key, entry]) => {
59
+ const compacted = compactStructuralSignalValue(entry, depth + 1);
60
+ return compacted === undefined ? [] : [[key, compacted]];
61
+ });
62
+ return compactedEntries.length > 0 ? Object.fromEntries(compactedEntries) : undefined;
63
+ }
64
+ function compactStructuralSignals(value) {
65
+ const compacted = compactStructuralSignalValue(value);
66
+ return compacted !== undefined && isPlainObject(compacted) ? compacted : null;
67
+ }
25
68
  function isStableKernelContextBlock(block) {
26
69
  if (block.id.includes(":event:") || block.id.includes(":teacher:")) {
27
70
  return false;
@@ -247,7 +290,7 @@ export function appendServeTimeRouteDecisionLog(input) {
247
290
  requestedBudget: {
248
291
  modeRequested: input.turn.mode ?? "heuristic",
249
292
  maxContextBlocks: effectiveMaxContextBlocks,
250
- maxContextChars: null
293
+ maxContextChars: input.turn.maxContextChars ?? null
251
294
  },
252
295
  actualBudget: {
253
296
  modeEffective: input.compileResult.ok ? input.compileResult.compileResponse.diagnostics.modeEffective : null,
@@ -255,28 +298,15 @@ export function appendServeTimeRouteDecisionLog(input) {
255
298
  selectedCharCount: input.compileResult.ok ? input.compileResult.compileResponse.diagnostics.selectedCharCount : 0,
256
299
  selectedTokenCount: input.compileResult.ok ? input.compileResult.compileResponse.diagnostics.selectedTokenCount : 0
257
300
  },
258
- candidateSetIds: ranked.map((entry) => entry.blockId),
301
+ candidateSetIds: ranked.slice(0, MAX_PERSISTED_ROUTE_DECISION_CANDIDATE_SET_IDS).map((entry) => entry.blockId),
259
302
  chosenContextIds: input.compileResult.ok ? input.compileResult.compileResponse.selectedContext.map((block) => block.id) : [],
260
303
  candidateScores: ranked.map((entry, index) => ({
261
304
  blockId: entry.blockId,
262
- source: entry.source,
263
305
  selected: selectedIds.has(entry.blockId),
264
- compactedFrom: entry.compactedFrom ?? [],
265
- matchedTokens: [...entry.matchedTokens],
266
- routingChannels: [...entry.routingChannels],
267
- channelScores: {
268
- graph: roundNumber(entry.channelScores.graph),
269
- shortTerm: roundNumber(entry.channelScores.shortTerm),
270
- vector: roundNumber(entry.channelScores.vector)
271
- },
272
- routeFnScore: roundNumber(entry.score),
273
306
  actionScore: roundNumber(entry.score),
274
- actionProbability: probabilities[index] ?? 0,
275
- actionLogProbability: probabilities[index] === undefined || probabilities[index] <= 0 ? null : roundNumber(Math.log(probabilities[index])),
276
- traversalScore: roundNumber(entry.traversalScore ?? 0),
277
- priority: entry.priority
307
+ actionProbability: probabilities[index] ?? 0
278
308
  })),
279
- structuralSignals: input.compileResult.ok ? input.compileResult.compileResponse.diagnostics.structuralSignals : null,
309
+ structuralSignals: compactStructuralSignals(input.compileResult.ok ? input.compileResult.compileResponse.structuralSignals : null),
280
310
  fallbackReason: serveFallbackReason(input.compileResult),
281
311
  hotPathTiming: input.compileResult.timing,
282
312
  kernelContextCount: selectedKernelContextIds.length,
@@ -458,4 +488,4 @@ export function appendLearningUpdateLogs(input) {
458
488
  pgRouteUpdate
459
489
  };
460
490
  }
461
- //# sourceMappingURL=learning-spine.js.map
491
+ //# sourceMappingURL=learning-spine.js.map
@@ -81,6 +81,35 @@ export interface CandidatePackPayloads {
81
81
  vectors: PackVectorsPayloadV1;
82
82
  router: RouterArtifactV1 | null;
83
83
  }
84
+ export interface TeacherObservationBindingStatsV1 {
85
+ totalObservationCount: number;
86
+ nonZeroObservationCount: number;
87
+ skippedZeroRewardCount: number;
88
+ accounting: {
89
+ exact: number;
90
+ heuristic: number;
91
+ unmatched: number;
92
+ ambiguous: number;
93
+ };
94
+ matched: {
95
+ exactDecisionId: number;
96
+ exactSelectionDigest: number;
97
+ turnCompileEventId: number;
98
+ legacyHeuristic: number;
99
+ };
100
+ unmatched: {
101
+ exactDecisionId: number;
102
+ exactSelectionDigest: number;
103
+ turnCompileEventId: number;
104
+ legacyHeuristic: number;
105
+ };
106
+ ambiguous: {
107
+ exactDecisionId: number;
108
+ exactSelectionDigest: number;
109
+ turnCompileEventId: number;
110
+ legacyHeuristic: number;
111
+ };
112
+ }
84
113
  export interface CandidatePackBuildResult {
85
114
  manifest: ArtifactManifestV1;
86
115
  payloads: CandidatePackPayloads;
@@ -91,6 +120,7 @@ export interface CandidatePackBuildResult {
91
120
  decisionLogCount: number;
92
121
  fallbackReason: string | null;
93
122
  updatedBaseline: BaselineStateV1 | null;
123
+ observationBindingStats: TeacherObservationBindingStatsV1 | null;
94
124
  };
95
125
  summary: {
96
126
  packId: string;