@openclawbrain/cli 0.4.14 → 0.4.16

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.
@@ -142,9 +142,10 @@ export function classifyOpenClawBrainConvergeVerification(input) {
142
142
  if (displayedStatus === "fail") {
143
143
  blockingReasons.push("status still reports fail");
144
144
  }
145
- const runtimeTruthAlreadyProven = displayedStatus === "ok"
146
- && runtimeLoad === "proven"
147
- && loadProof === "status_probe_ready";
145
+ const runtimeTruthAlreadyProven = runtimeLoad === "proven"
146
+ && loadProof === "status_probe_ready"
147
+ && installState === "installed"
148
+ && loadability === "loadable";
148
149
  if (input.restartRequired === true && input.restartPerformed !== true && !runtimeTruthAlreadyProven) {
149
150
  blockingReasons.push("restart is still required before runtime load can be trusted");
150
151
  }
@@ -23,6 +23,48 @@ function softmax(values) {
23
23
  }
24
24
  return numerators.map((value) => roundNumber(value / denominator));
25
25
  }
26
+ function isPlainObject(value) {
27
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
28
+ return false;
29
+ }
30
+ const prototype = Object.getPrototypeOf(value);
31
+ return prototype === Object.prototype || prototype === null;
32
+ }
33
+ function compactStructuralSignalValue(value, depth = 0) {
34
+ if (value === null || typeof value === "boolean") {
35
+ return value;
36
+ }
37
+ if (typeof value === "number") {
38
+ return Number.isFinite(value) ? roundNumber(value) : undefined;
39
+ }
40
+ if (typeof value === "string") {
41
+ return value.length <= 256 ? value : `${value.slice(0, 256)}...`;
42
+ }
43
+ if (depth >= 3) {
44
+ return undefined;
45
+ }
46
+ if (Array.isArray(value)) {
47
+ const compacted = value
48
+ .slice(0, 32)
49
+ .map((entry) => compactStructuralSignalValue(entry, depth + 1))
50
+ .filter((entry) => entry !== undefined);
51
+ return compacted;
52
+ }
53
+ if (!isPlainObject(value)) {
54
+ return undefined;
55
+ }
56
+ const compactedEntries = Object.entries(value)
57
+ .slice(0, 32)
58
+ .flatMap(([key, entry]) => {
59
+ const compacted = compactStructuralSignalValue(entry, depth + 1);
60
+ return compacted === undefined ? [] : [[key, compacted]];
61
+ });
62
+ return compactedEntries.length > 0 ? Object.fromEntries(compactedEntries) : undefined;
63
+ }
64
+ function compactStructuralSignals(value) {
65
+ const compacted = compactStructuralSignalValue(value);
66
+ return compacted !== undefined && isPlainObject(compacted) ? compacted : null;
67
+ }
26
68
  function isStableKernelContextBlock(block) {
27
69
  if (block.id.includes(":event:") || block.id.includes(":teacher:")) {
28
70
  return false;
@@ -264,7 +306,7 @@ export function appendServeTimeRouteDecisionLog(input) {
264
306
  actionScore: roundNumber(entry.score),
265
307
  actionProbability: probabilities[index] ?? 0
266
308
  })),
267
- structuralSignals: null,
309
+ structuralSignals: compactStructuralSignals(input.compileResult.ok ? input.compileResult.compileResponse.structuralSignals : null),
268
310
  fallbackReason: serveFallbackReason(input.compileResult),
269
311
  hotPathTiming: input.compileResult.timing,
270
312
  kernelContextCount: selectedKernelContextIds.length,
