@openclawbrain/cli 0.4.25 → 0.4.27

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.
@@ -7,10 +7,123 @@ function isSeedAwaitingFirstPromotion(status) {
7
7
  function normalizeOptionalString(value) {
8
8
  return typeof value === "string" && value.trim().length > 0 ? value : null;
9
9
  }
10
+ function normalizeCount(value) {
11
+ return Number.isFinite(value) && value >= 0 ? Math.trunc(value) : 0;
12
+ }
10
13
  function formatOptionalFeedbackLatest(tracedLearning) {
11
14
  const latestLabel = normalizeOptionalString(tracedLearning?.feedbackSummary?.latestLabel);
12
15
  return latestLabel === null ? "" : ` latest=${latestLabel}`;
13
16
  }
17
+ function isLearningSurfaceVisible(tracedLearning) {
18
+ return tracedLearning?.present !== false;
19
+ }
20
+ function hasKnownAttributionCoverage(coverage) {
21
+ return coverage !== null &&
22
+ typeof coverage === "object" &&
23
+ !Array.isArray(coverage) &&
24
+ (coverage.visible === true ||
25
+ coverage.gatingVisible === true ||
26
+ Number.isFinite(coverage.completedWithoutEvaluationCount) ||
27
+ Number.isFinite(coverage.readyCount) ||
28
+ Number.isFinite(coverage.delayedCount) ||
29
+ Number.isFinite(coverage.budgetDeferredCount));
30
+ }
31
+ function readSupervisedTraceCount(tracedLearning) {
32
+ return normalizeCount(tracedLearning?.feedbackSummary?.supervisedTraceCount ?? tracedLearning?.supervisionCount);
33
+ }
34
+ function hasLoadedLearningSignal(tracedLearning) {
35
+ return normalizeOptionalString(tracedLearning?.materializedPackId) !== null ||
36
+ normalizeCount(tracedLearning?.routeTraceCount) > 0 ||
37
+ readSupervisedTraceCount(tracedLearning) > 0 ||
38
+ normalizeCount(tracedLearning?.routerUpdateCount) > 0;
39
+ }
40
+ function summarizeOperatorLearningFlow(tracedLearning) {
41
+ const surfaceVisible = isLearningSurfaceVisible(tracedLearning);
42
+ return {
43
+ harvested: surfaceVisible ? normalizeCount(tracedLearning?.teacherArtifactCount) : null,
44
+ eligible: hasKnownAttributionCoverage(tracedLearning?.attributionCoverage)
45
+ ? normalizeCount(tracedLearning?.attributionCoverage?.readyCount)
46
+ : null,
47
+ loaded: surfaceVisible ? hasLoadedLearningSignal(tracedLearning) : null,
48
+ pack: surfaceVisible ? normalizeOptionalString(tracedLearning?.materializedPackId) ?? "none" : null,
49
+ matched: surfaceVisible ? normalizeCount(tracedLearning?.routeTraceCount) : null,
50
+ supervised: surfaceVisible ? readSupervisedTraceCount(tracedLearning) : null,
51
+ updated: surfaceVisible ? normalizeCount(tracedLearning?.routerUpdateCount) : null
52
+ };
53
+ }
54
+ function formatKnownOperatorValue(value) {
55
+ return value === null ? "unknown" : String(value);
56
+ }
57
+ function summarizeOperatorLearningState(flow) {
58
+ if (flow.harvested === null &&
59
+ flow.eligible === null &&
60
+ flow.loaded === null &&
61
+ flow.matched === null &&
62
+ flow.supervised === null &&
63
+ flow.updated === null) {
64
+ return {
65
+ state: "learning-unknown",
66
+ detail: "learning stage truth is not visible in the current status surface"
67
+ };
68
+ }
69
+ if (flow.harvested > 0 &&
70
+ flow.eligible !== null &&
71
+ flow.eligible > 0 &&
72
+ flow.matched === 0 &&
73
+ flow.supervised === 0 &&
74
+ flow.updated === 0) {
75
+ return {
76
+ state: "stalled-learning",
77
+ detail: "harvested artifacts and eligible feedback are visible, but no matched routes, supervision, or router updates are visible"
78
+ };
79
+ }
80
+ if ((flow.matched ?? 0) > 0 || (flow.supervised ?? 0) > 0 || (flow.updated ?? 0) > 0) {
81
+ return {
82
+ state: "progress-visible",
83
+ detail: `matched=${flow.matched ?? 0} supervised=${flow.supervised ?? 0} updated=${flow.updated ?? 0}`
84
+ };
85
+ }
86
+ if (flow.harvested > 0 && flow.eligible === 0) {
87
+ return {
88
+ state: "harvested-not-yet-eligible",
89
+ detail: "teacher artifacts are visible, but no eligible feedback is queued yet"
90
+ };
91
+ }
92
+ if (flow.harvested > 0 && flow.eligible === null) {
93
+ return {
94
+ state: "harvested-eligibility-unknown",
95
+ detail: "teacher artifacts are visible, but eligible feedback truth is not surfaced yet"
96
+ };
97
+ }
98
+ if (flow.harvested === 0 && flow.eligible !== null && flow.eligible > 0) {
99
+ return {
100
+ state: "eligible-without-harvest",
101
+ detail: "eligible feedback is visible without harvested teacher artifacts"
102
+ };
103
+ }
104
+ if (flow.harvested === 0 && flow.eligible === 0) {
105
+ return {
106
+ state: "idle-no-eligible-feedback",
107
+ detail: "no harvested teacher artifacts or eligible feedback are visible"
108
+ };
109
+ }
110
+ return {
111
+ state: "learning-unknown",
112
+ detail: "learning stage truth is incomplete"
113
+ };
114
+ }
115
+ function summarizeOperatorDaemonState(teacher) {
116
+ if (teacher?.enabled !== true) {
117
+ return "daemon-disabled";
118
+ }
119
+ if (teacher?.healthy === true) {
120
+ return "healthy-daemon";
121
+ }
122
+ if (teacher?.healthy === false) {
123
+ return "degraded-daemon";
124
+ }
125
+ return "daemon-unknown";
126
+ }
14
127
  function formatOperatorFeedbackSummary({ tracedLearning }) {
15
128
  const routeTraceCount = tracedLearning?.feedbackSummary?.routeTraceCount ?? tracedLearning?.routeTraceCount ?? 0;
16
129
  const supervisedTraceCount = tracedLearning?.feedbackSummary?.supervisedTraceCount ?? tracedLearning?.supervisionCount ?? 0;
@@ -78,4 +191,21 @@ export function formatOperatorLearningPathSummary({ status, learningPath, traced
78
191
  ...detailParts
79
192
  ].join(" ");
80
193
  }
194
+ export function formatOperatorLearningFlowSummary({ tracedLearning }) {
195
+ const flow = summarizeOperatorLearningFlow(tracedLearning);
196
+ return [
197
+ `harvested=${formatKnownOperatorValue(flow.harvested)}`,
198
+ `eligible=${formatKnownOperatorValue(flow.eligible)}`,
199
+ `loaded=${flow.loaded === null ? "unknown" : flow.loaded ? "yes" : "no"}`,
200
+ `pack=${flow.pack ?? "unknown"}`,
201
+ `matched=${formatKnownOperatorValue(flow.matched)}`,
202
+ `supervised=${formatKnownOperatorValue(flow.supervised)}`,
203
+ `updated=${formatKnownOperatorValue(flow.updated)}`
204
+ ].join(" ");
205
+ }
206
+ export function formatOperatorLearningHealthSummary({ tracedLearning, teacher }) {
207
+ const flow = summarizeOperatorLearningFlow(tracedLearning);
208
+ const learning = summarizeOperatorLearningState(flow);
209
+ return `daemon=${summarizeOperatorDaemonState(teacher)} learning=${learning.state} detail=${learning.detail}`;
210
+ }
81
211
  export { formatOperatorAttributionCoverageSummary, formatOperatorFeedbackSummary, formatOperatorLearningAttributionSummary };
@@ -1,5 +1,5 @@
1
1
  import { type NormalizedEventExportV1, type TeacherSupervisionArtifactV1 } from "@openclawbrain/contracts";
2
- import type { LearningSpineServeRouteDecisionLogEntryV1 } from "@openclawbrain/pack-format";
2
+ import type { LearningSpineServeRouteDecisionLogEntryV1 } from "./local-learner.js";
3
3
  export interface TeacherLabelerRunInputV1 {
4
4
  normalizedEventExport: NormalizedEventExportV1;
5
5
  observedAt: string;
@@ -141,11 +141,14 @@ function buildPrompt(candidates, config) {
141
141
  payload
142
142
  ].join("\n");
143
143
  }
144
+ function isTeacherLabelerCandidateInteraction(interaction) {
145
+ return interaction.kind === "memory_compiled" || interaction.kind === "message_delivered";
146
+ }
144
147
  function collectCandidates(input, config) {
145
148
  const decisions = [...(input.serveTimeDecisions ?? [])].sort((left, right) => Date.parse(right.recordedAt) - Date.parse(left.recordedAt));
146
149
  const matchServeTimeDecision = createServeTimeDecisionMatcher(decisions);
147
150
  return input.normalizedEventExport.interactionEvents
148
- .filter((interaction) => interaction.kind === "memory_compiled")
151
+ .filter((interaction) => isTeacherLabelerCandidateInteraction(interaction))
149
152
  .sort((left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt))
150
153
  .map((interaction) => {
151
154
  const decision = matchServeTimeDecision(interaction);
@@ -258,6 +258,169 @@ function buildDerivedAttributionCoverage(db) {
258
258
  : `completed_without_evaluation=${completedWithoutEvaluationCount}; ready=${readyCount}, delayed=${delayedCount}, budget_deferred=${budgetDeferredCount}`
259
259
  };
260
260
  }
261
+ function loadJsonFile(pathname) {
262
+ if (!existsSync(pathname)) {
263
+ return null;
264
+ }
265
+ try {
266
+ return JSON.parse(readFileSync(pathname, "utf8"));
267
+ }
268
+ catch {
269
+ return null;
270
+ }
271
+ }
272
+ function resolveActivePackPaths(activationRoot) {
273
+ const pointers = toRecord(loadJsonFile(path.join(path.resolve(activationRoot), "activation-pointers.json")));
274
+ const active = toRecord(pointers?.active);
275
+ const packRootDir = normalizeOptionalString(active?.packRootDir)
276
+ ?? (normalizeOptionalString(active?.packId) === null
277
+ ? null
278
+ : path.join(path.resolve(activationRoot), "packs", String(active.packId)));
279
+ const manifestPath = normalizeOptionalString(active?.manifestPath)
280
+ ?? (packRootDir === null ? null : path.join(packRootDir, "manifest.json"));
281
+ return {
282
+ packRootDir,
283
+ manifestPath
284
+ };
285
+ }
286
+ function buildActivePackFeedbackSummary(activationRoot) {
287
+ const active = resolveActivePackPaths(activationRoot);
288
+ if (active.packRootDir === null || active.manifestPath === null) {
289
+ return null;
290
+ }
291
+ const manifest = toRecord(loadJsonFile(active.manifestPath));
292
+ const runtimeAssets = toRecord(manifest?.runtimeAssets);
293
+ const router = toRecord(runtimeAssets?.router);
294
+ const routerArtifactPath = normalizeOptionalString(router?.artifactPath);
295
+ if (routerArtifactPath === null) {
296
+ return null;
297
+ }
298
+ const routerPath = path.isAbsolute(routerArtifactPath)
299
+ ? routerArtifactPath
300
+ : path.join(active.packRootDir, routerArtifactPath);
301
+ const routerArtifact = toRecord(loadJsonFile(routerPath));
302
+ const traces = Array.isArray(routerArtifact?.traces) ? routerArtifact.traces : [];
303
+ const verdictCounts = {
304
+ helpfulCount: 0,
305
+ irrelevantCount: 0,
306
+ harmfulCount: 0
307
+ };
308
+ for (const trace of traces) {
309
+ const traceRecord = toRecord(trace);
310
+ if (normalizeOptionalString(traceRecord?.supervisionKind) === "route_trace") {
311
+ continue;
312
+ }
313
+ const verdict = classifyContextFeedbackVerdict(Number(traceRecord?.reward ?? 0));
314
+ if (verdict === "helpful") {
315
+ verdictCounts.helpfulCount += 1;
316
+ }
317
+ else if (verdict === "harmful") {
318
+ verdictCounts.harmfulCount += 1;
319
+ }
320
+ else {
321
+ verdictCounts.irrelevantCount += 1;
322
+ }
323
+ }
324
+ const routeTraceCount = normalizeCount(routerArtifact?.training?.routeTraceCount) || traces.length;
325
+ const supervisedTraceCount = verdictCounts.helpfulCount + verdictCounts.irrelevantCount + verdictCounts.harmfulCount;
326
+ if (routeTraceCount === 0 && supervisedTraceCount === 0) {
327
+ return null;
328
+ }
329
+ return {
330
+ visible: true,
331
+ ...verdictCounts,
332
+ supervisedTraceCount,
333
+ routeTraceCount,
334
+ latestAgentIdentity: null,
335
+ latestLabel: null,
336
+ detail: routeTraceCount === 0
337
+ ? "no active-pack traced routes are visible"
338
+ : `${verdictCounts.helpfulCount} helpful, ${verdictCounts.irrelevantCount} irrelevant, ${verdictCounts.harmfulCount} harmful; ${supervisedTraceCount}/${routeTraceCount} active-pack traced routes are supervised`
339
+ };
340
+ }
341
+ function readIntegerNote(notes, prefix) {
342
+ if (!Array.isArray(notes)) {
343
+ return null;
344
+ }
345
+ const entry = notes.find((candidate) => typeof candidate === "string" && candidate.startsWith(prefix));
346
+ if (typeof entry !== "string") {
347
+ return null;
348
+ }
349
+ const parsed = Number.parseInt(entry.slice(prefix.length), 10);
350
+ return Number.isFinite(parsed) ? parsed : null;
351
+ }
352
+ function buildWatchSnapshotAttributionCoverage(activationRoot) {
353
+ const snapshot = toRecord(loadJsonFile(path.join(path.resolve(activationRoot), "watch", "teacher-snapshot.json")));
354
+ const notes = Array.isArray(snapshot?.notes)
355
+ ? snapshot.notes
356
+ : Array.isArray(snapshot?.snapshot?.diagnostics?.notes)
357
+ ? snapshot.snapshot.diagnostics.notes
358
+ : Array.isArray(snapshot?.diagnostics?.notes)
359
+ ? snapshot.diagnostics.notes
360
+ : [];
361
+ const readyCount = readIntegerNote(notes, "teacher_feedback_eligible=");
362
+ const delayedCount = readIntegerNote(notes, "teacher_feedback_delayed=");
363
+ const budgetDeferredCount = readIntegerNote(notes, "teacher_feedback_budgeted_out=");
364
+ const budgetPerTick = readIntegerNote(notes, "teacher_budget=");
365
+ const delayMs = readIntegerNote(notes, "teacher_delay_ms=");
366
+ if (readyCount === null && delayedCount === null && budgetDeferredCount === null && budgetPerTick === null && delayMs === null) {
367
+ return null;
368
+ }
369
+ return {
370
+ visible: true,
371
+ gatingVisible: budgetPerTick !== null || delayMs !== null,
372
+ completedWithoutEvaluationCount: 0,
373
+ readyCount: normalizeCount(readyCount),
374
+ delayedCount: normalizeCount(delayedCount),
375
+ budgetDeferredCount: normalizeCount(budgetDeferredCount),
376
+ detail: `watch sparse-feedback queue: completed_without_evaluation=0, ready=${normalizeCount(readyCount)}, delayed=${normalizeCount(delayedCount)}, budget_deferred=${normalizeCount(budgetDeferredCount)}`
377
+ };
378
+ }
379
+ function shouldPreferActivationFeedbackSummary(current, fallback) {
380
+ if (fallback === null) {
381
+ return false;
382
+ }
383
+ if (current.visible !== true) {
384
+ return true;
385
+ }
386
+ return normalizeCount(current.supervisedTraceCount) === 0 && normalizeCount(fallback.supervisedTraceCount) > 0;
387
+ }
388
+ function shouldPreferWatchAttributionCoverage(current, fallback) {
389
+ if (fallback === null) {
390
+ return false;
391
+ }
392
+ if (current.visible !== true || current.gatingVisible !== true) {
393
+ return normalizeCount(fallback.readyCount) > 0
394
+ || normalizeCount(fallback.delayedCount) > 0
395
+ || normalizeCount(fallback.budgetDeferredCount) > 0;
396
+ }
397
+ const currentKnown = normalizeCount(current.completedWithoutEvaluationCount)
398
+ + normalizeCount(current.readyCount)
399
+ + normalizeCount(current.delayedCount)
400
+ + normalizeCount(current.budgetDeferredCount);
401
+ const fallbackKnown = normalizeCount(fallback.completedWithoutEvaluationCount)
402
+ + normalizeCount(fallback.readyCount)
403
+ + normalizeCount(fallback.delayedCount)
404
+ + normalizeCount(fallback.budgetDeferredCount);
405
+ return currentKnown === 0 && fallbackKnown > 0;
406
+ }
407
+ function enrichBridgeWithActivationTruth(activationRoot, bridge) {
408
+ const feedbackSummary = buildActivePackFeedbackSummary(activationRoot);
409
+ const attributionCoverage = buildWatchSnapshotAttributionCoverage(activationRoot);
410
+ if (!shouldPreferActivationFeedbackSummary(bridge.feedbackSummary, feedbackSummary)
411
+ && !shouldPreferWatchAttributionCoverage(bridge.attributionCoverage, attributionCoverage)) {
412
+ return bridge;
413
+ }
414
+ return normalizeBridgePayload({
415
+ ...bridge,
416
+ feedbackSummary: shouldPreferActivationFeedbackSummary(bridge.feedbackSummary, feedbackSummary)
417
+ ? feedbackSummary
418
+ : bridge.feedbackSummary,
419
+ attributionCoverage: shouldPreferWatchAttributionCoverage(bridge.attributionCoverage, attributionCoverage)
420
+ ? attributionCoverage
421
+ : bridge.attributionCoverage
422
+ });
423
+ }
261
424
  function normalizeLastInterruptionSummary(value) {
262
425
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
263
426
  return null;
@@ -995,12 +1158,12 @@ export function buildTracedLearningStatusSurface(activationRoot, options = {}) {
995
1158
  const persisted = loadBrainStoreTracedLearningBridge(options);
996
1159
  const runtime = loadTracedLearningBridge(activationRoot);
997
1160
  if (persisted.bridge !== null) {
998
- return buildStatusSurface(persisted.path, mergeCanonicalStatusBridge(persisted.bridge, runtime), {
1161
+ return buildStatusSurface(persisted.path, enrichBridgeWithActivationTruth(activationRoot, mergeCanonicalStatusBridge(persisted.bridge, runtime)), {
999
1162
  runtimeState: describeBridgeRuntimeState(runtime)
1000
1163
  });
1001
1164
  }
1002
1165
  if (runtime.bridge !== null) {
1003
- return buildStatusSurface(runtime.path, runtime.bridge);
1166
+ return buildStatusSurface(runtime.path, enrichBridgeWithActivationTruth(activationRoot, runtime.bridge));
1004
1167
  }
1005
1168
  if (persisted.error !== null) {
1006
1169
  return defaultSurface(persisted.path, "brain_store_unreadable", persisted.error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclawbrain/cli",
3
- "version": "0.4.25",
3
+ "version": "0.4.27",
4
4
  "description": "OpenClawBrain operator CLI package with install/status helpers, daemon controls, and import/export tooling.",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",