@openclawbrain/cli 0.4.10 → 0.4.11

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/README.md CHANGED
@@ -1,31 +1,31 @@
1
1
  # @openclawbrain/cli
2
2
 
3
- `@openclawbrain/cli@0.4.10` is the published operator CLI package for OpenClawBrain.
3
+ Operator CLI for OpenClawBrain. Use it with `@openclawbrain/openclaw`.
4
4
 
5
- Primary public flow:
5
+ The public install story is three commands to install or update, then one command to verify.
6
6
 
7
7
  ```bash
8
- openclaw plugins install @openclawbrain/openclaw@0.4.0
9
- npx @openclawbrain/cli@0.4.10 install --openclaw-home ~/.openclaw
8
+ openclaw plugins install @openclawbrain/openclaw
9
+ npx @openclawbrain/cli install --openclaw-home ~/.openclaw
10
10
  openclaw gateway restart
11
- npx @openclawbrain/cli@0.4.10 status --openclaw-home ~/.openclaw --detailed
11
+ npx @openclawbrain/cli status --openclaw-home ~/.openclaw --detailed
12
12
  ```
13
13
 
14
- Patch note for `0.4.4`: the CLI now normalizes `plugins.allow` / `plugins.entries.openclawbrain` correctly on reinstall and reports the canonical `openclawbrain` install identity in status output.
14
+ The first three commands install or update OpenClawBrain. The last command verifies the selected OpenClaw home.
15
15
 
16
- Host release note: patched OpenClaw host builds no longer emit the old `openclaw` vs `openclawbrain` mismatch warning. Older host builds may still show that warning until the host-side alias fix is released there.
17
-
18
- This package carries the `openclawbrain` CLI, daemon controls, import/export helpers, and install/status/operator management code. `@openclawbrain/openclaw` is the plugin/runtime payload.
19
-
20
- ## Commands
16
+ ## Common commands
21
17
 
22
18
  ```bash
23
- npx @openclawbrain/cli@0.4.10 install --openclaw-home ~/.openclaw
24
- npx @openclawbrain/cli@0.4.10 status --openclaw-home ~/.openclaw --detailed
25
- npx @openclawbrain/cli@0.4.10 rollback --activation-root /var/openclawbrain/activation --dry-run
26
- npx @openclawbrain/cli@0.4.10 daemon status --activation-root /var/openclawbrain/activation
19
+ npx @openclawbrain/cli rollback --openclaw-home ~/.openclaw --dry-run
20
+ npx @openclawbrain/cli detach --openclaw-home ~/.openclaw
21
+ npx @openclawbrain/cli uninstall --openclaw-home ~/.openclaw --keep-data
22
+ npx @openclawbrain/cli learn --openclaw-home ~/.openclaw --json
23
+ npx @openclawbrain/cli daemon status --activation-root ~/.openclawbrain/activation
27
24
  ```
28
25
 
29
- If the CLI is already on your `PATH`, `openclawbrain ...` is the same command surface. The docs lead with `npx` because that is the clean-host public-registry lane that already passed on `redogfood`.
26
+ ## Docs
30
27
 
31
- The old `openclawbrain-ops` alias stays wired to the same entrypoint for compatibility.
28
+ - [Repo README](../../README.md)
29
+ - [Quick start](../../docs/getting-started/quick-start.md)
30
+ - [Lifecycle](../../docs/lifecycle.md)
31
+ - [Troubleshooting](../../docs/operating/troubleshooting.md)
package/dist/src/cli.js CHANGED
@@ -23,6 +23,7 @@ import { summarizePackVectorEmbeddingState } from "./embedding-status.js";
23
23
  import { buildTracedLearningBridgePayloadFromRuntime, buildTracedLearningStatusSurface, persistTracedLearningBridgeState } from "./traced-learning-bridge.js";
24
24
  import { discoverOpenClawSessionStores, loadOpenClawSessionIndex, readOpenClawSessionFile } from "./session-store.js";
25
25
  import { readOpenClawBrainProviderDefaults, readOpenClawBrainProviderConfig, readOpenClawBrainProviderConfigFromSources, resolveOpenClawBrainProviderDefaultsPath } from "./provider-config.js";
26
+ import { formatOperatorLearningPathSummary } from "./status-learning-path.js";
26
27
  const OPENCLAWBRAIN_EMBEDDER_BASE_URL_ENV = "OPENCLAWBRAIN_EMBEDDER_BASE_URL";
27
28
  const OPENCLAWBRAIN_EMBEDDER_PROVIDER_ENV = "OPENCLAWBRAIN_EMBEDDER_PROVIDER";
28
29
  const OPENCLAWBRAIN_EMBEDDER_MODEL_ENV = "OPENCLAWBRAIN_EMBEDDER_MODEL";