@@ -85,6 +85,12 @@ export interface TeacherObservationBindingStatsV1 {
85
85
  totalObservationCount: number;
86
86
  nonZeroObservationCount: number;
87
87
  skippedZeroRewardCount: number;
88
+ accounting: {
89
+ exact: number;
90
+ heuristic: number;
91
+ unmatched: number;
92
+ ambiguous: number;
93
+ };
88
94
  matched: {
89
95
  exactDecisionId: number;
90
96
  exactSelectionDigest: number;
@@ -3,9 +3,10 @@ import path from "node:path";
3
3
  import { DatabaseSync } from "node:sqlite";
4
4
  import { buildRouteArtifactReference, CONTRACT_IDS, PACK_GRAPH_SCHEMAS, ROUTER_PG_PROFILE_V1, ROUTER_PG_PROFILE_V2, checksumJsonPayload, computeRouterCollectedLabelCounts, computeRouterFreshnessChecksum, computeRouterObjectiveChecksum, computeRouterQueryChecksum, computeRouterWeightsChecksum, sortNormalizedEvents, validateTeacherSupervisionArtifact } from "@openclawbrain/contracts";
5
5
  import { buildNormalizedEventDedupId, buildNormalizedEventExport, buildNormalizedEventExportBridge, createDefaultLearningSurface, createEventExportCursor, createExplicitEventRange, validateNormalizedEventExport, validateNormalizedEventExportBridge, validateNormalizedEventExportSlice } from "@openclawbrain/event-export";
6
- import { computePayloadChecksum, loadPack, PACK_LAYOUT, summarizeStructuralGraphEvolution, writePackFile } from "@openclawbrain/pack-format";
6
+ import { computePayloadChecksum, loadPack, loadPackFromActivation, PACK_LAYOUT, summarizeStructuralGraphEvolution, writePackFile } from "@openclawbrain/pack-format";
7
7
  import { buildArtifactProvenance } from "@openclawbrain/provenance";
8
8
  import { createWorkspaceMetadata } from "@openclawbrain/workspace-metadata";
9
+ import { createServeTimeDecisionMatcher } from "./teacher-decision-match.js";
9
10
  export const DEFAULT_ALWAYS_ON_LEARNING_LIVE_SLICES_PER_CYCLE = 1;
10
11
  export const DEFAULT_ALWAYS_ON_LEARNING_BACKFILL_SLICES_PER_CYCLE = 1;
11
12
  export const DEFAULT_TEACHER_SUPERVISION_STALE_AFTER_MS = 5 * 60 * 1_000;
@@ -26,6 +27,7 @@ export const ALWAYS_ON_STRUCTURAL_PLASTICITY_OP_CEILING = {
26
27
  export const ALWAYS_ON_STRUCTURAL_PLASTICITY_MIN_INTERACTIONS = 2;
27
28
  export const ALWAYS_ON_STRUCTURAL_PLASTICITY_MIN_FEEDBACK = 1;
28
29
  const CONNECT_PAIR_SCORE_THRESHOLD = 2;
30
+ const MAX_CARRY_FORWARD_SEED_BLOCKS = 12;
29
31
  export const DEFAULT_SPARSE_FEEDBACK_POLICY = {
30
32
  teacherBudget: 32,
31
33
  teacherDelayMs: 0,
@@ -4254,6 +4256,12 @@ function emptyTeacherObservationBindingStats() {
4254
4256
  totalObservationCount: 0,
4255
4257
  nonZeroObservationCount: 0,
4256
4258
  skippedZeroRewardCount: 0,
4259
+ accounting: {
4260
+ exact: 0,
4261
+ heuristic: 0,
4262
+ unmatched: 0,
4263
+ ambiguous: 0
4264
+ },
4257
4265
  matched: {
4258
4266
  exactDecisionId: 0,
4259
4267
  exactSelectionDigest: 0,
@@ -4276,6 +4284,7 @@ function emptyTeacherObservationBindingStats() {
4276
4284
  }
4277
4285
  function summarizeTeacherObservationBindingStats(stats) {
4278
4286
  return [
4287
+ `accounting(exact=${stats.accounting.exact},heuristic=${stats.accounting.heuristic},unmatched=${stats.accounting.unmatched},ambiguous=${stats.accounting.ambiguous})`,
4279
4288
  `matched(exact_decision_id=${stats.matched.exactDecisionId},exact_selection_digest=${stats.matched.exactSelectionDigest},turn_compile_event_id=${stats.matched.turnCompileEventId},legacy_heuristic=${stats.matched.legacyHeuristic})`,
4280
4289
  `unmatched(exact_decision_id=${stats.unmatched.exactDecisionId},exact_selection_digest=${stats.unmatched.exactSelectionDigest},turn_compile_event_id=${stats.unmatched.turnCompileEventId},legacy_heuristic=${stats.unmatched.legacyHeuristic})`,
4281
4290
  `ambiguous(exact_decision_id=${stats.ambiguous.exactDecisionId},exact_selection_digest=${stats.ambiguous.exactSelectionDigest},turn_compile_event_id=${stats.ambiguous.turnCompileEventId},legacy_heuristic=${stats.ambiguous.legacyHeuristic})`,
@@ -4365,8 +4374,7 @@ function resolveTeacherObservationDecisionMatch(decisions, observation, indexes)
4365
4374
  ? { kind: "unmatched", mode: "exactSelectionDigest" }
4366
4375
  : { kind: "matched", mode: "exactSelectionDigest", decision };
4367
4376
  }
4368
- if (normalizeObservationOptionalString(observation.selectionDigest) !== null
4369
- || normalizeObservationOptionalString(observation.activePackGraphChecksum) !== null) {
4377
+ if (normalizeObservationOptionalString(observation.selectionDigest) !== null) {
4370
4378
  return { kind: "unmatched", mode: "exactSelectionDigest" };
4371
4379
  }
4372
4380
  const turnCompileEventId = normalizeObservationOptionalString(observation.turnCompileEventId);
@@ -4401,13 +4409,21 @@ function joinDecisionsWithTeacherObservationOutcomes(decisions, observationOutco
4401
4409
  stats.nonZeroObservationCount += 1;
4402
4410
  const match = resolveTeacherObservationDecisionMatch(decisions, observation, indexes);
4403
4411
  if (match.kind === "unmatched") {
4412
+ stats.accounting.unmatched += 1;
4404
4413
  stats.unmatched[match.mode] += 1;
4405
4414
  continue;
4406
4415
  }
4407
4416
  if (match.kind === "ambiguous") {
4417
+ stats.accounting.ambiguous += 1;
4408
4418
  stats.ambiguous[match.mode] += 1;
4409
4419
  continue;
4410
4420
  }
4421
+ if (match.mode === "legacyHeuristic") {
4422
+ stats.accounting.heuristic += 1;
4423
+ }
4424
+ else {
4425
+ stats.accounting.exact += 1;
4426
+ }
4411
4427
  stats.matched[match.mode] += 1;
4412
4428
  const current = outcomes.get(match.decision.recordId) ?? 0;
4413
4429
  if (current === 0 || Math.abs(reward) > Math.abs(current)) {
@@ -4426,149 +4442,7 @@ export function joinDecisionsWithFeedback(decisions, eventExport, maxDelayMs = 3
4426
4442
  return outcomes;
4427
4443
  }
4428
4444
  const normalizeOptionalString = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
4429
- const toTimestamp = (value) => {
4430
- const normalized = normalizeOptionalString(value);
4431
- if (normalized === undefined) {
4432
- return null;
4433
- }
4434
- const parsed = Date.parse(normalized);
4435
- return Number.isFinite(parsed) ? parsed : null;
4436
- };
4437
- const buildSessionChannelKey = (sessionId, channel) => {
4438
- const normalizedSessionId = normalizeOptionalString(sessionId);
4439
- const normalizedChannel = normalizeOptionalString(channel);
4440
- if (normalizedSessionId === undefined || normalizedChannel === undefined) {
4441
- return null;
4442
- }
4443
- return `${normalizedSessionId}|${normalizedChannel}`;
4444
- };
4445
- const buildCandidateKey = (sessionId, channel, createdAt) => {
4446
- const sessionChannelKey = buildSessionChannelKey(sessionId, channel);
4447
- const normalizedCreatedAt = normalizeOptionalString(createdAt);
4448
- if (sessionChannelKey === null || normalizedCreatedAt === undefined) {
4449
- return null;
4450
- }
4451
- return `${sessionChannelKey}|${normalizedCreatedAt}`;
4452
- };
4453
- const buildDecisionTimestamps = (decision) => {
4454
- const timestamps = [];
4455
- const turnCreatedAt = toTimestamp(decision.turnCreatedAt);
4456
- const recordedAt = toTimestamp(decision.recordedAt);
4457
- if (turnCreatedAt !== null) {
4458
- timestamps.push(turnCreatedAt);
4459
- }
4460
- if (recordedAt !== null && !timestamps.includes(recordedAt)) {
4461
- timestamps.push(recordedAt);
4462
- }
4463
- return timestamps;
4464
- };
4465
- const operationalPatterns = [
4466
- /^NO_REPLY$/i,
4467
- /^HEARTBEAT_OK$/i,
4468
- /^read heartbeat\.md if it exists/i,
4469
- /^a new session was started via \/new or \/reset\./i,
4470
- /\[cron:[a-f0-9-]+\s/i,
4471
- /\[system message\]\s*\[sessionid:/i,
4472
- ];
4473
- const isOperationalDecision = (decision) => {
4474
- const userMessage = normalizeOptionalString(decision.userMessage);
4475
- if (userMessage === undefined) {
4476
- return true;
4477
- }
4478
- return operationalPatterns.some((pattern) => pattern.test(userMessage));
4479
- };
4480
- const selectNearestDecision = (entries, interactionAt) => {
4481
- const candidates = entries
4482
- .map((entry) => {
4483
- const deltas = entry.timestamps.map((timestamp) => Math.abs(timestamp - interactionAt));
4484
- const bestDelta = deltas.length === 0 ? null : Math.min(...deltas);
4485
- return bestDelta === null || bestDelta > maxDelayMs
4486
- ? null
4487
- : {
4488
- decision: entry.decision,
4489
- deltaMs: bestDelta,
4490
- recordedAt: toTimestamp(entry.decision.recordedAt) ?? 0,
4491
- };
4492
- })
4493
- .filter((entry) => entry !== null)
4494
- .sort((left, right) => {
4495
- if (left.deltaMs !== right.deltaMs) {
4496
- return left.deltaMs - right.deltaMs;
4497
- }
4498
- return right.recordedAt - left.recordedAt;
4499
- });
4500
- const best = candidates[0] ?? null;
4501
- const runnerUp = candidates[1] ?? null;
4502
- if (best === null) {
4503
- return null;
4504
- }
4505
- if (runnerUp !== null && runnerUp.deltaMs === best.deltaMs && runnerUp.decision !== best.decision) {
4506
- return null;
4507
- }
4508
- return best.decision;
4509
- };
4510
- const exactDecisions = new Map();
4511
- const fallbackDecisions = new Map();
4512
- const decisionsBySessionChannel = new Map();
4513
- const globalFallbackDecisions = [];
4514
- for (const decision of [...decisions].sort((left, right) => Date.parse(right.recordedAt) - Date.parse(left.recordedAt))) {
4515
- const userMessage = normalizeOptionalString(decision.userMessage);
4516
- if (userMessage === undefined) {
4517
- continue;
4518
- }
4519
- const turnCompileEventId = normalizeOptionalString(decision.turnCompileEventId);
4520
- if (turnCompileEventId !== undefined && !exactDecisions.has(turnCompileEventId)) {
4521
- exactDecisions.set(turnCompileEventId, decision);
4522
- }
4523
- for (const candidateKey of [
4524
- buildCandidateKey(decision.sessionId, decision.channel, decision.turnCreatedAt),
4525
- buildCandidateKey(decision.sessionId, decision.channel, decision.recordedAt),
4526
- ]) {
4527
- if (candidateKey !== null && !fallbackDecisions.has(candidateKey)) {
4528
- fallbackDecisions.set(candidateKey, decision);
4529
- }
4530
- }
4531
- const sessionChannelKey = buildSessionChannelKey(decision.sessionId, decision.channel);
4532
- const indexedEntry = {
4533
- decision,
4534
- timestamps: buildDecisionTimestamps(decision),
4535
- operational: isOperationalDecision(decision),
4536
- };
4537
- if (sessionChannelKey !== null) {
4538
- const indexed = decisionsBySessionChannel.get(sessionChannelKey) ?? [];
4539
- indexed.push(indexedEntry);
4540
- decisionsBySessionChannel.set(sessionChannelKey, indexed);
4541
- }
4542
- if (!indexedEntry.operational) {
4543
- globalFallbackDecisions.push(indexedEntry);
4544
- }
4545
- }
4546
- const matchInteractionToDecision = (interaction) => {
4547
- const exact = exactDecisions.get(interaction.eventId);
4548
- if (exact !== undefined) {
4549
- return exact;
4550
- }
4551
- const exactFallbackKey = buildCandidateKey(interaction.sessionId, interaction.channel, interaction.createdAt);
4552
- if (exactFallbackKey !== null) {
4553
- const fallback = fallbackDecisions.get(exactFallbackKey);
4554
- if (fallback !== undefined) {
4555
- return fallback;
4556
- }
4557
- }
4558
- const interactionAt = toTimestamp(interaction.createdAt);
4559
- const sessionChannelKey = buildSessionChannelKey(interaction.sessionId, interaction.channel);
4560
- if (interactionAt === null) {
4561
- return null;
4562
- }
4563
- if (sessionChannelKey !== null) {
4564
- const sessionEntries = (decisionsBySessionChannel.get(sessionChannelKey) ?? []).filter((entry) => entry.operational !== true);
4565
- const sessionMatch = selectNearestDecision(sessionEntries, interactionAt);
4566
- if (sessionMatch !== null) {
4567
- return sessionMatch;
4568
- }
4569
- }
4570
- return selectNearestDecision(globalFallbackDecisions, interactionAt);
4571
- };
4445
+ const matchInteractionToDecision = createServeTimeDecisionMatcher(decisions, { maxTimeDeltaMs: maxDelayMs });
4572
4446
  const interactionById = new Map();
4573
4447
  for (const interaction of eventExport.interactionEvents ?? []) {
4574
4448
  const interactionId = normalizeOptionalString(interaction.eventId);
@@ -4576,7 +4450,7 @@ export function joinDecisionsWithFeedback(decisions, eventExport, maxDelayMs = 3
4576
4450
  interactionById.set(interactionId, interaction);
4577
4451
  }
4578
4452
  }
4579
- const matchedFeedbackIds = new Set();
4453
+ const consumedFeedbackIds = new Set();
4580
4454
  for (const event of eventExport.feedbackEvents ?? []) {
4581
4455
  const relatedInteractionId = normalizeOptionalString(event.relatedInteractionId);
4582
4456
  if (relatedInteractionId === undefined) {
@@ -4586,6 +4460,8 @@ export function joinDecisionsWithFeedback(decisions, eventExport, maxDelayMs = 3
4586
4460
  if (interaction === undefined) {
4587
4461
  continue;
4588
4462
  }
4463
+ const feedbackId = normalizeOptionalString(event.eventId) ?? `${relatedInteractionId}|${normalizeOptionalString(event.createdAt) ?? 'unknown'}`;
4464
+ consumedFeedbackIds.add(feedbackId);
4589
4465
  const matchedDecision = matchInteractionToDecision(interaction);
4590
4466
  if (matchedDecision === null) {
4591
4467
  continue;
@@ -4597,13 +4473,11 @@ export function joinDecisionsWithFeedback(decisions, eventExport, maxDelayMs = 3
4597
4473
  outcomes.set(matchedDecision.recordId, reward);
4598
4474
  }
4599
4475
  }
4600
- const feedbackId = normalizeOptionalString(event.eventId) ?? `${relatedInteractionId}|${normalizeOptionalString(event.createdAt) ?? 'unknown'}`;
4601
- matchedFeedbackIds.add(feedbackId);
4602
4476
  }
4603
4477
  const feedbackBySession = new Map();
4604
4478
  for (const event of eventExport.feedbackEvents ?? []) {
4605
4479
  const feedbackId = normalizeOptionalString(event.eventId) ?? `${normalizeOptionalString(event.relatedInteractionId) ?? '__none__'}|${normalizeOptionalString(event.createdAt) ?? 'unknown'}`;
4606
- if (matchedFeedbackIds.has(feedbackId)) {
4480
+ if (consumedFeedbackIds.has(feedbackId)) {
4607
4481
  continue;
4608
4482
  }
4609
4483
  const sessionId = event.sessionId ?? "__none__";
@@ -4972,6 +4846,57 @@ function defaultLearningSurface(workspace, offlineArtifacts, workspaceInit) {
4972
4846
  ...offlineArtifacts.map((artifact) => `offline:${artifact}`)
4973
4847
  ]));
4974
4848
  }
4849
+ function isCarryForwardSeedBlock(block) {
4850
+ const role = block.learning?.role ?? "";
4851
+ if (role !== "interaction" && role !== "feedback" && role !== "teacher_supervision" && role !== "label_surface") {
4852
+ return false;
4853
+ }
4854
+ if (typeof block.text !== "string" || block.text.trim().length === 0) {
4855
+ return false;
4856
+ }
4857
+ // Only true seed-session evidence should survive prefix-changing promotions.
4858
+ // Ordinary runtime-turn feedback is re-materialized from learnedEventExport;
4859
+ // carrying it forward under the old runtime-graph id duplicates evidence.
4860
+ return block.semantic?.sourceKind === "recorded_session_seed" ||
4861
+ block.source.includes("/seed:");
4862
+ }
4863
+ function carryForwardSeedBlockScore(block) {
4864
+ return (block.initSeed?.score ?? 0) +
4865
+ (block.state?.strength ?? 0) +
4866
+ (block.learning?.humanLabels ?? 0) * 2 +
4867
+ (block.learning?.selfLabels ?? 0);
4868
+ }
4869
+ function loadCarryForwardSeedBlocksFromActivation(activationRoot, currentGraphBlockIds) {
4870
+ try {
4871
+ const activePack = loadPackFromActivation(activationRoot, "active", { requireActivationReady: true });
4872
+ if (activePack === null) {
4873
+ return [];
4874
+ }
4875
+ const candidates = activePack.graph.blocks
4876
+ .filter((block) => isCarryForwardSeedBlock(block) && !currentGraphBlockIds.has(block.id));
4877
+ const preferred = candidates.filter((block) => block.learning?.role !== "interaction");
4878
+ const selected = (preferred.length > 0 ? preferred : candidates)
4879
+ .sort((left, right) => carryForwardSeedBlockScore(right) - carryForwardSeedBlockScore(left) ||
4880
+ right.priority - left.priority ||
4881
+ left.id.localeCompare(right.id))
4882
+ .slice(0, MAX_CARRY_FORWARD_SEED_BLOCKS);
4883
+ if (selected.length === 0) {
4884
+ return [];
4885
+ }
4886
+ const selectedIds = new Set(selected.map((block) => block.id));
4887
+ return selected.map((block) => ({
4888
+ ...structuredClone(block),
4889
+ ...(block.edges === undefined
4890
+ ? {}
4891
+ : {
4892
+ edges: block.edges.filter((edge) => selectedIds.has(edge.targetBlockId))
4893
+ })
4894
+ }));
4895
+ }
4896
+ catch {
4897
+ return [];
4898
+ }
4899
+ }
4975
4900
  function cloneRuntimeGraphForPack(packId, runtimeGraph, builtAt) {
4976
4901
  const cloned = structuredClone(runtimeGraph);
4977
4902
  cloned.packId = packId;
@@ -5045,9 +4970,18 @@ function buildRuntimeGraphSnapshot(input) {
5045
4970
  rootDir: workspace.rootDir,
5046
4971
  observedAt: builtAt
5047
4972
  }));
4973
+ const carryForwardSeedBlocks = input.activationRoot === undefined
4974
+ ? []
4975
+ : loadCarryForwardSeedBlocksFromActivation(input.activationRoot, new Set(graph.blocks.map((block) => block.id)));
4976
+ const graphWithCarryForwardSeed = carryForwardSeedBlocks.length === 0
4977
+ ? graph
4978
+ : {
4979
+ ...graph,
4980
+ blocks: [...graph.blocks, ...carryForwardSeedBlocks]
4981
+ };
5048
4982
  return {
5049
- graph,
5050
- plasticity: summarizeRuntimeGraphPlasticity("live_loop", graph, builtAt, null, normalizedEventExport)
4983
+ graph: graphWithCarryForwardSeed,
4984
+ plasticity: summarizeRuntimeGraphPlasticity("live_loop", graphWithCarryForwardSeed, builtAt, null, normalizedEventExport)
5051
4985
  };
5052
4986
  }
5053
4987
  export function buildCandidatePack(input) {
@@ -44,6 +44,32 @@ function sanitizeToken(value) {
44
44
  function slugifyIdentity(value) {
45
45
  return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
46
46
  }
47
+ function sortSessionRecordsDeterministically(records) {
48
+ return [...records].sort((left, right) => {
49
+ const leftTimestamp = Date.parse(left.timestamp);
50
+ const rightTimestamp = Date.parse(right.timestamp);
51
+ if (leftTimestamp !== rightTimestamp) {
52
+ return leftTimestamp - rightTimestamp;
53
+ }
54
+ const leftId = typeof left.id === "string" ? left.id : "";
55
+ const rightId = typeof right.id === "string" ? right.id : "";
56
+ const leftParentId = typeof left.parentId === "string" ? left.parentId : "";
57
+ const rightParentId = typeof right.parentId === "string" ? right.parentId : "";
58
+ if (rightParentId === leftId && leftParentId !== rightId) {
59
+ return -1;
60
+ }
61
+ if (leftParentId === rightId && rightParentId !== leftId) {
62
+ return 1;
63
+ }
64
+ if (leftId !== rightId) {
65
+ return leftId.localeCompare(rightId);
66
+ }
67
+ if (leftParentId !== rightParentId) {
68
+ return leftParentId.localeCompare(rightParentId);
69
+ }
70
+ return left.type.localeCompare(right.type);
71
+ });
72
+ }
47
73
  function deriveChannel(sessionKey, entry) {
48
74
  const deliveryChannel = normalizeString(entry.deliveryContext?.channel);
49
75
  if (deliveryChannel !== null) {
@@ -256,7 +282,7 @@ export function buildPassiveLearningSessionExportFromOpenClawSessionStore(input)
256
282
  let droppedRuntimeNoiseCount = 0;
257
283
  let nextSequence = sequenceStart;
258
284
  let latestAssistantInteractionId = null;
259
- for (const record of input.records) {
285
+ for (const record of sortSessionRecordsDeterministically(input.records)) {
260
286
  if (record.type !== "message") {
261
287
  continue;
262
288
  }
@@ -451,4 +477,4 @@ export function buildPassiveLearningStoreExportFromOpenClawSessionIndex(input) {
451
477
  warnings: sessions.flatMap((session) => session.warnings)
452
478
  };
453
479
  }
454
- //# sourceMappingURL=local-session-passive-learning.js.map
480
+ //# sourceMappingURL=local-session-passive-learning.js.map
@@ -0,0 +1,11 @@
1
+ import { reindexCandidatePackBuildResultWithEmbedder } from "./local-learner.js";
2
+
3
+ export async function reindexMaterializationCandidateWithEmbedder(materialization, embedder) {
4
+ if (materialization === null || embedder === null) {
5
+ return materialization;
6
+ }
7
+ return {
8
+ ...materialization,
9
+ candidate: await reindexCandidatePackBuildResultWithEmbedder(materialization.candidate, embedder)
10
+ };
11
+ }
@@ -2,6 +2,8 @@ import { type OpenClawBrainInstallLayout } from "./openclaw-plugin-install.js";
2
2
  export type OpenClawBrainHookInstallState = "installed" | "not_installed" | "blocked_by_allowlist" | "unverified";
3
3
  export type OpenClawBrainHookLoadability = "loadable" | "blocked" | "not_installed" | "unverified";
4
4
  export type OpenClawBrainHookLoadProof = "status_probe_ready" | "not_ready";
5
+ export type OpenClawBrainHookGuardSeverity = "none" | "degraded" | "blocking";
6
+ export type OpenClawBrainHookGuardActionability = "none" | "pin_openclaw_home" | "repair_install";
5
7
  export type OpenClawBrainPluginAllowlistState = "unrestricted" | "allowed" | "blocked" | "invalid" | "unverified";
6
8
  export interface OpenClawBrainHookInspection {
7
9
  scope: "exact_openclaw_home" | "activation_root_only";
@@ -24,6 +26,10 @@ export interface OpenClawBrainHookInspection {
24
26
  }
25
27
  export interface OpenClawBrainHookLoadSummary extends OpenClawBrainHookInspection {
26
28
  loadProof: OpenClawBrainHookLoadProof;
29
+ guardSeverity: OpenClawBrainHookGuardSeverity;
30
+ guardActionability: OpenClawBrainHookGuardActionability;
31
+ guardSummary: string;
32
+ guardAction: string;
27
33
  }
28
34
  export declare function inspectOpenClawBrainPluginAllowlist(openclawHome: string): {
29
35
  state: Exclude<OpenClawBrainPluginAllowlistState, "unverified">;
@@ -74,6 +74,32 @@ function describeAdditionalInstallDetail(additionalInstalls) {
74
74
  .map((install) => `${shortenPath(install.extensionDir)} (${describeOpenClawBrainInstallLayout(install.installLayout)}, ${describeOpenClawBrainInstallIdentity(install)})`)
75
75
  .join(", ")}`;
76
76
  }
77
+ function summarizeOpenClawBrainHookGuard(inspection) {
78
+ if (inspection.scope === "activation_root_only") {
79
+ return {
80
+ guardSeverity: "degraded",
81
+ guardActionability: "pin_openclaw_home",
82
+ guardSummary: "current-profile hook state is not self-proven from this status scope",
83
+ guardAction: "Rerun status with --openclaw-home <path> to prove the current profile hook."
84
+ };
85
+ }
86
+ if (inspection.installState === "installed" && inspection.loadability === "loadable") {
87
+ return {
88
+ guardSeverity: "none",
89
+ guardActionability: "none",
90
+ guardSummary: "profile hook is installed and loadable",
91
+ guardAction: "none"
92
+ };
93
+ }
94
+ return {
95
+ guardSeverity: "blocking",
96
+ guardActionability: "repair_install",
97
+ guardSummary: inspection.installState === "not_installed"
98
+ ? "profile hook is missing or incomplete"
99
+ : "profile hook is present but OpenClaw will not load it",
100
+ guardAction: "Run openclawbrain install --openclaw-home <path> to repair the installed hook."
101
+ };
102
+ }
77
103
  export function inspectOpenClawBrainPluginAllowlist(openclawHome) {
78
104
  const { path: openclawJsonPath, config } = readOpenClawJsonConfig(openclawHome);
79
105
  const installedPlugin = findInstalledOpenClawBrainPlugin(openclawHome);
@@ -252,6 +278,7 @@ export function inspectOpenClawBrainHookStatus(openclawHome) {
252
278
  export function summarizeOpenClawBrainHookLoad(inspection, statusProbeReady) {
253
279
  return {
254
280
  ...inspection,
281
+ ...summarizeOpenClawBrainHookGuard(inspection),
255
282
  loadProof: inspection.loadability === "loadable" && statusProbeReady
256
283
  ? "status_probe_ready"
257
284
  : "not_ready"