@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.
- package/README.md +23 -228
- package/dist/extension/index.js +3 -2
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/runtime-guard.js +1 -1
- package/dist/extension/runtime-guard.js.map +1 -1
- package/dist/src/attachment-truth.d.ts +32 -22
- package/dist/src/attachment-truth.js +338 -186
- package/dist/src/index.d.ts +75 -1719
- package/dist/src/index.js +7 -6882
- package/dist/src/runtime-core.js +574 -0
- package/extension/index.ts +3 -2
- package/extension/runtime-guard.ts +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +17 -17
- package/dist/src/attachment-truth.js.map +0 -1
- package/dist/src/cli.d.ts +0 -170
- package/dist/src/cli.js +0 -5583
- package/dist/src/cli.js.map +0 -1
- package/dist/src/daemon.d.ts +0 -70
- package/dist/src/daemon.js +0 -955
- package/dist/src/daemon.js.map +0 -1
- package/dist/src/import-export.d.ts +0 -36
- package/dist/src/import-export.js +0 -171
- package/dist/src/import-export.js.map +0 -1
- package/dist/src/index.js.map +0 -1
- package/dist/src/learning-spine.d.ts +0 -50
- package/dist/src/learning-spine.js.map +0 -1
- package/dist/src/local-session-passive-learning.d.ts +0 -61
- package/dist/src/local-session-passive-learning.js +0 -454
- package/dist/src/local-session-passive-learning.js.map +0 -1
- package/dist/src/ollama-client.d.ts +0 -46
- package/dist/src/ollama-client.js +0 -231
- package/dist/src/ollama-client.js.map +0 -1
- package/dist/src/openclaw-home-layout.d.ts +0 -17
- package/dist/src/openclaw-home-layout.js +0 -182
- package/dist/src/openclaw-home-layout.js.map +0 -1
- package/dist/src/openclaw-hook-truth.d.ts +0 -33
- package/dist/src/openclaw-hook-truth.js +0 -260
- package/dist/src/openclaw-hook-truth.js.map +0 -1
- package/dist/src/openclaw-plugin-install.d.ts +0 -40
- package/dist/src/openclaw-plugin-install.js +0 -282
- package/dist/src/provider-config.d.ts +0 -64
- package/dist/src/provider-config.js +0 -306
- package/dist/src/provider-config.js.map +0 -1
- package/dist/src/resolve-activation-root.d.ts +0 -27
- package/dist/src/resolve-activation-root.js +0 -190
- package/dist/src/resolve-activation-root.js.map +0 -1
- package/dist/src/semantic-metadata.d.ts +0 -5
- package/dist/src/semantic-metadata.js +0 -70
- package/dist/src/semantic-metadata.js.map +0 -1
- package/dist/src/session-store.d.ts +0 -168
- package/dist/src/session-store.js +0 -250
- package/dist/src/session-store.js.map +0 -1
- package/dist/src/session-tail.d.ts +0 -73
- package/dist/src/session-tail.js +0 -602
- package/dist/src/session-tail.js.map +0 -1
- package/dist/src/shadow-extension-proof.d.ts +0 -43
- package/dist/src/shadow-extension-proof.js +0 -218
- package/dist/src/shadow-extension-proof.js.map +0 -1
- package/dist/src/teacher-labeler.d.ts +0 -50
- package/dist/src/teacher-labeler.js +0 -424
- package/dist/src/teacher-labeler.js.map +0 -1
package/dist/src/daemon.js
DELETED
|
@@ -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
|