@openclawbrain/cli 0.4.21 → 0.4.23

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
@@ -33,6 +33,11 @@ const OPENCLAWBRAIN_EMBEDDER_MODEL_ENV = "OPENCLAWBRAIN_EMBEDDER_MODEL";
33
33
  const OPENCLAWBRAIN_INSTALL_SKIP_EMBEDDER_PROVISION_ENV = "OPENCLAWBRAIN_INSTALL_SKIP_EMBEDDER_PROVISION";
34
34
  const LEGACY_COMPAT_PACKAGE_NAME = "@jonathangu/openclawbrain";
35
35
  const INSTALL_COMPATIBLE_LOCAL_TEACHER_MODEL_PREFIXES = [
36
+ "unsloth-qwen3.5-27b:q4_k_m",
37
+ "unsloth-qwen3.5-27b",
38
+ "qwen3.5:32b",
39
+ "qwen3.5:27b",
40
+ "qwen3.5:14b",
36
41
  "qwen3.5:9b",
37
42
  "qwen3.5:8b",
38
43
  "qwen3:8b",
@@ -813,10 +818,14 @@ function summarizeStatusHookFilesState(installHook) {
813
818
  return "unverified";
814
819
  }
815
820
  function summarizeStatusAttachmentWatcher(status) {
816
- if (status.passiveLearning.watchState === "watching") {
821
+ const watchState = status.passiveLearning.watch?.state ?? status.passiveLearning.watchState;
822
+ if (watchState === "watching") {
817
823
  return "alive";
818
824
  }
819
- if (status.passiveLearning.watchState === "stale_snapshot") {
825
+ if (watchState === "lagging") {
826
+ return "lagging";
827
+ }
828
+ if (watchState === "stale_snapshot") {
820
829
  return "stale";
821
830
  }
822
831
  return "not_visible";
@@ -1145,13 +1154,14 @@ function summarizeStatusTeacher(report, providerConfig, localLlm) {
1145
1154
  detail: `${providerConfig.teacher.model} is enabled on Ollama, but no watch teacher snapshot is visible yet`
1146
1155
  };
1147
1156
  }
1148
- const stale = report.teacherLoop.watchState === "stale_snapshot" || (report.teacherLoop.latestFreshness === "stale" && report.teacherLoop.lastNoOpReason !== "no_teacher_artifacts");
1157
+ const watchState = report.teacherLoop.watch?.state ?? report.teacherLoop.watchState ?? "not_visible";
1158
+ const stale = watchState === "stale_snapshot" || (report.teacherLoop.latestFreshness === "stale" && report.teacherLoop.lastNoOpReason !== "no_teacher_artifacts");
1149
1159
  const idle = report.teacherLoop.running === false &&
1150
1160
  (report.teacherLoop.queueDepth ?? 0) === 0 &&
1151
1161
  report.teacherLoop.failureMode === "none";
1152
1162
  const healthy = report.teacherLoop.failureMode === "none" &&
1153
1163
  stale === false &&
1154
- report.teacherLoop.watchState !== "not_visible";
1164
+ watchState === "watching";
1155
1165
  const cycleDetail = report.teacherLoop.lastNoOpReason === "no_teacher_artifacts"
1156
1166
  ? summarizeTeacherNoArtifactCycle(report.teacherLoop.notes).detail
1157
1167
  : TEACHER_NO_OP_MESSAGES[report.teacherLoop.lastNoOpReason] ?? "the latest teacher cycle detail is unavailable";
@@ -1175,7 +1185,9 @@ function summarizeStatusTeacher(report, providerConfig, localLlm) {
1175
1185
  stale,
1176
1186
  idle,
1177
1187
  latestCycle,
1178
- detail: `${providerConfig.teacher.model} is enabled on Ollama; ${cycleDetail}`
1188
+ detail: watchState === "lagging"
1189
+ ? `${providerConfig.teacher.model} is enabled on Ollama, but the watch heartbeat is lagging; ${cycleDetail}`
1190
+ : `${providerConfig.teacher.model} is enabled on Ollama; ${cycleDetail}`
1179
1191
  };
1180
1192
  }
1181
1193
  function summarizeStatusEmbedder(embeddings) {
@@ -1329,7 +1341,7 @@ function summarizeStatusAlerts(report, providerConfig, embeddings, localLlm) {
1329
1341
  return buckets;
1330
1342
  }
1331
1343
  function summarizeStatusWatchState(status) {
1332
- return status.passiveLearning.watchState;
1344
+ return status.passiveLearning.watch?.state ?? status.passiveLearning.watchState;
1333
1345
  }
1334
1346
  function summarizeStatusServeReality(status) {
1335
1347
  if (status.brainStatus.serveState === "serving_active_pack") {
@@ -1478,7 +1490,7 @@ function formatCurrentProfileStatusSummary(status, report, targetInspection, opt
1478
1490
  `budget requested=${report.servePath.requestedBudgetStrategy ?? "none"} resolved=${report.servePath.resolvedBudgetStrategy ?? "none"} maxBlocks=${report.servePath.resolvedMaxContextBlocks ?? "none"} source=${report.servePath.structuralBudgetSource ?? "none"} origin=${status.brainStatus.structuralDecision.origin} basis=${status.brainStatus.structuralDecision.basis}`,
1479
1491
  `decision ${status.brainStatus.structuralDecision.detail}`,
1480
1492
  `principal latest=${formatPrincipalLatest(report)} pending=${report.principal.pendingCount ?? report.learning.pendingPrincipalCount ?? "none"} checkpoint=${formatPrincipalCheckpointFrontier(report)} downstream=${yesNo(report.principal.servingDownstreamOfLatestCorrection)} lag=${report.learning.principalLagToPromotion.sequenceLag ?? "none"}`,
1481
- `passive learner=${yesNo(status.passiveLearning.learnerRunning)} firstExport=${yesNo(status.passiveLearning.firstExportOccurred)} watch=${status.passiveLearning.watchState} export=${status.passiveLearning.exportState} backlog=${status.passiveLearning.backlogState} pending=${formatStatusNullableNumber(status.passiveLearning.pendingLive)}/${formatStatusNullableNumber(status.passiveLearning.pendingBackfill)} detail=${status.passiveLearning.detail}`,
1493
+ `passive learner=${yesNo(status.passiveLearning.learnerRunning)} firstExport=${yesNo(status.passiveLearning.firstExportOccurred)} watch=${summarizeStatusWatchState(status)} export=${status.passiveLearning.exportState} backlog=${status.passiveLearning.backlogState} pending=${formatStatusNullableNumber(status.passiveLearning.pendingLive)}/${formatStatusNullableNumber(status.passiveLearning.pendingBackfill)} detail=${status.passiveLearning.detail}`,
1482
1494
  `delta observed=${status.passiveLearning.lastObservedDelta.observedAt ?? "none"} exported=${formatStatusNullableYesNo(status.passiveLearning.lastObservedDelta.exported)} labeled=${formatStatusNullableYesNo(status.passiveLearning.lastObservedDelta.labeled)} promoted=${formatStatusNullableYesNo(status.passiveLearning.lastObservedDelta.promoted)} served=${formatStatusNullableYesNo(status.passiveLearning.lastObservedDelta.served)} transition=${formatStatusObservedDeltaTransition(status.passiveLearning.lastObservedDelta)} detail=${status.passiveLearning.lastObservedDelta.explanation}`,
1483
1495
  `scanner flowing=${yesNo(report.supervision.flowing)} scan=${report.supervision.scanPolicy ?? "none"} surfaces=${formatScannerSurfaces(report)} labels=${report.supervision.humanLabelCount ?? "none"}/${report.supervision.selfLabelCount ?? "none"} attributable=${report.supervision.attributedEventCount ?? "none"}/${report.supervision.totalEventCount ?? "none"} digests=${report.supervision.selectionDigestCount ?? "none"}`,
1484
1496
  `labels ${formatLabelFlowSummary(report.labelFlow)}`,
@@ -5111,7 +5123,13 @@ function listWatchRuntimeEventExportBundleRoots(scanRoot) {
5111
5123
  .map((entry) => path.join(scanRoot, entry.name))
5112
5124
  .sort((left, right) => left.localeCompare(right));
5113
5125
  }
5114
- async function replayWatchScanRootIntoTeacherLoop(teacherLoop, scanRoot) {
5126
+ async function replayWatchScanRootIntoTeacherLoop(teacherLoop, scanRoot, options = {}) {
5127
+ if (options.skip === true) {
5128
+ return {
5129
+ replayedBundleCount: 0,
5130
+ replayedEventCount: 0
5131
+ };
5132
+ }
5115
5133
  const seenExportDigests = new Set();
5116
5134
  const bundles = listWatchRuntimeEventExportBundleRoots(scanRoot)
5117
5135
  .map((rootDir) => {
@@ -5733,6 +5751,7 @@ export async function createWatchCommandRuntime(input) {
5733
5751
  const restoredSeenExportCount = restoredTeacherState.snapshot.state?.seenExportDigests.length ?? 0;
5734
5752
  log(`Restored teacher snapshot: seen=${restoredSeenExportCount} artifacts=${restoredTeacherState.snapshot.teacher.artifactCount}`);
5735
5753
  }
5754
+ const restoredSeenExportCount = restoredTeacherState.snapshot?.state?.seenExportDigests.length ?? 0;
5736
5755
  const resolvedWatchProfileScope = input.profileRoots === undefined
5737
5756
  ? resolveWatchProfileRootsForActivationRoot(activationRoot)
5738
5757
  : {
@@ -5774,7 +5793,13 @@ export async function createWatchCommandRuntime(input) {
5774
5793
  replayedEventCount: 0
5775
5794
  };
5776
5795
  try {
5777
- replayState = await replayWatchScanRootIntoTeacherLoop(teacherLoop, scanRoot);
5796
+ const skipStoredReplay = restoredSeenExportCount > 0 && startupWarnings.length === 0;
5797
+ if (skipStoredReplay) {
5798
+ log(`Stored replay skipped: restored teacher snapshot already tracks ${restoredSeenExportCount} export digest${restoredSeenExportCount === 1 ? "" : "s"}.`);
5799
+ }
5800
+ replayState = await replayWatchScanRootIntoTeacherLoop(teacherLoop, scanRoot, {
5801
+ skip: skipStoredReplay
5802
+ });
5778
5803
  }
5779
5804
  catch (error) {
5780
5805
  const message = formatWatchError(error);
@@ -6,6 +6,16 @@ import { type ActivationInspection, type ActivationObservabilityReport, type Gra
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";
9
+ export type OperatorPassiveLearningWatchState = CurrentProfilePassiveLearningWatchStateV1 | "lagging";
10
+ export interface OperatorPassiveLearningWatchSummary {
11
+ state: OperatorPassiveLearningWatchState;
12
+ detail: string;
13
+ lastHeartbeatAt: string | null;
14
+ lagSeconds: number | null;
15
+ intervalSeconds: number | null;
16
+ healthyWithinSeconds: number | null;
17
+ staleAfterSeconds: number | null;
18
+ }
9
19
  export declare const DEFAULT_ASYNC_TEACHER_QUEUE_CAPACITY = 8;
10
20
  declare const RECORDED_SESSION_TRACE_CONTRACT: "recorded_session_trace.v1";
11
21
  declare const RECORDED_SESSION_FIXTURE_CONTRACT: "recorded_session_replay_fixture.v1";
@@ -1687,7 +1697,8 @@ export interface OperatorTeacherLoopSummary {
1687
1697
  lastHeartbeatAt: string | null;
1688
1698
  lastScanAt: string | null;
1689
1699
  pollIntervalSeconds: number | null;
1690
- watchState: CurrentProfilePassiveLearningWatchStateV1;
1700
+ watchState: OperatorPassiveLearningWatchState;
1701
+ watch: OperatorPassiveLearningWatchSummary;
1691
1702
  lastProcessedAt: string | null;
1692
1703
  artifactCount: number | null;
1693
1704
  queueDepth: number | null;
package/dist/src/index.js CHANGED
@@ -6646,11 +6646,18 @@ function loadTeacherSurfaceFromInput(input) {
6646
6646
  }
6647
6647
  function summarizeTeacherLoopWatchState(input) {
6648
6648
  if (input.sourceKind !== "watch_snapshot" || input.watchSnapshot === null) {
6649
+ const watchState = input.sourceKind === "async_snapshot" ? "snapshot_only" : "not_visible";
6649
6650
  return {
6650
6651
  snapshotUpdatedAt: null,
6651
6652
  lastWatchHeartbeatAt: null,
6652
6653
  pollIntervalSeconds: null,
6653
- watchState: input.sourceKind === "async_snapshot" ? "snapshot_only" : "not_visible"
6654
+ watchState,
6655
+ watch: buildTeacherLoopWatchSummary({
6656
+ state: watchState,
6657
+ detail: watchState === "snapshot_only"
6658
+ ? "only a saved watcher snapshot is visible; no live heartbeat is available"
6659
+ : "no watcher snapshot is visible from the current activation root"
6660
+ })
6654
6661
  };
6655
6662
  }
6656
6663
  const lastWatchHeartbeatAt = input.watchSnapshot.snapshot.runtime?.lastHeartbeatAt ?? input.watchSnapshot.lastRunAt;
@@ -6660,16 +6667,55 @@ function summarizeTeacherLoopWatchState(input) {
6660
6667
  snapshotUpdatedAt: input.watchSnapshot.updatedAt,
6661
6668
  lastWatchHeartbeatAt: null,
6662
6669
  pollIntervalSeconds,
6663
- watchState: "snapshot_only"
6670
+ watchState: "snapshot_only",
6671
+ watch: buildTeacherLoopWatchSummary({
6672
+ state: "snapshot_only",
6673
+ detail: "only a saved watcher snapshot is visible; no live heartbeat is available",
6674
+ intervalSeconds: pollIntervalSeconds
6675
+ })
6664
6676
  };
6665
6677
  }
6666
- const lagMs = Date.parse(input.observedAt) - Date.parse(lastWatchHeartbeatAt);
6667
- const staleAfterMs = pollIntervalSeconds * 2000 + 15_000;
6678
+ const rawLagMs = Date.parse(input.observedAt) - Date.parse(lastWatchHeartbeatAt);
6679
+ const lagMs = Number.isFinite(rawLagMs) ? Math.max(0, rawLagMs) : null;
6680
+ const healthyWithinMs = pollIntervalSeconds * 2000 + 15_000;
6681
+ const staleAfterMs = healthyWithinMs + Math.max(15_000, pollIntervalSeconds * 1000);
6682
+ const watchState = lagMs !== null && lagMs <= healthyWithinMs
6683
+ ? "watching"
6684
+ : lagMs !== null && lagMs <= staleAfterMs
6685
+ ? "lagging"
6686
+ : "stale_snapshot";
6668
6687
  return {
6669
6688
  snapshotUpdatedAt: input.watchSnapshot.updatedAt,
6670
6689
  lastWatchHeartbeatAt,
6671
6690
  pollIntervalSeconds,
6672
- watchState: Number.isFinite(lagMs) && lagMs >= 0 && lagMs <= staleAfterMs ? "watching" : "stale_snapshot"
6691
+ watchState,
6692
+ watch: buildTeacherLoopWatchSummary({
6693
+ state: watchState,
6694
+ detail: watchState === "watching"
6695
+ ? "watch heartbeat is inside the healthy window"
6696
+ : watchState === "lagging"
6697
+ ? "watch heartbeat missed the healthy window but has not crossed the stale snapshot threshold"
6698
+ : "watch heartbeat is older than the stale snapshot threshold",
6699
+ lastHeartbeatAt: lastWatchHeartbeatAt,
6700
+ lagMs,
6701
+ intervalSeconds: pollIntervalSeconds,
6702
+ healthyWithinMs,
6703
+ staleAfterMs
6704
+ })
6705
+ };
6706
+ }
6707
+ function buildTeacherLoopWatchSummary(input) {
6708
+ const lagSeconds = Number.isFinite(input.lagMs) ? Math.round((input.lagMs / 1000) * 100) / 100 : null;
6709
+ const healthyWithinSeconds = Number.isFinite(input.healthyWithinMs) ? Math.round((input.healthyWithinMs / 1000) * 100) / 100 : null;
6710
+ const staleAfterSeconds = Number.isFinite(input.staleAfterMs) ? Math.round((input.staleAfterMs / 1000) * 100) / 100 : null;
6711
+ return {
6712
+ state: input.state,
6713
+ detail: input.detail,
6714
+ lastHeartbeatAt: input.lastHeartbeatAt ?? null,
6715
+ lagSeconds,
6716
+ intervalSeconds: input.intervalSeconds ?? null,
6717
+ healthyWithinSeconds,
6718
+ staleAfterSeconds
6673
6719
  };
6674
6720
  }
6675
6721
  function emptyOperatorLearningAttribution(source, snapshotKind, detail) {
@@ -6798,6 +6844,10 @@ function summarizeTeacherLoop(input) {
6798
6844
  const teacherSnapshotPath = resolveOperatorTeacherSnapshotPath(input.activationRoot, normalizeOptionalString(input.teacherSnapshotPath) ?? null);
6799
6845
  const unavailableFromMissing = buildUnavailableLastObservedDelta("no watch teacher snapshot is visible for the latest observed cycle");
6800
6846
  const unavailableFromAsync = buildUnavailableLastObservedDelta("raw async teacher snapshots do not record the last observed export/label/promotion delta");
6847
+ const notVisibleWatch = buildTeacherLoopWatchSummary({
6848
+ state: "not_visible",
6849
+ detail: "no watcher snapshot is visible from the current activation root"
6850
+ });
6801
6851
  if (loaded === null && teacherSnapshotPath === null) {
6802
6852
  return {
6803
6853
  available: false,
@@ -6812,6 +6862,7 @@ function summarizeTeacherLoop(input) {
6812
6862
  lastScanAt: null,
6813
6863
  pollIntervalSeconds: null,
6814
6864
  watchState: "not_visible",
6865
+ watch: notVisibleWatch,
6815
6866
  lastProcessedAt: null,
6816
6867
  artifactCount: null,
6817
6868
  queueDepth: null,
@@ -6852,6 +6903,7 @@ function summarizeTeacherLoop(input) {
6852
6903
  lastScanAt: null,
6853
6904
  pollIntervalSeconds: null,
6854
6905
  watchState: "not_visible",
6906
+ watch: notVisibleWatch,
6855
6907
  lastProcessedAt: null,
6856
6908
  artifactCount: null,
6857
6909
  queueDepth: null,
@@ -6898,6 +6950,7 @@ function summarizeTeacherLoop(input) {
6898
6950
  lastScanAt: snapshot.runtime?.lastScanAt ?? null,
6899
6951
  pollIntervalSeconds: watchState.pollIntervalSeconds,
6900
6952
  watchState: watchState.watchState,
6953
+ watch: watchState.watch,
6901
6954
  lastProcessedAt: snapshot.diagnostics.lastProcessedAt,
6902
6955
  artifactCount: watchSnapshot?.teacher.artifactCount ?? snapshot.teacher.artifactCount,
6903
6956
  queueDepth: snapshot.queue.depth,
@@ -7486,6 +7539,13 @@ function didCurrentProfileFirstExportOccur(report) {
7486
7539
  }
7487
7540
  function summarizeCurrentProfilePassiveLearning(report, activePackId) {
7488
7541
  const firstExportOccurred = didCurrentProfileFirstExportOccur(report);
7542
+ const watch = report.teacherLoop.watch ?? buildTeacherLoopWatchSummary({
7543
+ state: report.teacherLoop.watchState,
7544
+ detail: "watch state came from the legacy passive-learning surface",
7545
+ lastHeartbeatAt: report.teacherLoop.lastHeartbeatAt,
7546
+ intervalSeconds: report.teacherLoop.pollIntervalSeconds
7547
+ });
7548
+ const watchState = watch.state ?? report.teacherLoop.watchState;
7489
7549
  const exportState = !firstExportOccurred
7490
7550
  ? "awaiting_first_export"
7491
7551
  : report.supervision.exportedAt !== null
@@ -7495,28 +7555,33 @@ function summarizeCurrentProfilePassiveLearning(report, activePackId) {
7495
7555
  ? report.learning.backlogState
7496
7556
  : "unknown";
7497
7557
  const detail = !firstExportOccurred
7498
- ? report.teacherLoop.watchState === "watching"
7558
+ ? watchState === "watching"
7499
7559
  ? "watch heartbeat is fresh, but this activation root has not observed its first export yet"
7560
+ : watchState === "lagging"
7561
+ ? "watch heartbeat is lagging, and this activation root is still waiting for the first export before passive learning can advance"
7500
7562
  : "this activation root is still waiting for the first export before passive learning can advance"
7501
7563
  : backlogState === "unknown"
7502
7564
  ? "first export is proven, but passive backlog state is not visible from the current local artifacts"
7503
- : report.teacherLoop.watchState === "watching"
7565
+ : watchState === "watching"
7504
7566
  ? `watch heartbeat is fresh; passive backlog is ${backlogState} with live=${report.learning.pendingLive ?? 0} and backfill=${report.learning.pendingBackfill ?? 0}`
7505
- : report.teacherLoop.watchState === "stale_snapshot"
7567
+ : watchState === "lagging"
7568
+ ? `watch heartbeat is lagging; latest known passive backlog is ${backlogState} with live=${report.learning.pendingLive ?? 0} and backfill=${report.learning.pendingBackfill ?? 0}`
7569
+ : watchState === "stale_snapshot"
7506
7570
  ? `last saved watch snapshot is stale; latest known passive backlog is ${backlogState}`
7507
- : report.teacherLoop.watchState === "snapshot_only"
7571
+ : watchState === "snapshot_only"
7508
7572
  ? `passive backlog is visible from the last saved snapshot: ${backlogState}`
7509
7573
  : `passive backlog is visible from the last known learner state: ${backlogState}`;
7510
7574
  return {
7511
- learnerRunning: report.teacherLoop.watchState === "watching",
7575
+ learnerRunning: watchState === "watching",
7512
7576
  firstExportOccurred,
7513
- watchState: report.teacherLoop.watchState,
7577
+ watchState,
7578
+ watch,
7514
7579
  exportState,
7515
7580
  backlogState,
7516
7581
  pendingLive: report.learning.available ? report.learning.pendingLive : null,
7517
7582
  pendingBackfill: report.learning.available ? report.learning.pendingBackfill : null,
7518
- lastWatchHeartbeatAt: report.teacherLoop.lastHeartbeatAt,
7519
- watchIntervalSeconds: report.teacherLoop.pollIntervalSeconds,
7583
+ lastWatchHeartbeatAt: watch.lastHeartbeatAt ?? report.teacherLoop.lastHeartbeatAt,
7584
+ watchIntervalSeconds: watch.intervalSeconds ?? report.teacherLoop.pollIntervalSeconds,
7520
7585
  lastExportAt: report.supervision.exportedAt,
7521
7586
  lastPromotionAt: report.promotion.lastPromotion.at,
7522
7587
  currentServingPackId: activePackId,
@@ -928,13 +928,15 @@ export function advanceAlwaysOnLearningRuntime(input) {
928
928
  const schedule = selectScheduledSlices(pending, current.learnedEventExport, cadence);
929
929
  const selectedSlices = schedule.selected;
930
930
  const learnedEventExport = mergeNormalizedEventExports(current.learnedEventExport, selectedSlices);
931
- const runtimeGraphSnapshot = buildRuntimeGraphSnapshot({
932
- ...input,
933
- state: {
934
- ...current,
935
- structuralController
936
- }
937
- });
931
+ const runtimeGraphSnapshot = selectedSlices.length === 0
932
+ ? null
933
+ : buildRuntimeGraphSnapshot({
934
+ ...input,
935
+ state: {
936
+ ...current,
937
+ structuralController
938
+ }
939
+ });
938
940
  const runtimeGraph = runtimeGraphSnapshot?.graph ?? current.runtimeGraph;
939
941
  const runtimePlasticity = runtimeGraphSnapshot?.plasticity ?? current.runtimePlasticity;
940
942
  const sparseFeedbackObservedAt = input.builtAt ?? learnedEventExport?.range.lastCreatedAt ?? learnedEventExport?.range.firstCreatedAt ?? current.lastMaterializedAt ?? "1970-01-01T00:00:00.000Z";
@@ -171,6 +171,35 @@ function parseOpenClawSessionRecord(value, lineNumber) {
171
171
  timestamp: expectString(record.timestamp, `${lineNumber}.timestamp`)
172
172
  };
173
173
  }
174
+ case "compaction": {
175
+ const data = {};
176
+ if (record.summary !== undefined) {
177
+ data.summary = expectString(record.summary, `${lineNumber}.summary`);
178
+ }
179
+ if (record.firstKeptEntryId !== undefined) {
180
+ data.firstKeptEntryId = expectString(record.firstKeptEntryId, `${lineNumber}.firstKeptEntryId`);
181
+ }
182
+ if (record.tokensBefore !== undefined) {
183
+ data.tokensBefore = expectNumber(record.tokensBefore, `${lineNumber}.tokensBefore`);
184
+ }
185
+ if (record.details !== undefined) {
186
+ data.details = expectRecord(record.details, `${lineNumber}.details`);
187
+ }
188
+ if (record.fromHook !== undefined) {
189
+ if (typeof record.fromHook !== "boolean") {
190
+ throw new Error(`${lineNumber}.fromHook must be a boolean`);
191
+ }
192
+ data.fromHook = record.fromHook;
193
+ }
194
+ return {
195
+ type: "custom",
196
+ customType: "openclaw.compaction",
197
+ data,
198
+ id: expectString(record.id, `${lineNumber}.id`),
199
+ parentId: expectNullableString(record.parentId, `${lineNumber}.parentId`),
200
+ timestamp: expectString(record.timestamp, `${lineNumber}.timestamp`)
201
+ };
202
+ }
174
203
  case "message":
175
204
  return {
176
205
  type,
@@ -185,7 +214,9 @@ function parseOpenClawSessionRecord(value, lineNumber) {
185
214
  }
186
215
  function parseMessagePayload(value, path) {
187
216
  const payload = expectRecord(value, path);
188
- const content = expectArray(payload.content, `${path}.content`).map((entry, index) => parseContentPart(entry, `${path}.content[${index}]`));
217
+ const content = typeof payload.content === "string"
218
+ ? [{ type: "text", text: payload.content }]
219
+ : expectArray(payload.content, `${path}.content`).map((entry, index) => parseContentPart(entry, `${path}.content[${index}]`));
189
220
  return {
190
221
  ...payload,
191
222
  role: expectString(payload.role, `${path}.role`),
@@ -267,4 +298,4 @@ function expectNumber(value, path) {
267
298
  }
268
299
  return value;
269
300
  }
270
- //# sourceMappingURL=session-store.js.map
301
+ //# sourceMappingURL=session-store.js.map
@@ -383,6 +383,7 @@ export class OpenClawLocalSessionTail {
383
383
  const firstPoll = this.initialized === false;
384
384
  const warnings = [];
385
385
  const changes = [];
386
+ const observedKeys = new Set();
386
387
  const sources = discoverOpenClawSessionStores({
387
388
  ...(this.homeDir === undefined ? {} : { homeDir: this.homeDir }),
388
389
  ...(this.profileRoots === undefined ? {} : { profileRoots: this.profileRoots })
@@ -409,9 +410,21 @@ export class OpenClawLocalSessionTail {
409
410
  continue;
410
411
  }
411
412
  const key = cursorKey(source.indexPath, sessionKey);
413
+ observedKeys.add(key);
412
414
  const existing = this.cursorBySession.get(key) ?? null;
413
415
  const sessionFile = typeof entry.sessionFile === "string" && entry.sessionFile.trim().length > 0 ? path.resolve(entry.sessionFile) : null;
414
416
  if (sessionFile === null) {
417
+ const nextCursor = createCursor(source.indexPath, sessionKey, entry.sessionId, null, entry.updatedAt, 0, 0);
418
+ this.cursorBySession.set(key, nextCursor);
419
+ const changed = existing === null ||
420
+ existing.sessionId !== entry.sessionId ||
421
+ existing.sessionFile !== null ||
422
+ existing.updatedAt !== entry.updatedAt ||
423
+ existing.rawRecordCount !== 0 ||
424
+ existing.bridgedEventCount !== 0;
425
+ if (!changed) {
426
+ continue;
427
+ }
415
428
  changes.push(createChange({
416
429
  source,
417
430
  sessionKey,
@@ -425,6 +438,17 @@ export class OpenClawLocalSessionTail {
425
438
  continue;
426
439
  }
427
440
  if (!existsSync(sessionFile)) {
441
+ const nextCursor = createCursor(source.indexPath, sessionKey, entry.sessionId, sessionFile, entry.updatedAt, 0, 0);
442
+ this.cursorBySession.set(key, nextCursor);
443
+ const changed = existing === null ||
444
+ existing.sessionId !== entry.sessionId ||
445
+ existing.sessionFile !== sessionFile ||
446
+ existing.updatedAt !== entry.updatedAt ||
447
+ existing.rawRecordCount !== 0 ||
448
+ existing.bridgedEventCount !== 0;
449
+ if (!changed) {
450
+ continue;
451
+ }
428
452
  changes.push(createChange({
429
453
  source,
430
454
  sessionKey,
@@ -535,6 +559,17 @@ export class OpenClawLocalSessionTail {
535
559
  }));
536
560
  }
537
561
  }
562
+ let prunedCursorCount = 0;
563
+ for (const key of [...this.cursorBySession.keys()]) {
564
+ if (observedKeys.has(key)) {
565
+ continue;
566
+ }
567
+ this.cursorBySession.delete(key);
568
+ prunedCursorCount += 1;
569
+ }
570
+ if (prunedCursorCount > 0) {
571
+ warnings.push(`session tail pruned ${prunedCursorCount} stale cursor entr${prunedCursorCount === 1 ? "y" : "ies"}`);
572
+ }
538
573
  this.initialized = true;
539
574
  const noopReason = firstPoll && !changes.some((change) => change.scannedEventExport !== null)
540
575
  ? "seeded_existing_sessions"
@@ -599,4 +634,4 @@ export class OpenClawLocalSessionTail {
599
634
  export function createOpenClawLocalSessionTail(input = {}) {
600
635
  return new OpenClawLocalSessionTail(input);
601
636
  }
602
- //# sourceMappingURL=session-tail.js.map
637
+ //# sourceMappingURL=session-tail.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclawbrain/cli",
3
- "version": "0.4.21",
3
+ "version": "0.4.23",
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",