@jonathangu/openclawbrain 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 (113) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +412 -0
  3. package/bin/openclawbrain.js +15 -0
  4. package/docs/END_STATE.md +244 -0
  5. package/docs/EVIDENCE.md +128 -0
  6. package/docs/RELEASE_CONTRACT.md +91 -0
  7. package/docs/agent-tools.md +106 -0
  8. package/docs/architecture.md +224 -0
  9. package/docs/configuration.md +178 -0
  10. package/docs/evidence/2026-03-16/3188b50c4ed30f07dea111e35ce52aabefaced63/brain-teach-session-bound/status.json +87 -0
  11. package/docs/evidence/2026-03-16/3188b50c4ed30f07dea111e35ce52aabefaced63/brain-teach-session-bound/summary.md +16 -0
  12. package/docs/evidence/2026-03-16/3188b50c4ed30f07dea111e35ce52aabefaced63/brain-teach-session-bound/trace.json +273 -0
  13. package/docs/evidence/2026-03-16/3188b50c4ed30f07dea111e35ce52aabefaced63/brain-teach-session-bound/validation-report.json +652 -0
  14. package/docs/evidence/2026-03-16/4941429588810da5d6f7ef1509f229f83fa08031/channels-status.txt +31 -0
  15. package/docs/evidence/2026-03-16/4941429588810da5d6f7ef1509f229f83fa08031/config-snapshot.json +66 -0
  16. package/docs/evidence/2026-03-16/4941429588810da5d6f7ef1509f229f83fa08031/doctor.json +14 -0
  17. package/docs/evidence/2026-03-16/4941429588810da5d6f7ef1509f229f83fa08031/gateway-probe.txt +34 -0
  18. package/docs/evidence/2026-03-16/4941429588810da5d6f7ef1509f229f83fa08031/gateway-status.txt +41 -0
  19. package/docs/evidence/2026-03-16/4941429588810da5d6f7ef1509f229f83fa08031/logs.txt +428 -0
  20. package/docs/evidence/2026-03-16/4941429588810da5d6f7ef1509f229f83fa08031/status-all.txt +60 -0
  21. package/docs/evidence/2026-03-16/4941429588810da5d6f7ef1509f229f83fa08031/status.json +223 -0
  22. package/docs/evidence/2026-03-16/4941429588810da5d6f7ef1509f229f83fa08031/summary.md +13 -0
  23. package/docs/evidence/2026-03-16/4941429588810da5d6f7ef1509f229f83fa08031/trace.json +4 -0
  24. package/docs/evidence/2026-03-16/4941429588810da5d6f7ef1509f229f83fa08031/validation-report.json +334 -0
  25. package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/channels-status.txt +25 -0
  26. package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/config-snapshot.json +91 -0
  27. package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/doctor.json +14 -0
  28. package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/gateway-probe.txt +36 -0
  29. package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/gateway-status.txt +44 -0
  30. package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/logs.txt +428 -0
  31. package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/short-static-classification/preflight-doctor.json +10 -0
  32. package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/short-static-classification/preflight-sdk-probe.json +11 -0
  33. package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/short-static-classification/preflight-setup-only.json +12 -0
  34. package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/short-static-classification/summary.md +30 -0
  35. package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/short-static-classification/validation-report.json +72 -0
  36. package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/status-all.txt +63 -0
  37. package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/status.json +200 -0
  38. package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/summary.md +13 -0
  39. package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/trace.json +4 -0
  40. package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/validation-report.json +311 -0
  41. package/docs/evidence/README.md +16 -0
  42. package/docs/fts5.md +161 -0
  43. package/docs/tui.md +506 -0
  44. package/index.ts +1372 -0
  45. package/openclaw.plugin.json +136 -0
  46. package/package.json +66 -0
  47. package/src/assembler.ts +804 -0
  48. package/src/brain-cli.ts +316 -0
  49. package/src/brain-core/decay.ts +35 -0
  50. package/src/brain-core/episode.ts +82 -0
  51. package/src/brain-core/graph.ts +321 -0
  52. package/src/brain-core/health.ts +116 -0
  53. package/src/brain-core/mutator.ts +281 -0
  54. package/src/brain-core/pack.ts +117 -0
  55. package/src/brain-core/policy.ts +153 -0
  56. package/src/brain-core/replay.ts +1 -0
  57. package/src/brain-core/teacher.ts +105 -0
  58. package/src/brain-core/trace.ts +40 -0
  59. package/src/brain-core/traverse.ts +230 -0
  60. package/src/brain-core/types.ts +405 -0
  61. package/src/brain-core/update.ts +123 -0
  62. package/src/brain-harvest/human.ts +46 -0
  63. package/src/brain-harvest/scanner.ts +98 -0
  64. package/src/brain-harvest/self.ts +147 -0
  65. package/src/brain-runtime/assembler-extension.ts +230 -0
  66. package/src/brain-runtime/evidence-detectors.ts +68 -0
  67. package/src/brain-runtime/graph-io.ts +72 -0
  68. package/src/brain-runtime/harvester-extension.ts +98 -0
  69. package/src/brain-runtime/service.ts +659 -0
  70. package/src/brain-runtime/tools.ts +109 -0
  71. package/src/brain-runtime/worker-state.ts +106 -0
  72. package/src/brain-runtime/worker-supervisor.ts +169 -0
  73. package/src/brain-store/embedding.ts +179 -0
  74. package/src/brain-store/init.ts +347 -0
  75. package/src/brain-store/migrations.ts +188 -0
  76. package/src/brain-store/store.ts +816 -0
  77. package/src/brain-worker/child-runner.ts +321 -0
  78. package/src/brain-worker/jobs.ts +12 -0
  79. package/src/brain-worker/mutation-job.ts +5 -0
  80. package/src/brain-worker/promotion-job.ts +5 -0
  81. package/src/brain-worker/protocol.ts +79 -0
  82. package/src/brain-worker/teacher-job.ts +5 -0
  83. package/src/brain-worker/update-job.ts +5 -0
  84. package/src/brain-worker/worker.ts +422 -0
  85. package/src/compaction.ts +1332 -0
  86. package/src/db/config.ts +265 -0
  87. package/src/db/connection.ts +72 -0
  88. package/src/db/features.ts +42 -0
  89. package/src/db/migration.ts +561 -0
  90. package/src/engine.ts +1995 -0
  91. package/src/expansion-auth.ts +351 -0
  92. package/src/expansion-policy.ts +303 -0
  93. package/src/expansion.ts +383 -0
  94. package/src/integrity.ts +600 -0
  95. package/src/large-files.ts +527 -0
  96. package/src/openclaw-bridge.ts +22 -0
  97. package/src/retrieval.ts +357 -0
  98. package/src/store/conversation-store.ts +748 -0
  99. package/src/store/fts5-sanitize.ts +29 -0
  100. package/src/store/full-text-fallback.ts +74 -0
  101. package/src/store/index.ts +29 -0
  102. package/src/store/summary-store.ts +918 -0
  103. package/src/summarize.ts +847 -0
  104. package/src/tools/common.ts +53 -0
  105. package/src/tools/lcm-conversation-scope.ts +76 -0
  106. package/src/tools/lcm-describe-tool.ts +234 -0
  107. package/src/tools/lcm-expand-query-tool.ts +594 -0
  108. package/src/tools/lcm-expand-tool.delegation.ts +556 -0
  109. package/src/tools/lcm-expand-tool.ts +448 -0
  110. package/src/tools/lcm-expansion-recursion-guard.ts +286 -0
  111. package/src/tools/lcm-grep-tool.ts +200 -0
  112. package/src/transcript-repair.ts +301 -0
  113. package/src/types.ts +149 -0