@@ -1396,7 +1397,11 @@ function formatCurrentProfileStatusSummary(status, report, targetInspection, opt
1396
1397
  `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"}`,
1397
1398
  `labels ${formatLabelFlowSummary(report.labelFlow)}`,
1398
1399
  `graph source=${report.graph.runtimePlasticitySource ?? "none"} blocks=${report.graph.blockCount ?? "none"} strongest=${report.graph.strongestBlockId ?? "none"} ops=${formatStructuralOps(report)} latest=${report.graph.latestMaterialization.packId ?? "none"} latestChanged=${yesNo(report.graph.latestMaterialization.changed)} connect=${formatGraphConnectDiagnostics(report.graph.latestMaterialization.connectDiagnostics ?? report.graph.connectDiagnostics)} summary=${formatGraphSummary(report)}`,
1399
- `path ${formatLearningPathSummary(report.learningPath)}`,
1400
+ `path ${formatOperatorLearningPathSummary({
1401
+ status,
1402
+ learningPath: report.learningPath,
1403
+ tracedLearning
1404
+ })}`,
1400
1405
  `learning state=${report.learning.backlogState} bootstrapped=${yesNo(report.learning.bootstrapped)} mode=${report.learning.mode} next=${report.learning.nextPriorityLane} priority=${report.learning.nextPriorityBucket} pending=${report.learning.pendingLive ?? "none"}/${report.learning.pendingBackfill ?? "none"} buckets=${formatLearningBuckets(report)} warn=${formatLearningWarnings(report)} lastPack=${report.learning.lastMaterializedPackId ?? "none"} detail=${report.learning.detail}`,
1401
1406
  `traced ${formatTracedLearningSurface(tracedLearning)}`,
1402
1407
  `teacherProof ${formatTeacherLoopSummary(report)}`,
@@ -4238,7 +4243,8 @@ function runLearnCommand(parsed) {
4238
4243
  maxCycles: 16,
4239
4244
  pgVersion: serveTimeLearning.pgVersion,
4240
4245
  ...(serveTimeLearning.decisionLogCount > 0 ? { serveTimeDecisions: serveTimeLearning.serveTimeDecisions } : {}),
4241
- ...(serveTimeLearning.baselineState !== undefined ? { baselineState: serveTimeLearning.baselineState } : {})
4246
+ ...(serveTimeLearning.baselineState !== undefined ? { baselineState: serveTimeLearning.baselineState } : {}),
4247
+ activationRoot
4242
4248
  });
4243
4249
  const lastMaterialization = learnerResult.materializations.at(-1) ?? null;
4244
4250
  const plan = describeAlwaysOnLearningRuntimeState(learnerResult.state, lastMaterialization);
package/dist/src/index.js CHANGED
@@ -887,7 +887,8 @@ export class AsyncTeacherLiveLoop {
887
887
  ...(this.input.cadence !== undefined ? { cadence: this.input.cadence } : {}),
888
888
  ...(learnedRoutingState.pgVersion !== undefined ? { pgVersion: learnedRoutingState.pgVersion } : {}),
889
889
  ...(learnedRoutingState.serveTimeDecisions !== undefined ? { serveTimeDecisions: learnedRoutingState.serveTimeDecisions } : {}),
890
- ...(learnedRoutingState.baselineState !== undefined ? { baselineState: learnedRoutingState.baselineState } : {})
890
+ ...(learnedRoutingState.baselineState !== undefined ? { baselineState: learnedRoutingState.baselineState } : {}),
891
+ ...(this.input.activationRoot !== undefined ? { activationRoot: this.input.activationRoot } : {})
891
892
  });
892
893
  this.learnerState = structuredClone(learnerResult.state);
893
894
  this.lastMaterialization = cloneAlwaysOnLearningMaterializationJobOrNull(learnerResult.materialization);
@@ -996,6 +997,7 @@ export function scanLiveEventExport(input) {
996
997
  learnedRouting: input.learnedRouting ?? true,
997
998
  state: createAlwaysOnLearningRuntimeState(),
998
999
  builtAt: normalizeIsoTimestamp(input.builtAt, "builtAt", observedAt),
1000
+ ...(input.activationRoot !== undefined ? { activationRoot: input.activationRoot } : {}),
999
1001
  ...(input.liveSliceSize !== undefined ? { liveSliceSize: input.liveSliceSize } : {}),
1000
1002
  ...(input.backfillSliceSize !== undefined ? { backfillSliceSize: input.backfillSliceSize } : {})
1001
1003
  });
