@openclawbrain/openclaw 0.2.2 → 0.3.0

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.
Files changed (47) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +10 -0
  3. package/dist/extension/index.d.ts +1 -0
  4. package/dist/extension/index.js +73 -0
  5. package/dist/extension/index.js.map +1 -0
  6. package/dist/extension/runtime-guard.d.ts +61 -0
  7. package/dist/extension/runtime-guard.js +230 -0
  8. package/dist/extension/runtime-guard.js.map +1 -0
  9. package/dist/src/cli.d.ts +66 -4
  10. package/dist/src/cli.js +1845 -241
  11. package/dist/src/cli.js.map +1 -1
  12. package/dist/src/daemon.d.ts +7 -4
  13. package/dist/src/daemon.js +311 -28
  14. package/dist/src/daemon.js.map +1 -1
  15. package/dist/src/index.d.ts +213 -4
  16. package/dist/src/index.js +1151 -157
  17. package/dist/src/index.js.map +1 -1
  18. package/dist/src/learning-spine.d.ts +2 -1
  19. package/dist/src/learning-spine.js +8 -0
  20. package/dist/src/learning-spine.js.map +1 -1
  21. package/dist/src/local-session-passive-learning.d.ts +1 -0
  22. package/dist/src/local-session-passive-learning.js +97 -7
  23. package/dist/src/local-session-passive-learning.js.map +1 -1
  24. package/dist/src/ollama-client.d.ts +46 -0
  25. package/dist/src/ollama-client.js +231 -0
  26. package/dist/src/ollama-client.js.map +1 -0
  27. package/dist/src/provider-config.d.ts +28 -0
  28. package/dist/src/provider-config.js +150 -0
  29. package/dist/src/provider-config.js.map +1 -0
  30. package/dist/src/resolve-activation-root.d.ts +3 -3
  31. package/dist/src/resolve-activation-root.js +105 -35
  32. package/dist/src/resolve-activation-root.js.map +1 -1
  33. package/dist/src/session-store.d.ts +18 -0
  34. package/dist/src/session-store.js +40 -0
  35. package/dist/src/session-store.js.map +1 -1
  36. package/dist/src/session-tail.d.ts +6 -3
  37. package/dist/src/session-tail.js +35 -4
  38. package/dist/src/session-tail.js.map +1 -1
  39. package/dist/src/shadow-extension-proof.d.ts +40 -0
  40. package/dist/src/shadow-extension-proof.js +214 -0
  41. package/dist/src/shadow-extension-proof.js.map +1 -0
  42. package/dist/src/teacher-labeler.d.ts +50 -0
  43. package/dist/src/teacher-labeler.js +424 -0
  44. package/dist/src/teacher-labeler.js.map +1 -0
  45. package/extension/index.ts +74 -35
  46. package/extension/runtime-guard.ts +353 -0
  47. package/package.json +13 -13
