@openclawbrain/openclaw 0.3.6 → 0.4.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 (62) hide show
  1. package/README.md +23 -228
  2. package/dist/extension/index.js +3 -2
  3. package/dist/extension/index.js.map +1 -1
  4. package/dist/extension/runtime-guard.js +1 -1
  5. package/dist/extension/runtime-guard.js.map +1 -1
  6. package/dist/src/attachment-truth.d.ts +32 -22
  7. package/dist/src/attachment-truth.js +338 -186
  8. package/dist/src/index.d.ts +75 -1719
  9. package/dist/src/index.js +7 -6882
  10. package/dist/src/runtime-core.js +574 -0
  11. package/extension/index.ts +3 -2
  12. package/extension/runtime-guard.ts +1 -1
  13. package/openclaw.plugin.json +1 -1
  14. package/package.json +17 -17
  15. package/dist/src/attachment-truth.js.map +0 -1
  16. package/dist/src/cli.d.ts +0 -170
  17. package/dist/src/cli.js +0 -5583
  18. package/dist/src/cli.js.map +0 -1
  19. package/dist/src/daemon.d.ts +0 -70
  20. package/dist/src/daemon.js +0 -955
  21. package/dist/src/daemon.js.map +0 -1
  22. package/dist/src/import-export.d.ts +0 -36
  23. package/dist/src/import-export.js +0 -171
  24. package/dist/src/import-export.js.map +0 -1
  25. package/dist/src/index.js.map +0 -1
  26. package/dist/src/learning-spine.d.ts +0 -50
  27. package/dist/src/learning-spine.js.map +0 -1
  28. package/dist/src/local-session-passive-learning.d.ts +0 -61
  29. package/dist/src/local-session-passive-learning.js +0 -454
  30. package/dist/src/local-session-passive-learning.js.map +0 -1
  31. package/dist/src/ollama-client.d.ts +0 -46
  32. package/dist/src/ollama-client.js +0 -231
  33. package/dist/src/ollama-client.js.map +0 -1
  34. package/dist/src/openclaw-home-layout.d.ts +0 -17
  35. package/dist/src/openclaw-home-layout.js +0 -182
  36. package/dist/src/openclaw-home-layout.js.map +0 -1
  37. package/dist/src/openclaw-hook-truth.d.ts +0 -33
  38. package/dist/src/openclaw-hook-truth.js +0 -260
  39. package/dist/src/openclaw-hook-truth.js.map +0 -1
  40. package/dist/src/openclaw-plugin-install.d.ts +0 -40
  41. package/dist/src/openclaw-plugin-install.js +0 -282
  42. package/dist/src/provider-config.d.ts +0 -64
  43. package/dist/src/provider-config.js +0 -306
  44. package/dist/src/provider-config.js.map +0 -1
  45. package/dist/src/resolve-activation-root.d.ts +0 -27
  46. package/dist/src/resolve-activation-root.js +0 -190
  47. package/dist/src/resolve-activation-root.js.map +0 -1
  48. package/dist/src/semantic-metadata.d.ts +0 -5
  49. package/dist/src/semantic-metadata.js +0 -70
  50. package/dist/src/semantic-metadata.js.map +0 -1
  51. package/dist/src/session-store.d.ts +0 -168
  52. package/dist/src/session-store.js +0 -250
  53. package/dist/src/session-store.js.map +0 -1
  54. package/dist/src/session-tail.d.ts +0 -73
  55. package/dist/src/session-tail.js +0 -602
  56. package/dist/src/session-tail.js.map +0 -1
  57. package/dist/src/shadow-extension-proof.d.ts +0 -43
  58. package/dist/src/shadow-extension-proof.js +0 -218
  59. package/dist/src/shadow-extension-proof.js.map +0 -1
  60. package/dist/src/teacher-labeler.d.ts +0 -50
  61. package/dist/src/teacher-labeler.js +0 -424
  62. package/dist/src/teacher-labeler.js.map +0 -1