@@ -0,0 +1,321 @@
1
+ import process from "node:process";
2
+ import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { DatabaseSync } from "node:sqlite";
5
+ import { BrainGraph } from "../brain-core/graph.js";
6
+ import { BrainMutator } from "../brain-core/mutator.js";
7
+ import { PackManager } from "../brain-core/pack.js";
8
+ import { BrainTeacher, type BrainTeacherCompletion } from "../brain-core/teacher.js";
9
+ import type { BrainConfig } from "../brain-core/types.js";
10
+ import type { LcmDependencies } from "../types.js";
11
+ import { runBrainMigrations } from "../brain-store/migrations.js";
12
+ import { BrainStore } from "../brain-store/store.js";
13
+ import { promoteGraphSnapshot, reloadGraphFromStore } from "../brain-runtime/graph-io.js";
14
+ import type { ChildToParentMessage, ParentToChildMessage } from "./protocol.js";
15
+ import { BrainWorker } from "./worker.js";
16
+
17
+ function send(message: ChildToParentMessage): void {
18
+ process.send?.(message);
19
+ }
20
+
21
+ function parseConfig(): BrainConfig {
22
+ const raw = process.env.OPENCLAWBRAIN_CHILD_CONFIG_JSON;
23
+ if (!raw) {
24
+ throw new Error("OPENCLAWBRAIN_CHILD_CONFIG_JSON is required");
25
+ }
26
+ return JSON.parse(raw) as BrainConfig;
27
+ }
28
+
29
+ function parseResolvedTeacherModel(): { provider: string; model: string } | null {
30
+ const raw = process.env.OPENCLAWBRAIN_CHILD_TEACHER_MODEL_JSON?.trim();
31
+ if (!raw) {
32
+ return null;
33
+ }
34
+ return JSON.parse(raw) as { provider: string; model: string };
35
+ }
36
+
37
+ function isPidAlive(pid: number): boolean {
38
+ if (!Number.isFinite(pid) || pid <= 0) {
39
+ return false;
40
+ }
41
+ try {
42
+ process.kill(pid, 0);
43
+ return true;
44
+ } catch {
45
+ return false;
46
+ }
47
+ }
48
+
49
+ class LeaseManager {
50
+ private readonly leasePath: string;
51
+ private readonly startedAt = Date.now();
52
+
53
+ constructor(
54
+ private config: BrainConfig,
55
+ private store: BrainStore,
56
+ ) {
57
+ this.leasePath = join(config.root, "worker-lease.json");
58
+ }
59
+
60
+ acquire(): void {
61
+ const existing = this.readLease();
62
+ if (
63
+ existing
64
+ && existing.pid !== process.pid
65
+ && isPidAlive(existing.pid)
66
+ && (Date.now() - existing.heartbeatAt) < this.config.workerHeartbeatTimeoutMs
67
+ ) {
68
+ const heldPid = existing.pid;
69
+ throw new Error(`worker lease already held by pid ${heldPid}`);
70
+ }
71
+ this.refresh("running");
72
+ this.store.setTrainingState("worker_started_at", this.startedAt);
73
+ this.store.setTrainingState("worker_mode", "child");
74
+ }
75
+
76
+ refresh(status = "running"): void {
77
+ const now = Date.now();
78
+ writeFileSync(this.leasePath, JSON.stringify({
79
+ pid: process.pid,
80
+ startedAt: this.startedAt,
81
+ heartbeatAt: now,
82
+ status,
83
+ }, null, 2), "utf8");
84
+ this.store.setTrainingState("worker_pid", process.pid);
85
+ this.store.setTrainingState("worker_status", status);
86
+ this.store.setTrainingState("worker_last_heartbeat_at", now);
87
+ send({ type: "heartbeat", pid: process.pid, at: now, status });
88
+ }
89
+
90
+ release(status = "stopped"): void {
91
+ this.store.setTrainingState("worker_status", status);
92
+ this.store.setTrainingState("worker_last_heartbeat_at", Date.now());
93
+ const existing = this.readLease();
94
+ if (existing?.pid === process.pid && existsSync(this.leasePath)) {
95
+ rmSync(this.leasePath, { force: true });
96
+ }
97
+ }
98
+
99
+ private readLease(): { pid: number; heartbeatAt: number } | null {
100
+ if (!existsSync(this.leasePath)) {
101
+ return null;
102
+ }
103
+ try {
104
+ const parsed = JSON.parse(readFileSync(this.leasePath, "utf8")) as { pid?: number; heartbeatAt?: number };
105
+ return {
106
+ pid: Number(parsed.pid ?? 0),
107
+ heartbeatAt: Number(parsed.heartbeatAt ?? 0),
108
+ };
109
+ } catch {
110
+ return null;
111
+ }
112
+ }
113
+ }
114
+
115
+ class IpcCompletionBridge {
116
+ private pending = new Map<string, {
117
+ resolve: (value: { content?: Array<{ text?: string }> }) => void;
118
+ reject: (error: Error) => void;
119
+ }>();
120
+
121
+ handleMessage(message: ParentToChildMessage): boolean {
122
+ if (message.type !== "teacher-complete-result") {
123
+ return false;
124
+ }
125
+ const pending = this.pending.get(message.requestId);
126
+ if (!pending) {
127
+ return true;
128
+ }
129
+ this.pending.delete(message.requestId);
130
+ if (!message.ok) {
131
+ pending.reject(new Error(message.error));
132
+ return true;
133
+ }
134
+ pending.resolve({
135
+ content: (message.content ?? []).map((block) => ({ text: block.text })),
136
+ });
137
+ return true;
138
+ }
139
+
140
+ readonly complete: BrainTeacherCompletion = async (params) => {
141
+ const requestId = `btc_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
142
+ return await new Promise((resolve, reject) => {
143
+ this.pending.set(requestId, { resolve, reject });
144
+ send({
145
+ type: "teacher-complete",
146
+ requestId,
147
+ provider: params.provider,
148
+ model: params.model,
149
+ messages: params.messages,
150
+ system: params.system,
151
+ maxTokens: params.maxTokens,
152
+ temperature: params.temperature,
153
+ });
154
+ });
155
+ };
156
+ }
157
+
158
+ async function main(): Promise<void> {
159
+ const config = parseConfig();
160
+ const resolvedTeacherModel = parseResolvedTeacherModel();
161
+
162
+ const db = new DatabaseSync(join(config.root, "state.db"));
163
+ db.exec("PRAGMA journal_mode = WAL");
164
+ db.exec("PRAGMA foreign_keys = ON");
165
+ runBrainMigrations(db);
166
+
167
+ const store = new BrainStore(db, { brainRoot: config.root });
168
+ const graph = new BrainGraph();
169
+ reloadGraphFromStore(store, graph);
170
+
171
+ const log: LcmDependencies["log"] = {
172
+ info: (msg) => process.stdout.write(`${msg}\n`),
173
+ warn: (msg) => process.stderr.write(`${msg}\n`),
174
+ error: (msg) => process.stderr.write(`${msg}\n`),
175
+ debug: (msg) => process.stdout.write(`${msg}\n`),
176
+ };
177
+
178
+ const lease = new LeaseManager(config, store);
179
+ lease.acquire();
180
+
181
+ const bridge = new IpcCompletionBridge();
182
+ const teacher = config.teacherEnabled && resolvedTeacherModel
183
+ ? new BrainTeacher(
184
+ bridge.complete,
185
+ () => resolvedTeacherModel,
186
+ async () => undefined,
187
+ graph,
188
+ log,
189
+ )
190
+ : null;
191
+
192
+ const mutator = new BrainMutator(
193
+ {
194
+ insertNode: (node) => store.insertNode(node),
195
+ insertEdge: (edge) => store.insertEdge(edge),
196
+ deleteNode: (id) => store.deleteNode(id),
197
+ deleteEdge: (source, target, kind) => store.deleteEdge(source, target, kind as never),
198
+ resolveMutation: (id, status) => store.resolveMutation(id, status),
199
+ },
200
+ graph,
201
+ log,
202
+ );
203
+ const packManager = new PackManager(
204
+ {
205
+ insertPack: (pack) => store.insertPack(pack),
206
+ promotePack: (version) => store.promotePack(version),
207
+ rollbackPack: (version) => store.rollbackPack(version),
208
+ },
209
+ graph,
210
+ log,
211
+ );
212
+
213
+ const worker = new BrainWorker(
214
+ store,
215
+ graph,
216
+ teacher,
217
+ mutator,
218
+ packManager,
219
+ config,
220
+ log,
221
+ {
222
+ isEnabled: () => !existsSync(join(config.root, "DISABLED")),
223
+ onPromotionReady: async ({ healthJson }) => {
224
+ const version = promoteGraphSnapshot({
225
+ store,
226
+ graph,
227
+ packManager,
228
+ config,
229
+ reason: "worker",
230
+ metadata: {
231
+ healthJson,
232
+ workerPid: process.pid,
233
+ },
234
+ });
235
+ lease.refresh("running");
236
+ send({ type: "pack-promoted", pid: process.pid, version });
237
+ },
238
+ onTickResult: ({ ok, at, error }) => {
239
+ store.setTrainingState("worker_last_tick_result_at", at);
240
+ store.setTrainingState("worker_last_tick_ok", ok ? "true" : "false");
241
+ store.setTrainingState("worker_last_tick_error", error ?? "");
242
+ send({ type: "tick-result", pid: process.pid, at, ok, error });
243
+ },
244
+ },
245
+ );
246
+
247
+ let shuttingDown = false;
248
+ const heartbeatInterval = setInterval(() => {
249
+ lease.refresh(shuttingDown ? "stopping" : "running");
250
+ }, Math.max(1_000, Math.min(15_000, Math.floor(config.trainerIntervalMs / 2))));
251
+
252
+ const cleanup = (status: string) => {
253
+ if (shuttingDown) {
254
+ return;
255
+ }
256
+ shuttingDown = true;
257
+ clearInterval(heartbeatInterval);
258
+ worker.stop();
259
+ lease.release(status);
260
+ };
261
+
262
+ process.on("message", async (message: ParentToChildMessage) => {
263
+ if (bridge.handleMessage(message)) {
264
+ return;
265
+ }
266
+ if (message.type === "reload-graph") {
267
+ reloadGraphFromStore(store, graph);
268
+ const reloadedAt = Date.now();
269
+ store.setTrainingState("worker_last_reload_ack_at", reloadedAt);
270
+ lease.refresh("running");
271
+ send({
272
+ type: "reload-graph-ack",
273
+ pid: process.pid,
274
+ at: reloadedAt,
275
+ nodeCount: graph.nodeCount(),
276
+ edgeCount: graph.edgeCount(),
277
+ });
278
+ return;
279
+ }
280
+ if (message.type === "shutdown") {
281
+ cleanup("stopped");
282
+ process.exit(0);
283
+ }
284
+ });
285
+
286
+ process.on("disconnect", () => {
287
+ cleanup("parent_disconnected");
288
+ process.exit(0);
289
+ });
290
+ process.on("SIGTERM", () => {
291
+ cleanup("sigterm");
292
+ process.exit(0);
293
+ });
294
+ process.on("SIGINT", () => {
295
+ cleanup("sigint");
296
+ process.exit(0);
297
+ });
298
+ process.on("uncaughtException", (error) => {
299
+ send({ type: "fatal-error", pid: process.pid, error: error.message });
300
+ cleanup("crashed");
301
+ process.exit(1);
302
+ });
303
+ process.on("unhandledRejection", (error) => {
304
+ const message = error instanceof Error ? error.message : String(error);
305
+ send({ type: "fatal-error", pid: process.pid, error: message });
306
+ cleanup("crashed");
307
+ process.exit(1);
308
+ });
309
+
310
+ worker.start();
311
+ const readyAt = Date.now();
312
+ store.setTrainingState("worker_last_ready_at", readyAt);
313
+ send({ type: "ready", pid: process.pid, at: readyAt });
314
+ }
315
+
316
+ void main().catch((error) => {
317
+ const message = (error as Error).message;
318
+ send({ type: "fatal-error", pid: process.pid, error: message });
319
+ process.stderr.write(`${message}\n`);
320
+ process.exit(1);
321
+ });
@@ -0,0 +1,12 @@
1
+ export type BrainWorkerJobName =
2
+ | "process-labels"
3
+ | "teacher"
4
+ | "update"
5
+ | "mutation"
6
+ | "promotion";
7
+
8
+ export interface BrainWorkerJobResult {
9
+ job: BrainWorkerJobName;
10
+ changed: boolean;
11
+ details?: Record<string, unknown>;
12
+ }
@@ -0,0 +1,5 @@
1
+ import type { BrainWorkerJobResult } from "./jobs.js";
2
+
3
+ export function mutationJobResult(changed: boolean, details?: Record<string, unknown>): BrainWorkerJobResult {
4
+ return { job: "mutation", changed, details };
5
+ }
@@ -0,0 +1,5 @@
1
+ import type { BrainWorkerJobResult } from "./jobs.js";
2
+
3
+ export function promotionJobResult(changed: boolean, details?: Record<string, unknown>): BrainWorkerJobResult {
4
+ return { job: "promotion", changed, details };
5
+ }
@@ -0,0 +1,79 @@
1
+ import type { CompletionContentBlock } from "../types.js";
2
+
3
+ export type WorkerReadyMessage = {
4
+ type: "ready";
5
+ pid: number;
6
+ at: number;
7
+ };
8
+
9
+ export type WorkerHeartbeatMessage = {
10
+ type: "heartbeat";
11
+ pid: number;
12
+ at: number;
13
+ status?: string;
14
+ };
15
+
16
+ export type WorkerReloadGraphAckMessage = {
17
+ type: "reload-graph-ack";
18
+ pid: number;
19
+ at: number;
20
+ nodeCount: number;
21
+ edgeCount: number;
22
+ };
23
+
24
+ export type WorkerTickResultMessage = {
25
+ type: "tick-result";
26
+ pid: number;
27
+ at: number;
28
+ ok: boolean;
29
+ error?: string;
30
+ };
31
+
32
+ export type WorkerTeacherCompleteRequestMessage = {
33
+ type: "teacher-complete";
34
+ requestId: string;
35
+ provider?: string;
36
+ model: string;
37
+ messages: Array<{ role: string; content: unknown }>;
38
+ system?: string;
39
+ maxTokens: number;
40
+ temperature?: number;
41
+ };
42
+
43
+ export type WorkerPackPromotedMessage = {
44
+ type: "pack-promoted";
45
+ pid: number;
46
+ version: number | null;
47
+ };
48
+
49
+ export type WorkerFatalErrorMessage = {
50
+ type: "fatal-error";
51
+ pid: number;
52
+ error: string;
53
+ };
54
+
55
+ export type ChildToParentMessage =
56
+ | WorkerReadyMessage
57
+ | WorkerHeartbeatMessage
58
+ | WorkerReloadGraphAckMessage
59
+ | WorkerTickResultMessage
60
+ | WorkerTeacherCompleteRequestMessage
61
+ | WorkerPackPromotedMessage
62
+ | WorkerFatalErrorMessage;
63
+
64
+ export type ParentTeacherCompleteResultMessage =
65
+ | { type: "teacher-complete-result"; requestId: string; ok: true; content: CompletionContentBlock[] }
66
+ | { type: "teacher-complete-result"; requestId: string; ok: false; error: string };
67
+
68
+ export type ParentReloadGraphMessage = {
69
+ type: "reload-graph";
70
+ };
71
+
72
+ export type ParentShutdownMessage = {
73
+ type: "shutdown";
74
+ };
75
+
76
+ export type ParentToChildMessage =
77
+ | ParentTeacherCompleteResultMessage
78
+ | ParentReloadGraphMessage
79
+ | ParentShutdownMessage;
@@ -0,0 +1,5 @@
1
+ import type { BrainWorkerJobResult } from "./jobs.js";
2
+
3
+ export function teacherJobResult(changed: boolean, details?: Record<string, unknown>): BrainWorkerJobResult {
4
+ return { job: "teacher", changed, details };
5
+ }
@@ -0,0 +1,5 @@
1
+ import type { BrainWorkerJobResult } from "./jobs.js";
2
+
3
+ export function updateJobResult(changed: boolean, details?: Record<string, unknown>): BrainWorkerJobResult {
4
+ return { job: "update", changed, details };
5
+ }