@@ -0,0 +1,353 @@
1
+ export interface ExtensionCompileInput {
2
+ activationRoot: string;
3
+ message: string;
4
+ sessionId?: string;
5
+ channel?: string;
6
+ _serveRouteBreadcrumbs?: {
7
+ invocationSurface: "installed_extension_before_prompt_build";
8
+ hostEvent: "before_prompt_build";
9
+ installedEntryPath: string;
10
+ };
11
+ }
12
+
13
+ export interface ExtensionCompileSuccess {
14
+ ok: true;
15
+ brainContext: string;
16
+ }
17
+
18
+ export interface ExtensionCompileFailure {
19
+ ok: false;
20
+ hardRequirementViolated: boolean;
21
+ error: string;
22
+ brainContext: string;
23
+ }
24
+
25
+ export type ExtensionCompileResult = ExtensionCompileSuccess | ExtensionCompileFailure;
26
+
27
+ export type ExtensionCompileRuntimeContext = (input: ExtensionCompileInput) => ExtensionCompileResult;
28
+
29
+ export interface ExtensionDiagnostic {
30
+ key: string;
31
+ message: string;
32
+ once?: boolean;
33
+ }
34
+
35
+ export interface ExtensionRegistrationApi {
36
+ on(eventName: string, handler: (event: unknown, ctx: unknown) => Promise<Record<string, unknown>>, options?: { priority?: number }): void;
37
+ }
38
+
39
+ export interface NormalizedPromptBuildEvent {
40
+ message: string;
41
+ sessionId?: string;
42
+ channel?: string;
43
+ warnings: ExtensionDiagnostic[];
44
+ }
45
+
46
+ export function isActivationRootPlaceholder(activationRoot: string): boolean {
47
+ return activationRoot === "__ACTIVATION_" + "ROOT__" || activationRoot.trim().length === 0;
48
+ }
49
+
50
+ export function validateExtensionRegistrationApi(api: unknown): { ok: true; api: ExtensionRegistrationApi } | { ok: false; diagnostic: ExtensionDiagnostic } {
51
+ if (!isRecord(api) || typeof api.on !== "function") {
52
+ return {
53
+ ok: false,
54
+ diagnostic: {
55
+ key: "registration-api-invalid",
56
+ once: true,
57
+ message:
58
+ `[openclawbrain] extension inactive: host registration API is missing api.on(event, handler, options) ` +
59
+ `(received=${describeValue(api)})`
60
+ }
61
+ };
62
+ }
63
+
64
+ return {
65
+ ok: true,
66
+ api: api as unknown as ExtensionRegistrationApi
67
+ };
68
+ }
69
+
70
+ export function normalizePromptBuildEvent(event: unknown): { ok: true; event: NormalizedPromptBuildEvent } | { ok: false; diagnostic: ExtensionDiagnostic } {
71
+ if (!isRecord(event)) {
72
+ return {
73
+ ok: false,
74
+ diagnostic: failOpenDiagnostic(
75
+ "runtime-event-not-object",
76
+ "before_prompt_build event is not an object",
77
+ `event=${describeValue(event)}`
78
+ )
79
+ };
80
+ }
81
+
82
+ const messages = event.messages;
83
+ if (!Array.isArray(messages)) {
84
+ return {
85
+ ok: false,
86
+ diagnostic: failOpenDiagnostic(
87
+ "runtime-messages-not-array",
88
+ "before_prompt_build event.messages is not an array",
89
+ `event=${describeValue(event)} messages=${describeValue(messages)}`
90
+ )
91
+ };
92
+ }
93
+
94
+ const warnings: ExtensionDiagnostic[] = [];
95
+ const sessionId = normalizeOptionalScalarField(event.sessionId, "sessionId", warnings);
96
+ const channel = normalizeOptionalScalarField(event.channel, "channel", warnings);
97
+ let extractedMessage = "";
98
+
99
+ if (messages.length === 0) {
100
+ warnings.push(
101
+ failOpenDiagnostic(
102
+ "runtime-messages-empty",
103
+ "before_prompt_build event.messages is empty",
104
+ `event=${describeValue(event)}`
105
+ )
106
+ );
107
+ } else {
108
+ const lastMessage = messages.at(-1);
109
+ extractedMessage = extractPromptMessage(lastMessage) ?? "";
110
+ if (extractedMessage.length === 0) {
111
+ warnings.push(
112
+ failOpenDiagnostic(
113
+ "runtime-last-message-invalid",
114
+ "before_prompt_build last message has no usable text content",
115
+ `lastMessage=${describeValue(lastMessage)}`
116
+ )
117
+ );
118
+ }
119
+ }
120
+
121
+ return {
122
+ ok: true,
123
+ event: {
124
+ message: extractedMessage,
125
+ ...(sessionId !== undefined ? { sessionId } : {}),
126
+ ...(channel !== undefined ? { channel } : {}),
127
+ warnings
128
+ }
129
+ };
130
+ }
131
+
132
+ export function createBeforePromptBuildHandler(input: {
133
+ activationRoot: string;
134
+ compileRuntimeContext: ExtensionCompileRuntimeContext;
135
+ reportDiagnostic: (diagnostic: ExtensionDiagnostic) => void | Promise<void>;
136
+ debug?: (message: string) => void;
137
+ extensionEntryPath?: string;
138
+ }): (event: unknown, ctx: unknown) => Promise<Record<string, unknown>> {
139
+ return async (event: unknown, _ctx: unknown) => {
140
+ if (isActivationRootPlaceholder(input.activationRoot)) {
141
+ await input.reportDiagnostic({
142
+ key: "activation-root-placeholder",
143
+ once: true,
144
+ message:
145
+ "[openclawbrain] extension inactive: ACTIVATION_ROOT is still a placeholder. Run: openclawbrain install --openclaw-home <path>"
146
+ });
147
+ return {};
148
+ }
149
+
150
+ const normalized = normalizePromptBuildEvent(event);
151
+ if (!normalized.ok) {
152
+ await input.reportDiagnostic(normalized.diagnostic);
153
+ return {};
154
+ }
155
+
156
+ for (const warning of normalized.event.warnings) {
157
+ await input.reportDiagnostic(warning);
158
+ }
159
+
160
+ try {
161
+ const result = input.compileRuntimeContext({
162
+ activationRoot: input.activationRoot,
163
+ message: normalized.event.message,
164
+ ...(normalized.event.sessionId !== undefined ? { sessionId: normalized.event.sessionId } : {}),
165
+ ...(normalized.event.channel !== undefined ? { channel: normalized.event.channel } : {}),
166
+ ...(input.extensionEntryPath === undefined
167
+ ? {}
168
+ : {
169
+ _serveRouteBreadcrumbs: {
170
+ invocationSurface: "installed_extension_before_prompt_build" as const,
171
+ hostEvent: "before_prompt_build" as const,
172
+ installedEntryPath: input.extensionEntryPath
173
+ }
174
+ })
175
+ });
176
+
177
+ if (!result.ok) {
178
+ const mode = result.hardRequirementViolated ? "hard-fail" : "fail-open";
179
+ await input.reportDiagnostic({
180
+ key: `compile-${mode}`,
181
+ message:
182
+ `[openclawbrain] ${mode}: ${result.error} ` +
183
+ `(activationRoot=${input.activationRoot}, sessionId=${normalized.event.sessionId ?? "unknown"}, channel=${normalized.event.channel ?? "unknown"})`
184
+ });
185
+ return {};
186
+ }
187
+
188
+ if (result.brainContext.length > 0) {
189
+ input.debug?.(`[openclawbrain] compiled context, chars: ${result.brainContext.length}`);
190
+ return {
191
+ appendSystemContext: result.brainContext
192
+ };
193
+ }
194
+ } catch (error) {
195
+ const detail = error instanceof Error ? error.stack ?? error.message : String(error);
196
+ await input.reportDiagnostic({
197
+ key: "compile-threw",
198
+ message:
199
+ `[openclawbrain] compile threw: ${detail} ` +
200
+ `(activationRoot=${input.activationRoot}, sessionId=${normalized.event.sessionId ?? "unknown"}, channel=${normalized.event.channel ?? "unknown"})`
201
+ });
202
+ }
203
+
204
+ return {};
205
+ };
206
+ }
207
+
208
+ function failOpenDiagnostic(key: string, reason: string, detail: string): ExtensionDiagnostic {
209
+ return {
210
+ key,
211
+ message: `[openclawbrain] fail-open: ${reason} (${detail})`
212
+ };
213
+ }
214
+
215
+ function normalizeOptionalScalarField(
216
+ value: unknown,
217
+ fieldName: "sessionId" | "channel",
218
+ warnings: ExtensionDiagnostic[]
219
+ ): string | undefined {
220
+ if (value === undefined || value === null) {
221
+ return undefined;
222
+ }
223
+
224
+ if (typeof value === "string") {
225
+ const trimmed = value.trim();
226
+ return trimmed.length > 0 ? trimmed : undefined;
227
+ }
228
+
229
+ if (typeof value === "number" || typeof value === "bigint" || typeof value === "boolean") {
230
+ return String(value);
231
+ }
232
+
233
+ warnings.push({
234
+ key: `runtime-${fieldName}-ignored`,
235
+ message:
236
+ `[openclawbrain] fail-open: ignored unsupported before_prompt_build ${fieldName} ` +
237
+ `(${fieldName}=${describeValue(value)})`
238
+ });
239
+
240
+ return undefined;
241
+ }
242
+
243
+ function extractPromptMessage(message: unknown): string | undefined {
244
+ if (typeof message === "string") {
245
+ return normalizeText(message);
246
+ }
247
+
248
+ if (!isRecord(message)) {
249
+ return undefined;
250
+ }
251
+
252
+ return extractTextContent(message.content);
253
+ }
254
+
255
+ function extractTextContent(content: unknown): string | undefined {
256
+ if (typeof content === "string") {
257
+ return normalizeText(content);
258
+ }
259
+
260
+ if (Array.isArray(content)) {
261
+ const parts = content
262
+ .map((part) => extractTextPart(part))
263
+ .filter((part): part is string => part !== undefined);
264
+ return parts.length > 0 ? parts.join("\n") : undefined;
265
+ }
266
+
267
+ if (isRecord(content)) {
268
+ if (typeof content.text === "string") {
269
+ return normalizeText(content.text);
270
+ }
271
+
272
+ if (typeof content.content === "string") {
273
+ return normalizeText(content.content);
274
+ }
275
+ }
276
+
277
+ return undefined;
278
+ }
279
+
280
+ function extractTextPart(part: unknown): string | undefined {
281
+ if (typeof part === "string") {
282
+ return normalizeText(part);
283
+ }
284
+
285
+ if (!isRecord(part)) {
286
+ return undefined;
287
+ }
288
+
289
+ if ("text" in part && typeof part.text === "string") {
290
+ return normalizeText(part.text);
291
+ }
292
+
293
+ if ("content" in part && typeof part.content === "string") {
294
+ return normalizeText(part.content);
295
+ }
296
+
297
+ return undefined;
298
+ }
299
+
300
+ function normalizeText(value: string): string | undefined {
301
+ const trimmed = value.trim();
302
+ return trimmed.length > 0 ? trimmed : undefined;
303
+ }
304
+
305
+ function describeValue(value: unknown): string {
306
+ if (value === null) {
307
+ return "null";
308
+ }
309
+
310
+ if (Array.isArray(value)) {
311
+ const itemKinds = Array.from(
312
+ new Set(
313
+ value.slice(0, 4).map((entry) => {
314
+ if (entry === null) {
315
+ return "null";
316
+ }
317
+ if (Array.isArray(entry)) {
318
+ return "array";
319
+ }
320
+ return typeof entry;
321
+ })
322
+ )
323
+ );
324
+ const suffix = value.length > 4 ? ",..." : "";
325
+ return `array(len=${value.length}, itemKinds=${itemKinds.join("|") || "none"}${suffix})`;
326
+ }
327
+
328
+ if (typeof value === "string") {
329
+ const preview = value.replace(/\s+/g, " ").trim().slice(0, 48);
330
+ const suffix = value.trim().length > 48 ? "..." : "";
331
+ return `string(len=${value.length}, preview=${JSON.stringify(preview + suffix)})`;
332
+ }
333
+
334
+ if (typeof value === "object") {
335
+ const keys = Object.keys(value).slice(0, 6);
336
+ const suffix = Object.keys(value).length > 6 ? ",..." : "";
337
+ return `object(keys=${keys.join(",") || "none"}${suffix})`;
338
+ }
339
+
340
+ if (typeof value === "function") {
341
+ return "function";
342
+ }
343
+
344
+ if (typeof value === "symbol") {
345
+ return `symbol(${String(value.description ?? "")})`;
346
+ }
347
+
348
+ return `${typeof value}(${String(value)})`;
349
+ }
350
+
351
+ function isRecord(value: unknown): value is Record<string, unknown> {
352
+ return typeof value === "object" && value !== null;
353
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclawbrain/openclaw",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Primary OpenClawBrain front door for OpenClaw attach, compile, event export, status, and rollback.",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",
@@ -38,27 +38,27 @@
38
38
  },