@@ -1,955 +0,0 @@
1
- /**
2
- * macOS launchd daemon management for OpenClawBrain.
3
- *
4
- * Manages macOS launchd user agents that run `openclawbrain watch` in the background.
5
- * Service identity is derived per activation root so one profile/service boundary
6
- * does not collide with another.
7
- *
8
- * Commands:
9
- * daemon start — generate and load a launchd plist
10
- * daemon stop — unload the plist
11
- * daemon status — show running/stopped + PID + last log lines
12
- * daemon logs — tail the daemon log file
13
- */
14
- import { execSync } from "node:child_process";
15
- import { createHash } from "node:crypto";
16
- import { existsSync, mkdirSync, readFileSync, realpathSync, unlinkSync, writeFileSync } from "node:fs";
17
- import path from "node:path";
18
- import { fileURLToPath } from "node:url";
19
- import { loadTeacherSurface, resolveWatchSessionTailCursorPath, resolveWatchStateRoot, resolveWatchTeacherSnapshotPath } from "./index.js";
20
- const LABEL_PREFIX = "com.openclawbrain.daemon";
21
- const LOG_ROOT_DIRNAME = "daemon";
22
- const DEFAULT_SCAN_ROOT_DIRNAME = "event-exports";
23
- const BASELINE_STATE_BASENAME = "baseline-state.json";
24
- const SCANNER_CHECKPOINT_BASENAME = ".openclawbrain-scanner-checkpoint.json";
25
- const DEFAULT_DAEMON_COMMAND_RUNNER = (command) => execSync(command, {
26
- encoding: "utf8",
27
- stdio: "pipe",
28
- });
29
- let daemonCommandRunner = DEFAULT_DAEMON_COMMAND_RUNNER;
30
- function getHomeDir() {
31
- return process.env.HOME ?? process.env.USERPROFILE ?? "~";
32
- }
33
- function canonicalizeActivationRoot(activationRoot) {
34
- const resolvedActivationRoot = path.resolve(activationRoot);
35
- return existsSync(resolvedActivationRoot) ? safeRealpath(resolvedActivationRoot) : resolvedActivationRoot;
36
- }
37
- function sanitizeActivationRootSlug(value) {
38
- const sanitized = value
39
- .toLowerCase()
40
- .replace(/[^a-z0-9]+/g, "-")
41
- .replace(/^-+|-+$/g, "");
42
- return sanitized.length > 0 ? sanitized.slice(0, 32) : "activation-root";
43
- }
44
- export function buildDaemonServiceIdentity(activationRoot) {
45
- const requestedActivationRoot = path.resolve(activationRoot);
46
- const canonicalActivationRoot = canonicalizeActivationRoot(requestedActivationRoot);
47
- const activationRootHash = createHash("sha256").update(canonicalActivationRoot).digest("hex").slice(0, 12);
48
- const activationRootSlug = sanitizeActivationRootSlug(path.basename(canonicalActivationRoot));
49
- const label = `${LABEL_PREFIX}.${activationRootSlug}.${activationRootHash}`;
50
- const plistFilename = `${label}.plist`;
51
- return {
52
- requestedActivationRoot,
53
- canonicalActivationRoot,
54
- activationRootHash,
55
- activationRootSlug,
56
- label,
57
- plistFilename,
58
- plistPath: path.join(getHomeDir(), "Library", "LaunchAgents", plistFilename),
59
- logPath: path.join(getHomeDir(), ".openclawbrain", LOG_ROOT_DIRNAME, `${activationRootSlug}-${activationRootHash}.log`)
60
- };
61
- }
62
- export function setDaemonCommandRunnerForTesting(runner) {
63
- daemonCommandRunner = runner ?? DEFAULT_DAEMON_COMMAND_RUNNER;
64
- }
65
- function getOpenclawbrainBinPath() {
66
- try {
67
- const resolved = daemonCommandRunner("which openclawbrain").trim();
68
- return resolved.length > 0 ? resolved : null;
69
- }
70
- catch {
71
- return null;
72
- }
73
- }
74
- function safeRealpath(filePath) {
75
- try {
76
- return realpathSync(filePath);
77
- }
78
- catch {
79
- return filePath;
80
- }
81
- }
82
- function resolvePackageRoot(startDir) {
83
- let currentDir = path.resolve(startDir);
84
- while (true) {
85
- const packageJsonPath = path.join(currentDir, "package.json");
86
- if (existsSync(packageJsonPath)) {
87
- try {
88
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
89
- if (packageJson.name === "@openclawbrain/openclaw") {
90
- return currentDir;
91
- }
92
- }
93
- catch {
94
- // Ignore malformed package.json while searching upward for the real package root.
95
- }
96
- }
97
- const parentDir = path.dirname(currentDir);
98
- if (parentDir === currentDir) {
99
- return null;
100
- }
101
- currentDir = parentDir;
102
- }
103
- }
104
- function resolveCliScriptCandidate(candidatePath) {
105
- if (typeof candidatePath !== "string" || candidatePath.trim().length === 0) {
106
- return null;
107
- }
108
- const absoluteCandidate = path.resolve(candidatePath);
109
- if (!existsSync(absoluteCandidate)) {
110
- return null;
111
- }
112
- const resolvedCandidate = safeRealpath(absoluteCandidate);
113
- const basename = path.basename(resolvedCandidate);
114
- if (basename !== "cli.js" && basename !== "cli.cjs" && basename !== "cli.mjs") {
115
- return null;
116
- }
117
- return resolvedCandidate;
118
- }
119
- function getOpenclawbrainCliScriptPath() {
120
- const moduleFilePath = fileURLToPath(import.meta.url);
121
- const moduleDir = path.dirname(moduleFilePath);
122
- const packageRoot = resolvePackageRoot(moduleDir);
123
- const candidates = [
124
- process.argv[1],
125
- path.join(moduleDir, "cli.js"),
126
- packageRoot === null ? null : path.join(packageRoot, "dist", "src", "cli.js")
127
- ];
128
- for (const candidate of candidates) {
129
- const resolved = resolveCliScriptCandidate(candidate);
130
- if (resolved !== null) {
131
- return resolved;
132
- }
133
- }
134
- return null;
135
- }
136
- function resolveDaemonProgramArguments() {
137
- const cliScriptPath = getOpenclawbrainCliScriptPath();
138
- if (cliScriptPath !== null) {
139
- return [process.execPath, cliScriptPath];
140
- }
141
- const binPath = getOpenclawbrainBinPath();
142
- if (binPath !== null) {
143
- return [binPath];
144
- }
145
- return null;
146
- }
147
- function buildPlistXml(serviceIdentity, programArguments) {
148
- const logPath = serviceIdentity.logPath;
149
- const homeDir = getHomeDir();
150
- const daemonProgramArguments = [...programArguments, "watch", "--activation-root", serviceIdentity.requestedActivationRoot]
151
- .map((argument) => ` <string>${argument}</string>`)
152
- .join("\n");
153
- return `<?xml version="1.0" encoding="UTF-8"?>
154
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
155
- <plist version="1.0">
156
- <dict>
157
- <key>Label</key>
158
- <string>${serviceIdentity.label}</string>
159
- <key>ProgramArguments</key>
160
- <array>
161
- ${daemonProgramArguments}
162
- </array>
163
- <key>WorkingDirectory</key>
164
- <string>${serviceIdentity.requestedActivationRoot}</string>
165
- <key>StandardOutPath</key>
166
- <string>${logPath}</string>
167
- <key>StandardErrorPath</key>
168
- <string>${logPath}</string>
169
- <key>KeepAlive</key>
170
- <true/>
171
- <key>RunAtLoad</key>
172
- <true/>
173
- <key>EnvironmentVariables</key>
174
- <dict>
175
- <key>HOME</key>
176
- <string>${homeDir}</string>
177
- <key>PATH</key>
178
- <string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
179
- </dict>
180
- </dict>
181
- </plist>
182
- `;
183
- }
184
- function ensureLogDir(logPath) {
185
- const logDir = path.dirname(logPath);
186
- if (!existsSync(logDir)) {
187
- mkdirSync(logDir, { recursive: true });
188
- }
189
- }
190
- function hasLaunchctl() {
191
- try {
192
- return daemonCommandRunner("command -v launchctl").trim().length > 0;
193
- }
194
- catch {
195
- return false;
196
- }
197
- }
198
- function launchctlLoad(plistPath) {
199
- try {
200
- daemonCommandRunner(`launchctl load -w ${JSON.stringify(plistPath)}`);
201
- return { ok: true, message: "Daemon started." };
202
- }
203
- catch (err) {
204
- const message = err instanceof Error ? err.stderr?.toString() ?? err.message : String(err);
205
- return { ok: false, message: `Failed to load plist: ${message}` };
206
- }
207
- }
208
- function launchctlUnload(plistPath) {
209
- try {
210
- daemonCommandRunner(`launchctl unload ${JSON.stringify(plistPath)}`);
211
- return { ok: true, message: "Daemon stopped." };
212
- }
213
- catch (err) {
214
- const message = err instanceof Error ? err.stderr?.toString() ?? err.message : String(err);
215
- return { ok: false, message: `Failed to unload plist: ${message}` };
216
- }
217
- }
218
- function getLaunchctlInfo(label) {
219
- try {
220
- const output = daemonCommandRunner("launchctl list");
221
- for (const line of output.split("\n")) {
222
- if (line.includes(label)) {
223
- const parts = line.trim().split(/\s+/);
224
- const pidStr = parts[0];
225
- const pid = pidStr && pidStr !== "-" ? parseInt(pidStr, 10) : null;
226
- return { running: pid !== null && !isNaN(pid), pid: pid !== null && !isNaN(pid) ? pid : null };
227
- }
228
- }
229
- }
230
- catch {
231
- // launchctl list failed — treat as not running
232
- }
233
- return { running: false, pid: null };
234
- }
235
- function inspectManagedLearnerServiceInternal(activationRoot) {
236
- const serviceIdentity = buildDaemonServiceIdentity(activationRoot);
237
- const configuredActivationRoot = readDaemonActivationRoot(serviceIdentity.plistPath);
238
- const info = getLaunchctlInfo(serviceIdentity.label);
239
- return {
240
- requestedActivationRoot: serviceIdentity.requestedActivationRoot,
241
- canonicalActivationRoot: serviceIdentity.canonicalActivationRoot,
242
- serviceLabel: serviceIdentity.label,
243
- plistPath: serviceIdentity.plistPath,
244
- logPath: serviceIdentity.logPath,
245
- installed: existsSync(serviceIdentity.plistPath),
246
- running: info.running,
247
- pid: info.pid,
248
- configuredActivationRoot,
249
- matchesRequestedActivationRoot: configuredActivationRoot === null
250
- ? null
251
- : canonicalizeActivationRoot(configuredActivationRoot) === serviceIdentity.canonicalActivationRoot,
252
- launchctlAvailable: hasLaunchctl()
253
- };
254
- }
255
- function startManagedLearnerService(activationRoot) {
256
- const inspectionBeforeStart = inspectManagedLearnerServiceInternal(activationRoot);
257
- const serviceIdentity = buildDaemonServiceIdentity(activationRoot);
258
- if (!inspectionBeforeStart.launchctlAvailable) {
259
- return {
260
- ok: false,
261
- message: "launchctl is unavailable on this host",
262
- inspection: inspectionBeforeStart
263
- };
264
- }
265
- const programArguments = resolveDaemonProgramArguments();
266
- if (programArguments === null) {
267
- return {
268
- ok: false,
269
- message: "Failed to resolve an OpenClawBrain CLI launch command.",
270
- inspection: inspectionBeforeStart
271
- };
272
- }
273
- const launchAgentsDir = path.dirname(inspectionBeforeStart.plistPath);
274
- if (!existsSync(launchAgentsDir)) {
275
- mkdirSync(launchAgentsDir, { recursive: true });
276
- }
277
- ensureLogDir(serviceIdentity.logPath);
278
- const plistContent = buildPlistXml(serviceIdentity, programArguments);
279
- writeFileSync(inspectionBeforeStart.plistPath, plistContent, "utf8");
280
- const result = launchctlLoad(inspectionBeforeStart.plistPath);
281
- if (!result.ok && !inspectionBeforeStart.installed) {
282
- try {
283
- unlinkSync(inspectionBeforeStart.plistPath);
284
- }
285
- catch {
286
- // Best effort cleanup for failed first-time auto-start attempts.
287
- }
288
- }
289
- return {
290
- ok: result.ok,
291
- message: result.message,
292
- inspection: inspectManagedLearnerServiceInternal(activationRoot)
293
- };
294
- }
295
- function stopManagedLearnerService(activationRoot) {
296
- const inspectionBeforeStop = inspectManagedLearnerServiceInternal(activationRoot);
297
- if (!inspectionBeforeStop.installed) {
298
- return {
299
- ok: true,
300
- message: "No daemon plist found.",
301
- inspection: inspectionBeforeStop
302
- };
303
- }
304
- if (!inspectionBeforeStop.launchctlAvailable) {
305
- return {
306
- ok: false,
307
- message: "launchctl is unavailable on this host",
308
- inspection: inspectionBeforeStop
309
- };
310
- }
311
- const result = launchctlUnload(inspectionBeforeStop.plistPath);
312
- if (result.ok) {
313
- try {
314
- unlinkSync(inspectionBeforeStop.plistPath);
315
- }
316
- catch {
317
- // best effort
318
- }
319
- }
320
- return {
321
- ok: result.ok,
322
- message: result.message,
323
- inspection: inspectManagedLearnerServiceInternal(activationRoot)
324
- };
325
- }
326
- export function inspectManagedLearnerService(activationRoot) {
327
- return inspectManagedLearnerServiceInternal(activationRoot);
328
- }
329
- export function ensureManagedLearnerServiceForActivationRoot(activationRoot) {
330
- const inspection = inspectManagedLearnerServiceInternal(activationRoot);
331
- if (inspection.matchesRequestedActivationRoot === true && inspection.running) {
332
- return {
333
- state: "ensured",
334
- reason: "already_running_exact_root",
335
- detail: `Learner auto-start already ensured for ${inspection.requestedActivationRoot}; the matching background learner service is running.`,
336
- inspection
337
- };
338
- }
339
- const startResult = startManagedLearnerService(activationRoot);
340
- if (startResult.ok) {
341
- return {
342
- state: "started",
343
- reason: "started_exact_root",
344
- detail: `Started the background learner service for ${startResult.inspection.requestedActivationRoot}; passive learning can begin for this attached profile now.`,
345
- inspection: startResult.inspection
346
- };
347
- }
348
- const reason = !inspection.launchctlAvailable
349
- ? "launchctl_unavailable"
350
- : startResult.message === "Failed to resolve an OpenClawBrain CLI launch command."
351
- ? "launch_command_unavailable"
352
- : "launch_failed";
353
- return {
354
- state: "deferred",
355
- reason,
356
- detail: `Learner auto-start deferred for ${inspection.requestedActivationRoot}: ${startResult.message}`,
357
- inspection: startResult.inspection
358
- };
359
- }
360
- export function removeManagedLearnerServiceForActivationRoot(activationRoot) {
361
- const inspection = inspectManagedLearnerServiceInternal(activationRoot);
362
- if (!inspection.installed) {
363
- return {
364
- state: "already_absent",
365
- reason: "not_installed",
366
- detail: `No background learner service is installed for ${inspection.requestedActivationRoot}.`,
367
- inspection
368
- };
369
- }
370
- if (inspection.matchesRequestedActivationRoot === false) {
371
- return {
372
- state: "preserved",
373
- reason: "configured_root_mismatch",
374
- detail: `Preserved the background learner service because ${inspection.plistPath} is configured for ${inspection.configuredActivationRoot}, ` +
375
- `not the requested exact root ${inspection.requestedActivationRoot}.`,
376
- inspection
377
- };
378
- }
379
- const stopResult = stopManagedLearnerService(activationRoot);
380
- if (stopResult.ok) {
381
- return {
382
- state: "removed",
383
- reason: "removed_exact_root",
384
- detail: `Removed the background learner service for ${stopResult.inspection.requestedActivationRoot}.`,
385
- inspection: stopResult.inspection
386
- };
387
- }
388
- return {
389
- state: "preserved",
390
- reason: inspection.launchctlAvailable ? "stop_failed" : "launchctl_unavailable",
391
- detail: `Preserved the background learner service for ${inspection.requestedActivationRoot}: ${stopResult.message}`,
392
- inspection: stopResult.inspection
393
- };
394
- }
395
- function readLastLines(filePath, count) {
396
- if (!existsSync(filePath))
397
- return [];
398
- try {
399
- const content = readFileSync(filePath, "utf8");
400
- const lines = content.split("\n");
401
- // Remove trailing empty line from split
402
- if (lines.length > 0 && lines[lines.length - 1] === "") {
403
- lines.pop();
404
- }
405
- return lines.slice(-count);
406
- }
407
- catch {
408
- return [];
409
- }
410
- }
411
- function readOptionalJsonFile(filePath) {
412
- if (!existsSync(filePath)) {
413
- return null;
414
- }
415
- try {
416
- return JSON.parse(readFileSync(filePath, "utf8"));
417
- }
418
- catch {
419
- return null;
420
- }
421
- }
422
- function readDaemonActivationRoot(plistPath) {
423
- if (!existsSync(plistPath)) {
424
- return null;
425
- }
426
- try {
427
- const plist = readFileSync(plistPath, "utf8");
428
- const match = plist.match(/<string>--activation-root<\/string>\s*<string>([^<]+)<\/string>/);
429
- return match?.[1] ?? null;
430
- }
431
- catch {
432
- return null;
433
- }
434
- }
435
- function getWatchStatePaths(activationRoot) {
436
- if (activationRoot === null) {
437
- return {
438
- watchStateRoot: null,
439
- scanRoot: null,
440
- scannerCheckpointPath: null,
441
- sessionTailCursorPath: null,
442
- teacherSnapshotPath: null,
443
- baselineStatePath: null
444
- };
445
- }
446
- const scanRoot = path.join(activationRoot, DEFAULT_SCAN_ROOT_DIRNAME);
447
- return {
448
- watchStateRoot: resolveWatchStateRoot(activationRoot),
449
- scanRoot,
450
- scannerCheckpointPath: path.join(scanRoot, SCANNER_CHECKPOINT_BASENAME),
451
- sessionTailCursorPath: resolveWatchSessionTailCursorPath(activationRoot),
452
- teacherSnapshotPath: resolveWatchTeacherSnapshotPath(activationRoot),
453
- baselineStatePath: path.join(activationRoot, BASELINE_STATE_BASENAME)
454
- };
455
- }
456
- function readWatchStateSummary(activationRoot) {
457
- const paths = getWatchStatePaths(activationRoot);
458
- const cursorFile = paths.sessionTailCursorPath === null
459
- ? null
460
- : readOptionalJsonFile(paths.sessionTailCursorPath);
461
- const teacherSurface = paths.teacherSnapshotPath === null
462
- ? null
463
- : loadTeacherSurface(paths.teacherSnapshotPath);
464
- const teacherSnapshotFile = teacherSurface?.watchSnapshot ?? null;
465
- const teacherSnapshot = teacherSurface?.snapshot ?? null;
466
- const resolvedScanRoot = typeof teacherSnapshotFile?.scanRoot === "string" && teacherSnapshotFile.scanRoot.trim().length > 0
467
- ? teacherSnapshotFile.scanRoot
468
- : paths.scanRoot;
469
- const scannerCheckpointPath = typeof teacherSnapshotFile?.scannerCheckpointPath === "string" && teacherSnapshotFile.scannerCheckpointPath.trim().length > 0
470
- ? teacherSnapshotFile.scannerCheckpointPath
471
- : resolvedScanRoot === null ? null : path.join(resolvedScanRoot, SCANNER_CHECKPOINT_BASENAME);
472
- const scannerCheckpointFile = teacherSnapshotFile?.scannerCheckpoint ??
473
- (scannerCheckpointPath === null
474
- ? null
475
- : readOptionalJsonFile(scannerCheckpointPath));
476
- const baselineFile = paths.baselineStatePath === null ? null : readOptionalJsonFile(paths.baselineStatePath);
477
- const lastMaterializationPackId = teacherSnapshot?.learner.lastMaterialization?.candidate.summary.packId ?? null;
478
- const teacherSummary = teacherSnapshotFile?.teacher;
479
- const learningSummary = teacherSnapshotFile?.learning;
480
- const teacherDiagnostics = teacherSnapshot?.diagnostics;
481
- const teacherState = teacherSnapshot?.state;
482
- const teacherLearnerState = teacherSnapshot?.learner.state;
483
- return {
484
- watchStateRoot: paths.watchStateRoot,
485
- scanRoot: resolvedScanRoot,
486
- scannerCheckpoint: {
487
- path: scannerCheckpointPath,
488
- exists: scannerCheckpointPath !== null && existsSync(scannerCheckpointPath),
489
- updatedAt: typeof scannerCheckpointFile?.updatedAt === "string" ? scannerCheckpointFile.updatedAt : null,
490
- processedExportDigestCount: Array.isArray(scannerCheckpointFile?.processedExportDigests)
491
- ? scannerCheckpointFile.processedExportDigests.length
492
- : null,
493
- scanPasses: typeof scannerCheckpointFile?.stats?.scanPasses === "number" ? scannerCheckpointFile.stats.scanPasses : null,
494
- liveBundlesScanned: typeof scannerCheckpointFile?.stats?.liveBundlesScanned === "number" ? scannerCheckpointFile.stats.liveBundlesScanned : null,
495
- backfillBundlesScanned: typeof scannerCheckpointFile?.stats?.backfillBundlesScanned === "number" ? scannerCheckpointFile.stats.backfillBundlesScanned : null,
496
- },
497
- sessionTailCursor: {
498
- path: paths.sessionTailCursorPath,
499
- exists: paths.sessionTailCursorPath !== null && existsSync(paths.sessionTailCursorPath),
500
- updatedAt: typeof teacherSnapshotFile?.sessionTailCursorUpdatedAt === "string"
501
- ? teacherSnapshotFile.sessionTailCursorUpdatedAt
502
- : typeof cursorFile?.updatedAt === "string"
503
- ? cursorFile.updatedAt
504
- : null,
505
- sessionCount: typeof teacherSnapshotFile?.sessionTailSessionsTracked === "number"
506
- ? teacherSnapshotFile.sessionTailSessionsTracked
507
- : Array.isArray(cursorFile?.cursor)
508
- ? cursorFile.cursor.length
509
- : null,
510
- bridgedEventCount: typeof teacherSnapshotFile?.sessionTailBridgedEventCount === "number"
511
- ? teacherSnapshotFile.sessionTailBridgedEventCount
512
- : null,
513
- },
514
- teacherSnapshot: {
515
- path: paths.teacherSnapshotPath,
516
- exists: paths.teacherSnapshotPath !== null && existsSync(paths.teacherSnapshotPath),
517
- updatedAt: typeof teacherSnapshotFile?.updatedAt === "string" ? teacherSnapshotFile.updatedAt : null,
518
- sourceKind: teacherSurface?.sourceKind ?? "missing",
519
- lastRunAt: typeof teacherSnapshotFile?.lastRunAt === "string" ? teacherSnapshotFile.lastRunAt : null,
520
- scanRoot: resolvedScanRoot,
521
- artifactCount: typeof teacherSnapshotFile?.teacher?.artifactCount === "number"
522
- ? teacherSnapshotFile.teacher.artifactCount
523
- : teacherSnapshot?.teacher.artifactCount ?? null,
524
- latestFreshness: typeof teacherSnapshotFile?.teacher?.latestFreshness === "string"
525
- ? teacherSnapshotFile.teacher.latestFreshness
526
- : teacherSnapshot?.teacher.latestFreshness ?? null,
527
- replayedBundleCount: typeof teacherSnapshotFile?.replayedBundleCount === "number" ? teacherSnapshotFile.replayedBundleCount : null,
528
- replayedEventCount: typeof teacherSnapshotFile?.replayedEventCount === "number" ? teacherSnapshotFile.replayedEventCount : null,
529
- exportedBundleCount: typeof teacherSnapshotFile?.exportedBundleCount === "number" ? teacherSnapshotFile.exportedBundleCount : null,
530
- exportedEventCount: typeof teacherSnapshotFile?.exportedEventCount === "number" ? teacherSnapshotFile.exportedEventCount : null,
531
- startupWarningCount: Array.isArray(teacherSnapshotFile?.startupWarnings) ? teacherSnapshotFile.startupWarnings.length : null,
532
- lastTeacherError: typeof teacherSnapshotFile?.lastTeacherError === "string" ? teacherSnapshotFile.lastTeacherError : null,
533
- localSessionTailNoopReason: typeof teacherSnapshotFile?.localSessionTailNoopReason === "string" ? teacherSnapshotFile.localSessionTailNoopReason : null,
534
- learningCadence: typeof teacherSnapshotFile?.labeling?.learningCadence === "string" ? teacherSnapshotFile.labeling.learningCadence : null,
535
- scanPolicy: typeof teacherSnapshotFile?.labeling?.scanPolicy === "string" ? teacherSnapshotFile.labeling.scanPolicy : null,
536
- liveSlicesPerCycle: typeof teacherSnapshotFile?.labeling?.liveSlicesPerCycle === "number"
537
- ? teacherSnapshotFile.labeling.liveSlicesPerCycle
538
- : null,
539
- backfillSlicesPerCycle: typeof teacherSnapshotFile?.labeling?.backfillSlicesPerCycle === "number"
540
- ? teacherSnapshotFile.labeling.backfillSlicesPerCycle
541
- : null,
542
- failureMode: typeof teacherSnapshotFile?.failure?.mode === "string" ? teacherSnapshotFile.failure.mode : null,
543
- failureDetail: typeof teacherSnapshotFile?.failure?.detail === "string" ? teacherSnapshotFile.failure.detail : null,
544
- lastHandledMaterializationPackId: typeof teacherSnapshotFile?.lastHandledMaterializationPackId === "string"
545
- ? teacherSnapshotFile.lastHandledMaterializationPackId
546
- : null,
547
- lastMaterializationPackId: typeof lastMaterializationPackId === "string" ? lastMaterializationPackId : null,
548
- cadence: {
549
- acceptedExportCount: typeof teacherSummary?.acceptedExportCount === "number"
550
- ? teacherSummary.acceptedExportCount
551
- : typeof teacherDiagnostics?.acceptedExportCount === "number"
552
- ? teacherDiagnostics.acceptedExportCount
553
- : null,
554
- processedExportCount: typeof teacherSummary?.processedExportCount === "number"
555
- ? teacherSummary.processedExportCount
556
- : typeof teacherDiagnostics?.processedExportCount === "number"
557
- ? teacherDiagnostics.processedExportCount
558
- : null,
559
- duplicateExportCount: typeof teacherSummary?.duplicateExportCount === "number"
560
- ? teacherSummary.duplicateExportCount
561
- : typeof teacherDiagnostics?.duplicateExportCount === "number"
562
- ? teacherDiagnostics.duplicateExportCount
563
- : null,
564
- droppedExportCount: typeof teacherSummary?.droppedExportCount === "number"
565
- ? teacherSummary.droppedExportCount
566
- : typeof teacherDiagnostics?.droppedExportCount === "number"
567
- ? teacherDiagnostics.droppedExportCount
568
- : null,
569
- emittedArtifactCount: typeof teacherSummary?.emittedArtifactCount === "number"
570
- ? teacherSummary.emittedArtifactCount
571
- : typeof teacherDiagnostics?.emittedArtifactCount === "number"
572
- ? teacherDiagnostics.emittedArtifactCount
573
- : null,
574
- dedupedArtifactCount: typeof teacherSummary?.dedupedArtifactCount === "number"
575
- ? teacherSummary.dedupedArtifactCount
576
- : typeof teacherDiagnostics?.dedupedArtifactCount === "number"
577
- ? teacherDiagnostics.dedupedArtifactCount
578
- : null,
579
- seenExportDigestCount: Array.isArray(teacherState?.seenExportDigests)
580
- ? teacherState.seenExportDigests.length
581
- : null,
582
- materializationCount: typeof learningSummary?.materializationCount === "number"
583
- ? learningSummary.materializationCount
584
- : typeof teacherLearnerState?.materializationCount === "number"
585
- ? teacherLearnerState.materializationCount
586
- : null,
587
- lastProcessedAt: typeof teacherSummary?.lastProcessedAt === "string"
588
- ? teacherSummary.lastProcessedAt
589
- : typeof teacherDiagnostics?.lastProcessedAt === "string"
590
- ? teacherDiagnostics.lastProcessedAt
591
- : null,
592
- lastMaterializedAt: typeof learningSummary?.lastMaterializedAt === "string"
593
- ? learningSummary.lastMaterializedAt
594
- : typeof teacherLearnerState?.lastMaterializedAt === "string"
595
- ? teacherLearnerState.lastMaterializedAt
596
- : null,
597
- }
598
- },
599
- baselineState: {
600
- path: paths.baselineStatePath,
601
- exists: paths.baselineStatePath !== null && existsSync(paths.baselineStatePath),
602
- lastUpdatedAt: typeof baselineFile?.lastUpdatedAt === "string" ? baselineFile.lastUpdatedAt : null,
603
- count: typeof baselineFile?.count === "number" ? baselineFile.count : null,
604
- movingAverage: typeof baselineFile?.movingAverage === "number" ? baselineFile.movingAverage : null,
605
- alpha: typeof baselineFile?.alpha === "number" ? baselineFile.alpha : null,
606
- }
607
- };
608
- }
609
- // ─── Subcommand implementations ─────────────────────────────────────────────
610
- export function daemonStart(activationRoot, json) {
611
- const serviceIdentity = buildDaemonServiceIdentity(activationRoot);
612
- const plistPath = serviceIdentity.plistPath;
613
- const logPath = serviceIdentity.logPath;
614
- const programArguments = resolveDaemonProgramArguments();
615
- if (programArguments === null) {
616
- const message = "Failed to resolve an OpenClawBrain CLI launch command. Install/build the local package or make `openclawbrain` available on PATH.";
617
- if (json) {
618
- console.log(JSON.stringify({
619
- command: "daemon start",
620
- ok: false,
621
- plistPath,
622
- logPath,
623
- activationRoot: serviceIdentity.requestedActivationRoot,
624
- serviceLabel: serviceIdentity.label,
625
- message,
626
- }, null, 2));
627
- }
628
- else {
629
- console.error(`✗ ${message}`);
630
- }
631
- return 1;
632
- }
633
- // Ensure LaunchAgents dir exists
634
- const launchAgentsDir = path.dirname(plistPath);
635
- if (!existsSync(launchAgentsDir)) {
636
- mkdirSync(launchAgentsDir, { recursive: true });
637
- }
638
- ensureLogDir(logPath);
639
- // Write the plist
640
- const plistContent = buildPlistXml(serviceIdentity, programArguments);
641
- writeFileSync(plistPath, plistContent, "utf8");
642
- // Load it
643
- const result = launchctlLoad(plistPath);
644
- if (json) {
645
- console.log(JSON.stringify({
646
- command: "daemon start",
647
- ok: result.ok,
648
- plistPath,
649
- logPath,
650
- activationRoot: serviceIdentity.requestedActivationRoot,
651
- serviceLabel: serviceIdentity.label,
652
- message: result.message,
653
- }, null, 2));
654
- }
655
- else {
656
- if (result.ok) {
657
- console.log(`✓ Daemon started`);
658
- console.log(` Label: ${serviceIdentity.label}`);
659
- console.log(` Plist: ${plistPath}`);
660
- console.log(` Log: ${logPath}`);
661
- console.log(` Root: ${serviceIdentity.requestedActivationRoot}`);
662
- }
663
- else {
664
- console.error(`✗ ${result.message}`);
665
- }
666
- }
667
- return result.ok ? 0 : 1;
668
- }
669
- export function daemonStop(activationRoot, json) {
670
- const serviceIdentity = buildDaemonServiceIdentity(activationRoot);
671
- const plistPath = serviceIdentity.plistPath;
672
- if (!existsSync(plistPath)) {
673
- const msg = "No daemon plist found. Daemon is not installed.";
674
- if (json) {
675
- console.log(JSON.stringify({
676
- command: "daemon stop",
677
- ok: false,
678
- activationRoot: serviceIdentity.requestedActivationRoot,
679
- serviceLabel: serviceIdentity.label,
680
- plistPath,
681
- message: msg
682
- }, null, 2));
683
- }
684
- else {
685
- console.log(msg);
686
- }
687
- return 1;
688
- }
689
- const result = launchctlUnload(plistPath);
690
- // Remove the plist file after unloading
691
- if (result.ok) {
692
- try {
693
- unlinkSync(plistPath);
694
- }
695
- catch {
696
- // best effort
697
- }
698
- }
699
- if (json) {
700
- console.log(JSON.stringify({
701
- command: "daemon stop",
702
- activationRoot: serviceIdentity.requestedActivationRoot,
703
- serviceLabel: serviceIdentity.label,
704
- ok: result.ok,
705
- plistPath,
706
- message: result.message,
707
- }, null, 2));
708
- }
709
- else {
710
- if (result.ok) {
711
- console.log(`✓ Daemon stopped and plist removed.`);
712
- console.log(` Label: ${serviceIdentity.label}`);
713
- }
714
- else {
715
- console.error(`✗ ${result.message}`);
716
- }
717
- }
718
- return result.ok ? 0 : 1;
719
- }
720
- export function daemonStatus(activationRoot, json) {
721
- const serviceIdentity = buildDaemonServiceIdentity(activationRoot);
722
- const plistPath = serviceIdentity.plistPath;
723
- const logPath = serviceIdentity.logPath;
724
- const plistInstalled = existsSync(plistPath);
725
- const info = getLaunchctlInfo(serviceIdentity.label);
726
- const lastLogLines = readLastLines(logPath, 5);
727
- const configuredActivationRoot = readDaemonActivationRoot(plistPath);
728
- const requestedActivationRoot = serviceIdentity.requestedActivationRoot;
729
- const watchStatePaths = getWatchStatePaths(requestedActivationRoot);
730
- const watchState = readWatchStateSummary(requestedActivationRoot);
731
- const matchesRequestedActivationRoot = configuredActivationRoot === null
732
- ? null
733
- : canonicalizeActivationRoot(configuredActivationRoot) === serviceIdentity.canonicalActivationRoot;
734
- if (json) {
735
- console.log(JSON.stringify({
736
- command: "daemon status",
737
- installed: plistInstalled,
738
- running: info.running,
739
- pid: info.pid,
740
- serviceLabel: serviceIdentity.label,
741
- plistPath,
742
- logPath,
743
- activationRoot: requestedActivationRoot,
744
- configuredActivationRoot,
745
- matchesRequestedActivationRoot,
746
- ...watchStatePaths,
747
- watchState,
748
- lastLogLines,
749
- }, null, 2));
750
- }
751
- else {
752
- const stateIcon = info.running ? "●" : "○";
753
- const stateText = info.running ? "running" : plistInstalled ? "stopped" : "not installed";
754
- console.log(`${stateIcon} Daemon: ${stateText}`);
755
- if (info.pid !== null) {
756
- console.log(` PID: ${info.pid}`);
757
- }
758
- if (plistInstalled) {
759
- console.log(` Label: ${serviceIdentity.label}`);
760
- console.log(` Plist: ${plistPath}`);
761
- }
762
- console.log(` Requested root: ${requestedActivationRoot}`);
763
- if (configuredActivationRoot !== null) {
764
- console.log(` Configured root: ${configuredActivationRoot}`);
765
- }
766
- if (matchesRequestedActivationRoot === false) {
767
- console.log(" Requested root does not match the installed daemon plist.");
768
- }
769
- console.log(` Log: ${logPath}`);
770
- if (watchState.scanRoot !== null) {
771
- console.log(` Scan root: ${watchState.scanRoot}`);
772
- }
773
- if (watchState.scannerCheckpoint.path !== null) {
774
- const checkpointSummary = watchState.scannerCheckpoint.exists
775
- ? `processed=${watchState.scannerCheckpoint.processedExportDigestCount ?? "?"} passes=${watchState.scannerCheckpoint.scanPasses ?? "?"}`
776
- : "missing";
777
- console.log(` Scanner: ${watchState.scannerCheckpoint.path} (${checkpointSummary})`);
778
- }
779
- if (watchState.teacherSnapshot.path !== null) {
780
- const snapshotSummary = watchState.teacherSnapshot.exists
781
- ? `updated=${watchState.teacherSnapshot.updatedAt ?? "unknown"} lastRun=${watchState.teacherSnapshot.lastRunAt ?? "unknown"} artifacts=${watchState.teacherSnapshot.artifactCount ?? "?"} replayed=${watchState.teacherSnapshot.replayedBundleCount ?? "?"}/${watchState.teacherSnapshot.replayedEventCount ?? "?"} exported=${watchState.teacherSnapshot.exportedBundleCount ?? "?"}/${watchState.teacherSnapshot.exportedEventCount ?? "?"}`
782
- : "missing";
783
- console.log(` Snapshot: ${watchState.teacherSnapshot.path} (${snapshotSummary})`);
784
- }
785
- if (watchState.teacherSnapshot.cadence.processedExportCount !== null) {
786
- console.log(` Teacher cadence: processed=${watchState.teacherSnapshot.cadence.processedExportCount} materialized=${watchState.teacherSnapshot.cadence.materializationCount ?? "?"} seen=${watchState.teacherSnapshot.cadence.seenExportDigestCount ?? "?"}`);
787
- }
788
- if (watchState.sessionTailCursor.path !== null) {
789
- const cursorSummary = watchState.sessionTailCursor.exists
790
- ? `sessions=${watchState.sessionTailCursor.sessionCount ?? "?"} bridged=${watchState.sessionTailCursor.bridgedEventCount ?? "?"} updated=${watchState.sessionTailCursor.updatedAt ?? "unknown"}`
791
- : "missing";
792
- console.log(` Cursor: ${watchState.sessionTailCursor.path} (${cursorSummary})`);
793
- }
794
- if (watchState.baselineState.path !== null) {
795
- const baselineSummary = watchState.baselineState.exists
796
- ? `count=${watchState.baselineState.count ?? "?"} avg=${watchState.baselineState.movingAverage ?? "?"} updated=${watchState.baselineState.lastUpdatedAt ?? "unknown"}`
797
- : "missing";
798
- console.log(` Baseline: ${watchState.baselineState.path} (${baselineSummary})`);
799
- }
800
- if (watchState.teacherSnapshot.lastHandledMaterializationPackId !== null) {
801
- console.log(` Last handled pack: ${watchState.teacherSnapshot.lastHandledMaterializationPackId}`);
802
- }
803
- if (watchState.teacherSnapshot.lastMaterializationPackId !== null &&
804
- watchState.teacherSnapshot.lastMaterializationPackId !== watchState.teacherSnapshot.lastHandledMaterializationPackId) {
805
- console.log(` Last materialized pack: ${watchState.teacherSnapshot.lastMaterializationPackId}`);
806
- }
807
- if (watchState.teacherSnapshot.localSessionTailNoopReason !== null) {
808
- console.log(` Tail state: ${watchState.teacherSnapshot.localSessionTailNoopReason}`);
809
- }
810
- if (watchState.teacherSnapshot.startupWarningCount !== null && watchState.teacherSnapshot.startupWarningCount > 0) {
811
- console.log(` Startup warnings: ${watchState.teacherSnapshot.startupWarningCount}`);
812
- }
813
- if (watchState.teacherSnapshot.lastTeacherError !== null) {
814
- console.log(` Teacher fail-open: ${watchState.teacherSnapshot.lastTeacherError}`);
815
- }
816
- if (watchState.teacherSnapshot.learningCadence !== null || watchState.teacherSnapshot.scanPolicy !== null) {
817
- console.log(` Passive labeling: cadence=${watchState.teacherSnapshot.learningCadence ?? "unknown"} scan=${watchState.teacherSnapshot.scanPolicy ?? "unknown"} slices=${watchState.teacherSnapshot.liveSlicesPerCycle ?? "?"}/${watchState.teacherSnapshot.backfillSlicesPerCycle ?? "?"}`);
818
- }
819
- if (watchState.teacherSnapshot.failureMode !== null) {
820
- console.log(` Failure: ${watchState.teacherSnapshot.failureMode}${watchState.teacherSnapshot.failureDetail === null ? "" : ` (${watchState.teacherSnapshot.failureDetail})`}`);
821
- }
822
- if (lastLogLines.length > 0) {
823
- console.log(`\nRecent log:`);
824
- for (const line of lastLogLines) {
825
- console.log(` ${line}`);
826
- }
827
- }
828
- }
829
- return 0;
830
- }
831
- export function daemonLogs(activationRoot, json) {
832
- const serviceIdentity = buildDaemonServiceIdentity(activationRoot);
833
- const logPath = serviceIdentity.logPath;
834
- if (!existsSync(logPath)) {
835
- const msg = `No log file found at ${logPath}`;
836
- if (json) {
837
- console.log(JSON.stringify({
838
- command: "daemon logs",
839
- ok: false,
840
- activationRoot: serviceIdentity.requestedActivationRoot,
841
- serviceLabel: serviceIdentity.label,
842
- logPath,
843
- message: msg,
844
- lines: []
845
- }, null, 2));
846
- }
847
- else {
848
- console.log(msg);
849
- }
850
- return 1;
851
- }
852
- const lines = readLastLines(logPath, 50);
853
- if (json) {
854
- console.log(JSON.stringify({
855
- command: "daemon logs",
856
- ok: true,
857
- activationRoot: serviceIdentity.requestedActivationRoot,
858
- serviceLabel: serviceIdentity.label,
859
- logPath,
860
- lines
861
- }, null, 2));
862
- }
863
- else {
864
- if (lines.length === 0) {
865
- console.log("(log file is empty)");
866
- }
867
- else {
868
- for (const line of lines) {
869
- console.log(line);
870
- }
871
- }
872
- }
873
- return 0;
874
- }
875
- export function runDaemonCommand(args) {
876
- if (args.help) {
877
- console.log(daemonHelp());
878
- return 0;
879
- }
880
- switch (args.subcommand) {
881
- case "start": {
882
- return daemonStart(args.activationRoot, args.json);
883
- }
884
- case "stop":
885
- return daemonStop(args.activationRoot, args.json);
886
- case "status":
887
- return daemonStatus(args.activationRoot, args.json);
888
- case "logs":
889
- return daemonLogs(args.activationRoot, args.json);
890
- default:
891
- console.error(`Unknown daemon subcommand: ${args.subcommand}`);
892
- console.error(daemonHelp());
893
- return 1;
894
- }
895
- }
896
- export function daemonHelp() {
897
- return [
898
- "Usage:",
899
- " openclawbrain daemon start --activation-root <path> [--json]",
900
- " openclawbrain daemon stop --activation-root <path> [--json]",
901
- " openclawbrain daemon status --activation-root <path> [--json]",
902
- " openclawbrain daemon logs --activation-root <path> [--json]",
903
- "",
904
- "Subcommands:",
905
- " start Generate a macOS launchd plist and start the daemon (runs openclawbrain watch).",
906
- " stop Stop the daemon and remove the launchd plist.",
907
- " status Show whether the daemon is running, its PID, and recent log lines.",
908
- " logs Show the last 50 lines of the per-activation-root daemon log under ~/.openclawbrain/daemon/.",
909
- "",
910
- "Options:",
911
- " --activation-root <path> Explicit activation root for the wrapped watch daemon.",
912
- " --json Emit machine-readable JSON output.",
913
- " --help Show this help.",
914
- ].join("\n");
915
- }
916
- export function parseDaemonArgs(argv) {
917
- // argv should be everything after "daemon", e.g. ["start", "--activation-root", "/path"]
918
- const args = [...argv];
919
- let subcommand = "status";
920
- let activationRoot = null;
921
- let json = false;
922
- let help = false;
923
- if (args.length > 0 && (args[0] === "start" || args[0] === "stop" || args[0] === "status" || args[0] === "logs")) {
924
- subcommand = args.shift();
925
- }
926
- for (let i = 0; i < args.length; i++) {
927
- const arg = args[i];
928
- if (arg === "--help" || arg === "-h") {
929
- help = true;
930
- continue;
931
- }
932
- if (arg === "--json") {
933
- json = true;
934
- continue;
935
- }
936
- if (arg === "--activation-root") {
937
- const next = args[i + 1];
938
- if (next === undefined) {
939
- throw new Error("--activation-root requires a value");
940
- }
941
- activationRoot = next;
942
- i += 1;
943
- continue;
944
- }
945
- throw new Error(`Unknown daemon argument: ${arg}`);
946
- }
947
- if (help) {
948
- return { command: "daemon", subcommand, activationRoot: "", json, help };
949
- }
950
- if (activationRoot === null || activationRoot.trim().length === 0) {
951
- throw new Error(`daemon ${subcommand} requires --activation-root <path>`);
952
- }
953
- return { command: "daemon", subcommand, activationRoot: path.resolve(activationRoot), json, help };
954
- }
955
- //# sourceMappingURL=daemon.js.map