@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.
- package/dist/extension/index.js +30 -4
- package/dist/extension/runtime-guard.d.ts +6 -0
- package/dist/extension/runtime-guard.js +71 -15
- package/dist/src/cli.js +163 -23
- package/dist/src/index.d.ts +248 -4
- package/dist/src/index.js +1199 -44
- package/dist/src/install-converge.js +4 -3
- package/dist/src/learning-spine.js +43 -1
- package/dist/src/local-learner.d.ts +6 -0
- package/dist/src/local-learner.js +86 -152
- package/dist/src/local-session-passive-learning.js +28 -2
- package/dist/src/materialization-embedder.js +11 -0
- package/dist/src/openclaw-hook-truth.d.ts +6 -0
- package/dist/src/openclaw-hook-truth.js +27 -0
- package/dist/src/proof-command.js +251 -4
- package/dist/src/runtime-core.js +87 -7
- package/dist/src/status-learning-path.js +32 -2
- package/dist/src/teacher-decision-match.js +47 -10
- package/dist/src/teacher-labeler.d.ts +12 -0
- package/dist/src/teacher-labeler.js +42 -0
- package/dist/src/traced-learning-bridge.js +17 -1
- package/extension/index.ts +35 -4
- package/extension/runtime-guard.ts +92 -14
- package/package.json +1 -1
|
@@ -80,6 +80,14 @@ function readInteractionActivePackGraphChecksum(interaction) {
|
|
|
80
80
|
?? undefined;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
function readInteractionExplicitTurnCompileEventId(interaction) {
|
|
84
|
+
return normalizeOptionalString(interaction?.turnCompileEventId)
|
|
85
|
+
?? normalizeOptionalString(toRecord(interaction?.routeMetadata)?.turnCompileEventId)
|
|
86
|
+
?? normalizeOptionalString(toRecord(interaction?.decisionProvenance)?.turnCompileEventId)
|
|
87
|
+
?? normalizeOptionalString(toRecord(interaction?.metadata)?.turnCompileEventId)
|
|
88
|
+
?? undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
83
91
|
function buildDecisionTimestamps(decision) {
|
|
84
92
|
const timestamps = [];
|
|
85
93
|
const turnCreatedAt = toTimestamp(decision.turnCreatedAt);
|
|
@@ -139,8 +147,10 @@ export function createServeTimeDecisionMatcher(decisions, options = {}) {
|
|
|
139
147
|
const decisionsByRecordId = new Map();
|
|
140
148
|
const decisionsBySelectionDigest = new Map();
|
|
141
149
|
const ambiguousSelectionDigests = new Set();
|
|
142
|
-
const
|
|
150
|
+
const decisionsByTurnCompileEventId = new Map();
|
|
151
|
+
const ambiguousTurnCompileEventIds = new Set();
|
|
143
152
|
const fallbackDecisions = new Map();
|
|
153
|
+
const ambiguousFallbackDecisionKeys = new Set();
|
|
144
154
|
const decisionsBySessionChannel = new Map();
|
|
145
155
|
const globalFallbackDecisions = [];
|
|
146
156
|
|
|
@@ -164,15 +174,27 @@ export function createServeTimeDecisionMatcher(decisions, options = {}) {
|
|
|
164
174
|
}
|
|
165
175
|
}
|
|
166
176
|
const turnCompileEventId = normalizeOptionalString(decision.turnCompileEventId);
|
|
167
|
-
if (turnCompileEventId !== undefined
|
|
168
|
-
|
|
177
|
+
if (turnCompileEventId !== undefined) {
|
|
178
|
+
if (decisionsByTurnCompileEventId.has(turnCompileEventId)) {
|
|
179
|
+
decisionsByTurnCompileEventId.delete(turnCompileEventId);
|
|
180
|
+
ambiguousTurnCompileEventIds.add(turnCompileEventId);
|
|
181
|
+
}
|
|
182
|
+
else if (!ambiguousTurnCompileEventIds.has(turnCompileEventId)) {
|
|
183
|
+
decisionsByTurnCompileEventId.set(turnCompileEventId, decision);
|
|
184
|
+
}
|
|
169
185
|
}
|
|
170
186
|
for (const candidateKey of [
|
|
171
187
|
buildCandidateKey(decision.sessionId, decision.channel, decision.turnCreatedAt),
|
|
172
188
|
buildCandidateKey(decision.sessionId, decision.channel, decision.recordedAt),
|
|
173
189
|
]) {
|
|
174
|
-
if (candidateKey !== null
|
|
175
|
-
fallbackDecisions.
|
|
190
|
+
if (candidateKey !== null) {
|
|
191
|
+
if (fallbackDecisions.has(candidateKey)) {
|
|
192
|
+
fallbackDecisions.delete(candidateKey);
|
|
193
|
+
ambiguousFallbackDecisionKeys.add(candidateKey);
|
|
194
|
+
}
|
|
195
|
+
else if (!ambiguousFallbackDecisionKeys.has(candidateKey)) {
|
|
196
|
+
fallbackDecisions.set(candidateKey, decision);
|
|
197
|
+
}
|
|
176
198
|
}
|
|
177
199
|
}
|
|
178
200
|
const sessionChannelKey = buildSessionChannelKey(decision.sessionId, decision.channel);
|
|
@@ -203,22 +225,37 @@ export function createServeTimeDecisionMatcher(decisions, options = {}) {
|
|
|
203
225
|
if (decisionRecordId !== undefined) {
|
|
204
226
|
return decisionsByRecordId.get(decisionRecordId) ?? null;
|
|
205
227
|
}
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
);
|
|
228
|
+
const interactionSelectionDigest = readInteractionSelectionDigest(interaction);
|
|
229
|
+
const interactionGraphChecksum = readInteractionActivePackGraphChecksum(interaction);
|
|
230
|
+
const selectionDigestKey = buildSelectionDigestKey(interactionSelectionDigest, interactionGraphChecksum);
|
|
210
231
|
if (selectionDigestKey !== null) {
|
|
211
232
|
if (ambiguousSelectionDigests.has(selectionDigestKey)) {
|
|
212
233
|
return null;
|
|
213
234
|
}
|
|
214
235
|
return decisionsBySelectionDigest.get(selectionDigestKey) ?? null;
|
|
215
236
|
}
|
|
216
|
-
|
|
237
|
+
if (interactionSelectionDigest !== undefined || interactionGraphChecksum !== undefined) {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
const explicitTurnCompileEventId = readInteractionExplicitTurnCompileEventId(interaction);
|
|
241
|
+
if (explicitTurnCompileEventId !== undefined) {
|
|
242
|
+
if (ambiguousTurnCompileEventIds.has(explicitTurnCompileEventId)) {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
return decisionsByTurnCompileEventId.get(explicitTurnCompileEventId) ?? null;
|
|
246
|
+
}
|
|
247
|
+
const softTurnCompileEventId = normalizeOptionalString(interaction.eventId);
|
|
248
|
+
const exact = softTurnCompileEventId === undefined || ambiguousTurnCompileEventIds.has(softTurnCompileEventId)
|
|
249
|
+
? undefined
|
|
250
|
+
: decisionsByTurnCompileEventId.get(softTurnCompileEventId);
|
|
217
251
|
if (exact !== undefined) {
|
|
218
252
|
return exact;
|
|
219
253
|
}
|
|
220
254
|
const exactFallbackKey = buildCandidateKey(interaction.sessionId, interaction.channel, interaction.createdAt);
|
|
221
255
|
if (exactFallbackKey !== null) {
|
|
256
|
+
if (ambiguousFallbackDecisionKeys.has(exactFallbackKey)) {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
222
259
|
const fallback = fallbackDecisions.get(exactFallbackKey);
|
|
223
260
|
if (fallback !== undefined) {
|
|
224
261
|
return fallback;
|
|
@@ -15,6 +15,17 @@ export interface TeacherLabelerResultV1 {
|
|
|
15
15
|
export interface TeacherLabeler {
|
|
16
16
|
label(input: TeacherLabelerRunInputV1): Promise<TeacherLabelerResultV1>;
|
|
17
17
|
}
|
|
18
|
+
export interface TeacherLabelerOpportunityInputV1 {
|
|
19
|
+
normalizedEventExport: NormalizedEventExportV1;
|
|
20
|
+
serveTimeDecisions?: readonly LearningSpineServeRouteDecisionLogEntryV1[];
|
|
21
|
+
}
|
|
22
|
+
export interface TeacherLabelerOpportunityV1 {
|
|
23
|
+
enabled: boolean;
|
|
24
|
+
candidateCount: number;
|
|
25
|
+
budgetedCandidateCount: number;
|
|
26
|
+
status: "disabled" | "ready" | "skipped";
|
|
27
|
+
detail: string;
|
|
28
|
+
}
|
|
18
29
|
export interface OllamaTeacherLabelerGenerateInputV1 {
|
|
19
30
|
model: string;
|
|
20
31
|
prompt: string;
|
|
@@ -47,4 +58,5 @@ export interface AsyncTeacherNoopLabelerConfigV1 {
|
|
|
47
58
|
export type AsyncTeacherLabelerConfigV1 = AsyncTeacherNoopLabelerConfigV1 | AsyncTeacherOllamaLabelerConfigV1;
|
|
48
59
|
export declare function createHttpOllamaTeacherLabelerClient(baseUrl?: string): OllamaTeacherLabelerClient;
|
|
49
60
|
export declare function createOllamaTeacherLabeler(config: AsyncTeacherOllamaLabelerConfigV1): TeacherLabeler;
|
|
61
|
+
export declare function summarizeTeacherLabelerOpportunity(input: TeacherLabelerOpportunityInputV1, config?: AsyncTeacherLabelerConfigV1 | null): TeacherLabelerOpportunityV1;
|
|
50
62
|
export declare function createTeacherLabeler(config: AsyncTeacherLabelerConfigV1 | null | undefined): TeacherLabeler | null;
|
|
@@ -246,6 +246,48 @@ function normalizeOllamaTeacherLabelerConfig(config) {
|
|
|
246
246
|
client: config.client ?? createHttpOllamaTeacherLabelerClient(normalizeBaseUrl(config.baseUrl))
|
|
247
247
|
};
|
|
248
248
|
}
|
|
249
|
+
export function summarizeTeacherLabelerOpportunity(input, config) {
|
|
250
|
+
const normalized = config === undefined || config === null || config.provider === "none"
|
|
251
|
+
? {
|
|
252
|
+
enabled: false,
|
|
253
|
+
maxPromptChars: DEFAULT_OLLAMA_MAX_PROMPT_CHARS,
|
|
254
|
+
maxArtifactsPerExport: DEFAULT_OLLAMA_MAX_ARTIFACTS_PER_EXPORT,
|
|
255
|
+
maxInteractionsPerExport: DEFAULT_OLLAMA_MAX_INTERACTIONS,
|
|
256
|
+
maxUserMessageChars: DEFAULT_OLLAMA_MAX_USER_MESSAGE_CHARS,
|
|
257
|
+
maxContextIdsPerDecision: DEFAULT_OLLAMA_MAX_CONTEXT_IDS
|
|
258
|
+
}
|
|
259
|
+
: {
|
|
260
|
+
enabled: true,
|
|
261
|
+
...normalizeOllamaTeacherLabelerConfig(config)
|
|
262
|
+
};
|
|
263
|
+
const candidates = collectCandidates(input, normalized);
|
|
264
|
+
if (candidates.length === 0) {
|
|
265
|
+
return {
|
|
266
|
+
enabled: normalized.enabled,
|
|
267
|
+
candidateCount: 0,
|
|
268
|
+
budgetedCandidateCount: 0,
|
|
269
|
+
status: normalized.enabled ? "skipped" : "disabled",
|
|
270
|
+
detail: "no_matching_interaction_text"
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
const budgetedCandidates = fitCandidatesToPromptBudget(candidates, normalized);
|
|
274
|
+
if (budgetedCandidates.length === 0) {
|
|
275
|
+
return {
|
|
276
|
+
enabled: normalized.enabled,
|
|
277
|
+
candidateCount: candidates.length,
|
|
278
|
+
budgetedCandidateCount: 0,
|
|
279
|
+
status: normalized.enabled ? "skipped" : "disabled",
|
|
280
|
+
detail: "prompt_budget_exhausted"
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
enabled: normalized.enabled,
|
|
285
|
+
candidateCount: candidates.length,
|
|
286
|
+
budgetedCandidateCount: budgetedCandidates.length,
|
|
287
|
+
status: normalized.enabled ? "ready" : "disabled",
|
|
288
|
+
detail: `candidates=${budgetedCandidates.length}`
|
|
289
|
+
};
|
|
290
|
+
}
|
|
249
291
|
class HttpOllamaTeacherLabelerClient {
|
|
250
292
|
baseUrl;
|
|
251
293
|
constructor(baseUrl) {
|
|
@@ -18,6 +18,22 @@ function normalizeOptionalString(value) {
|
|
|
18
18
|
function normalizeSource(value) {
|
|
19
19
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
20
20
|
}
|
|
21
|
+
function summarizeBridgeSource(value) {
|
|
22
|
+
const source = normalizeSource(value);
|
|
23
|
+
if (source === null) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const summarized = {
|
|
27
|
+
command: normalizeOptionalString(source.command),
|
|
28
|
+
bridge: normalizeOptionalString(source.bridge),
|
|
29
|
+
brainRoot: normalizeOptionalString(source.brainRoot),
|
|
30
|
+
stateDbPath: normalizeOptionalString(source.stateDbPath),
|
|
31
|
+
persistedKey: normalizeOptionalString(source.persistedKey),
|
|
32
|
+
candidatePackVersion: Number.isFinite(source.candidatePackVersion) ? Math.trunc(source.candidatePackVersion) : undefined,
|
|
33
|
+
candidateUpdateCount: normalizeCount(source.candidateUpdateCount)
|
|
34
|
+
};
|
|
35
|
+
return Object.fromEntries(Object.entries(summarized).filter(([, candidate]) => candidate !== null && candidate !== undefined));
|
|
36
|
+
}
|
|
21
37
|
function normalizeBridgePayload(payload) {
|
|
22
38
|
if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
|
|
23
39
|
throw new Error("expected traced-learning bridge payload object");
|
|
@@ -579,7 +595,7 @@ export function mergeTracedLearningBridgePayload(payload, persisted) {
|
|
|
579
595
|
supervisionCount: persistedBridge.supervisionCount,
|
|
580
596
|
routerUpdateCount: persistedBridge.routerUpdateCount,
|
|
581
597
|
teacherArtifactCount: persistedBridge.teacherArtifactCount,
|
|
582
|
-
source: persistedBridge.source
|
|
598
|
+
source: summarizeBridgeSource(persistedBridge.source)
|
|
583
599
|
}
|
|
584
600
|
}
|
|
585
601
|
});
|
package/extension/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
} from "@openclawbrain/openclaw";
|
|
19
19
|
import {
|
|
20
20
|
createBeforePromptBuildHandler,
|
|
21
|
+
type ExtensionDiagnostic,
|
|
21
22
|
isActivationRootPlaceholder,
|
|
22
23
|
validateExtensionRegistrationApi
|
|
23
24
|
} from "./runtime-guard.js";
|
|
@@ -52,7 +53,7 @@ async function appendLocalDiagnosticLog(message: string): Promise<void> {
|
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
async function reportDiagnostic(input:
|
|
56
|
+
async function reportDiagnostic(input: ExtensionDiagnostic): Promise<void> {
|
|
56
57
|
if (input.once) {
|
|
57
58
|
if (warnedDiagnostics.has(input.key)) {
|
|
58
59
|
return;
|
|
@@ -60,8 +61,30 @@ async function reportDiagnostic(input: { key: string; message: string; once?: bo
|
|
|
60
61
|
warnedDiagnostics.add(input.key);
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
const formatted = formatDiagnosticMessage(input);
|
|
65
|
+
console.warn(formatted);
|
|
66
|
+
await appendLocalDiagnosticLog(formatted);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function formatDiagnosticMessage(input: ExtensionDiagnostic): string {
|
|
70
|
+
if (
|
|
71
|
+
input.severity === undefined ||
|
|
72
|
+
input.actionability === undefined ||
|
|
73
|
+
input.summary === undefined ||
|
|
74
|
+
input.action === undefined
|
|
75
|
+
) {
|
|
76
|
+
return input.message;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const detail = input.message.replace(/^\[openclawbrain\]\s*/, "");
|
|
80
|
+
return [
|
|
81
|
+
"[openclawbrain]",
|
|
82
|
+
`severity=${input.severity}`,
|
|
83
|
+
`actionability=${input.actionability}`,
|
|
84
|
+
`summary=${JSON.stringify(input.summary)}`,
|
|
85
|
+
`action=${JSON.stringify(input.action)}`,
|
|
86
|
+
`detail=${JSON.stringify(detail)}`
|
|
87
|
+
].join(" ");
|
|
65
88
|
}
|
|
66
89
|
|
|
67
90
|
function announceStartupBreadcrumb(): void {
|
|
@@ -107,8 +130,12 @@ export default function register(api: unknown) {
|
|
|
107
130
|
} catch (error) {
|
|
108
131
|
const detail = error instanceof Error ? error.message : String(error);
|
|
109
132
|
void reportDiagnostic({
|
|
110
|
-
key:
|
|
133
|
+
key: "runtime-load-proof-failed",
|
|
111
134
|
once: true,
|
|
135
|
+
severity: "degraded",
|
|
136
|
+
actionability: "inspect_local_proof_write",
|
|
137
|
+
summary: "runtime-load proof write failed after hook registration",
|
|
138
|
+
action: "Inspect local filesystem permissions and the activation-root proof path if proof capture is expected.",
|
|
112
139
|
message: `[openclawbrain] runtime load proof failed: ${detail}`
|
|
113
140
|
});
|
|
114
141
|
}
|
|
@@ -119,6 +146,10 @@ export default function register(api: unknown) {
|
|
|
119
146
|
void reportDiagnostic({
|
|
120
147
|
key: "registration-failed",
|
|
121
148
|
once: true,
|
|
149
|
+
severity: "blocking",
|
|
150
|
+
actionability: "rerun_install",
|
|
151
|
+
summary: "extension registration threw before the runtime hook was fully attached",
|
|
152
|
+
action: "Rerun openclawbrain install --openclaw-home <path>; if it still fails, inspect the extension loader/runtime.",
|
|
122
153
|
message: `[openclawbrain] extension registration failed: ${detail}`
|
|
123
154
|
});
|
|
124
155
|
}
|
|
@@ -27,10 +27,23 @@ export type ExtensionCompileResult = ExtensionCompileSuccess | ExtensionCompileF
|
|
|
27
27
|
|
|
28
28
|
export type ExtensionCompileRuntimeContext = (input: ExtensionCompileInput) => ExtensionCompileResult;
|
|
29
29
|
|
|
30
|
+
export type ExtensionDiagnosticSeverity = "degraded" | "blocking";
|
|
31
|
+
|
|
32
|
+
export type ExtensionDiagnosticActionability =
|
|
33
|
+
| "inspect_host_event_shape"
|
|
34
|
+
| "inspect_host_registration_api"
|
|
35
|
+
| "inspect_local_proof_write"
|
|
36
|
+
| "inspect_runtime_compile"
|
|
37
|
+
| "rerun_install";
|
|
38
|
+
|
|
30
39
|
export interface ExtensionDiagnostic {
|
|
31
40
|
key: string;
|
|
32
41
|
message: string;
|
|
33
42
|
once?: boolean;
|
|
43
|
+
severity?: ExtensionDiagnosticSeverity;
|
|
44
|
+
actionability?: ExtensionDiagnosticActionability;
|
|
45
|
+
summary?: string;
|
|
46
|
+
action?: string;
|
|
34
47
|
}
|
|
35
48
|
|
|
36
49
|
export interface ExtensionRegistrationApi {
|
|
@@ -53,13 +66,13 @@ export function validateExtensionRegistrationApi(api: unknown): { ok: true; api:
|
|
|
53
66
|
if (!isRecord(api) || typeof api.on !== "function") {
|
|
54
67
|
return {
|
|
55
68
|
ok: false,
|
|
56
|
-
diagnostic: {
|
|
69
|
+
diagnostic: shapeDiagnostic({
|
|
57
70
|
key: "registration-api-invalid",
|
|
58
71
|
once: true,
|
|
59
72
|
message:
|
|
60
73
|
`[openclawbrain] extension inactive: host registration API is missing api.on(event, handler, options) ` +
|
|
61
74
|
`(received=${describeValue(api)})`
|
|
62
|
-
}
|
|
75
|
+
})
|
|
63
76
|
};
|
|
64
77
|
}
|
|
65
78
|
|
|
@@ -145,12 +158,12 @@ export function createBeforePromptBuildHandler(input: {
|
|
|
145
158
|
}): (event: unknown, ctx: unknown) => Promise<Record<string, unknown>> {
|
|
146
159
|
return async (event: unknown, _ctx: unknown) => {
|
|
147
160
|
if (isActivationRootPlaceholder(input.activationRoot)) {
|
|
148
|
-
await input.reportDiagnostic({
|
|
161
|
+
await input.reportDiagnostic(shapeDiagnostic({
|
|
149
162
|
key: "activation-root-placeholder",
|
|
150
163
|
once: true,
|
|
151
164
|
message:
|
|
152
165
|
"[openclawbrain] BRAIN NOT YET LOADED: ACTIVATION_ROOT is still a placeholder. Install @openclawbrain/cli, then run: openclawbrain install --openclaw-home <path>"
|
|
153
|
-
});
|
|
166
|
+
}));
|
|
154
167
|
return {};
|
|
155
168
|
}
|
|
156
169
|
|
|
@@ -190,12 +203,12 @@ export function createBeforePromptBuildHandler(input: {
|
|
|
190
203
|
|
|
191
204
|
if (!result.ok) {
|
|
192
205
|
const mode = result.hardRequirementViolated ? "hard-fail" : "fail-open";
|
|
193
|
-
await input.reportDiagnostic({
|
|
206
|
+
await input.reportDiagnostic(shapeDiagnostic({
|
|
194
207
|
key: `compile-${mode}`,
|
|
195
208
|
message:
|
|
196
209
|
`[openclawbrain] ${mode}: ${result.error} ` +
|
|
197
210
|
`(activationRoot=${input.activationRoot}, sessionId=${normalized.event.sessionId ?? "unknown"}, channel=${normalized.event.channel ?? "unknown"})`
|
|
198
|
-
});
|
|
211
|
+
}));
|
|
199
212
|
return {};
|
|
200
213
|
}
|
|
201
214
|
|
|
@@ -207,12 +220,12 @@ export function createBeforePromptBuildHandler(input: {
|
|
|
207
220
|
}
|
|
208
221
|
} catch (error) {
|
|
209
222
|
const detail = error instanceof Error ? error.stack ?? error.message : String(error);
|
|
210
|
-
await input.reportDiagnostic({
|
|
223
|
+
await input.reportDiagnostic(shapeDiagnostic({
|
|
211
224
|
key: "compile-threw",
|
|
212
225
|
message:
|
|
213
226
|
`[openclawbrain] compile threw: ${detail} ` +
|
|
214
227
|
`(activationRoot=${input.activationRoot}, sessionId=${normalized.event.sessionId ?? "unknown"}, channel=${normalized.event.channel ?? "unknown"})`
|
|
215
|
-
});
|
|
228
|
+
}));
|
|
216
229
|
}
|
|
217
230
|
|
|
218
231
|
return {};
|
|
@@ -220,10 +233,10 @@ export function createBeforePromptBuildHandler(input: {
|
|
|
220
233
|
}
|
|
221
234
|
|
|
222
235
|
function failOpenDiagnostic(key: string, reason: string, detail: string): ExtensionDiagnostic {
|
|
223
|
-
return {
|
|
236
|
+
return shapeDiagnostic({
|
|
224
237
|
key,
|
|
225
238
|
message: `[openclawbrain] fail-open: ${reason} (${detail})`
|
|
226
|
-
};
|
|
239
|
+
});
|
|
227
240
|
}
|
|
228
241
|
|
|
229
242
|
function normalizeOptionalScalarField(
|
|
@@ -244,12 +257,12 @@ function normalizeOptionalScalarField(
|
|
|
244
257
|
return String(value);
|
|
245
258
|
}
|
|
246
259
|
|
|
247
|
-
warnings.push({
|
|
260
|
+
warnings.push(shapeDiagnostic({
|
|
248
261
|
key: `runtime-${fieldName}-ignored`,
|
|
249
262
|
message:
|
|
250
263
|
`[openclawbrain] fail-open: ignored unsupported before_prompt_build ${fieldName} ` +
|
|
251
264
|
`(${fieldName}=${describeValue(value)})`
|
|
252
|
-
});
|
|
265
|
+
}));
|
|
253
266
|
|
|
254
267
|
return undefined;
|
|
255
268
|
}
|
|
@@ -284,12 +297,12 @@ function normalizeOptionalNonNegativeIntegerField(
|
|
|
284
297
|
}
|
|
285
298
|
}
|
|
286
299
|
|
|
287
|
-
warnings.push({
|
|
300
|
+
warnings.push(shapeDiagnostic({
|
|
288
301
|
key: `runtime-${fieldName}-ignored`,
|
|
289
302
|
message:
|
|
290
303
|
`[openclawbrain] fail-open: ignored unsupported before_prompt_build ${fieldName} ` +
|
|
291
304
|
`(${fieldName}=${describeValue(value)})`
|
|
292
|
-
});
|
|
305
|
+
}));
|
|
293
306
|
|
|
294
307
|
return undefined;
|
|
295
308
|
}
|
|
@@ -402,6 +415,71 @@ function describeValue(value: unknown): string {
|
|
|
402
415
|
return `${typeof value}(${String(value)})`;
|
|
403
416
|
}
|
|
404
417
|
|
|
418
|
+
function shapeDiagnostic(diagnostic: ExtensionDiagnostic): ExtensionDiagnostic {
|
|
419
|
+
if (
|
|
420
|
+
diagnostic.severity !== undefined &&
|
|
421
|
+
diagnostic.actionability !== undefined &&
|
|
422
|
+
diagnostic.summary !== undefined &&
|
|
423
|
+
diagnostic.action !== undefined
|
|
424
|
+
) {
|
|
425
|
+
return diagnostic;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (diagnostic.key === "activation-root-placeholder") {
|
|
429
|
+
return {
|
|
430
|
+
...diagnostic,
|
|
431
|
+
severity: "blocking",
|
|
432
|
+
actionability: "rerun_install",
|
|
433
|
+
summary: "extension hook is installed but ACTIVATION_ROOT is still unpinned",
|
|
434
|
+
action: "Run openclawbrain install --openclaw-home <path> to pin the runtime hook."
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (diagnostic.key === "registration-api-invalid") {
|
|
439
|
+
return {
|
|
440
|
+
...diagnostic,
|
|
441
|
+
severity: "blocking",
|
|
442
|
+
actionability: "inspect_host_registration_api",
|
|
443
|
+
summary: "extension host registration API is missing or incompatible",
|
|
444
|
+
action: "Repair or upgrade the host extension API so api.on(event, handler, options) is available."
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (diagnostic.key === "compile-hard-fail") {
|
|
449
|
+
return {
|
|
450
|
+
...diagnostic,
|
|
451
|
+
severity: "blocking",
|
|
452
|
+
actionability: "inspect_runtime_compile",
|
|
453
|
+
summary: "brain context compile hit a hard requirement",
|
|
454
|
+
action: "Inspect the activation root and compile error; rerun install if the pinned hook may be stale."
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (diagnostic.key === "compile-fail-open" || diagnostic.key === "compile-threw") {
|
|
459
|
+
return {
|
|
460
|
+
...diagnostic,
|
|
461
|
+
severity: "degraded",
|
|
462
|
+
actionability: "inspect_runtime_compile",
|
|
463
|
+
summary: diagnostic.key === "compile-threw"
|
|
464
|
+
? "brain context compile threw during before_prompt_build"
|
|
465
|
+
: "brain context compile failed open during before_prompt_build",
|
|
466
|
+
action: "Inspect the activation root and compile error if brain context is unexpectedly empty."
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (diagnostic.key.startsWith("runtime-")) {
|
|
471
|
+
return {
|
|
472
|
+
...diagnostic,
|
|
473
|
+
severity: "degraded",
|
|
474
|
+
actionability: "inspect_host_event_shape",
|
|
475
|
+
summary: "before_prompt_build payload was partial or malformed",
|
|
476
|
+
action: "Inspect the host before_prompt_build event shape; OpenClawBrain fail-opened safely."
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return diagnostic;
|
|
481
|
+
}
|
|
482
|
+
|
|
405
483
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
406
484
|
return typeof value === "object" && value !== null;
|
|
407
485
|
}
|
package/package.json
CHANGED