39
39
  "files": [
40
40
  "dist/src",
41
+ "dist/extension",
41
42
  "extension"
42
43
  ],
43
44
  "publishConfig": {
44
45
  "access": "public"
45
46
  },
46
- "scripts": {
47
- "build": "rm -rf dist && tsc -b",
48
- "clean": "rm -rf dist && tsc -b --clean",
49
- "prepack": "pnpm run build",
50
- "test": "node --test dist/test/*.test.js"
51
- },
52
47
  "dependencies": {
53
- "@openclawbrain/compiler": "^0.2.1",
54
- "@openclawbrain/contracts": "^0.2.1",
55
- "@openclawbrain/events": "^0.2.1",
56
- "@openclawbrain/learner": "^0.2.1",
57
- "@openclawbrain/pack-format": "^0.2.1",
58
- "@openclawbrain/event-export": "^0.2.1"
48
+ "@openclawbrain/compiler": "0.3.0",
49
+ "@openclawbrain/contracts": "0.3.0",
50
+ "@openclawbrain/events": "0.3.0",
51
+ "@openclawbrain/learner": "0.3.0",
52
+ "@openclawbrain/pack-format": "0.3.0",
53
+ "@openclawbrain/event-export": "0.3.0"
59
54
  },
60
55
  "bin": {
61
56
  "openclawbrain": "./dist/src/cli.js",
62
57
  "openclawbrain-ops": "./dist/src/cli.js"
58
+ },
59
+ "scripts": {
60
+ "build": "rm -rf dist && tsc -b",
61
+ "clean": "rm -rf dist && tsc -b --clean",
62
+ "test": "node --test dist/test/*.test.js"
63
63
  }
64
64
  }