@openclawbrain/cli 0.4.26 → 0.4.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/cli.js CHANGED
@@ -13,7 +13,7 @@ import { buildTeacherSupervisionArtifactsFromNormalizedEventExport, createAlways
13
13
  import { inspectActivationState, loadPackFromActivation, promoteCandidatePack, resolveLearningSpineLogPath, 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 { inspectOpenClawBrainHookStatus, inspectOpenClawBrainPluginAllowlist } from "./openclaw-hook-truth.js";
16
+ import { describeOpenClawBrainHotfixBoundary, inspectOpenClawBrainHookStatus, inspectOpenClawBrainPluginAllowlist } from "./openclaw-hook-truth.js";
17
17
  import { describeOpenClawBrainInstallIdentity, describeOpenClawBrainInstallLayout, findInstalledOpenClawBrainPlugin, getOpenClawBrainKnownPluginIds, normalizeOpenClawBrainPluginsConfig, pinInstalledOpenClawBrainPluginActivationRoot, resolveOpenClawBrainInstallTarget } from "./openclaw-plugin-install.js";
18
18
  import { buildOpenClawBrainConvergeRestartPlan, classifyOpenClawBrainConvergeVerification, describeOpenClawBrainConvergeChangeReasons, diffOpenClawBrainConvergeRuntimeFingerprint, finalizeOpenClawBrainConvergeResult, planOpenClawBrainConvergePluginAction, shouldReplaceOpenClawBrainInstallBeforeConverge } from "./install-converge.js";
19
19
  import { loadAttachmentPolicyDeclaration, resolveEffectiveAttachmentPolicyTruth, writeAttachmentPolicyDeclaration } from "./attachment-policy-truth.js";
@@ -25,7 +25,7 @@ import { summarizePackVectorEmbeddingState } from "./embedding-status.js";
25
25
  import { buildTracedLearningBridgePayloadFromRuntime, buildTracedLearningStatusSurface, persistTracedLearningBridgeState } from "./traced-learning-bridge.js";
26
26
  import { discoverOpenClawSessionStores, loadOpenClawSessionIndex, readOpenClawSessionFile } from "./session-store.js";
27
27
  import { readOpenClawBrainProviderDefaults, readOpenClawBrainProviderConfig, readOpenClawBrainProviderConfigFromSources, resolveOpenClawBrainProviderDefaultsPath } from "./provider-config.js";
28
- import { formatOperatorAttributionCoverageSummary, formatOperatorFeedbackSummary, formatOperatorLearningAttributionSummary, formatOperatorLearningPathSummary } from "./status-learning-path.js";
28
+ import { formatOperatorAttributionCoverageSummary, formatOperatorFeedbackSummary, formatOperatorLearningAttributionSummary, formatOperatorLearningFlowSummary, formatOperatorLearningHealthSummary, formatOperatorLearningPathSummary } from "./status-learning-path.js";
29
29
  import { buildProofCommandForOpenClawHome, buildProofCommandHelpSection, captureOperatorProofBundle, formatOperatorProofResult, parseProofCliArgs } from "./proof-command.js";
30
30
  const OPENCLAWBRAIN_EMBEDDER_BASE_URL_ENV = "OPENCLAWBRAIN_EMBEDDER_BASE_URL";
31
31
  const OPENCLAWBRAIN_EMBEDDER_PROVIDER_ENV = "OPENCLAWBRAIN_EMBEDDER_PROVIDER";
@@ -950,6 +950,27 @@ function summarizeStatusAttachmentTruth(input) {
950
950
  })
951
951
  };
952
952
  }
