@openclawbrain/cli 0.4.10 → 0.4.12

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.
@@ -1,5 +1,6 @@
1
1
  import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
2
2
  import path from "node:path";
3
+ import { DatabaseSync } from "node:sqlite";
3
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";
4
5
  import { buildNormalizedEventDedupId, buildNormalizedEventExport, buildNormalizedEventExportBridge, createDefaultLearningSurface, createEventExportCursor, createExplicitEventRange, validateNormalizedEventExport, validateNormalizedEventExportBridge, validateNormalizedEventExportSlice } from "@openclawbrain/event-export";
5
6
  import { computePayloadChecksum, loadPack, PACK_LAYOUT, summarizeStructuralGraphEvolution, writePackFile } from "@openclawbrain/pack-format";
@@ -891,7 +892,8 @@ function buildAlwaysOnLearningMaterializationJob(input, current, selectedSlices,
891
892
  principalBacklog,
892
893
  ...(input.pgVersion !== undefined ? { pgVersion: input.pgVersion } : {}),
893
894
  ...(input.serveTimeDecisions !== undefined ? { serveTimeDecisions: [...input.serveTimeDecisions] } : {}),
894
- ...(input.baselineState !== undefined ? { baselineState: { ...input.baselineState } } : {})
895
+ ...(input.baselineState !== undefined ? { baselineState: { ...input.baselineState } } : {}),
896
+ ...(input.activationRoot !== undefined ? { activationRoot: input.activationRoot } : {})
895
897
  };
896
898
  const candidate = buildCandidatePackFromNormalizedEventExport(candidateInput);
897
899
  const selectedSliceIds = selectedSlices.map((slice) => slice.sliceId);
