@openclawbrain/cli 0.4.17 → 0.4.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,65 @@
1
+ import { existsSync, openSync, readSync, closeSync, statSync, readFileSync } from "node:fs";
2
+
3
+ const DEFAULT_TAIL_BYTES = 4 * 1024 * 1024;
4
+ const DEFAULT_MAX_ENTRIES = 512;
5
+ const DEFAULT_MAX_LINE_BYTES = 128 * 1024;
6
+
7
+ export function readBoundedJsonlTail(filePath, options = {}) {
8
+ const tailBytes = options.tailBytes ?? DEFAULT_TAIL_BYTES;
9
+ const maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
10
+ const maxLineBytes = options.maxLineBytes ?? DEFAULT_MAX_LINE_BYTES;
11
+ if (!existsSync(filePath)) {
12
+ return { entries: [], fallbackReason: null };
13
+ }
14
+ let fallbackReason = null;
15
+ let raw;
16
+ try {
17
+ const fileSize = statSync(filePath).size;
18
+ if (fileSize <= tailBytes) {
19
+ raw = readFileSync(filePath, "utf8");
20
+ }
21
+ else {
22
+ fallbackReason = "tail_truncated";
23
+ const offset = fileSize - tailBytes;
24
+ const buf = Buffer.alloc(tailBytes);
25
+ const fd = openSync(filePath, "r");
26
+ try {
27
+ readSync(fd, buf, 0, tailBytes, offset);
28
+ }
29
+ finally {
30
+ closeSync(fd);
31
+ }
32
+ raw = buf.toString("utf8");
33
+ const firstNewline = raw.indexOf("\n");
34
+ if (firstNewline >= 0) {
35
+ raw = raw.slice(firstNewline + 1);
36
+ }
37
+ }
38
+ }
39
+ catch {
40
+ return { entries: [], fallbackReason: "read_error" };
41
+ }
42
+ const lines = raw.split(/\r?\n/u).map((l) => l.trim()).filter((l) => l.length > 0);
43
+ const entries = [];
44
+ let skippedOversized = 0;
45
+ for (const line of lines) {
46
+ if (Buffer.byteLength(line, "utf8") > maxLineBytes) {
47
+ skippedOversized++;
48
+ continue;
49
+ }
50
+ try {
51
+ entries.push(JSON.parse(line));
52
+ }
53
+ catch {
54
+ // skip malformed lines
55
+ }
56
+ }
57
+ if (skippedOversized > 0) {
58
+ fallbackReason = fallbackReason ? `${fallbackReason}+oversized_lines_skipped` : "oversized_lines_skipped";
59
+ }
60
+ const trimmed = entries.length > maxEntries ? entries.slice(-maxEntries) : entries;
61
+ if (entries.length > maxEntries) {
62
+ fallbackReason = fallbackReason ? `${fallbackReason}+entry_count_capped` : "entry_count_capped";
63
+ }
64
+ return { entries: trimmed, fallbackReason };
65
+ }
package/dist/src/cli.js CHANGED
@@ -10,7 +10,7 @@ import { ensureManagedLearnerServiceForActivationRoot, inspectManagedLearnerServ
10
10
  import { exportBrain, importBrain } from "./import-export.js";
11
11
  import { buildNormalizedEventExport } from "@openclawbrain/contracts";
12
12
  import { buildTeacherSupervisionArtifactsFromNormalizedEventExport, createAlwaysOnLearningRuntimeState, describeAlwaysOnLearningRuntimeState, drainAlwaysOnLearningRuntime, loadOrInitBaseline, materializeAlwaysOnLearningCandidatePack, persistBaseline } from "./local-learner.js";
13
- import { inspectActivationState, loadPackFromActivation, promoteCandidatePack, readLearningSpineLogEntries, stageCandidatePack } from "@openclawbrain/pack-format";
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
16
  import { inspectOpenClawBrainHookStatus, inspectOpenClawBrainPluginAllowlist } from "./openclaw-hook-truth.js";
@@ -37,6 +37,68 @@ const INSTALL_COMPATIBLE_LOCAL_TEACHER_MODEL_PREFIXES = [
37
37
  "qwen3:8b",
38
38
  "qwen2.5:7b"
39
39
  ];
40
+ const DEFAULT_BOUNDED_JSONL_TAIL_BYTES = 4 * 1024 * 1024;
41
+ const DEFAULT_BOUNDED_JSONL_MAX_ENTRIES = 512;
42
+ const DEFAULT_BOUNDED_JSONL_MAX_LINE_BYTES = 128 * 1024;
43
+ function readBoundedJsonlTail(filePath, options = {}) {
44
+ const tailBytes = options.tailBytes ?? DEFAULT_BOUNDED_JSONL_TAIL_BYTES;
45
+ const maxEntries = options.maxEntries ?? DEFAULT_BOUNDED_JSONL_MAX_ENTRIES;
46
+ const maxLineBytes = options.maxLineBytes ?? DEFAULT_BOUNDED_JSONL_MAX_LINE_BYTES;
47
+ if (!existsSync(filePath)) {
48
+ return { entries: [], fallbackReason: null };
49
+ }
50
+ let fallbackReason = null;
51
+ let raw;
52
+ try {
53
+ const fileSize = statSync(filePath).size;
54
+ if (fileSize <= tailBytes) {
55
+ raw = readFileSync(filePath, "utf8");
56
+ }
57
+ else {
58
+ fallbackReason = "tail_truncated";
59
+ const offset = fileSize - tailBytes;
60
+ const buffer = Buffer.alloc(tailBytes);
61
+ const fileDescriptor = openSync(filePath, "r");
62
+ try {
63
+ readSync(fileDescriptor, buffer, 0, tailBytes, offset);
64
+ }
65
+ finally {
66
+ closeSync(fileDescriptor);
67
+ }
68
+ raw = buffer.toString("utf8");
69
+ const firstNewline = raw.indexOf("\n");
70
+ if (firstNewline >= 0) {
71
+ raw = raw.slice(firstNewline + 1);
72
+ }
73
+ }
74
+ }
75
+ catch {
76
+ return { entries: [], fallbackReason: "read_error" };
77
+ }
78
+ const lines = raw.split(/\r?\n/u).map((line) => line.trim()).filter((line) => line.length > 0);
79
+ const entries = [];
80
+ let skippedOversized = 0;
81
+ for (const line of lines) {
82
+ if (Buffer.byteLength(line, "utf8") > maxLineBytes) {
83
+ skippedOversized++;
84
+ continue;
85
+ }
86
+ try {
87
+ entries.push(JSON.parse(line));
88
+ }
89
+ catch {
90
+ // skip malformed lines
91
+ }
92
+ }
93
+ if (skippedOversized > 0) {
94
+ fallbackReason = fallbackReason === null ? "oversized_lines_skipped" : `${fallbackReason}+oversized_lines_skipped`;
95
+ }
96
+ const trimmedEntries = entries.length > maxEntries ? entries.slice(-maxEntries) : entries;
97
+ if (entries.length > maxEntries) {
98
+ fallbackReason = fallbackReason === null ? "entry_count_capped" : `${fallbackReason}+entry_count_capped`;
99
+ }
100
+ return { entries: trimmedEntries, fallbackReason };
101
+ }
40
102
  function quoteShellArg(value) {
41
103
  return `'${value.replace(/'/g, `"'"'`)}'`;
42
104
  }
@@ -1171,6 +1233,7 @@ function summarizeStatusEmbedder(embeddings) {
1171
1233
  }
1172
1234
  function summarizeStatusRouteFn(status, report) {
1173
1235
  const freshness = report.servePath.refreshStatus ?? status.brain.routeFreshness;
1236
+ const fallbackReason = report.routeFn.fallbackReason ?? null;
1174
1237
  if (!report.routeFn.available) {
1175
1238
  return {
1176
1239
  available: false,
@@ -1178,6 +1241,7 @@ function summarizeStatusRouteFn(status, report) {
1178
1241
  trainedAt: report.routeFn.trainedAt,
1179
1242
  updatedAt: report.routeFn.updatedAt,
1180
1243
  usedAt: report.routeFn.usedAt,
1244
+ fallbackReason,
1181
1245
  detail: report.routeFn.detail
1182
1246
  };
1183
1247
  }
@@ -1191,12 +1255,16 @@ function summarizeStatusRouteFn(status, report) {
1191
1255
  else if (report.routeFn.updatedAt !== null) {
1192
1256
  detail = `active route_fn was last updated at ${report.routeFn.updatedAt}, but no learned serve use is visible yet for the current pack`;
1193
1257
  }
1258
+ if (fallbackReason !== null && !detail.includes(fallbackReason)) {
1259
+ detail = `${detail}; bounded_log_read=${fallbackReason}`;
1260
+ }
1194
1261
  return {
1195
1262
  available: true,
1196
1263
  freshness,
1197
1264
  trainedAt: report.routeFn.trainedAt,
1198
1265
  updatedAt: report.routeFn.updatedAt,
1199
1266
  usedAt: report.routeFn.usedAt,
1267
+ fallbackReason,
1200
1268
  detail
1201
1269
  };
1202
1270
  }
@@ -4453,14 +4521,8 @@ function runUninstallCommand(parsed) {
4453
4521
  return 0;
4454
4522
  }
4455
4523
  function resolveServeTimeLearningRuntimeInput(activationRoot) {
4456
- let serveTimeDecisions = [];
4457
- let fallbackReason = null;
4458
- try {
4459
- serveTimeDecisions = readLearningSpineLogEntries(activationRoot, "serveTimeRouteDecisions");
4460
- }
4461
- catch {
4462
- fallbackReason = "serve_time_decision_log_read_failed";
4463
- }
4524
+ const logPath = resolveLearningSpineLogPath(activationRoot, "serveTimeRouteDecisions");
4525
+ const { entries: serveTimeDecisions, fallbackReason } = readBoundedJsonlTail(logPath);
4464
4526
  const decisionLogCount = serveTimeDecisions.length;
4465
4527
  const pgVersion = decisionLogCount > 0 ? "v2" : "v1";
4466
4528
  return {
@@ -4468,7 +4530,7 @@ function resolveServeTimeLearningRuntimeInput(activationRoot) {
4468
4530
  serveTimeDecisions,
4469
4531
  decisionLogCount,
4470
4532
  baselineState: pgVersion === "v2" ? loadOrInitBaseline(activationRoot) : undefined,
4471
- fallbackReason
4533
+ fallbackReason: fallbackReason === null ? null : `serve_time_decision_log_${fallbackReason}`
4472
4534
  };
4473
4535
  }
4474
4536
  function resolveActivationInspectionPackId(inspection, slot) {
package/dist/src/index.js CHANGED
@@ -7,9 +7,10 @@ import { compileRuntimeFromActivation } from "@openclawbrain/compiler";
7
7
  import { CONTRACT_IDS, buildEventSemanticSurface, buildNormalizedEventExport, canonicalJson, checksumJsonPayload, createFeedbackEvent, createInteractionEvent, sortNormalizedEvents, validateKernelSurface, validateNormalizedEventExport } from "@openclawbrain/contracts";
8
8
  import { classifyFeedbackSignalContent, describeNormalizedEventExportObservability } from "@openclawbrain/event-export";
9
9
  import { DEFAULT_TEACHER_SUPERVISION_STALE_AFTER_MS, advanceAlwaysOnLearningRuntime, buildTeacherSupervisionArtifactsFromNormalizedEventExport, createAlwaysOnLearningRuntimeState, describeAlwaysOnLearningRuntimeState, materializeAlwaysOnLearningCandidatePack, materializeCandidatePackFromNormalizedEventExport } from "./local-learner.js";
10
- import { LEARNING_SPINE_LOG_LAYOUT, activatePack, describeActivationObservability, describeActivationTarget, describePackCompileTarget, inspectActivationState, loadPackFromActivation, promoteCandidatePack, readLearningSpineLogEntries, rollbackActivePack, stageCandidatePack } from "@openclawbrain/pack-format";
10
+ import { LEARNING_SPINE_LOG_LAYOUT, activatePack, describeActivationObservability, describeActivationTarget, describePackCompileTarget, inspectActivationState, loadPackFromActivation, promoteCandidatePack, resolveLearningSpineLogPath, rollbackActivePack, stageCandidatePack } from "@openclawbrain/pack-format";
11
11
  import { inspectOpenClawBrainHookStatus, summarizeOpenClawBrainHookLoad } from "./openclaw-hook-truth.js";
12
12
  import { appendLearningUpdateLogs, appendServeTimeRouteDecisionLog } from "./learning-spine.js";
13
+ import { readBoundedJsonlTail } from "./bounded-jsonl-reader.js";
13
14
  import { buildFeedbackSemanticMetadata, buildInteractionSemanticMetadata } from "./semantic-metadata.js";
14
15
  export { clearOpenClawProfileRuntimeLoadProof, listOpenClawProfileRuntimeLoadProofs, recordOpenClawProfileRuntimeLoadProof, resolveAttachmentRuntimeLoadProofsPath } from "./attachment-truth.js";
15
16
  import { createTeacherLabeler, summarizeTeacherLabelerOpportunity } from "./teacher-labeler.js";
@@ -6930,6 +6931,10 @@ function summarizeTeacherLoop(input) {
6930
6931
  : "raw async teacher snapshot loaded"
6931
6932
  };
6932
6933
  }
6934
+ function readBoundedLearningSpineLogEntries(activationRoot, stream) {
6935
+ const logPath = resolveLearningSpineLogPath(activationRoot, stream);
6936
+ return readBoundedJsonlTail(logPath).entries;
6937
+ }
6933
6938
  function matchesActiveRouteFnLog(input) {
6934
6939
  if (input.activePackId !== null && input.entryPackId === input.activePackId) {
6935
6940
  return true;
@@ -6970,8 +6975,8 @@ function summarizeRouteFnFreshness(input) {
6970
6975
  : `active pack ${input.activePackId} does not require a learned route_fn`
6971
6976
  };
6972
6977
  }
6973
- const updates = readLearningSpineLogEntries(input.activationRoot, "pgRouteUpdates");
6974
- const decisions = readLearningSpineLogEntries(input.activationRoot, "serveTimeRouteDecisions");
6978
+ const updates = readBoundedLearningSpineLogEntries(input.activationRoot, "pgRouteUpdates");
6979
+ const decisions = readBoundedLearningSpineLogEntries(input.activationRoot, "serveTimeRouteDecisions");
6975
6980
  const updated = [...updates].reverse().find((entry) => matchesActiveRouteFnLog({
6976
6981
  activePackId: input.activePackId,
6977
6982
  routerChecksum: input.learnedRouting.routerChecksum,
@@ -7458,7 +7463,7 @@ function summarizeCurrentProfileLogRoot(activationRoot) {
7458
7463
  return existsSync(logRoot) ? logRoot : null;
7459
7464
  }
7460
7465
  function summarizeCurrentProfileLastLearningUpdateAt(activationRoot, learning, teacherLoop) {
7461
- const updates = readLearningSpineLogEntries(activationRoot, "pgRouteUpdates");
7466
+ const updates = readBoundedLearningSpineLogEntries(activationRoot, "pgRouteUpdates");
7462
7467
  return updates.at(-1)?.recordedAt ?? teacherLoop.lastRunAt ?? learning.lastMaterializedAt ?? null;
7463
7468
  }
7464
7469
  function didCurrentProfileFirstExportOccur(report) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclawbrain/cli",
3
- "version": "0.4.17",
3
+ "version": "0.4.18",
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",