@openclawbrain/cli 0.4.16 → 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.
- package/dist/src/bounded-jsonl-reader.js +65 -0
- package/dist/src/cli.js +72 -10
- package/dist/src/index.js +9 -4
- package/dist/src/traced-learning-bridge.js +90 -4
- package/package.json +1 -1
|
@@ -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,
|
|
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
|
-
|
|
4457
|
-
|
|
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,
|
|
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 =
|
|
6974
|
-
const decisions =
|
|
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 =
|
|
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) {
|
|
@@ -15,9 +15,66 @@ function normalizeCount(value) {
|
|
|
15
15
|
function normalizeOptionalString(value) {
|
|
16
16
|
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
17
17
|
}
|
|
18
|
+
function normalizeUnitInterval(value) {
|
|
19
|
+
return Number.isFinite(value) ? Math.max(0, Math.min(1, Number(value))) : 0;
|
|
20
|
+
}
|
|
18
21
|
function normalizeSource(value) {
|
|
19
22
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
20
23
|
}
|
|
24
|
+
function normalizeLastInterruptionSummary(value) {
|
|
25
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const normalized = {
|
|
29
|
+
reason: normalizeOptionalString(value.reason),
|
|
30
|
+
stage: normalizeOptionalString(value.stage),
|
|
31
|
+
servedPartial: value.servedPartial === true,
|
|
32
|
+
droppedFrontierCount: normalizeCount(value.droppedFrontierCount),
|
|
33
|
+
droppedProposalCount: normalizeCount(value.droppedProposalCount),
|
|
34
|
+
budgetUtilization: normalizeUnitInterval(value.budgetUtilization)
|
|
35
|
+
};
|
|
36
|
+
return normalized.reason !== null ||
|
|
37
|
+
normalized.stage !== null ||
|
|
38
|
+
normalized.servedPartial ||
|
|
39
|
+
normalized.droppedFrontierCount > 0 ||
|
|
40
|
+
normalized.droppedProposalCount > 0 ||
|
|
41
|
+
normalized.budgetUtilization > 0
|
|
42
|
+
? normalized
|
|
43
|
+
: null;
|
|
44
|
+
}
|
|
45
|
+
function formatLastInterruptionDetail(value) {
|
|
46
|
+
const summary = normalizeLastInterruptionSummary(value);
|
|
47
|
+
if (summary === null) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
return [
|
|
51
|
+
`interrupt=${summary.reason ?? summary.stage ?? "unknown"}`,
|
|
52
|
+
`partial=${summary.servedPartial ? "yes" : "no"}`,
|
|
53
|
+
`frontier=${summary.droppedFrontierCount}`,
|
|
54
|
+
`proposals=${summary.droppedProposalCount}`,
|
|
55
|
+
`budget=${Math.round(summary.budgetUtilization * 100)}%`
|
|
56
|
+
].join(" ");
|
|
57
|
+
}
|
|
58
|
+
function buildLastInterruptionSummaryFromAssemblyDecision(value) {
|
|
59
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const accounting = value.interruptionAccounting !== null &&
|
|
63
|
+
typeof value.interruptionAccounting === "object" &&
|
|
64
|
+
!Array.isArray(value.interruptionAccounting)
|
|
65
|
+
? value.interruptionAccounting
|
|
66
|
+
: null;
|
|
67
|
+
return normalizeLastInterruptionSummary({
|
|
68
|
+
reason: normalizeOptionalString(value.brainDropReason) ?? normalizeOptionalString(value.interruptionReason),
|
|
69
|
+
stage: normalizeOptionalString(value.interruptionStage),
|
|
70
|
+
servedPartial: value.servedPartial === true,
|
|
71
|
+
droppedFrontierCount: Array.isArray(accounting?.droppedFrontierNodeIds)
|
|
72
|
+
? accounting.droppedFrontierNodeIds.filter((entry) => typeof entry === "string" && entry.trim().length > 0).length
|
|
73
|
+
: normalizeCount(accounting?.droppedFrontierCount),
|
|
74
|
+
droppedProposalCount: normalizeCount(accounting?.droppedProposalCount),
|
|
75
|
+
budgetUtilization: accounting?.budgetUtilization
|
|
76
|
+
});
|
|
77
|
+
}
|
|
21
78
|
function summarizeBridgeSource(value) {
|
|
22
79
|
const source = normalizeSource(value);
|
|
23
80
|
if (source === null) {
|
|
@@ -53,6 +110,7 @@ function normalizeBridgePayload(payload) {
|
|
|
53
110
|
materializedPackId: normalizeOptionalString(payload.materializedPackId),
|
|
54
111
|
promoted: payload.promoted === true,
|
|
55
112
|
baselinePersisted: payload.baselinePersisted === true,
|
|
113
|
+
lastInterruptionSummary: normalizeLastInterruptionSummary(payload.lastInterruptionSummary),
|
|
56
114
|
source: normalizeSource(payload.source)
|
|
57
115
|
};
|
|
58
116
|
}
|
|
@@ -79,6 +137,7 @@ function normalizePersistedStatusSurface(payload) {
|
|
|
79
137
|
materializedPackId: normalizeOptionalString(payload.materializedPackId),
|
|
80
138
|
promoted: payload.promoted === true,
|
|
81
139
|
baselinePersisted: payload.baselinePersisted === true,
|
|
140
|
+
lastInterruptionSummary: normalizeLastInterruptionSummary(payload.lastInterruptionSummary),
|
|
82
141
|
source
|
|
83
142
|
};
|
|
84
143
|
}
|
|
@@ -97,6 +156,7 @@ function defaultSurface(pathname, detail, error = null) {
|
|
|
97
156
|
materializedPackId: null,
|
|
98
157
|
promoted: false,
|
|
99
158
|
baselinePersisted: false,
|
|
159
|
+
lastInterruptionSummary: null,
|
|
100
160
|
source: null,
|
|
101
161
|
detail,
|
|
102
162
|
error
|
|
@@ -145,6 +205,10 @@ function loadTrainingStateJson(db, key) {
|
|
|
145
205
|
};
|
|
146
206
|
}
|
|
147
207
|
}
|
|
208
|
+
function loadLastAssemblyInterruptionSummary(db) {
|
|
209
|
+
const loaded = loadTrainingStateJson(db, "last_assembly_decision_json");
|
|
210
|
+
return loaded.value === null ? null : buildLastInterruptionSummaryFromAssemblyDecision(loaded.value);
|
|
211
|
+
}
|
|
148
212
|
function writeTrainingStateJson(db, key, value) {
|
|
149
213
|
db.prepare(`INSERT OR REPLACE INTO brain_training_state (key, value) VALUES (?, ?)`).run(key, JSON.stringify(value));
|
|
150
214
|
}
|
|
@@ -187,6 +251,7 @@ export function buildTracedLearningBridgePayloadFromRuntime(input) {
|
|
|
187
251
|
materializedPackId: input?.materializedPackId ?? lastMaterialization?.candidate?.summary?.packId ?? null,
|
|
188
252
|
promoted: input?.promoted === true,
|
|
189
253
|
baselinePersisted: input?.baselinePersisted === true,
|
|
254
|
+
lastInterruptionSummary: input?.lastInterruptionSummary ?? null,
|
|
190
255
|
source: input?.source
|
|
191
256
|
});
|
|
192
257
|
}
|
|
@@ -212,6 +277,7 @@ function buildPersistedStatusSurfaceBridge(summary, context) {
|
|
|
212
277
|
materializedPackId: summary.materializedPackId,
|
|
213
278
|
promoted: summary.promoted,
|
|
214
279
|
baselinePersisted: summary.baselinePersisted,
|
|
280
|
+
lastInterruptionSummary: summary.lastInterruptionSummary,
|
|
215
281
|
source: {
|
|
216
282
|
command: "brain-store",
|
|
217
283
|
bridge: TRACED_LEARNING_STATUS_SURFACE_BRIDGE,
|
|
@@ -246,7 +312,7 @@ function loadPersistedStatusSurface(db, context) {
|
|
|
246
312
|
};
|
|
247
313
|
}
|
|
248
314
|
}
|
|
249
|
-
function buildDerivedBrainStoreBridge(db, context) {
|
|
315
|
+
function buildDerivedBrainStoreBridge(db, context, lastInterruptionSummary = null) {
|
|
250
316
|
const routeTraceCount = countRows(db, "brain_traces");
|
|
251
317
|
const supervisionCount = countRows(db, "brain_trace_supervision");
|
|
252
318
|
const candidateUpdateRaw = loadTrainingStateValue(db, "last_pg_candidate_update_json");
|
|
@@ -269,6 +335,7 @@ function buildDerivedBrainStoreBridge(db, context) {
|
|
|
269
335
|
materializedPackId: null,
|
|
270
336
|
promoted: false,
|
|
271
337
|
baselinePersisted: false,
|
|
338
|
+
lastInterruptionSummary,
|
|
272
339
|
source: {
|
|
273
340
|
command: "brain-store",
|
|
274
341
|
bridge: "brain_store_state",
|
|
@@ -288,6 +355,7 @@ function hasMeaningfulTracedLearningSignal(bridge) {
|
|
|
288
355
|
bridge.materializedPackId !== null ||
|
|
289
356
|
bridge.promoted ||
|
|
290
357
|
bridge.baselinePersisted ||
|
|
358
|
+
bridge.lastInterruptionSummary !== null ||
|
|
291
359
|
bridge.pgVersionRequested !== null ||
|
|
292
360
|
bridge.pgVersionUsed !== null ||
|
|
293
361
|
bridge.fallbackReason !== null ||
|
|
@@ -410,21 +478,28 @@ export function loadBrainStoreTracedLearningBridge(options = {}) {
|
|
|
410
478
|
let db;
|
|
411
479
|
try {
|
|
412
480
|
db = new sqlite.DatabaseSync(dbPath, { readOnly: true });
|
|
481
|
+
const lastInterruptionSummary = loadLastAssemblyInterruptionSummary(db);
|
|
413
482
|
const persisted = loadPersistedStatusSurface(db, {
|
|
414
483
|
brainRoot,
|
|
415
484
|
dbPath
|
|
416
485
|
});
|
|
417
486
|
if (persisted.bridge !== null) {
|
|
487
|
+
const bridge = lastInterruptionSummary === null
|
|
488
|
+
? persisted.bridge
|
|
489
|
+
: normalizeBridgePayload({
|
|
490
|
+
...persisted.bridge,
|
|
491
|
+
lastInterruptionSummary
|
|
492
|
+
});
|
|
418
493
|
return {
|
|
419
494
|
path: dbPath,
|
|
420
|
-
bridge
|
|
495
|
+
bridge,
|
|
421
496
|
error: null
|
|
422
497
|
};
|
|
423
498
|
}
|
|
424
499
|
const bridge = buildDerivedBrainStoreBridge(db, {
|
|
425
500
|
brainRoot,
|
|
426
501
|
dbPath
|
|
427
|
-
});
|
|
502
|
+
}, lastInterruptionSummary);
|
|
428
503
|
if (!hasMeaningfulTracedLearningSignal(bridge)) {
|
|
429
504
|
return {
|
|
430
505
|
path: dbPath,
|
|
@@ -471,6 +546,10 @@ function buildStatusSurface(pathname, bridge, options = {}) {
|
|
|
471
546
|
if (bridge.routerNoOpReason !== null) {
|
|
472
547
|
detailParts.push(`noOp=${bridge.routerNoOpReason}`);
|
|
473
548
|
}
|
|
549
|
+
const interruptionDetail = formatLastInterruptionDetail(bridge.lastInterruptionSummary);
|
|
550
|
+
if (interruptionDetail !== null) {
|
|
551
|
+
detailParts.push(interruptionDetail);
|
|
552
|
+
}
|
|
474
553
|
return {
|
|
475
554
|
path: pathname,
|
|
476
555
|
present: true,
|
|
@@ -485,6 +564,7 @@ function buildStatusSurface(pathname, bridge, options = {}) {
|
|
|
485
564
|
materializedPackId: bridge.materializedPackId,
|
|
486
565
|
promoted: bridge.promoted,
|
|
487
566
|
baselinePersisted: bridge.baselinePersisted,
|
|
567
|
+
lastInterruptionSummary: bridge.lastInterruptionSummary,
|
|
488
568
|
source: bridge.source,
|
|
489
569
|
detail: detailParts.join(" "),
|
|
490
570
|
error: options.error ?? null
|
|
@@ -507,6 +587,7 @@ function buildRuntimeMaterializationMetadata(loaded) {
|
|
|
507
587
|
materializedPackId: loaded.bridge.materializedPackId,
|
|
508
588
|
promoted: loaded.bridge.promoted,
|
|
509
589
|
baselinePersisted: loaded.bridge.baselinePersisted,
|
|
590
|
+
lastInterruptionSummary: loaded.bridge.lastInterruptionSummary,
|
|
510
591
|
fallbackReason: loaded.bridge.fallbackReason,
|
|
511
592
|
routerNoOpReason: loaded.bridge.routerNoOpReason,
|
|
512
593
|
source: loaded.bridge.source
|
|
@@ -529,6 +610,7 @@ function mergeCanonicalStatusBridge(canonicalBridge, runtimeLoaded) {
|
|
|
529
610
|
materializedPackId: canonicalBridge.materializedPackId,
|
|
530
611
|
promoted: canonicalBridge.promoted,
|
|
531
612
|
baselinePersisted: canonicalBridge.baselinePersisted,
|
|
613
|
+
lastInterruptionSummary: canonicalBridge.lastInterruptionSummary ?? runtimeBridge?.lastInterruptionSummary ?? null,
|
|
532
614
|
fallbackReason: canonicalBridge.fallbackReason,
|
|
533
615
|
routerNoOpReason: canonicalBridge.routerNoOpReason,
|
|
534
616
|
source: runtimeMaterialized === null
|
|
@@ -551,6 +633,7 @@ function mergeCanonicalStatusBridge(canonicalBridge, runtimeLoaded) {
|
|
|
551
633
|
materializedPackId: runtimeBridge?.materializedPackId ?? canonicalBridge.materializedPackId ?? null,
|
|
552
634
|
promoted: runtimeBridge?.promoted ?? canonicalBridge.promoted,
|
|
553
635
|
baselinePersisted: runtimeBridge?.baselinePersisted ?? canonicalBridge.baselinePersisted,
|
|
636
|
+
lastInterruptionSummary: canonicalBridge.lastInterruptionSummary ?? runtimeBridge?.lastInterruptionSummary ?? null,
|
|
554
637
|
fallbackReason: runtimeBridge?.fallbackReason ?? canonicalBridge.fallbackReason ?? null,
|
|
555
638
|
routerNoOpReason: runtimeBridge?.routerNoOpReason ?? canonicalBridge.routerNoOpReason ?? null,
|
|
556
639
|
source: runtimeMaterialized === null
|
|
@@ -571,10 +654,12 @@ export function mergeTracedLearningBridgePayload(payload, persisted) {
|
|
|
571
654
|
const supervisionCount = Math.max(current.supervisionCount, persistedBridge.supervisionCount);
|
|
572
655
|
const routerUpdateCount = Math.max(current.routerUpdateCount, persistedBridge.routerUpdateCount);
|
|
573
656
|
const teacherArtifactCount = Math.max(current.teacherArtifactCount, persistedBridge.teacherArtifactCount);
|
|
657
|
+
const lastInterruptionSummary = current.lastInterruptionSummary ?? persistedBridge.lastInterruptionSummary ?? null;
|
|
574
658
|
const usedBridge = routeTraceCount !== current.routeTraceCount ||
|
|
575
659
|
supervisionCount !== current.supervisionCount ||
|
|
576
660
|
routerUpdateCount !== current.routerUpdateCount ||
|
|
577
|
-
teacherArtifactCount !== current.teacherArtifactCount
|
|
661
|
+
teacherArtifactCount !== current.teacherArtifactCount ||
|
|
662
|
+
lastInterruptionSummary !== current.lastInterruptionSummary;
|
|
578
663
|
if (!usedBridge) {
|
|
579
664
|
return current;
|
|
580
665
|
}
|
|
@@ -584,6 +669,7 @@ export function mergeTracedLearningBridgePayload(payload, persisted) {
|
|
|
584
669
|
supervisionCount,
|
|
585
670
|
routerUpdateCount,
|
|
586
671
|
teacherArtifactCount,
|
|
672
|
+
lastInterruptionSummary,
|
|
587
673
|
routerNoOpReason: supervisionCount > 0 || routerUpdateCount > 0 ? null : current.routerNoOpReason,
|
|
588
674
|
source: {
|
|
589
675
|
...(current.source ?? {}),
|
package/package.json
CHANGED