@@ -4040,6 +4042,201 @@ function createRouterArtifact(packId, builtAt, graph, vectors, eventExport, spar
4040
4042
  * Join serve-time decisions with feedback events to assign outcome rewards.
4041
4043
  * Returns decisionRecordId → outcome (z_T).
4042
4044
  */
4045
+ function clampTeacherObservationOutcome(value) {
4046
+ if (!Number.isFinite(value)) {
4047
+ return 0;
4048
+ }
4049
+ return Math.max(-1, Math.min(1, value));
4050
+ }
4051
+ function normalizeOutcomeMatchText(value) {
4052
+ return typeof value === "string" && value.trim().length > 0
4053
+ ? value.replace(/\s+/g, " ").trim().toLowerCase()
4054
+ : null;
4055
+ }
4056
+ function parseObservationRouteMetadata(value) {
4057
+ if (typeof value !== "string" || value.trim().length === 0) {
4058
+ return {
4059
+ selectedNodeIds: [],
4060
+ selectedPathNodeIds: []
4061
+ };
4062
+ }
4063
+ try {
4064
+ const parsed = JSON.parse(value);
4065
+ const selectedNodeIds = Array.isArray(parsed?.selectedNodeIds)
4066
+ ? parsed.selectedNodeIds.filter((entry) => typeof entry === "string")
4067
+ : [];
4068
+ const selectedPathNodeIds = Array.isArray(parsed?.selectedPathNodeIds)
4069
+ ? parsed.selectedPathNodeIds.filter((entry) => typeof entry === "string")
4070
+ : [];
4071
+ return {
4072
+ selectedNodeIds,
4073
+ selectedPathNodeIds
4074
+ };
4075
+ }
4076
+ catch {
4077
+ return {
4078
+ selectedNodeIds: [],
4079
+ selectedPathNodeIds: []
4080
+ };
4081
+ }
4082
+ }
4083
+ function loadTeacherObservationOutcomesFromActivation(activationRoot) {
4084
+ const resolvedActivationRoot = typeof activationRoot === "string" && activationRoot.trim().length > 0
4085
+ ? path.resolve(activationRoot)
4086
+ : null;
4087
+ if (resolvedActivationRoot === null) {
4088
+ return [];
4089
+ }
4090
+ const dbPath = path.join(resolvedActivationRoot, "state.db");
4091
+ if (!existsSync(dbPath)) {
4092
+ return [];
4093
+ }
4094
+ let db = null;
4095
+ try {
4096
+ db = new DatabaseSync(dbPath);
4097
+ const table = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'brain_observations' LIMIT 1").get();
4098
+ if (table === undefined) {
4099
+ return [];
4100
+ }
4101
+ const rows = db.prepare(`
4102
+ SELECT id, query_text, route_metadata_json, final_score, confidence, created_at, updated_at, evaluated_at
4103
+ FROM brain_observations
4104
+ WHERE final_score IS NOT NULL
4105
+ ORDER BY created_at DESC
4106
+ `).all();
4107
+ return rows
4108
+ .map((row) => {
4109
+ const routeMetadata = parseObservationRouteMetadata(row.route_metadata_json);
4110
+ return {
4111
+ observationId: typeof row.id === "string" ? row.id : null,
4112
+ queryText: typeof row.query_text === "string" ? row.query_text : "",
4113
+ finalScore: clampTeacherObservationOutcome(Number(row.final_score)),
4114
+ confidence: Number.isFinite(Number(row.confidence)) ? Number(row.confidence) : null,
4115
+ createdAt: Number.isFinite(Number(row.created_at)) ? Number(row.created_at) : null,
4116
+ updatedAt: Number.isFinite(Number(row.updated_at)) ? Number(row.updated_at) : null,
4117
+ evaluatedAt: Number.isFinite(Number(row.evaluated_at)) ? Number(row.evaluated_at) : null,
4118
+ selectedNodeIds: routeMetadata.selectedNodeIds,
4119
+ selectedPathNodeIds: routeMetadata.selectedPathNodeIds
4120
+ };
4121
+ })
4122
+ .filter((row) => row.createdAt !== null && row.finalScore !== 0);
4123
+ }
4124
+ catch {
4125
+ return [];
4126
+ }
4127
+ finally {
4128
+ try {
4129
+ db?.close();
4130
+ }
4131
+ catch {
4132
+ }
4133
+ }
4134
+ }
4135
+ function decisionTimestampsForObservationMatch(decision) {
4136
+ const timestamps = [];
4137
+ for (const value of [decision.turnCreatedAt, decision.recordedAt]) {
4138
+ if (typeof value !== "string" || value.trim().length === 0) {
4139
+ continue;
4140
+ }
4141
+ const parsed = Date.parse(value);
4142
+ if (Number.isFinite(parsed) && !timestamps.includes(parsed)) {
4143
+ timestamps.push(parsed);
4144
+ }
4145
+ }
4146
+ return timestamps;
4147
+ }
4148
+ function decisionObservationMatchKey(decision, observation) {
4149
+ const normalizedDecisionText = normalizeOutcomeMatchText(decision.userMessage);
4150
+ const normalizedObservationText = normalizeOutcomeMatchText(observation.queryText);
4151
+ const decisionSelected = new Set([
4152
+ ...(Array.isArray(decision.selectedBrainContextIds) ? decision.selectedBrainContextIds : []),
4153
+ ...(Array.isArray(decision.chosenContextIds) ? decision.chosenContextIds : [])
4154
+ ].filter((entry) => typeof entry === "string"));
4155
+ const observationSelected = new Set([
4156
+ ...observation.selectedNodeIds,
4157
+ ...observation.selectedPathNodeIds
4158
+ ]);
4159
+ let overlapCount = 0;
4160
+ for (const blockId of observationSelected) {
4161
+ if (decisionSelected.has(blockId)) {
4162
+ overlapCount += 1;
4163
+ }
4164
+ }
4165
+ const timestamps = decisionTimestampsForObservationMatch(decision);
4166
+ const observationCreatedAt = observation.createdAt;
4167
+ const bestDelta = observationCreatedAt === null || timestamps.length === 0
4168
+ ? Number.POSITIVE_INFINITY
4169
+ : Math.min(...timestamps.map((timestamp) => Math.abs(timestamp - observationCreatedAt)));
4170
+ const sameQuery = normalizedDecisionText !== null && normalizedDecisionText === normalizedObservationText;
4171
+ if (!sameQuery && overlapCount === 0) {
4172
+ return null;
4173
+ }
4174
+ if (!Number.isFinite(bestDelta) || bestDelta > 300_000) {
4175
+ return null;
4176
+ }
4177
+ return {
4178
+ sameQuery,
4179
+ overlapCount,
4180
+ deltaMs: bestDelta,
4181
+ confidence: observation.confidence ?? 0
4182
+ };
4183
+ }
4184
+ function compareDecisionObservationMatch(left, right) {
4185
+ if (left.sameQuery !== right.sameQuery) {
4186
+ return left.sameQuery ? -1 : 1;
4187
+ }
4188
+ if (left.overlapCount !== right.overlapCount) {
4189
+ return right.overlapCount - left.overlapCount;
4190
+ }
4191
+ if (left.deltaMs !== right.deltaMs) {
4192
+ return left.deltaMs - right.deltaMs;
4193
+ }
4194
+ if (left.confidence !== right.confidence) {
4195
+ return right.confidence - left.confidence;
4196
+ }
4197
+ return 0;
4198
+ }
4199
+ function joinDecisionsWithTeacherObservationOutcomes(decisions, observationOutcomes) {
4200
+ const outcomes = new Map();
4201
+ for (const decision of decisions) {
4202
+ outcomes.set(decision.recordId, 0);
4203
+ }
4204
+ if (!Array.isArray(observationOutcomes) || observationOutcomes.length === 0) {
4205
+ return outcomes;
4206
+ }
4207
+ for (const observation of observationOutcomes) {
4208
+ const matches = decisions
4209
+ .map((decision) => {
4210
+ const key = decisionObservationMatchKey(decision, observation);
4211
+ return key === null
4212
+ ? null
4213
+ : {
4214
+ decision,
4215
+ key
4216
+ };
4217
+ })
4218
+ .filter((entry) => entry !== null)
4219
+ .sort((left, right) => compareDecisionObservationMatch(left.key, right.key));
4220
+ const best = matches[0] ?? null;
4221
+ const runnerUp = matches[1] ?? null;
4222
+ if (best === null) {
4223
+ continue;
4224
+ }
4225
+ if (runnerUp !== null && compareDecisionObservationMatch(best.key, runnerUp.key) === 0) {
4226
+ continue;
4227
+ }
4228
+ const reward = clampTeacherObservationOutcome(observation.finalScore);
4229
+ if (reward === 0) {
4230
+ continue;
4231
+ }
4232
+ const current = outcomes.get(best.decision.recordId) ?? 0;
4233
+ if (current === 0 || Math.abs(reward) > Math.abs(current)) {
4234
+ outcomes.set(best.decision.recordId, reward);
4235
+ }
4236
+ }
4237
+ return outcomes;
4238
+ }
4239
+
4043
4240
  export function joinDecisionsWithFeedback(decisions, eventExport, maxDelayMs = 300_000) {
4044
4241
  const outcomes = new Map();
4045
4242
  for (const decision of decisions) {
@@ -4421,15 +4618,21 @@ function computeTrajectoryPolicyGradientV2(trajectory, adjacency, graph, vectors
4421
4618
  * 6. Aggregates into RouterPolicyUpdateV1[] format
4422
4619
  * 7. Builds the router artifact with ROUTER_PG_PROFILE_V2
4423
4620
  */
4424
- function createRouterArtifactV2(packId, builtAt, graph, vectors, eventExport, serveTimeDecisions, baselineState, sparseFeedback, principalBacklog) {
4621
+ function createRouterArtifactV2(packId, builtAt, graph, vectors, eventExport, serveTimeDecisions, baselineState, sparseFeedback, principalBacklog, teacherObservationOutcomes = []) {
4425
4622
  const tau = DEFAULT_TAU;
4426
4623
  const pgScale = 8;
4427
4624
  // 1. Build adjacency map from graph
4428
4625
  const adjacency = buildAdjacencyMap(graph);
4429
4626
  const resolveBlockId = buildGraphBlockIdResolver(graph);
4430
4627
  const remappedServeTimeDecisions = serveTimeDecisions.map((decision) => remapServeTimeDecisionToGraph(decision, resolveBlockId));
4431
- // 2. Join serve-time decisions with feedback events to get outcomes
4628
+ // 2. Join serve-time decisions with explicit feedback first, then let teacher-v2 observations override when available.
4432
4629
  const outcomeMap = joinDecisionsWithFeedback(remappedServeTimeDecisions, eventExport);
4630
+ const teacherObservationOutcomeMap = joinDecisionsWithTeacherObservationOutcomes(remappedServeTimeDecisions, teacherObservationOutcomes);
4631
+ for (const [decisionId, reward] of teacherObservationOutcomeMap.entries()) {
4632
+ if (reward !== 0) {
4633
+ outcomeMap.set(decisionId, reward);
4634
+ }
4635
+ }
4433
4636
  // 3 & 4. Reconstruct trajectories and update baseline
4434
4637
  let currentBaseline = { ...baselineState };
4435
4638
  const trajectories = [];
@@ -4719,9 +4922,12 @@ export function buildCandidatePack(input) {
4719
4922
  const vectors = createVectorsPayload(graph);
4720
4923
  let router = null;
4721
4924
  let updatedBaseline = null;
4925
+ const teacherObservationOutcomes = input.activationRoot === undefined
4926
+ ? []
4927
+ : loadTeacherObservationOutcomesFromActivation(input.activationRoot);
4722
4928
  if (input.learnedRouting) {
4723
4929
  if (useV2) {
4724
- const v2Result = createRouterArtifactV2(packId, builtAt, graph, vectors, eventExport, input.serveTimeDecisions, input.baselineState ?? initBaseline(), input.sparseFeedback, input.principalBacklog);
4930
+ const v2Result = createRouterArtifactV2(packId, builtAt, graph, vectors, eventExport, input.serveTimeDecisions, input.baselineState ?? initBaseline(), input.sparseFeedback, input.principalBacklog, teacherObservationOutcomes);
4725
4931
  router = v2Result.artifact;
4726
4932
  updatedBaseline = v2Result.updatedBaseline;
4727
4933
  }
@@ -4883,7 +5089,8 @@ export function buildCandidatePackFromNormalizedEventExport(input) {
4883
5089
  ...(input.principalBacklog !== undefined ? { principalBacklog: input.principalBacklog } : {}),
4884
5090
  ...(input.pgVersion !== undefined ? { pgVersion: input.pgVersion } : {}),
4885
5091
  ...(input.serveTimeDecisions !== undefined ? { serveTimeDecisions: [...input.serveTimeDecisions] } : {}),
4886
- ...(input.baselineState !== undefined ? { baselineState: { ...input.baselineState } } : {})
5092
+ ...(input.baselineState !== undefined ? { baselineState: { ...input.baselineState } } : {}),
5093
+ ...(input.activationRoot !== undefined ? { activationRoot: input.activationRoot } : {})
4887
5094
  };
4888
5095
  return buildCandidatePack(candidateInput);
4889
5096
  }