@@ -4037,7 +4039,8 @@ export function runContinuousProductLoopTurn(input) {
4037
4039
  ...(input.sparseFeedback !== undefined ? { sparseFeedback: input.sparseFeedback } : {}),
4038
4040
  ...(input.liveSliceSize !== undefined ? { liveSliceSize: input.liveSliceSize } : {}),
4039
4041
  ...(input.backfillSliceSize !== undefined ? { backfillSliceSize: input.backfillSliceSize } : {}),
4040
- ...(input.cadence !== undefined ? { cadence: input.cadence } : {})
4042
+ ...(input.cadence !== undefined ? { cadence: input.cadence } : {}),
4043
+ activationRoot
4041
4044
  });
4042
4045
  currentState.learner = structuredClone(learnerResult.state);
4043
4046
  currentState.runtimePlasticity = learnerResult.state.runtimePlasticity === null ? null : structuredClone(learnerResult.state.runtimePlasticity);
@@ -29,6 +29,7 @@ export interface CandidatePackBuildInput {
29
29
  serveTimeDecisions?: LearningSpineServeRouteDecisionLogEntryV1[];
30
30
  /** Baseline state for V2 variance reduction. */
31
31
  baselineState?: BaselineStateV1;
32
+ activationRoot?: string;
32
33
  }
33
34
  export interface CandidatePackFromNormalizedEventExportInput {
34
35
  packLabel: string;
@@ -45,6 +46,7 @@ export interface CandidatePackFromNormalizedEventExportInput {
45
46
  pgVersion?: "v1" | "v2";
46
47
  serveTimeDecisions?: LearningSpineServeRouteDecisionLogEntryV1[];
47
48
  baselineState?: BaselineStateV1;
49
+ activationRoot?: string;
48
50
  }
49
51
  export interface BuildTeacherSupervisionArtifactsInput {
50
52
  normalizedEventExport: NormalizedEventExportV1;
@@ -66,6 +68,7 @@ interface CandidatePackBridgeInputBase {
66
68
  pgVersion?: "v1" | "v2";
67
69
  serveTimeDecisions?: LearningSpineServeRouteDecisionLogEntryV1[];
68
70
  baselineState?: BaselineStateV1;
71
+ activationRoot?: string;
69
72
  }
70
73
  export interface CandidatePackFromNormalizedEventExportSliceInput extends CandidatePackBridgeInputBase {
71
74
  normalizedEventExportSlice: NormalizedEventExportSliceV1;
@@ -325,6 +328,7 @@ export interface AdvanceAlwaysOnLearningRuntimeInput {
325
328
  pgVersion?: "v1" | "v2";
326
329
  serveTimeDecisions?: LearningSpineServeRouteDecisionLogEntryV1[];
327
330
  baselineState?: BaselineStateV1;
331
+ activationRoot?: string;
328
332
  }
329
333
  export interface AlwaysOnLearningMaterializationJobV1 {
330
334
  jobId: string;
@@ -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
  }
@@ -0,0 +1,28 @@
1
+ function formatRawLearningPathSummary(learningPath) {
2
+ return `source=${learningPath.source} pg=${learningPath.policyGradientVersion} method=${learningPath.policyGradientMethod ?? "none"} target=${learningPath.targetConstruction ?? "none"} connect=${learningPath.connectOpsFired ?? "none"} trajectories=${learningPath.reconstructedTrajectoryCount ?? "none"}`;
3
+ }
4
+ function isSeedAwaitingFirstPromotion(status) {
5
+ return status?.brain?.state === "seed_state_authoritative" && status?.brainStatus?.awaitingFirstExport === true;
6
+ }
7
+ function normalizeOptionalString(value) {
8
+ return typeof value === "string" && value.trim().length > 0 ? value : null;
9
+ }
10
+ export function formatOperatorLearningPathSummary({ status, learningPath, tracedLearning }) {
11
+ if (!isSeedAwaitingFirstPromotion(status)) {
12
+ return formatRawLearningPathSummary(learningPath);
13
+ }
14
+ const detailParts = [
15
+ "detail=seed_state_awaiting_first_promotion",
16
+ `tracedPg=${normalizeOptionalString(tracedLearning?.pgVersionUsed) ?? "none"}`,
17
+ `tracedPack=${normalizeOptionalString(tracedLearning?.materializedPackId) ?? "none"}`
18
+ ];
19
+ return [
20
+ "source=seed_state",
21
+ "pg=seed",
22
+ "method=not_yet_promoted",
23
+ "target=not_yet_promoted",
24
+ "connect=none",
25
+ "trajectories=none",
26
+ ...detailParts
27
+ ].join(" ");
28
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclawbrain/cli",
3
- "version": "0.4.10",
3
+ "version": "0.4.11",
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",
@@ -62,4 +62,3 @@
62
62
  "test": "node --test dist/test/*.test.js"
63
63
  }
64
64
  }
65
-