953
+ function summarizeStatusHotfixBoundary(status) {
954
+ return describeOpenClawBrainHotfixBoundary({
955
+ hookInspection: status.hook,
956
+ daemonInspection: inspectManagedLearnerService(status.host.activationRoot)
957
+ });
958
+ }
959
+ function formatStatusHotfixBoundarySummary(boundary) {
960
+ return [
961
+ `boundary=${boundary.boundary}`,
962
+ `skew=${boundary.skew}`,
963
+ `daemon=${boundary.daemonPackage ?? "unverified"}`,
964
+ `hook=${boundary.hookPackage ?? "unverified"}`
965
+ ].join(" ");
966
+ }
967
+ function formatStatusHotfixBoundaryPaths(boundary) {
968
+ return [
969
+ `daemonPath=${boundary.daemonPath === null ? "none" : shortenPath(boundary.daemonPath)}`,
970
+ `hookPath=${boundary.hookPath === null ? "unverified" : shortenPath(boundary.hookPath)}`,
971
+ `runtimeGuard=${boundary.runtimeGuardPath === null ? "unverified" : shortenPath(boundary.runtimeGuardPath)}`
972
+ ].join(" ");
973
+ }
953
974
  function normalizeAttachmentPolicyMode(value) {
954
975
  return value === "undeclared" || value === "dedicated" || value === "shared"
955
976
  ? value
@@ -1417,6 +1438,7 @@ function formatTracedLearningSurface(surface) {
1417
1438
  function buildCompactStatusHeader(status, report, options) {
1418
1439
  const installHook = summarizeStatusInstallHook(options.openclawHome);
1419
1440
  const hookLoad = summarizeStatusHookLoad(installHook, status);
1441
+ const hotfixBoundary = summarizeStatusHotfixBoundary(status);
1420
1442
  const embeddings = summarizeStatusEmbeddings(report, options.providerConfig);
1421
1443
  const localLlm = summarizeStatusLocalLlm(options.providerConfig);
1422
1444
  const teacher = summarizeStatusTeacher(report, options.providerConfig, localLlm);
@@ -1433,6 +1455,9 @@ function buildCompactStatusHeader(status, report, options) {
1433
1455
  return [
1434
1456
  `lifecycle attach=${status.attachment.state} learner=${yesNo(status.passiveLearning.learnerRunning)} watch=${summarizeStatusWatchState(status)} export=${status.passiveLearning.exportState} promote=${summarizeStatusPromotionState(status)} serve=${summarizeStatusServeReality(status)}`,
1435
1457
  `hook install=${hookLoad.installState} loadability=${hookLoad.loadability} loadProof=${hookLoad.loadProof} layout=${status.hook.installLayout ?? "unverified"} additional=${status.hook.additionalInstallCount ?? 0} severity=${hookLoad.guardSeverity} actionability=${hookLoad.guardActionability} summary=${hookLoad.guardSummary}`,
1458
+ `surface ${formatStatusHotfixBoundarySummary(hotfixBoundary)}`,
1459
+ `surfaces ${formatStatusHotfixBoundaryPaths(hotfixBoundary)}`,
1460
+ `hotfix ${hotfixBoundary.guidance}`,
1436
1461
  `attachTruth current=${attachmentTruth.currentProfileLabel} hook=${attachmentTruth.hookFiles} config=${attachmentTruth.configLoad} runtime=${attachmentTruth.runtimeLoad} watcher=${attachmentTruth.watcher} attachedSet=${formatAttachedProfileTruthCompactList(attachmentTruth.attachedProfiles)} why=${attachmentTruth.detail}`,
1437
1462
  `passive firstExport=${yesNo(status.passiveLearning.firstExportOccurred)} backlog=${status.passiveLearning.backlogState} pending=${formatStatusNullableNumber(status.passiveLearning.pendingLive)}/${formatStatusNullableNumber(status.passiveLearning.pendingBackfill)}`,
1438
1463
  `serving pack=${status.passiveLearning.currentServingPackId ?? "none"} lastExport=${status.passiveLearning.lastExportAt ?? "none"} lastPromotion=${status.passiveLearning.lastPromotionAt ?? "none"}`,
@@ -1442,6 +1467,8 @@ function buildCompactStatusHeader(status, report, options) {
1442
1467
  `explain ${status.brain.summary}`,
1443
1468
  `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)}`,
1444
1469
  `attribution ${formatOperatorLearningAttributionSummary({ status })}`,
1470
+ `learnFlow ${formatOperatorLearningFlowSummary({ tracedLearning })}`,
1471
+ `health ${formatOperatorLearningHealthSummary({ tracedLearning, teacher })}`,
1445
1472
  `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}`,
1446
1473
  `embedder model=${embedder.model} provisioned=${yesNo(embedder.provisioned)} live=${yesNo(embedder.live)} why=${embedder.detail}`,
1447
1474
  `routeFn available=${yesNo(routeFn.available)} freshness=${routeFn.freshness} trained=${routeFn.trainedAt ?? "none"} updated=${routeFn.updatedAt ?? "none"} used=${routeFn.usedAt ?? "none"} why=${routeFn.detail}`,
@@ -1454,8 +1481,10 @@ function buildCompactStatusHeader(status, report, options) {
1454
1481
  function formatCurrentProfileStatusSummary(status, report, targetInspection, options) {
1455
1482
  const installHook = summarizeStatusInstallHook(options.openclawHome);
1456
1483
  const displayedStatus = summarizeDisplayedStatus(status, installHook);
1484
+ const hotfixBoundary = summarizeStatusHotfixBoundary(status);
1457
1485
  const embeddings = summarizeStatusEmbeddings(report, options.providerConfig);
1458
1486
  const localLlm = summarizeStatusLocalLlm(options.providerConfig);
1487
+ const teacher = summarizeStatusTeacher(report, options.providerConfig, localLlm);
1459
1488
  const liveModels = embeddings.models.length === 0 ? "none" : embeddings.models.join("|");
1460
1489
  const attachmentTruth = summarizeStatusAttachmentTruth({
1461
1490
  activationRoot: status.host.activationRoot,
@@ -1480,6 +1509,7 @@ function formatCurrentProfileStatusSummary(status, report, targetInspection, opt
1480
1509
  `host runtime=${status.host.runtimeOwner} activation=${status.host.activationRoot}`,
1481
1510
  `profile selector=${status.profile.selector}${profileIdSuffix} attachment=${status.attachment.state} policy=${status.attachment.policyMode}`,
1482
1511
  `guard severity=${status.hook.guardSeverity} actionability=${status.hook.guardActionability} action=${status.hook.guardAction} summary=${status.hook.guardSummary}`,
1512
+ `surfaceNote ${hotfixBoundary.detail}`,
1483
1513
  `attachTruth current=${attachmentTruth.currentProfileLabel} hook=${attachmentTruth.hookFiles} config=${attachmentTruth.configLoad} runtime=${attachmentTruth.runtimeLoad} watcher=${attachmentTruth.watcher} detail=${attachmentTruth.detail}`,
1484
1514
  `attachedSet ${formatAttachedProfileTruthDetailedList(attachmentTruth.attachedProfiles)} proofPath=${shortenPath(attachmentTruth.runtimeProofPath)} proofError=${attachmentTruth.runtimeProofError ?? "none"}`,
1485
1515
  `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)}`,
@@ -1500,6 +1530,8 @@ function formatCurrentProfileStatusSummary(status, report, targetInspection, opt
1500
1530
  learningPath: report.learningPath,
1501
1531
  tracedLearning
1502
1532
  })}`,
1533
+ `learnFlow ${formatOperatorLearningFlowSummary({ tracedLearning })}`,
1534
+ `health ${formatOperatorLearningHealthSummary({ tracedLearning, teacher })}`,
1503
1535
  `feedback ${formatOperatorFeedbackSummary({ tracedLearning })}`,
1504
1536
  `attribution ${formatOperatorLearningAttributionSummary({ status })}`,
1505
1537
  `attrCover ${formatOperatorAttributionCoverageSummary({ tracedLearning })}`,
@@ -4596,19 +4628,170 @@ function runUninstallCommand(parsed) {
4596
4628
  }
4597
4629
  return 0;
4598
4630
  }
4599
- function resolveServeTimeLearningRuntimeInput(activationRoot) {
4631
+ function resolveServeTimeLearningRuntimeInput(activationRoot, normalizedEventExport = null) {
4600
4632
  const logPath = resolveLearningSpineLogPath(activationRoot, "serveTimeRouteDecisions");
4601
- const { entries: serveTimeDecisions, fallbackReason } = readBoundedJsonlTail(logPath);
4633
+ const { entries: boundedServeTimeDecisions, fallbackReason } = readBoundedJsonlTail(logPath);
4634
+ const historicalRecovery = fallbackReason === null
4635
+ ? { decisions: [], scanFailed: false }
4636
+ : readHistoricalServeTimeDecisions(logPath, collectServeTimeDecisionRecoveryTargets(normalizedEventExport));
4637
+ const serveTimeDecisions = mergeHistoricalServeTimeDecisions(historicalRecovery.decisions, boundedServeTimeDecisions);
4602
4638
  const decisionLogCount = serveTimeDecisions.length;
4603
4639
  const pgVersion = decisionLogCount > 0 ? "v2" : "v1";
4640
+ const resolvedFallbackReason = combineServeTimeLearningFallbackReasons(fallbackReason, historicalRecovery.scanFailed ? "historical_recovery_scan_failed" : null);
4604
4641
  return {
4605
4642
  pgVersion,
4606
4643
  serveTimeDecisions,
4607
4644
  decisionLogCount,
4608
4645
  baselineState: pgVersion === "v2" ? loadOrInitBaseline(activationRoot) : undefined,
4609
- fallbackReason: fallbackReason === null ? null : `serve_time_decision_log_${fallbackReason}`
4646
+ fallbackReason: resolvedFallbackReason === null ? null : `serve_time_decision_log_${resolvedFallbackReason}`
4610
4647
  };
4611
4648
  }
4649
+ function combineServeTimeLearningFallbackReasons(...reasons) {
4650
+ const resolved = reasons
4651
+ .filter((reason) => typeof reason === "string" && reason.length > 0);
4652
+ if (resolved.length === 0) {
4653
+ return null;
4654
+ }
4655
+ return [...new Set(resolved.flatMap((reason) => reason.split("+").filter((part) => part.length > 0)))].join("+");
4656
+ }
4657
+ function mergeHistoricalServeTimeDecisions(historicalDecisions, boundedServeTimeDecisions) {
4658
+ const merged = [];
4659
+ const seenRecordIds = new Set();
4660
+ for (const decision of [...historicalDecisions, ...boundedServeTimeDecisions]) {
4661
+ const recordId = normalizeServeTimeDecisionRecoveryString(decision?.recordId);
4662
+ if (recordId !== null) {
4663
+ if (seenRecordIds.has(recordId)) {
4664
+ continue;
4665
+ }
4666
+ seenRecordIds.add(recordId);
4667
+ }
4668
+ merged.push(decision);
4669
+ }
4670
+ return merged;
4671
+ }
4672
+ function readHistoricalServeTimeDecisions(logPath, targets) {
4673
+ if (!hasServeTimeDecisionRecoveryTargets(targets)) {
4674
+ return { decisions: [], scanFailed: false };
4675
+ }
4676
+ let raw;
4677
+ try {
4678
+ raw = readFileSync(logPath, "utf8");
4679
+ }
4680
+ catch {
4681
+ return { decisions: [], scanFailed: true };
4682
+ }
4683
+ const decisions = [];
4684
+ for (const line of raw.split(/\r?\n/u)) {
4685
+ const trimmed = line.trim();
4686
+ if (trimmed.length === 0) {
4687
+ continue;
4688
+ }
4689
+ try {
4690
+ const decision = JSON.parse(trimmed);
4691
+ const recordId = normalizeServeTimeDecisionRecoveryString(decision?.recordId);
4692
+ const selectionDigestKey = buildServeTimeDecisionRecoverySelectionKey(decision?.selectionDigest, decision?.activePackGraphChecksum);
4693
+ const turnCompileEventId = normalizeServeTimeDecisionRecoveryString(decision?.turnCompileEventId);
4694
+ if ((recordId !== null && targets.recordIds.has(recordId)) ||
4695
+ (selectionDigestKey !== null && targets.selectionDigestKeys.has(selectionDigestKey)) ||
4696
+ (turnCompileEventId !== null && targets.turnCompileEventIds.has(turnCompileEventId))) {
4697
+ decisions.push(decision);
4698
+ }
4699
+ }
4700
+ catch {
4701
+ // skip malformed lines
4702
+ }
4703
+ }
4704
+ return { decisions, scanFailed: false };
4705
+ }
4706
+ function hasServeTimeDecisionRecoveryTargets(targets) {
4707
+ return targets.recordIds.size > 0 ||
4708
+ targets.selectionDigestKeys.size > 0 ||
4709
+ targets.turnCompileEventIds.size > 0;
4710
+ }
4711
+ function collectServeTimeDecisionRecoveryTargets(normalizedEventExport) {
4712
+ const targets = {
4713
+ recordIds: new Set(),
4714
+ selectionDigestKeys: new Set(),
4715
+ turnCompileEventIds: new Set()
4716
+ };
4717
+ const interactions = Array.isArray(normalizedEventExport?.interactionEvents)
4718
+ ? normalizedEventExport.interactionEvents
4719
+ : [];
4720
+ const feedbackEvents = Array.isArray(normalizedEventExport?.feedbackEvents)
4721
+ ? normalizedEventExport.feedbackEvents
4722
+ : [];
4723
+ const interactionsById = new Map();
4724
+ for (const interaction of interactions) {
4725
+ const interactionId = normalizeServeTimeDecisionRecoveryString(interaction?.eventId);
4726
+ if (interactionId !== null && !interactionsById.has(interactionId)) {
4727
+ interactionsById.set(interactionId, interaction);
4728
+ }
4729
+ }
4730
+ const candidateInteractions = [];
4731
+ for (const feedback of feedbackEvents) {
4732
+ const relatedInteractionId = normalizeServeTimeDecisionRecoveryString(feedback?.relatedInteractionId);
4733
+ if (relatedInteractionId === null) {
4734
+ continue;
4735
+ }
4736
+ targets.turnCompileEventIds.add(relatedInteractionId);
4737
+ const relatedInteraction = interactionsById.get(relatedInteractionId);
4738
+ if (relatedInteraction !== undefined) {
4739
+ candidateInteractions.push(relatedInteraction);
4740
+ }
4741
+ }
4742
+ for (const interaction of candidateInteractions) {
4743
+ const routeMetadata = readServeTimeDecisionRecoveryRecord(interaction?.routeMetadata);
4744
+ const decisionProvenance = readServeTimeDecisionRecoveryRecord(interaction?.decisionProvenance);
4745
+ const metadata = readServeTimeDecisionRecoveryRecord(interaction?.metadata);
4746
+ const serveDecisionRecordId = normalizeServeTimeDecisionRecoveryString(interaction?.serveDecisionRecordId)
4747
+ ?? normalizeServeTimeDecisionRecoveryString(routeMetadata?.serveDecisionRecordId)
4748
+ ?? normalizeServeTimeDecisionRecoveryString(decisionProvenance?.serveDecisionRecordId)
4749
+ ?? normalizeServeTimeDecisionRecoveryString(metadata?.serveDecisionRecordId);
4750
+ if (serveDecisionRecordId !== null) {
4751
+ targets.recordIds.add(serveDecisionRecordId);
4752
+ }
4753
+ const selectionDigestKey = buildServeTimeDecisionRecoverySelectionKey(normalizeServeTimeDecisionRecoveryString(interaction?.selectionDigest)
4754
+ ?? normalizeServeTimeDecisionRecoveryString(routeMetadata?.selectionDigest)
4755
+ ?? normalizeServeTimeDecisionRecoveryString(decisionProvenance?.selectionDigest)
4756
+ ?? normalizeServeTimeDecisionRecoveryString(metadata?.selectionDigest), normalizeServeTimeDecisionRecoveryString(interaction?.activePackGraphChecksum)
4757
+ ?? normalizeServeTimeDecisionRecoveryString(routeMetadata?.activePackGraphChecksum)
4758
+ ?? normalizeServeTimeDecisionRecoveryString(decisionProvenance?.activePackGraphChecksum)
4759
+ ?? normalizeServeTimeDecisionRecoveryString(metadata?.activePackGraphChecksum));
4760
+ if (selectionDigestKey !== null) {
4761
+ targets.selectionDigestKeys.add(selectionDigestKey);
4762
+ }
4763
+ const explicitTurnCompileEventId = normalizeServeTimeDecisionRecoveryString(interaction?.turnCompileEventId)
4764
+ ?? normalizeServeTimeDecisionRecoveryString(routeMetadata?.turnCompileEventId)
4765
+ ?? normalizeServeTimeDecisionRecoveryString(decisionProvenance?.turnCompileEventId)
4766
+ ?? normalizeServeTimeDecisionRecoveryString(metadata?.turnCompileEventId);
4767
+ if (explicitTurnCompileEventId !== null) {
4768
+ targets.turnCompileEventIds.add(explicitTurnCompileEventId);
4769
+ }
4770
+ const softTurnCompileEventId = normalizeServeTimeDecisionRecoveryString(interaction?.eventId);
4771
+ if (softTurnCompileEventId !== null) {
4772
+ targets.turnCompileEventIds.add(softTurnCompileEventId);
4773
+ }
4774
+ }
4775
+ return targets;
4776
+ }
4777
+ function readServeTimeDecisionRecoveryRecord(value) {
4778
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
4779
+ }
4780
+ function normalizeServeTimeDecisionRecoveryString(value) {
4781
+ if (typeof value !== "string") {
4782
+ return null;
4783
+ }
4784
+ const trimmed = value.trim();
4785
+ return trimmed.length > 0 ? trimmed : null;
4786
+ }
4787
+ function buildServeTimeDecisionRecoverySelectionKey(selectionDigest, activePackGraphChecksum) {
4788
+ const normalizedSelectionDigest = normalizeServeTimeDecisionRecoveryString(selectionDigest);
4789
+ const normalizedGraphChecksum = normalizeServeTimeDecisionRecoveryString(activePackGraphChecksum);
4790
+ if (normalizedSelectionDigest === null || normalizedGraphChecksum === null) {
4791
+ return null;
4792
+ }
4793
+ return `${normalizedGraphChecksum}|${normalizedSelectionDigest}`;
4794
+ }
4612
4795
  function resolveActivationInspectionPackId(inspection, slot) {
4613
4796
  return inspection?.[slot]?.packId ?? inspection?.pointers?.[slot]?.packId ?? null;
4614
4797
  }
@@ -4854,7 +5037,7 @@ async function runLearnCommand(parsed) {
4854
5037
  }
4855
5038
  const learningExport = normalizedEventExport;
4856
5039
  const resolvedEmbedder = resolveCliEmbedderConfig(undefined, activationRoot);
4857
- const serveTimeLearning = resolveServeTimeLearningRuntimeInput(activationRoot);
5040
+ const serveTimeLearning = resolveServeTimeLearningRuntimeInput(activationRoot, normalizedEventExport);
4858
5041
  const learnerResult = drainAlwaysOnLearningRuntime({
4859
5042
  packLabel: "learn-cli",
4860
5043
  workspace: {
@@ -5678,8 +5861,8 @@ export async function createWatchCommandRuntime(input) {
5678
5861
  },
5679
5862
  learnedRouting: true,
5680
5863
  ...(teacherLabeler !== null ? { teacherLabeler } : {}),
5681
- resolveLearnedRoutingState: () => {
5682
- const resolved = resolveServeTimeLearningRuntimeInput(activationRoot);
5864
+ resolveLearnedRoutingState: (normalizedEventExport) => {
5865
+ const resolved = resolveServeTimeLearningRuntimeInput(activationRoot, normalizedEventExport);
5683
5866
  if (resolved.fallbackReason !== null && resolved.fallbackReason !== lastServeTimeFallbackReason) {
5684
5867
  log(`Serve-time routing fallback: ${resolved.fallbackReason}`);
5685
5868
  }
@@ -6367,10 +6550,12 @@ export function runOperatorCli(argv = process.argv.slice(2)) {
6367
6550
  const status = statusWithReport.status;
6368
6551
  const tracedLearning = buildTracedLearningStatusSurface(activationRoot);
6369
6552
  const normalizedStatusAndReport = applyAttachmentPolicyTruth(status, statusOrRollback.json ? null : statusWithReport.report);
6553
+ const hotfixBoundaryTruth = summarizeStatusHotfixBoundary(normalizedStatusAndReport.status);
6370
6554
  if (statusOrRollback.json) {
6371
6555
  console.log(JSON.stringify({
6372
6556
  ...normalizedStatusAndReport.status,
6373
- tracedLearning
6557
+ tracedLearning,
6558
+ hotfixBoundaryTruth
6374
6559
  }, null, 2));
6375
6560
  }
6376
6561
  else {
@@ -46,6 +46,8 @@ export interface ManagedLearnerServiceInspection {
46
46
  configuredCommand: string | null;
47
47
  configuredRuntimePath: string | null;
48
48
  configuredRuntimePackageSpec: string | null;
49
+ configuredRuntimePackageName: string | null;
50
+ configuredRuntimePackageVersion: string | null;
49
51
  configuredRuntimeLooksEphemeral: boolean | null;
50
52
  matchesRequestedActivationRoot: boolean | null;
51
53
  launchctlAvailable: boolean;
@@ -306,6 +306,17 @@ export function describeManagedLearnerServiceRuntimeGuard(inspection) {
306
306
  detail: `Learner service points at the durable ${CLI_PACKAGE_NAME} runtime.`
307
307
  };
308
308
  }
309
+ function buildDaemonHotfixBoundary(inspection) {
310
+ return {
311
+ surface: "daemon_runtime",
312
+ separateFromInstalledHookSurface: true,
313
+ runtimePath: inspection.configuredRuntimePath ?? null,
314
+ guidance: "Patch this daemon runtime path for background watch/learner fixes. Use `openclawbrain status --openclaw-home <path> --detailed` to inspect the separate installed hook/runtime-guard surface before patching OpenClaw load behavior.",
315
+ detail: inspection.configuredRuntimePath === null
316
+ ? "daemon status is only reporting the background watch surface; no configured runtime path is visible yet."
317
+ : `daemon status is reporting the background watch runtime at ${inspection.configuredRuntimePath}; installed hook/runtime-guard paths live on the OpenClaw profile side.`
318
+ };
319
+ }
309
320
  function resolveDaemonProgramArguments() {
310
321
  for (const candidate of getOpenclawbrainCliScriptPathCandidates()) {
311
322
  const cliScriptPath = resolveCliScriptCandidate(candidate);
@@ -970,6 +981,11 @@ export function daemonStatus(activationRoot, json) {
970
981
  ? null
971
982
  : canonicalizeActivationRoot(configuredActivationRoot) === serviceIdentity.canonicalActivationRoot;
972
983
  const daemonLaunchDescription = describeDaemonProgramArguments(configuredProgramArguments);
984
+ const hotfixBoundary = buildDaemonHotfixBoundary({
985
+ installed: plistInstalled,
986
+ configuredProgramArguments,
987
+ ...daemonLaunchDescription,
988
+ });
973
989
  if (json) {
974
990
  console.log(JSON.stringify({
975
991
  command: "daemon status",
@@ -986,6 +1002,7 @@ export function daemonStatus(activationRoot, json) {
986
1002
  ...watchStatePaths,
987
1003
  watchState,
988
1004
  lastLogLines,
1005
+ hotfixBoundary,
989
1006
  }, null, 2));
990
1007
  }
991
1008
  else {
@@ -1015,6 +1032,8 @@ export function daemonStatus(activationRoot, json) {
1015
1032
  const runtimeWarning = daemonLaunchDescription.configuredRuntimeLooksEphemeral ? " [ephemeral]" : "";
1016
1033
  console.log(` Runtime: ${daemonLaunchDescription.configuredRuntimePath}${runtimePackageSuffix}${runtimeWarning}`);
1017
1034
  }
1035
+ console.log(" Runtime surface: daemon watch/learner runtime");
1036
+ console.log(` Hotfix boundary: ${hotfixBoundary.guidance}`);
1018
1037
  const runtimeGuard = describeManagedLearnerServiceRuntimeGuard({
1019
1038
  installed: plistInstalled,
1020
1039
  configuredProgramArguments,
@@ -1,8 +1,8 @@
1
1
  import { type CompileSelectionMode } from "@openclawbrain/compiler";
2
2
  import { CONTRACT_IDS, type ArtifactManifestV1, type ActivationPointerRecordV1, type ActivationPointerSlot, type RuntimeTurnBrainAttachmentPolicyV1, type ContextCompactionMode, type ContextContributionEvidenceStateV1, type CurrentProfileBrainStatusAnswerV1, type CurrentProfileActivationStateV1, type CurrentProfilePassiveLearningDeltaSummaryV1, type CurrentProfilePassiveLearningWatchStateV1, type CurrentProfileAttachmentStateV1, type CurrentProfileAttachmentProofStateV1, type CurrentProfileHookInstallStateV1, type CurrentProfileHookLoadabilityV1, type CurrentProfileHookLoadProofV1, type BrainServeHotPathTimingV1, type CurrentProfileStructuralDecisionV1, type FeedbackEventKind, type PackGraphConnectDiagnosticsV1, type PackGraphEvolutionV1, type EventSemanticSurfaceV1, type FeedbackEventV1, type KernelSurfaceValidationResultV1, type LearningBootProfile, type LearningCadence, type LearningScanPolicy, type InteractionEventV1, type NormalizedEventExportV1, type NormalizedEventV1, type PrincipalPriorityClassV1, type PrincipalRoleV1, type RouteMode, type RuntimeCompileResponseV1, type RuntimeCompileStructuralSignalsV1, type RuntimeCompileTargetV1, type RuntimeGraphPlasticityStateV1, type RuntimePlasticitySourceV1, type SparseFeedbackPolicyV1, type TeacherAuthorityV1, type TeacherSupervisionArtifactV1, type WorkspaceInjectionSurfaceV1 } from "@openclawbrain/contracts";
3
3
  import { type EventExportLaneV1 } from "@openclawbrain/event-export";
4
- import { type AdvanceAlwaysOnLearningRuntimeInput, type AlwaysOnLearningCadenceV1, type AlwaysOnLearningMaterializationJobV1, type AlwaysOnLearningRuntimePlanV1, type AlwaysOnLearningRuntimeStateV1, type BaselineStateV1, type PendingPrincipalEventV1, type PrincipalLearningCheckpointV1 } from "./local-learner.js";
5
- import { type ActivationInspection, type ActivationObservabilityReport, type GraphEvolutionLogV1, type LearningSpineServeRouteBreadcrumbsV1, type ActivationSlotInspection, type InitHandoffState, type LearningSpineServeRouteDecisionLogEntryV1 } from "@openclawbrain/pack-format";
4
+ import { type AdvanceAlwaysOnLearningRuntimeInput, type AlwaysOnLearningCadenceV1, type AlwaysOnLearningMaterializationJobV1, type AlwaysOnLearningRuntimePlanV1, type AlwaysOnLearningRuntimeStateV1, type BaselineStateV1, type PendingPrincipalEventV1, type PrincipalLearningCheckpointV1, type LearningSpineServeRouteDecisionLogEntryV1 } from "./local-learner.js";
5
+ import { type ActivationInspection, type ActivationObservabilityReport, type GraphEvolutionLogV1, type LearningSpineServeRouteBreadcrumbsV1, type ActivationSlotInspection, type InitHandoffState } from "@openclawbrain/pack-format";
6
6
  export { clearOpenClawProfileRuntimeLoadProof, listOpenClawProfileRuntimeLoadProofs, recordOpenClawProfileRuntimeLoadProof, resolveAttachmentRuntimeLoadProofsPath, type OpenClawProfileRuntimeLoadProofRecordV1, type OpenClawProfileRuntimeLoadProofSetV1, type OpenClawProfileRuntimeLoadProofsV1 } from "./attachment-truth.js";
7
7
  import { type AsyncTeacherLabelerConfigV1 } from "./teacher-labeler.js";
8
8
  export { createHttpOllamaTeacherLabelerClient, createOllamaTeacherLabeler, createTeacherLabeler, summarizeTeacherLabelerOpportunity, type AsyncTeacherLabelerConfigV1, type AsyncTeacherNoopLabelerConfigV1, type AsyncTeacherOllamaLabelerConfigV1, type OllamaTeacherLabelerClient, type TeacherLabeler, type TeacherLabelerOpportunityInputV1, type TeacherLabelerOpportunityV1, type TeacherLabelerResultV1, type TeacherLabelerRunInputV1 } from "./teacher-labeler.js";
package/dist/src/index.js CHANGED
@@ -904,7 +904,7 @@ export class AsyncTeacherLiveLoop {
904
904
  interactionEvents: this.interactionEvents,
905
905
  feedbackEvents: this.feedbackEvents
906
906
  });
907
- const learnedRoutingState = this.input.resolveLearnedRoutingState?.() ?? {};
907
+ const learnedRoutingState = this.input.resolveLearnedRoutingState?.(mergedNormalizedEventExport) ?? {};
908
908
  const currentDedupIds = new Set(this.teacherArtifacts.map((artifact) => artifact.dedupId));
909
909
  const currentCycleBuiltArtifacts = buildTeacherSupervisionArtifactsFromNormalizedEventExport({
910
910
  normalizedEventExport: job.normalizedEventExport,
@@ -1,6 +1,7 @@
1
1
  import { type AlwaysOnLearningMaterializationJobV1 } from "@openclawbrain/learner";
2
2
  import { type BrainServeHotPathTimingV1, type NormalizedEventExportV1, type RouteMode, type RuntimeCompileResponseV1 } from "@openclawbrain/contracts";
3
- import { type LearningSpinePgRouteUpdateLogEntryV1, type LearningSpineServeRouteBreadcrumbsV1, type LearningSpineServeRouteDecisionLogEntryV1, type PackDescriptor } from "@openclawbrain/pack-format";
3
+ import { type LearningSpinePgRouteUpdateLogEntryV1, type LearningSpineServeRouteBreadcrumbsV1, type PackDescriptor } from "@openclawbrain/pack-format";
4
+ import { type LearningSpineServeRouteDecisionLogEntryV1 } from "./local-learner.js";
4
5
  type CompileFailureLike = {
5
6
  ok: false;
6
7
  fallbackToStaticContext: boolean;
@@ -1,8 +1,24 @@
1
1
  import { type ArtifactManifestV1, type FeedbackEventV1, type InteractionEventV1, type NormalizedEventExportV1, type NormalizedEventV1, type PrincipalPriorityClassV1, type PrincipalRoleV1, type PackGraphPayloadV1, type PackVectorsPayloadV1, type RouterArtifactV1, type RouterPolicyUpdateV1, type RuntimeCompileStructuralSignalsV1, type RuntimeGraphPlasticityStateV1, type SparseFeedbackPolicyV1, type TeacherSupervisionArtifactV1 } from "@openclawbrain/contracts";
2
2
  import { type EventExportCursorV1, type EventExportLaneV1, type NormalizedEventExportBridgeV1, type NormalizedEventExportSliceV1 } from "@openclawbrain/event-export";
3
3
  import type { TextEmbedder } from "@openclawbrain/compiler";
4
- import { type PackDescriptor, type GraphEvolutionLogV1, type LearningSpineServeRouteDecisionLogEntryV1 } from "@openclawbrain/pack-format";
4
+ import { type PackDescriptor, type GraphEvolutionLogV1, type LearningSpineServeRouteDecisionLogEntryV1 as PackFormatLearningSpineServeRouteDecisionLogEntryV1 } from "@openclawbrain/pack-format";
5
5
  import { type WorkspaceMetadataInput } from "@openclawbrain/workspace-metadata";
6
+ export interface LearningSpineServeRouteCandidateScoreV1 {
7
+ blockId: string;
8
+ selected: boolean;
9
+ actionScore: number;
10
+ actionProbability: number;
11
+ compactedFrom?: string[];
12
+ matchedTokens?: string[];
13
+ routingChannels?: string[];
14
+ }
15
+ export type LearningSpineServeRouteDecisionLogEntryV1 = Omit<PackFormatLearningSpineServeRouteDecisionLogEntryV1, "candidateSetIds" | "chosenContextIds" | "candidateScores" | "selectedKernelContextIds" | "selectedBrainContextIds"> & {
16
+ candidateSetIds: string[];
17
+ chosenContextIds: string[];
18
+ candidateScores: LearningSpineServeRouteCandidateScoreV1[];
19
+ selectedKernelContextIds: string[];
20
+ selectedBrainContextIds: string[];
21
+ };
6
22
  export interface CandidatePackEventExports {
7
23
  interactionEvents: InteractionEventV1[];
8
24
  feedbackEvents: FeedbackEventV1[];
@@ -455,6 +455,98 @@ function normalizeTeacherSupervisionArtifacts(artifacts) {
455
455
  .filter((artifact) => !artifact.sourceEventIds.some((eventId) => supersededEventIds.has(eventId)))
456
456
  .sort(compareTeacherSupervisionArtifacts);
457
457
  }
458
+ function normalizeServeTimeDecisionStringArray(values) {
459
+ if (!Array.isArray(values)) {
460
+ return [];
461
+ }
462
+ return values.filter((value) => typeof value === "string" && value.length > 0);
463
+ }
464
+ function normalizeServeTimeDecisionScore(score) {
465
+ if (score === null || typeof score !== "object" || Array.isArray(score)) {
466
+ return null;
467
+ }
468
+ if (typeof score.blockId !== "string" || score.blockId.length === 0) {
469
+ return null;
470
+ }
471
+ return {
472
+ blockId: score.blockId,
473
+ selected: score.selected === true,
474
+ actionScore: Number.isFinite(score.actionScore) ? score.actionScore : 0,
475
+ actionProbability: Number.isFinite(score.actionProbability) ? score.actionProbability : 0,
476
+ ...(Array.isArray(score.compactedFrom) ? { compactedFrom: normalizeServeTimeDecisionStringArray(score.compactedFrom) } : {}),
477
+ ...(Array.isArray(score.matchedTokens) ? { matchedTokens: normalizeServeTimeDecisionStringArray(score.matchedTokens) } : {}),
478
+ ...(Array.isArray(score.routingChannels) ? { routingChannels: normalizeServeTimeDecisionStringArray(score.routingChannels) } : {})
479
+ };
480
+ }
481
+ function normalizeServeTimeDecisionLogEntry(decision) {
482
+ if (decision === null || typeof decision !== "object" || Array.isArray(decision)) {
483
+ return null;
484
+ }
485
+ const selectedKernelContextIds = normalizeServeTimeDecisionStringArray(decision.selectedKernelContextIds);
486
+ const selectedBrainContextIds = normalizeServeTimeDecisionStringArray(decision.selectedBrainContextIds);
487
+ return {
488
+ ...decision,
489
+ candidateSetIds: normalizeServeTimeDecisionStringArray(decision.candidateSetIds),
490
+ chosenContextIds: normalizeServeTimeDecisionStringArray(decision.chosenContextIds),
491
+ candidateScores: Array.isArray(decision.candidateScores)
492
+ ? decision.candidateScores.map((score) => normalizeServeTimeDecisionScore(score)).filter((score) => score !== null)
493
+ : [],
494
+ kernelContextCount: Number.isInteger(decision.kernelContextCount) && decision.kernelContextCount >= 0
495
+ ? decision.kernelContextCount
496
+ : selectedKernelContextIds.length,
497
+ brainContextCount: Number.isInteger(decision.brainContextCount) && decision.brainContextCount >= 0
498
+ ? decision.brainContextCount
499
+ : selectedBrainContextIds.length,
500
+ selectedKernelContextIds,
501
+ selectedBrainContextIds
502
+ };
503
+ }
504
+ function normalizeServeTimeDecisionsForLearner(serveTimeDecisions) {
505
+ if (serveTimeDecisions === undefined) {
506
+ return undefined;
507
+ }
508
+ if (!Array.isArray(serveTimeDecisions)) {
509
+ return [];
510
+ }
511
+ return serveTimeDecisions
512
+ .map((decision) => normalizeServeTimeDecisionLogEntry(decision))
513
+ .filter((decision) => decision !== null);
514
+ }
515
+ function projectServeTimeDecisionForRoutingSeed(decision) {
516
+ return {
517
+ recordId: typeof decision.recordId === "string" ? decision.recordId : null,
518
+ recordedAt: typeof decision.recordedAt === "string" ? decision.recordedAt : null,
519
+ sessionId: typeof decision.sessionId === "string" ? decision.sessionId : null,
520
+ channel: typeof decision.channel === "string" ? decision.channel : null,
521
+ turnCompileEventId: typeof decision.turnCompileEventId === "string" ? decision.turnCompileEventId : null,
522
+ turnCreatedAt: typeof decision.turnCreatedAt === "string" ? decision.turnCreatedAt : null,
523
+ activePackId: typeof decision.activePackId === "string" ? decision.activePackId : null,
524
+ activePackGraphChecksum: typeof decision.activePackGraphChecksum === "string" ? decision.activePackGraphChecksum : null,
525
+ routerIdentity: typeof decision.routerIdentity === "string" ? decision.routerIdentity : null,
526
+ selectionDigest: typeof decision.selectionDigest === "string" ? decision.selectionDigest : null,
527
+ usedLearnedRouteFn: decision.usedLearnedRouteFn === true,
528
+ fallbackReason: typeof decision.fallbackReason === "string" ? decision.fallbackReason : null,
529
+ candidateSetIds: decision.candidateSetIds,
530
+ chosenContextIds: decision.chosenContextIds,
531
+ candidateScores: decision.candidateScores.map((score) => ({
532
+ blockId: score.blockId,
533
+ selected: score.selected,
534
+ actionScore: score.actionScore,
535
+ actionProbability: score.actionProbability,
536
+ compactedFrom: score.compactedFrom ?? []
537
+ })),
538
+ kernelContextCount: decision.kernelContextCount,
539
+ brainContextCount: decision.brainContextCount,
540
+ selectedKernelContextIds: decision.selectedKernelContextIds,
541
+ selectedBrainContextIds: decision.selectedBrainContextIds
542
+ };
543
+ }
544
+ function checksumServeTimeDecisionsForRoutingSeed(serveTimeDecisions) {
545
+ if (serveTimeDecisions === undefined) {
546
+ return null;
547
+ }
548
+ return checksumJsonPayload(serveTimeDecisions.map((decision) => projectServeTimeDecisionForRoutingSeed(decision)));
549
+ }
458
550
  function teacherSupervisionContentForInteraction(event) {
459
551
  const message = event.messageId === undefined ? "" : ` Message: ${event.messageId}.`;
460
552
  const pack = event.packId === undefined ? "" : ` Pack: ${event.packId}.`;
@@ -893,7 +985,7 @@ function buildAlwaysOnLearningMaterializationJob(input, current, selectedSlices,
893
985
  ...(input.sparseFeedback !== undefined ? { sparseFeedback: input.sparseFeedback } : {}),
894
986
  principalBacklog,
895
987
  ...(input.pgVersion !== undefined ? { pgVersion: input.pgVersion } : {}),
896
- ...(input.serveTimeDecisions !== undefined ? { serveTimeDecisions: [...input.serveTimeDecisions] } : {}),
988
+ ...(input.serveTimeDecisions !== undefined ? { serveTimeDecisions: normalizeServeTimeDecisionsForLearner(input.serveTimeDecisions) } : {}),
897
989
  ...(input.baselineState !== undefined ? { baselineState: { ...input.baselineState } } : {}),
898
990
  ...(input.activationRoot !== undefined ? { activationRoot: input.activationRoot } : {})
899
991
  };
@@ -1049,7 +1141,7 @@ export function buildCandidatePackFromNormalizedEventExportSlice(input) {
1049
1141
  ...(input.sparseFeedback !== undefined ? { sparseFeedback: input.sparseFeedback } : {}),
1050
1142
  ...(input.principalBacklog !== undefined ? { principalBacklog: input.principalBacklog } : {}),
1051
1143
  ...(input.pgVersion !== undefined ? { pgVersion: input.pgVersion } : {}),
1052
- ...(input.serveTimeDecisions !== undefined ? { serveTimeDecisions: [...input.serveTimeDecisions] } : {}),
1144
+ ...(input.serveTimeDecisions !== undefined ? { serveTimeDecisions: normalizeServeTimeDecisionsForLearner(input.serveTimeDecisions) } : {}),
1053
1145
  ...(input.baselineState !== undefined ? { baselineState: { ...input.baselineState } } : {})
1054
1146
  });
1055
1147
  }
@@ -1080,7 +1172,7 @@ export function buildCandidatePackBundleFromNormalizedEventExportBridge(input) {
1080
1172
  ...(input.sparseFeedback !== undefined ? { sparseFeedback: input.sparseFeedback } : {}),
1081
1173
  ...(input.principalBacklog !== undefined ? { principalBacklog: input.principalBacklog } : {}),
1082
1174
  ...(input.pgVersion !== undefined ? { pgVersion: input.pgVersion } : {}),
1083
- ...(input.serveTimeDecisions !== undefined ? { serveTimeDecisions: [...input.serveTimeDecisions] } : {}),
1175
+ ...(input.serveTimeDecisions !== undefined ? { serveTimeDecisions: normalizeServeTimeDecisionsForLearner(input.serveTimeDecisions) } : {}),
1084
1176
  ...(input.baselineState !== undefined ? { baselineState: { ...input.baselineState } } : {})
1085
1177
  })
1086
1178
  };
@@ -3565,7 +3657,8 @@ function remapServeTimeDecisionToGraph(decision, resolveBlockId) {
3565
3657
  const remappedSelectedBrainContextIds = remapIds(decision.selectedBrainContextIds);
3566
3658
  const chosenSet = new Set(remappedChosenContextIds);
3567
3659
  const remappedScoresByBlockId = new Map();
3568
- for (const score of decision.candidateScores) {
3660
+ const candidateScores = Array.isArray(decision.candidateScores) ? decision.candidateScores : [];
3661
+ for (const score of candidateScores) {
3569
3662
  const resolvedBlockId = resolveBlockId(score.blockId);
3570
3663
  if (resolvedBlockId === null) {
3571
3664
  continue;
@@ -4540,13 +4633,15 @@ function buildGraphLocalActionSetFromScores(nodeBlockId, neighborBlockIds, graph
4540
4633
  * Traces through the graph starting from the highest-scoring selected block.
4541
4634
  */
4542
4635
  export function reconstructTrajectoryFromServeDecision(decision, graph, vectors, adjacency, tau, outcome, baselineValue) {
4543
- const chosenSet = new Set(decision.chosenContextIds);
4636
+ const chosenContextIds = Array.isArray(decision.chosenContextIds) ? decision.chosenContextIds : [];
4637
+ const candidateScores = Array.isArray(decision.candidateScores) ? decision.candidateScores : [];
4638
+ const chosenSet = new Set(chosenContextIds);
4544
4639
  const candidateScoresMap = new Map();
4545
- for (const cs of decision.candidateScores) {
4640
+ for (const cs of candidateScores) {
4546
4641
  candidateScoresMap.set(cs.blockId, cs.actionScore);
4547
4642
  }
4548
4643
  // Sort chosen blocks by score descending to find entry point
4549
- const chosenWithScores = decision.chosenContextIds
4644
+ const chosenWithScores = chosenContextIds
4550
4645
  .map((blockId) => ({ blockId, score: candidateScoresMap.get(blockId) ?? 0 }))
4551
4646
  .sort((a, b) => b.score - a.score);
4552
4647
  const steps = [];
@@ -4679,7 +4774,7 @@ function createRouterArtifactV2(packId, builtAt, graph, vectors, eventExport, se
4679
4774
  // 1. Build adjacency map from graph
4680
4775
  const adjacency = buildAdjacencyMap(graph);
4681
4776
  const resolveBlockId = buildGraphBlockIdResolver(graph);
4682
- const remappedServeTimeDecisions = serveTimeDecisions.map((decision) => remapServeTimeDecisionToGraph(decision, resolveBlockId));
4777
+ const remappedServeTimeDecisions = (serveTimeDecisions ?? []).map((decision) => remapServeTimeDecisionToGraph(decision, resolveBlockId));
4683
4778
  // 2. Join serve-time decisions with explicit feedback first, then let teacher-v2 observations override when available.
4684
4779
  const outcomeMap = joinDecisionsWithFeedback(remappedServeTimeDecisions, eventExport);
4685
4780
  const teacherObservationBindings = joinDecisionsWithTeacherObservationOutcomes(remappedServeTimeDecisions, teacherObservationOutcomes);
@@ -4710,7 +4805,7 @@ function createRouterArtifactV2(packId, builtAt, graph, vectors, eventExport, se
4710
4805
  `traj-${stableHash(checksumJsonPayload({ decisionId: d.recordId, steps: trajectory.steps.map((s) => s.nodeBlockId) }))}` === trajectory.trajectoryId);
4711
4806
  const candidateScoresMap = new Map();
4712
4807
  if (decision !== undefined) {
4713
- for (const cs of decision.candidateScores) {
4808
+ for (const cs of decision.candidateScores ?? []) {
4714
4809
  candidateScoresMap.set(cs.blockId, cs.actionScore);
4715
4810
  }
4716
4811
  }
@@ -5000,7 +5095,8 @@ export function buildCandidatePack(input) {
5000
5095
  });
5001
5096
  const learningSurface = eventExport?.provenance.learningSurface ?? defaultLearningSurface(workspace, offlineArtifacts, workspaceInit);
5002
5097
  const sparseFeedback = normalizeSparseFeedbackPolicy(input.sparseFeedback);
5003
- const decisionLogCount = input.serveTimeDecisions?.length ?? 0;
5098
+ const serveTimeDecisions = normalizeServeTimeDecisionsForLearner(input.serveTimeDecisions);
5099
+ const decisionLogCount = serveTimeDecisions?.length ?? 0;
5004
5100
  const useV2 = input.pgVersion === "v2" && decisionLogCount > 0;
5005
5101
  const fallbackReason = !input.learnedRouting
5006
5102
  ? null
@@ -5010,7 +5106,7 @@ export function buildCandidatePack(input) {
5010
5106
  const routingSeed = input.learnedRouting && (input.pgVersion === "v2" || decisionLogCount > 0 || input.baselineState !== undefined)
5011
5107
  ? {
5012
5108
  pgVersionRequested: input.pgVersion ?? "v1",
5013
- serveTimeDecisionDigest: input.serveTimeDecisions === undefined ? null : checksumJsonPayload(input.serveTimeDecisions),
5109
+ serveTimeDecisionDigest: checksumServeTimeDecisionsForRoutingSeed(serveTimeDecisions),
5014
5110
  baselineState: input.baselineState ?? null
5015
5111
  }
5016
5112
  : null;
@@ -5048,7 +5144,7 @@ export function buildCandidatePack(input) {
5048
5144
  : loadTeacherObservationOutcomesFromActivation(input.activationRoot);
5049
5145
  if (input.learnedRouting) {
5050
5146
  if (useV2) {
5051
- const v2Result = createRouterArtifactV2(packId, builtAt, graph, vectors, eventExport, input.serveTimeDecisions, input.baselineState ?? initBaseline(), input.sparseFeedback, input.principalBacklog, teacherObservationOutcomes);
5147
+ const v2Result = createRouterArtifactV2(packId, builtAt, graph, vectors, eventExport, serveTimeDecisions ?? [], input.baselineState ?? initBaseline(), input.sparseFeedback, input.principalBacklog, teacherObservationOutcomes);
5052
5148
  router = v2Result.artifact;
5053
5149
  updatedBaseline = v2Result.updatedBaseline;
5054
5150
  observationBindingStats = v2Result.observationBindingStats;
@@ -5211,7 +5307,7 @@ export function buildCandidatePackFromNormalizedEventExport(input) {
5211
5307
  ...(input.sparseFeedback !== undefined ? { sparseFeedback: input.sparseFeedback } : {}),
5212
5308
  ...(input.principalBacklog !== undefined ? { principalBacklog: input.principalBacklog } : {}),
5213
5309
  ...(input.pgVersion !== undefined ? { pgVersion: input.pgVersion } : {}),
5214
- ...(input.serveTimeDecisions !== undefined ? { serveTimeDecisions: [...input.serveTimeDecisions] } : {}),
5310
+ ...(input.serveTimeDecisions !== undefined ? { serveTimeDecisions: normalizeServeTimeDecisionsForLearner(input.serveTimeDecisions) } : {}),
5215
5311
  ...(input.baselineState !== undefined ? { baselineState: { ...input.baselineState } } : {}),
5216
5312
  ...(input.activationRoot !== undefined ? { activationRoot: input.activationRoot } : {})
5217
5313
  };
@@ -16,6 +16,7 @@ export interface OpenClawBrainHookInspection {
16
16
  manifestId: string | null;
17
17
  installId: string | null;
18
18
  packageName: string | null;
19
+ packageVersion: string | null;
19
20
  installLayout: OpenClawBrainInstallLayout | null;
20
21
  additionalInstallCount: number;
21
22
  installState: OpenClawBrainHookInstallState;
@@ -31,9 +32,30 @@ export interface OpenClawBrainHookLoadSummary extends OpenClawBrainHookInspectio
31
32
  guardSummary: string;
32
33
  guardAction: string;
33
34
  }
35
+ export interface OpenClawBrainDaemonRuntimeSurfaceInspection {
36
+ configuredRuntimePath: string | null;
37
+ configuredRuntimePackageSpec?: string | null;
38
+ configuredRuntimePackageName?: string | null;
39
+ configuredRuntimePackageVersion?: string | null;
40
+ }
41
+ export interface OpenClawBrainHotfixBoundarySummary {
42
+ boundary: string;
43
+ skew: string;
44
+ daemonPath: string | null;
45
+ hookPath: string | null;
46
+ runtimeGuardPath: string | null;
47
+ daemonPackage: string | null;
48
+ hookPackage: string | null;
49
+ guidance: string;
50
+ detail: string;
51
+ }
34
52
  export declare function inspectOpenClawBrainPluginAllowlist(openclawHome: string): {
35
53
  state: Exclude<OpenClawBrainPluginAllowlistState, "unverified">;
36
54
  detail: string;
37
55
  };
38
56
  export declare function inspectOpenClawBrainHookStatus(openclawHome: string | null | undefined): OpenClawBrainHookInspection;
39
57
  export declare function summarizeOpenClawBrainHookLoad(inspection: OpenClawBrainHookInspection, statusProbeReady: boolean): OpenClawBrainHookLoadSummary;
58
+ export declare function describeOpenClawBrainHotfixBoundary(input: {
59
+ hookInspection: OpenClawBrainHookInspection;
60
+ daemonInspection?: OpenClawBrainDaemonRuntimeSurfaceInspection | null;
61
+ }): OpenClawBrainHotfixBoundarySummary;
@@ -35,6 +35,37 @@ function shortenPath(fullPath) {
35
35
  }
36
36
  return fullPath;
37
37
  }
38
+ function readInstalledHookPackageVersion(packageJsonPath) {
39
+ if (typeof packageJsonPath !== "string" || packageJsonPath.trim().length === 0) {
40
+ return null;
41
+ }
42
+ try {
43
+ const parsed = JSON.parse(readFileSync(packageJsonPath, "utf8"));
44
+ return typeof parsed?.version === "string" && parsed.version.trim().length > 0
45
+ ? parsed.version.trim()
46
+ : null;
47
+ }
48
+ catch {
49
+ return null;
50
+ }
51
+ }
52
+ function formatPackageIdentity(packageName, packageVersion, packageSpec = null) {
53
+ if (typeof packageSpec === "string" && packageSpec.trim().length > 0) {
54
+ return packageSpec.trim();
55
+ }
56
+ if (typeof packageName !== "string" || packageName.trim().length === 0) {
57
+ return null;
58
+ }
59
+ const normalizedName = packageName.trim();
60
+ return typeof packageVersion === "string" && packageVersion.trim().length > 0
61
+ ? `${normalizedName}@${packageVersion.trim()}`
62
+ : normalizedName;
63
+ }
64
+ function normalizeSurfacePath(filePath) {
65
+ return typeof filePath === "string" && filePath.trim().length > 0
66
+ ? path.resolve(filePath)
67
+ : null;
68
+ }
38
69
  function inspectInstalledHookActivationRoot(loaderEntryPath) {
39
70
  let content;
40
71
  try {
@@ -147,6 +178,7 @@ export function inspectOpenClawBrainHookStatus(openclawHome) {
147
178
  manifestId: null,
148
179
  installId: null,
149
180
  packageName: null,
181
+ packageVersion: null,
150
182
  installLayout: null,
151
183
  additionalInstallCount: 0,
152
184
  installState: "unverified",
@@ -173,6 +205,7 @@ export function inspectOpenClawBrainHookStatus(openclawHome) {
173
205
  manifestId: incompleteInstall?.manifestId ?? null,
174
206
  installId: incompleteInstall?.installId ?? null,
175
207
  packageName: incompleteInstall?.packageName ?? null,
208
+ packageVersion: readInstalledHookPackageVersion(incompleteInstall?.packageJsonPath ?? null),
176
209
  installLayout: incompleteInstall?.installLayout ?? null,
177
210
  additionalInstallCount: installedPlugin.additionalInstalls.length,
178
211
  installState: "not_installed",
@@ -189,6 +222,7 @@ export function inspectOpenClawBrainHookStatus(openclawHome) {
189
222
  const allowlist = inspectOpenClawBrainPluginAllowlist(resolvedHome);
190
223
  const layoutLabel = describeOpenClawBrainInstallLayout(selectedInstall.installLayout);
191
224
  const identityDetail = describeOpenClawBrainInstallIdentity(selectedInstall);
225
+ const packageVersion = readInstalledHookPackageVersion(selectedInstall.packageJsonPath);
192
226
  const activationRootState = inspectInstalledHookActivationRoot(selectedInstall.loaderEntryPath);
193
227
  if (allowlist.state === "blocked") {
194
228
  return {
@@ -202,6 +236,7 @@ export function inspectOpenClawBrainHookStatus(openclawHome) {
202
236
  manifestId: selectedInstall.manifestId,
203
237
  installId: selectedInstall.installId,
204
238
  packageName: selectedInstall.packageName,
239
+ packageVersion,
205
240
  installLayout: selectedInstall.installLayout,
206
241
  additionalInstallCount: installedPlugin.additionalInstalls.length,
207
242
  installState: "blocked_by_allowlist",
@@ -224,6 +259,7 @@ export function inspectOpenClawBrainHookStatus(openclawHome) {
224
259
  manifestId: selectedInstall.manifestId,
225
260
  installId: selectedInstall.installId,
226
261
  packageName: selectedInstall.packageName,
262
+ packageVersion,
227
263
  installLayout: selectedInstall.installLayout,
228
264
  additionalInstallCount: installedPlugin.additionalInstalls.length,
229
265
  installState: "blocked_by_allowlist",
@@ -246,6 +282,7 @@ export function inspectOpenClawBrainHookStatus(openclawHome) {
246
282
  manifestId: selectedInstall.manifestId,
247
283
  installId: selectedInstall.installId,
248
284
  packageName: selectedInstall.packageName,
285
+ packageVersion,
249
286
  installLayout: selectedInstall.installLayout,
250
287
  additionalInstallCount: installedPlugin.additionalInstalls.length,
251
288
  installState: "installed",
@@ -266,6 +303,7 @@ export function inspectOpenClawBrainHookStatus(openclawHome) {
266
303
  manifestId: selectedInstall.manifestId,
267
304
  installId: selectedInstall.installId,
268
305
  packageName: selectedInstall.packageName,
306
+ packageVersion,
269
307
  installLayout: selectedInstall.installLayout,
270
308
  additionalInstallCount: installedPlugin.additionalInstalls.length,
271
309
  installState: "installed",
@@ -284,4 +322,83 @@ export function summarizeOpenClawBrainHookLoad(inspection, statusProbeReady) {
284
322
  : "not_ready"
285
323
  };
286
324
  }
325
+ export function describeOpenClawBrainHotfixBoundary(input) {
326
+ const hookInspection = input.hookInspection;
327
+ const daemonInspection = input.daemonInspection ?? null;
328
+ const daemonPath = normalizeSurfacePath(daemonInspection?.configuredRuntimePath ?? null);
329
+ const hookPath = normalizeSurfacePath(hookInspection.hookPath);
330
+ const runtimeGuardPath = normalizeSurfacePath(hookInspection.runtimeGuardPath);
331
+ const daemonPackage = formatPackageIdentity(daemonInspection?.configuredRuntimePackageName ?? null, daemonInspection?.configuredRuntimePackageVersion ?? null, daemonInspection?.configuredRuntimePackageSpec ?? null);
332
+ const hookPackage = formatPackageIdentity(hookInspection.packageName, hookInspection.packageVersion);
333
+ if (hookInspection.scope === "activation_root_only") {
334
+ return {
335
+ boundary: "hook_surface_unverified",
336
+ skew: "unverified",
337
+ daemonPath,
338
+ hookPath: null,
339
+ runtimeGuardPath: null,
340
+ daemonPackage,
341
+ hookPackage: null,
342
+ guidance: "Pin --openclaw-home before patching the installed hook/runtime-guard surface; activation-root-only status does not prove which OpenClaw install you would be changing.",
343
+ detail: daemonPath === null
344
+ ? "activation-root-only status does not expose the installed hook/runtime-guard surface."
345
+ : `daemon runtime path ${shortenPath(daemonPath)} is visible, but activation-root-only status still does not expose the installed hook/runtime-guard surface.`
346
+ };
347
+ }
348
+ if (hookPath === null && runtimeGuardPath === null) {
349
+ return {
350
+ boundary: "hook_surface_unverified",
351
+ skew: "unverified",
352
+ daemonPath,
353
+ hookPath: null,
354
+ runtimeGuardPath: null,
355
+ daemonPackage,
356
+ hookPackage,
357
+ guidance: daemonPath === null
358
+ ? "Repair or reinstall the installed hook surface before patching OpenClaw load behavior."
359
+ : "Patch the daemon runtime path for background watch fixes, but repair or reinstall the installed hook/runtime-guard surface before patching OpenClaw load behavior.",
360
+ detail: daemonPath === null
361
+ ? "no verified daemon runtime path or installed hook/runtime-guard path is visible from this status snapshot."
362
+ : `daemon runtime path ${shortenPath(daemonPath)} is visible, but the installed hook/runtime-guard surface is not yet loadable.`
363
+ };
364
+ }
365
+ if (daemonPath === null) {
366
+ return {
367
+ boundary: "daemon_surface_only",
368
+ skew: "unverified",
369
+ daemonPath: null,
370
+ hookPath,
371
+ runtimeGuardPath,
372
+ daemonPackage: null,
373
+ hookPackage,
374
+ guidance: "Patch the installed hook/runtime-guard surface for OpenClaw load fixes. No configured daemon runtime path is visible from status.",
375
+ detail: `installed hook loads from ${hookPath === null ? "unverified" : shortenPath(hookPath)}${runtimeGuardPath === null ? "" : ` with runtime-guard ${shortenPath(runtimeGuardPath)}`}, but no configured daemon runtime path is visible.`
376
+ };
377
+ }
378
+ const samePath = daemonPath === hookPath || daemonPath === runtimeGuardPath;
379
+ const daemonVersion = daemonInspection?.configuredRuntimePackageVersion ?? null;
380
+ const hookVersion = hookInspection.packageVersion ?? null;
381
+ const skew = samePath
382
+ ? "same_path"
383
+ : daemonVersion !== null && hookVersion !== null
384
+ ? daemonVersion === hookVersion
385
+ ? "split_path_same_version"
386
+ : "split_path_version_skew"
387
+ : "split_path_version_unverified";
388
+ return {
389
+ boundary: samePath ? "same_surface" : "split_surfaces",
390
+ skew,
391
+ daemonPath,
392
+ hookPath,
393
+ runtimeGuardPath,
394
+ daemonPackage,
395
+ hookPackage,
396
+ guidance: samePath
397
+ ? "Daemon and installed hook paths currently collapse to the same file; verify both surfaces before patching anyway."
398
+ : "Patch the daemon runtime path for background watch/learner fixes. Patch the installed hook/runtime-guard paths for OpenClaw load fixes.",
399
+ detail: samePath
400
+ ? `daemon runtime path ${shortenPath(daemonPath)} currently resolves to the same file as the installed OpenClaw hook surface.`
401
+ : `daemon background watch runs from ${shortenPath(daemonPath)}${daemonPackage === null ? "" : ` (${daemonPackage})`}; OpenClaw loads the installed hook from ${hookPath === null ? "unverified" : shortenPath(hookPath)}${hookPackage === null ? "" : ` (${hookPackage})`}${runtimeGuardPath === null ? "" : ` and runtime-guard ${shortenPath(runtimeGuardPath)}`}.`
402
+ };
403
+ }
287
404
  //# sourceMappingURL=openclaw-hook-truth.js.map
@@ -546,7 +546,7 @@ function buildVerdict({ steps, gatewayStatus, pluginInspect, statusSignals, brea
546
546
  };
547
547
  }
548
548
 
549
- function buildSummary({ options, steps, verdict, gatewayStatusText, pluginInspectText, statusSignals, breadcrumbs, runtimeLoadProofSnapshot, guardLine, feedbackLine, attributionLine, attributionCoverageLine, learningPathLine, coverageSnapshot, hardeningSnapshot }) {
549
+ function buildSummary({ options, steps, verdict, gatewayStatusText, pluginInspectText, statusSignals, breadcrumbs, runtimeLoadProofSnapshot, surfaceLine, surfacesLine, hotfixLine, guardLine, feedbackLine, attributionLine, attributionCoverageLine, learningPathLine, learningFlowLine, learningHealthLine, coverageSnapshot, hardeningSnapshot }) {
550
550
  const passed = [];
551
551
  const missing = [];
552
552
  const warnings = Array.isArray(verdict.warnings) ? verdict.warnings : [];
@@ -602,11 +602,33 @@ function buildSummary({ options, steps, verdict, gatewayStatusText, pluginInspec
602
602
  "## Warnings",
603
603
  ...(warnings.length === 0 ? ["- none"] : warnings.map((item) => `- ${item}`)),
604
604
  "",
605
+ "## Hotfix Boundary",
606
+ ...(surfaceLine === null
607
+ ? ["- hotfix boundary line not reported by detailed status"]
608
+ : [`- ${surfaceLine}`]),
609
+ ...(surfacesLine === null
610
+ ? ["- hotfix paths line not reported by detailed status"]
611
+ : [`- ${surfacesLine}`]),
612
+ ...(hotfixLine === null
613
+ ? ["- hotfix guidance line not reported by detailed status"]
614
+ : [`- ${hotfixLine}`]),
615
+ "",
605
616
  "## Runtime Guard",
606
617
  ...(guardLine === null
607
618
  ? ["- runtime guard line not reported by detailed status"]
608
619
  : [`- ${guardLine}`]),
609
620
  "",
621
+ "## Learning Flow",
622
+ ...(learningPathLine === null
623
+ ? ["- learning path line not reported by detailed status"]
624
+ : [`- ${learningPathLine}`]),
625
+ ...(learningFlowLine === null
626
+ ? ["- learning flow line not reported by detailed status"]
627
+ : [`- ${learningFlowLine}`]),
628
+ ...(learningHealthLine === null
629
+ ? ["- learning health line not reported by detailed status"]
630
+ : [`- ${learningHealthLine}`]),
631
+ "",
610
632
  "## Learning Attribution",
611
633
  ...(feedbackLine === null
612
634
  ? ["- feedback line not reported by detailed status"]
@@ -617,9 +639,6 @@ function buildSummary({ options, steps, verdict, gatewayStatusText, pluginInspec
617
639
  ...(attributionCoverageLine === null
618
640
  ? ["- attribution coverage line not reported by detailed status"]
619
641
  : [`- ${attributionCoverageLine}`]),
620
- ...(learningPathLine === null
621
- ? []
622
- : [`- ${learningPathLine}`]),
623
642
  "",
624
643
  "## Coverage snapshot",
625
644
  `- attached profiles: ${coverageSnapshot.attachedProfileCount}`,
@@ -916,11 +935,16 @@ export function captureOperatorProofBundle(options) {
916
935
  const gatewayLogPath = extractGatewayLogPath(gatewayStatusCapture.stdout);
917
936
  const activationRoot = extractActivationRoot(statusCapture.stdout, options.activationRoot ?? null);
918
937
  const statusSignals = extractStatusSignals(statusCapture.stdout);
938
+ const surfaceLine = extractDetailedStatusLine(statusCapture.stdout, "surface");
939
+ const surfacesLine = extractDetailedStatusLine(statusCapture.stdout, "surfaces");
940
+ const hotfixLine = extractDetailedStatusLine(statusCapture.stdout, "hotfix");
919
941
  const attachTruthLine = extractDetailedStatusLine(statusCapture.stdout, "attachTruth");
920
942
  const attachedSetLine = extractDetailedStatusLine(statusCapture.stdout, "attachedSet");
921
943
  const serveLine = extractDetailedStatusLine(statusCapture.stdout, "serve");
922
944
  const routeFnLine = extractDetailedStatusLine(statusCapture.stdout, "routeFn");
923
945
  const guardLine = extractDetailedStatusLine(statusCapture.stdout, "guard");
946
+ const learningFlowLine = extractDetailedStatusLine(statusCapture.stdout, "learnFlow");
947
+ const learningHealthLine = extractDetailedStatusLine(statusCapture.stdout, "health");
924
948
  const feedbackLine = extractDetailedStatusLine(statusCapture.stdout, "feedback");
925
949
  const attributionLine = extractDetailedStatusLine(statusCapture.stdout, "attribution");
926
950
  const attributionCoverageLine = extractDetailedStatusLine(statusCapture.stdout, "attrCover");
@@ -978,7 +1002,12 @@ export function captureOperatorProofBundle(options) {
978
1002
  },
979
1003
  runtimeLoadProofPath,
980
1004
  runtimeLoadProofError: runtimeLoadProofSnapshot.error,
1005
+ surfaceLine,
1006
+ surfacesLine,
1007
+ hotfixLine,
981
1008
  guardLine,
1009
+ learningFlowLine,
1010
+ learningHealthLine,
982
1011
  feedbackLine,
983
1012
  attributionLine,
984
1013
  attributionCoverageLine,
@@ -994,7 +1023,12 @@ export function captureOperatorProofBundle(options) {
994
1023
  statusSignals,
995
1024
  breadcrumbs,
996
1025
  runtimeLoadProofSnapshot,
1026
+ surfaceLine,
1027
+ surfacesLine,
1028
+ hotfixLine,
997
1029
  guardLine,
1030
+ learningFlowLine,
1031
+ learningHealthLine,
998
1032
  feedbackLine,
999
1033
  attributionLine,
1000
1034
  attributionCoverageLine,
@@ -1015,7 +1049,12 @@ export function captureOperatorProofBundle(options) {
1015
1049
  hardeningSnapshot,
1016
1050
  verdict,
1017
1051
  statusSignals,
1052
+ surfaceLine,
1053
+ surfacesLine,
1054
+ hotfixLine,
1018
1055
  guardLine,
1056
+ learningFlowLine,
1057
+ learningHealthLine,
1019
1058
  feedbackLine,
1020
1059
  attributionLine,
1021
1060
  attributionCoverageLine,
@@ -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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclawbrain/cli",
3
- "version": "0.4.26",
3
+ "version": "0.4.28",
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",