@beeos-ai/cli 1.0.13 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1748 -307
- package/package.json +2 -2
- package/scripts/_existing_install_actions.json +40 -0
- package/scripts/install.Tests.ps1 +169 -0
- package/scripts/install.ps1 +252 -9
- package/scripts/install.sh +61 -9
package/dist/index.js
CHANGED
|
@@ -91,6 +91,10 @@ function configPath() {
|
|
|
91
91
|
const p = getPlatformAdapter();
|
|
92
92
|
return p.joinPath(beeoHome(), "config.toml");
|
|
93
93
|
}
|
|
94
|
+
function spawnEnvJsonPath() {
|
|
95
|
+
const p = getPlatformAdapter();
|
|
96
|
+
return p.joinPath(beeoHome(), "config.spawn-env.json");
|
|
97
|
+
}
|
|
94
98
|
function bindingPath() {
|
|
95
99
|
const p = getPlatformAdapter();
|
|
96
100
|
return p.joinPath(beeoHome(), "binding.json");
|
|
@@ -110,6 +114,336 @@ var init_paths = __esm({
|
|
|
110
114
|
}
|
|
111
115
|
});
|
|
112
116
|
|
|
117
|
+
// ../core/dist/config/spawn-env.js
|
|
118
|
+
function projectSpawnEnv(cfg) {
|
|
119
|
+
const out = {};
|
|
120
|
+
const agw = cfg.platform.agent_gateway_url?.trim();
|
|
121
|
+
if (agw)
|
|
122
|
+
out.agent_gateway_url = agw;
|
|
123
|
+
const bridge = cfg.platform.bridge_url?.trim();
|
|
124
|
+
if (bridge)
|
|
125
|
+
out.bridge_url = bridge;
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
async function saveSpawnEnvSnapshot(cfg) {
|
|
129
|
+
const p = getPlatformAdapter();
|
|
130
|
+
const path10 = spawnEnvJsonPath();
|
|
131
|
+
const snapshot = projectSpawnEnv(cfg);
|
|
132
|
+
try {
|
|
133
|
+
await p.mkdir(p.dirname(path10));
|
|
134
|
+
await p.writeFile(path10, JSON.stringify(snapshot, null, 2) + "\n");
|
|
135
|
+
} catch (err) {
|
|
136
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
137
|
+
console.error(`! warning: could not write ${path10}: ${message}
|
|
138
|
+
device-agent fleet will fall back to parsing config.toml directly.`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
var init_spawn_env = __esm({
|
|
142
|
+
"../core/dist/config/spawn-env.js"() {
|
|
143
|
+
"use strict";
|
|
144
|
+
init_platform_adapter();
|
|
145
|
+
init_paths();
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// ../core/dist/errors.js
|
|
150
|
+
function isBeeosError(err) {
|
|
151
|
+
return err instanceof BeeosError;
|
|
152
|
+
}
|
|
153
|
+
function toJsonFailurePayload(err) {
|
|
154
|
+
return {
|
|
155
|
+
ok: false,
|
|
156
|
+
error: {
|
|
157
|
+
code: err.code,
|
|
158
|
+
message: err.message,
|
|
159
|
+
...err.hint !== void 0 ? { hint: err.hint } : {},
|
|
160
|
+
...err.details !== void 0 ? { details: err.details } : {}
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function formatHumanError(err) {
|
|
165
|
+
const parts = [`error [${err.code}]: ${err.message}`];
|
|
166
|
+
if (err.hint)
|
|
167
|
+
parts.push(` $ ${err.hint}`);
|
|
168
|
+
return parts.join("\n");
|
|
169
|
+
}
|
|
170
|
+
var BeeosError;
|
|
171
|
+
var init_errors = __esm({
|
|
172
|
+
"../core/dist/errors.js"() {
|
|
173
|
+
"use strict";
|
|
174
|
+
BeeosError = class extends Error {
|
|
175
|
+
code;
|
|
176
|
+
hint;
|
|
177
|
+
details;
|
|
178
|
+
constructor(options) {
|
|
179
|
+
super(options.message, options.cause !== void 0 ? { cause: options.cause } : void 0);
|
|
180
|
+
this.name = "BeeosError";
|
|
181
|
+
this.code = options.code;
|
|
182
|
+
this.hint = options.hint;
|
|
183
|
+
this.details = options.details;
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// ../core/dist/config/state.js
|
|
190
|
+
function statePath() {
|
|
191
|
+
const p = getPlatformAdapter();
|
|
192
|
+
return p.joinPath(beeoHome(), "state.json");
|
|
193
|
+
}
|
|
194
|
+
function legacyDevicesPath() {
|
|
195
|
+
const p = getPlatformAdapter();
|
|
196
|
+
return p.joinPath(beeoHome(), "devices.json");
|
|
197
|
+
}
|
|
198
|
+
function defaultState() {
|
|
199
|
+
return {
|
|
200
|
+
version: 2,
|
|
201
|
+
platform: {
|
|
202
|
+
api_url: DEFAULT_API_URL,
|
|
203
|
+
agent_gateway_url: DEFAULT_AGENT_GATEWAY_URL,
|
|
204
|
+
dashboard_base_url: DEFAULT_DASHBOARD_URL
|
|
205
|
+
},
|
|
206
|
+
agents: []
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
async function loadState() {
|
|
210
|
+
const p = getPlatformAdapter();
|
|
211
|
+
const path10 = statePath();
|
|
212
|
+
if (await p.exists(path10)) {
|
|
213
|
+
let raw;
|
|
214
|
+
try {
|
|
215
|
+
raw = await p.readFile(path10);
|
|
216
|
+
} catch (e) {
|
|
217
|
+
throw new BeeosError({
|
|
218
|
+
code: "state_corrupted",
|
|
219
|
+
message: `failed to read ${path10}: ${describeError(e)}`,
|
|
220
|
+
hint: "Inspect the file or move it aside and re-run `beeos doctor` to regenerate from legacy state.",
|
|
221
|
+
details: { path: path10 },
|
|
222
|
+
cause: e
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
let parsed;
|
|
226
|
+
try {
|
|
227
|
+
parsed = JSON.parse(raw);
|
|
228
|
+
} catch (e) {
|
|
229
|
+
throw new BeeosError({
|
|
230
|
+
code: "state_corrupted",
|
|
231
|
+
message: `${path10} is not valid JSON: ${describeError(e)}`,
|
|
232
|
+
hint: "Move the file aside (e.g. `mv ~/.beeos/state.json ~/.beeos/state.json.broken`) and re-run `beeos doctor` to migrate from the legacy state files.",
|
|
233
|
+
details: { path: path10 }
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
return validateState(parsed, path10);
|
|
237
|
+
}
|
|
238
|
+
return migrateLegacy();
|
|
239
|
+
}
|
|
240
|
+
async function saveState(state) {
|
|
241
|
+
const p = getPlatformAdapter();
|
|
242
|
+
const path10 = statePath();
|
|
243
|
+
await p.mkdir(p.dirname(path10));
|
|
244
|
+
await p.writeFile(path10, JSON.stringify(state, null, 2) + "\n");
|
|
245
|
+
if (p.platform() !== "win32") {
|
|
246
|
+
try {
|
|
247
|
+
await p.chmod(path10, 384);
|
|
248
|
+
} catch {
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function upsertOpenClawAgent(state, entry) {
|
|
253
|
+
const next = state.agents.filter((a) => !(a.kind === "openclaw" && a.framework === entry.framework));
|
|
254
|
+
next.push(entry);
|
|
255
|
+
return { ...state, agents: next };
|
|
256
|
+
}
|
|
257
|
+
function upsertDeviceAgent(state, entry) {
|
|
258
|
+
const next = state.agents.filter((a) => !(a.kind === "device" && a.serial === entry.serial));
|
|
259
|
+
next.push(entry);
|
|
260
|
+
return { ...state, agents: next };
|
|
261
|
+
}
|
|
262
|
+
async function mutateStateBestEffort(mutator, ctx) {
|
|
263
|
+
try {
|
|
264
|
+
const state = await loadState();
|
|
265
|
+
const next = mutator(state);
|
|
266
|
+
if (next === null)
|
|
267
|
+
return;
|
|
268
|
+
await saveState(next);
|
|
269
|
+
} catch (err) {
|
|
270
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
271
|
+
const subject = ctx.subject ? ` for ${ctx.subject}` : "";
|
|
272
|
+
if (typeof console !== "undefined" && typeof console.warn === "function") {
|
|
273
|
+
console.warn(`Warning: could not update ~/.beeos/state.json${subject} (${msg}). ${ctx.authority} is still authoritative; run \`beeos doctor\` to repair.`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
async function migrateLegacy() {
|
|
278
|
+
const state = defaultState();
|
|
279
|
+
await tryReadSpawnEnvIntoPlatform(state);
|
|
280
|
+
const binding = await tryReadBinding();
|
|
281
|
+
if (binding) {
|
|
282
|
+
const platformAgw = state.platform.agent_gateway_url;
|
|
283
|
+
state.agents.push({
|
|
284
|
+
kind: "openclaw",
|
|
285
|
+
framework: binding.framework ?? "openclaw",
|
|
286
|
+
instance_id: binding.instance_id,
|
|
287
|
+
bound_at: binding.bound_at,
|
|
288
|
+
fingerprint: binding.fingerprint,
|
|
289
|
+
// Old binding.json never recorded per-binding AGW, so fall back
|
|
290
|
+
// to whatever the platform projection shows; saveConfig keeps
|
|
291
|
+
// this fresh on every config write.
|
|
292
|
+
agent_gateway_url: platformAgw
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
const devices = await tryReadDevices();
|
|
296
|
+
for (const d of devices) {
|
|
297
|
+
if (d.instance_id == null) {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
const entry = {
|
|
301
|
+
kind: "device",
|
|
302
|
+
instance_id: d.instance_id,
|
|
303
|
+
bound_at: d.bound_at ?? 0,
|
|
304
|
+
fingerprint: d.fingerprint ?? "",
|
|
305
|
+
agent_gateway_url: d.agent_gateway_url && d.agent_gateway_url !== "" ? d.agent_gateway_url : state.platform.agent_gateway_url,
|
|
306
|
+
serial: d.serial,
|
|
307
|
+
name: d.name,
|
|
308
|
+
key_file: d.key_file,
|
|
309
|
+
http_port: d.http_port,
|
|
310
|
+
...d.video_mode !== void 0 ? { video_mode: d.video_mode } : {},
|
|
311
|
+
...d.backend !== void 0 ? { backend: d.backend } : {},
|
|
312
|
+
...d.vnc_host !== void 0 ? { vnc_host: d.vnc_host } : {},
|
|
313
|
+
...d.vnc_port !== void 0 ? { vnc_port: d.vnc_port } : {}
|
|
314
|
+
};
|
|
315
|
+
state.agents.push(entry);
|
|
316
|
+
}
|
|
317
|
+
await tryFoldVlmConfigs(state);
|
|
318
|
+
return state;
|
|
319
|
+
}
|
|
320
|
+
async function tryReadBinding() {
|
|
321
|
+
const p = getPlatformAdapter();
|
|
322
|
+
const path10 = bindingPath();
|
|
323
|
+
if (!await p.exists(path10))
|
|
324
|
+
return null;
|
|
325
|
+
try {
|
|
326
|
+
const raw = await p.readFile(path10);
|
|
327
|
+
return JSON.parse(raw);
|
|
328
|
+
} catch {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
async function tryReadDevices() {
|
|
333
|
+
const p = getPlatformAdapter();
|
|
334
|
+
const path10 = legacyDevicesPath();
|
|
335
|
+
if (!await p.exists(path10))
|
|
336
|
+
return [];
|
|
337
|
+
try {
|
|
338
|
+
const raw = await p.readFile(path10);
|
|
339
|
+
const parsed = JSON.parse(raw);
|
|
340
|
+
return Array.isArray(parsed?.devices) ? parsed.devices : [];
|
|
341
|
+
} catch {
|
|
342
|
+
return [];
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
async function tryReadSpawnEnvIntoPlatform(state) {
|
|
346
|
+
const p = getPlatformAdapter();
|
|
347
|
+
const path10 = spawnEnvJsonPath();
|
|
348
|
+
if (!await p.exists(path10))
|
|
349
|
+
return;
|
|
350
|
+
try {
|
|
351
|
+
const raw = await p.readFile(path10);
|
|
352
|
+
const parsed = JSON.parse(raw);
|
|
353
|
+
if (!parsed || typeof parsed !== "object")
|
|
354
|
+
return;
|
|
355
|
+
if (typeof parsed.agent_gateway_url === "string" && parsed.agent_gateway_url.length > 0) {
|
|
356
|
+
state.platform.agent_gateway_url = parsed.agent_gateway_url;
|
|
357
|
+
}
|
|
358
|
+
if (typeof parsed.bridge_url === "string" && parsed.bridge_url.length > 0) {
|
|
359
|
+
state.platform.bridge_url = parsed.bridge_url;
|
|
360
|
+
}
|
|
361
|
+
} catch {
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
async function tryFoldVlmConfigs(state) {
|
|
365
|
+
const p = getPlatformAdapter();
|
|
366
|
+
const root = beeoHome();
|
|
367
|
+
let entries = [];
|
|
368
|
+
try {
|
|
369
|
+
entries = await p.readdir(root);
|
|
370
|
+
} catch {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const byInstanceId = /* @__PURE__ */ new Map();
|
|
374
|
+
for (const a of state.agents)
|
|
375
|
+
byInstanceId.set(a.instance_id, a);
|
|
376
|
+
for (const name of entries) {
|
|
377
|
+
if (!name.endsWith(".config.json"))
|
|
378
|
+
continue;
|
|
379
|
+
if (name === "config.spawn-env.json")
|
|
380
|
+
continue;
|
|
381
|
+
const instanceId = name.slice(0, -".config.json".length);
|
|
382
|
+
const target = byInstanceId.get(instanceId);
|
|
383
|
+
if (!target)
|
|
384
|
+
continue;
|
|
385
|
+
const filePath = p.joinPath(root, name);
|
|
386
|
+
try {
|
|
387
|
+
const raw = await p.readFile(filePath);
|
|
388
|
+
const parsed = JSON.parse(raw);
|
|
389
|
+
target.agent_config = parsed;
|
|
390
|
+
} catch {
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
function validateState(parsed, path10) {
|
|
395
|
+
if (!parsed || typeof parsed !== "object") {
|
|
396
|
+
throw new BeeosError({
|
|
397
|
+
code: "state_corrupted",
|
|
398
|
+
message: `${path10} did not deserialise to a JSON object.`,
|
|
399
|
+
hint: "Move the file aside and re-run `beeos doctor` to regenerate.",
|
|
400
|
+
details: { path: path10 }
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
const obj = parsed;
|
|
404
|
+
const version = obj.version;
|
|
405
|
+
if (version !== 2) {
|
|
406
|
+
throw new BeeosError({
|
|
407
|
+
code: "state_version_mismatch",
|
|
408
|
+
message: `${path10} declares version=${JSON.stringify(version)}; this CLI only understands version=2.`,
|
|
409
|
+
hint: "Either upgrade the CLI (`beeos --version` to see what you have) or move the file aside and re-bind your agents with the older CLI's flow.",
|
|
410
|
+
details: { path: path10, observed: version, expected: 2 }
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
const platform = obj.platform;
|
|
414
|
+
if (!platform || typeof platform !== "object") {
|
|
415
|
+
throw new BeeosError({
|
|
416
|
+
code: "state_corrupted",
|
|
417
|
+
message: `${path10} is missing a \`platform\` object.`,
|
|
418
|
+
hint: "Move the file aside and re-run `beeos doctor`.",
|
|
419
|
+
details: { path: path10 }
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
if (!Array.isArray(obj.agents)) {
|
|
423
|
+
throw new BeeosError({
|
|
424
|
+
code: "state_corrupted",
|
|
425
|
+
message: `${path10} \`agents\` is not an array.`,
|
|
426
|
+
hint: "Move the file aside and re-run `beeos doctor`.",
|
|
427
|
+
details: { path: path10 }
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
return obj;
|
|
431
|
+
}
|
|
432
|
+
function describeError(e) {
|
|
433
|
+
if (e instanceof Error)
|
|
434
|
+
return e.message;
|
|
435
|
+
return String(e);
|
|
436
|
+
}
|
|
437
|
+
var init_state = __esm({
|
|
438
|
+
"../core/dist/config/state.js"() {
|
|
439
|
+
"use strict";
|
|
440
|
+
init_errors();
|
|
441
|
+
init_platform_adapter();
|
|
442
|
+
init_paths();
|
|
443
|
+
init_types();
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
|
|
113
447
|
// ../core/dist/config/toml.js
|
|
114
448
|
import * as TOML from "smol-toml";
|
|
115
449
|
async function loadOrCreateConfig() {
|
|
@@ -119,6 +453,8 @@ async function loadOrCreateConfig() {
|
|
|
119
453
|
const raw = await p.readFile(path10);
|
|
120
454
|
const parsed = TOML.parse(raw);
|
|
121
455
|
const cfg2 = mergeWithDefaults(parsed);
|
|
456
|
+
await saveSpawnEnvSnapshot(cfg2);
|
|
457
|
+
await refreshStatePlatformBestEffort(cfg2);
|
|
122
458
|
return applyEnvOverrides(cfg2);
|
|
123
459
|
}
|
|
124
460
|
const cfg = defaultConfig();
|
|
@@ -144,12 +480,16 @@ async function saveConfig(cfg) {
|
|
|
144
480
|
const p = getPlatformAdapter();
|
|
145
481
|
const path10 = configPath();
|
|
146
482
|
await p.mkdir(p.dirname(path10));
|
|
483
|
+
const platformBlock = {
|
|
484
|
+
api_url: cfg.platform.api_url,
|
|
485
|
+
agent_gateway_url: cfg.platform.agent_gateway_url,
|
|
486
|
+
dashboard_base_url: cfg.platform.dashboard_base_url
|
|
487
|
+
};
|
|
488
|
+
if (cfg.platform.bridge_url && cfg.platform.bridge_url.trim() !== "") {
|
|
489
|
+
platformBlock.bridge_url = cfg.platform.bridge_url;
|
|
490
|
+
}
|
|
147
491
|
const serializable = {
|
|
148
|
-
platform:
|
|
149
|
-
api_url: cfg.platform.api_url,
|
|
150
|
-
agent_gateway_url: cfg.platform.agent_gateway_url,
|
|
151
|
-
dashboard_base_url: cfg.platform.dashboard_base_url
|
|
152
|
-
},
|
|
492
|
+
platform: platformBlock,
|
|
153
493
|
device: {
|
|
154
494
|
http_enabled: cfg.device.http_enabled,
|
|
155
495
|
http_port: cfg.device.http_port
|
|
@@ -157,6 +497,20 @@ async function saveConfig(cfg) {
|
|
|
157
497
|
};
|
|
158
498
|
const raw = TOML.stringify(serializable);
|
|
159
499
|
await p.writeFile(path10, raw);
|
|
500
|
+
await saveSpawnEnvSnapshot(cfg);
|
|
501
|
+
await refreshStatePlatformBestEffort(cfg);
|
|
502
|
+
}
|
|
503
|
+
async function refreshStatePlatformBestEffort(cfg) {
|
|
504
|
+
const platform = {
|
|
505
|
+
api_url: cfg.platform.api_url,
|
|
506
|
+
agent_gateway_url: cfg.platform.agent_gateway_url,
|
|
507
|
+
dashboard_base_url: cfg.platform.dashboard_base_url,
|
|
508
|
+
...cfg.platform.bridge_url && cfg.platform.bridge_url.trim() !== "" ? { bridge_url: cfg.platform.bridge_url } : {}
|
|
509
|
+
};
|
|
510
|
+
await mutateStateBestEffort((state) => platformEqual(state.platform, platform) ? null : { ...state, platform }, { authority: "config.spawn-env.json (legacy mirror)" });
|
|
511
|
+
}
|
|
512
|
+
function platformEqual(a, b) {
|
|
513
|
+
return a.api_url === b.api_url && a.agent_gateway_url === b.agent_gateway_url && a.dashboard_base_url === b.dashboard_base_url && a.bridge_url === b.bridge_url;
|
|
160
514
|
}
|
|
161
515
|
function mergeWithDefaults(parsed) {
|
|
162
516
|
const platform = parsed.platform ?? {};
|
|
@@ -207,6 +561,8 @@ var init_toml = __esm({
|
|
|
207
561
|
"use strict";
|
|
208
562
|
init_platform_adapter();
|
|
209
563
|
init_paths();
|
|
564
|
+
init_spawn_env();
|
|
565
|
+
init_state();
|
|
210
566
|
init_types();
|
|
211
567
|
}
|
|
212
568
|
});
|
|
@@ -587,14 +943,18 @@ async function bindAgent(opts) {
|
|
|
587
943
|
const instanceId = await pollUntilBound(opts.apiUrl, bindId, pollTimeoutMs);
|
|
588
944
|
return { status: "bound", instanceId, source: "fresh" };
|
|
589
945
|
} catch (e) {
|
|
590
|
-
|
|
591
|
-
if (/timed out|expired/i.test(msg)) {
|
|
946
|
+
if (e instanceof BeeosError && e.code === "bind_pending_expired") {
|
|
592
947
|
return { status: "pending_expired" };
|
|
593
948
|
}
|
|
594
949
|
throw e;
|
|
595
950
|
}
|
|
596
951
|
}
|
|
597
|
-
throw new
|
|
952
|
+
throw new BeeosError({
|
|
953
|
+
code: "bind_protocol_unexpected",
|
|
954
|
+
message: `Unexpected bind protocol response (status: '${resp.status}').`,
|
|
955
|
+
hint: "Re-run the command. If this persists, check the platform's `/api/v1/agent/bind` health and contact support with the requested fingerprint.",
|
|
956
|
+
details: { received: resp.status, fingerprint: opts.fingerprint }
|
|
957
|
+
});
|
|
598
958
|
}
|
|
599
959
|
async function presentBindUrl(bindUrl, forceHeadless, log = console.log) {
|
|
600
960
|
const p = getPlatformAdapter();
|
|
@@ -624,7 +984,12 @@ async function pollUntilBound(apiUrl, bindId, timeoutMs) {
|
|
|
624
984
|
const deadline = Date.now() + timeoutMs;
|
|
625
985
|
while (true) {
|
|
626
986
|
if (Date.now() > deadline) {
|
|
627
|
-
throw new
|
|
987
|
+
throw new BeeosError({
|
|
988
|
+
code: "bind_pending_expired",
|
|
989
|
+
message: "Bind approval timed out \u2014 please re-run the command.",
|
|
990
|
+
hint: "Approve the request in the dashboard sooner, or re-run with the same identity to start a fresh bind.",
|
|
991
|
+
details: { source: "client_timeout", bindId }
|
|
992
|
+
});
|
|
628
993
|
}
|
|
629
994
|
try {
|
|
630
995
|
const resp = await pollBind(apiUrl, bindId);
|
|
@@ -632,12 +997,17 @@ async function pollUntilBound(apiUrl, bindId, timeoutMs) {
|
|
|
632
997
|
return resp.instance_id ?? "";
|
|
633
998
|
}
|
|
634
999
|
if (resp.status === "expired") {
|
|
635
|
-
throw new
|
|
1000
|
+
throw new BeeosError({
|
|
1001
|
+
code: "bind_pending_expired",
|
|
1002
|
+
message: "Bind session expired \u2014 please re-run the command.",
|
|
1003
|
+
hint: "Re-run `beeos start` to start a fresh bind session.",
|
|
1004
|
+
details: { source: "server_expired", bindId }
|
|
1005
|
+
});
|
|
636
1006
|
}
|
|
637
1007
|
} catch (e) {
|
|
638
|
-
|
|
639
|
-
if (msg.includes("expired") || msg.includes("timed out"))
|
|
1008
|
+
if (e instanceof BeeosError && e.code === "bind_pending_expired") {
|
|
640
1009
|
throw e;
|
|
1010
|
+
}
|
|
641
1011
|
await sleep(2e3);
|
|
642
1012
|
}
|
|
643
1013
|
}
|
|
@@ -692,6 +1062,139 @@ var init_orchestrator = __esm({
|
|
|
692
1062
|
init_client();
|
|
693
1063
|
init_process();
|
|
694
1064
|
init_qr();
|
|
1065
|
+
init_errors();
|
|
1066
|
+
}
|
|
1067
|
+
});
|
|
1068
|
+
|
|
1069
|
+
// ../core/dist/upgrade.js
|
|
1070
|
+
function readPinSourcesFromEnv() {
|
|
1071
|
+
const env = globalThis.process?.env ?? {};
|
|
1072
|
+
return {
|
|
1073
|
+
env: {
|
|
1074
|
+
[NPM_PKGS.CLI]: trimOrUndef(env.BEEOS_CLI_VERSION),
|
|
1075
|
+
[NPM_PKGS.DEVICE_AGENT]: trimOrUndef(env.BEEOS_DEVICE_AGENT_VERSION),
|
|
1076
|
+
[NPM_PKGS.DEVICE_MCP_SERVER]: trimOrUndef(env.BEEOS_MCP_SERVER_VERSION)
|
|
1077
|
+
},
|
|
1078
|
+
distTag: trimOrUndef(env.BEEOS_CLI_TAG)
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
function resolveInstallSpec(pkg, sources) {
|
|
1082
|
+
const pin = sources.env?.[pkg];
|
|
1083
|
+
if (pin)
|
|
1084
|
+
return `${pkg}@${pin}`;
|
|
1085
|
+
const tag = sources.distTag ?? "latest";
|
|
1086
|
+
return `${pkg}@${tag}`;
|
|
1087
|
+
}
|
|
1088
|
+
async function npmRegistryVersion(spec) {
|
|
1089
|
+
const p = getPlatformAdapter();
|
|
1090
|
+
try {
|
|
1091
|
+
const result = await p.exec("npm", ["view", spec, "version"], { timeout: 3e4 });
|
|
1092
|
+
if (result.code !== 0)
|
|
1093
|
+
return null;
|
|
1094
|
+
const trimmed = result.stdout.trim();
|
|
1095
|
+
return trimmed || null;
|
|
1096
|
+
} catch {
|
|
1097
|
+
return null;
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
async function npmGlobalVersion(pkg) {
|
|
1101
|
+
const p = getPlatformAdapter();
|
|
1102
|
+
try {
|
|
1103
|
+
const result = await p.exec("npm", ["ls", "-g", "--depth=0", "--json", pkg], { timeout: 3e4 });
|
|
1104
|
+
if (!result.stdout)
|
|
1105
|
+
return null;
|
|
1106
|
+
const parsed = JSON.parse(result.stdout);
|
|
1107
|
+
const ver = parsed.dependencies?.[pkg]?.version;
|
|
1108
|
+
return typeof ver === "string" ? ver : null;
|
|
1109
|
+
} catch {
|
|
1110
|
+
return null;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
async function upgradeBeeosSuite(opts) {
|
|
1114
|
+
const p = getPlatformAdapter();
|
|
1115
|
+
const sources = opts.sources ?? readPinSourcesFromEnv();
|
|
1116
|
+
const progress = opts.progress;
|
|
1117
|
+
const specs = opts.packages.map((pkg) => ({
|
|
1118
|
+
pkg,
|
|
1119
|
+
spec: resolveInstallSpec(pkg, sources)
|
|
1120
|
+
}));
|
|
1121
|
+
const beforeVers = /* @__PURE__ */ new Map();
|
|
1122
|
+
for (const { pkg } of specs) {
|
|
1123
|
+
beforeVers.set(pkg, await npmGlobalVersion(pkg));
|
|
1124
|
+
}
|
|
1125
|
+
let needsInstall = opts.forceInstall === true;
|
|
1126
|
+
if (!needsInstall) {
|
|
1127
|
+
for (const { pkg, spec } of specs) {
|
|
1128
|
+
const target = await npmRegistryVersion(spec);
|
|
1129
|
+
const current = beforeVers.get(pkg) ?? null;
|
|
1130
|
+
if (target === null || current === null || target !== current) {
|
|
1131
|
+
needsInstall = true;
|
|
1132
|
+
break;
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
let installFailure;
|
|
1137
|
+
if (needsInstall) {
|
|
1138
|
+
const args = ["install", "-g", ...specs.map((s) => s.spec)];
|
|
1139
|
+
progress?.onStatus(`npm ${args.join(" ")}`);
|
|
1140
|
+
try {
|
|
1141
|
+
const result2 = await p.exec("npm", args, { timeout: 6e5 });
|
|
1142
|
+
if (result2.code !== 0) {
|
|
1143
|
+
installFailure = (result2.stderr || result2.stdout || `npm install -g exited ${result2.code}`).trim();
|
|
1144
|
+
}
|
|
1145
|
+
} catch (e) {
|
|
1146
|
+
installFailure = e instanceof Error ? e.message : String(e);
|
|
1147
|
+
}
|
|
1148
|
+
} else {
|
|
1149
|
+
progress?.onStatus("Already up to date \u2014 skipping npm install -g");
|
|
1150
|
+
}
|
|
1151
|
+
const result = { packages: [], anyChanged: false, anyFailed: false };
|
|
1152
|
+
for (const { pkg, spec } of specs) {
|
|
1153
|
+
const before = beforeVers.get(pkg) ?? null;
|
|
1154
|
+
const after = await npmGlobalVersion(pkg);
|
|
1155
|
+
const changed = before !== after;
|
|
1156
|
+
const entry = {
|
|
1157
|
+
pkg,
|
|
1158
|
+
spec,
|
|
1159
|
+
before,
|
|
1160
|
+
after,
|
|
1161
|
+
changed,
|
|
1162
|
+
...installFailure ? { failed: installFailure } : {}
|
|
1163
|
+
};
|
|
1164
|
+
result.packages.push(entry);
|
|
1165
|
+
if (changed)
|
|
1166
|
+
result.anyChanged = true;
|
|
1167
|
+
if (entry.failed)
|
|
1168
|
+
result.anyFailed = true;
|
|
1169
|
+
if (changed) {
|
|
1170
|
+
progress?.onStatus(`${pkg}: ${before ?? "(none)"} \u2192 ${after}`);
|
|
1171
|
+
} else if (entry.failed) {
|
|
1172
|
+
progress?.onStatus(`${pkg}: install failed (${entry.failed})`);
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
return result;
|
|
1176
|
+
}
|
|
1177
|
+
function trimOrUndef(v) {
|
|
1178
|
+
if (v === void 0)
|
|
1179
|
+
return void 0;
|
|
1180
|
+
const t = v.trim();
|
|
1181
|
+
return t.length > 0 ? t : void 0;
|
|
1182
|
+
}
|
|
1183
|
+
var NPM_PKGS, ALL_BEEOS_PKGS;
|
|
1184
|
+
var init_upgrade = __esm({
|
|
1185
|
+
"../core/dist/upgrade.js"() {
|
|
1186
|
+
"use strict";
|
|
1187
|
+
init_platform_adapter();
|
|
1188
|
+
NPM_PKGS = {
|
|
1189
|
+
CLI: "@beeos-ai/cli",
|
|
1190
|
+
DEVICE_AGENT: "@beeos-ai/device-agent",
|
|
1191
|
+
DEVICE_MCP_SERVER: "@beeos-ai/device-mcp-server"
|
|
1192
|
+
};
|
|
1193
|
+
ALL_BEEOS_PKGS = [
|
|
1194
|
+
NPM_PKGS.CLI,
|
|
1195
|
+
NPM_PKGS.DEVICE_AGENT,
|
|
1196
|
+
NPM_PKGS.DEVICE_MCP_SERVER
|
|
1197
|
+
];
|
|
695
1198
|
}
|
|
696
1199
|
});
|
|
697
1200
|
|
|
@@ -718,8 +1221,21 @@ function describeFound(bin, progress) {
|
|
|
718
1221
|
}
|
|
719
1222
|
}
|
|
720
1223
|
async function upgradeDeviceAgent(progress) {
|
|
721
|
-
|
|
722
|
-
|
|
1224
|
+
const outcome = await upgradeBeeosSuite({
|
|
1225
|
+
packages: [NPM_PKGS.DEVICE_AGENT, NPM_PKGS.DEVICE_MCP_SERVER],
|
|
1226
|
+
progress
|
|
1227
|
+
});
|
|
1228
|
+
if (outcome.anyFailed) {
|
|
1229
|
+
const failed = outcome.packages.find((p) => p.failed)?.failed;
|
|
1230
|
+
progress.onStatus(`device-agent upgrade did not finish cleanly (${failed ?? "unknown error"}).
|
|
1231
|
+
Manual fix: npm i -g @beeos-ai/device-agent @beeos-ai/device-mcp-server`);
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
if (!outcome.anyChanged) {
|
|
1235
|
+
progress.onComplete("device-agent already up to date");
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
progress.onComplete("device-agent upgraded");
|
|
723
1239
|
}
|
|
724
1240
|
async function findExistingTs() {
|
|
725
1241
|
const p = getPlatformAdapter();
|
|
@@ -776,6 +1292,7 @@ var init_device_setup = __esm({
|
|
|
776
1292
|
"../core/dist/device-setup.js"() {
|
|
777
1293
|
"use strict";
|
|
778
1294
|
init_platform_adapter();
|
|
1295
|
+
init_upgrade();
|
|
779
1296
|
}
|
|
780
1297
|
});
|
|
781
1298
|
|
|
@@ -870,6 +1387,14 @@ async function ensureAdb(progress, options = {}) {
|
|
|
870
1387
|
return installAdb(progress);
|
|
871
1388
|
return null;
|
|
872
1389
|
}
|
|
1390
|
+
function detectAdbLicenseDecision() {
|
|
1391
|
+
const env = globalThis.process?.env ?? {};
|
|
1392
|
+
if (env.BEEOS_ACCEPT_ADB_LICENSE === "1")
|
|
1393
|
+
return "accepted";
|
|
1394
|
+
if (env.BEEOS_REJECT_ADB_LICENSE === "1")
|
|
1395
|
+
return "rejected";
|
|
1396
|
+
return "unknown";
|
|
1397
|
+
}
|
|
873
1398
|
function adbZipName() {
|
|
874
1399
|
const p = getPlatformAdapter();
|
|
875
1400
|
return PLATFORM_TOOLS_ARCHIVE[p.platform()] ?? null;
|
|
@@ -884,7 +1409,12 @@ function verifyPlatformToolsChecksum(zipName, data, progress) {
|
|
|
884
1409
|
progress.onStatus(`BEEOS_ADB_SKIP_CHECKSUM=1 \u2014 skipping SHA-256 verification (no pin recorded for ${zipName}).`);
|
|
885
1410
|
return;
|
|
886
1411
|
}
|
|
887
|
-
throw new
|
|
1412
|
+
throw new BeeosError({
|
|
1413
|
+
code: "adb_checksum_missing",
|
|
1414
|
+
message: `platform-tools download: no SHA-256 pin recorded for "${zipName}".`,
|
|
1415
|
+
hint: `Run scripts/refresh-adb-hashes.mjs ${PLATFORM_TOOLS_REVISION} to populate the table, or set BEEOS_ADB_SKIP_CHECKSUM=1 to override (offline mirror / pre-pin development only; not safe for CI/release).`,
|
|
1416
|
+
details: { archive: zipName, revision: PLATFORM_TOOLS_REVISION }
|
|
1417
|
+
});
|
|
888
1418
|
}
|
|
889
1419
|
if (actual === expected)
|
|
890
1420
|
return;
|
|
@@ -892,23 +1422,20 @@ function verifyPlatformToolsChecksum(zipName, data, progress) {
|
|
|
892
1422
|
progress.onStatus(`BEEOS_ADB_SKIP_CHECKSUM=1 \u2014 mismatched SHA-256 IGNORED: got ${actual.slice(0, 16)}\u2026, expected ${expected.slice(0, 16)}\u2026.`);
|
|
893
1423
|
return;
|
|
894
1424
|
}
|
|
895
|
-
throw new
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
- Supply-chain tampering (investigate before overriding)
|
|
904
|
-
Set BEEOS_ADB_SKIP_CHECKSUM=1 to bypass (not recommended).`);
|
|
905
|
-
}
|
|
906
|
-
var PLATFORM_TOOLS_BASE, PLATFORM_TOOLS_REVISION, PLATFORM_TOOLS_ARCHIVE, PLATFORM_TOOLS_SHA256;
|
|
1425
|
+
throw new BeeosError({
|
|
1426
|
+
code: "adb_checksum_mismatch",
|
|
1427
|
+
message: `platform-tools SHA-256 mismatch for ${zipName}.`,
|
|
1428
|
+
hint: "Likely causes:\n - CDN mirror served a different revision than we pinned (rotate via scripts/refresh-adb-hashes.mjs after cross-checking against https://dl.google.com/android/repository/repository2-3.xml)\n - Archive corrupted in transit (retry)\n - Supply-chain tampering (investigate before overriding)\nSet BEEOS_ADB_SKIP_CHECKSUM=1 to bypass (not recommended).",
|
|
1429
|
+
details: { archive: zipName, expected, actual }
|
|
1430
|
+
});
|
|
1431
|
+
}
|
|
1432
|
+
var PLATFORM_TOOLS_BASE, PLATFORM_TOOLS_REVISION, PLATFORM_TOOLS_ARCHIVE, PLATFORM_TOOLS_SHA256, ADB_LICENSE_URL;
|
|
907
1433
|
var init_adb_setup = __esm({
|
|
908
1434
|
"../core/dist/adb-setup.js"() {
|
|
909
1435
|
"use strict";
|
|
910
1436
|
init_platform_adapter();
|
|
911
1437
|
init_paths();
|
|
1438
|
+
init_errors();
|
|
912
1439
|
PLATFORM_TOOLS_BASE = "https://dl.google.com/android/repository";
|
|
913
1440
|
PLATFORM_TOOLS_REVISION = "r37.0.0";
|
|
914
1441
|
PLATFORM_TOOLS_ARCHIVE = {
|
|
@@ -917,8 +1444,41 @@ var init_adb_setup = __esm({
|
|
|
917
1444
|
win32: `platform-tools_${PLATFORM_TOOLS_REVISION}-win.zip`
|
|
918
1445
|
};
|
|
919
1446
|
PLATFORM_TOOLS_SHA256 = {
|
|
920
|
-
//
|
|
921
|
-
|
|
1447
|
+
// sha1 (google manifest): 8c4c926d0ca192376b2a04b0318484724319e67c
|
|
1448
|
+
"platform-tools_r37.0.0-darwin.zip": "094a1395683c509fd4d48667da0d8b5ef4d42b2abfcd29f2e8149e2f989357c7",
|
|
1449
|
+
// sha1 (google manifest): bcf323933980a59dccc3f14c339aed5fb2171163
|
|
1450
|
+
"platform-tools_r37.0.0-linux.zip": "198ae156ab285fa555987219af237b31102fefe8b9d2bc274708a8d4f2865a07",
|
|
1451
|
+
// sha1 (google manifest): f29bfb58d0d6f9a57d7dbcba6cc259f9ca6f58f1
|
|
1452
|
+
"platform-tools_r37.0.0-win.zip": "4fe305812db074cea32903a489d061eb4454cbc90a49e8fea677f4b7af764918"
|
|
1453
|
+
};
|
|
1454
|
+
ADB_LICENSE_URL = "https://developer.android.com/studio/terms";
|
|
1455
|
+
}
|
|
1456
|
+
});
|
|
1457
|
+
|
|
1458
|
+
// ../core/dist/runtime/spawn-env.js
|
|
1459
|
+
function buildDeviceAgentSpawnEnv(input) {
|
|
1460
|
+
const env = {};
|
|
1461
|
+
if (input.agentGatewayUrl) {
|
|
1462
|
+
env[DEVICE_AGENT_SPAWN_ENV_KEYS.AGENT_GATEWAY_URL] = input.agentGatewayUrl;
|
|
1463
|
+
}
|
|
1464
|
+
if (input.extra) {
|
|
1465
|
+
for (const [k, v] of Object.entries(input.extra)) {
|
|
1466
|
+
if (v)
|
|
1467
|
+
env[k] = v;
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
return env;
|
|
1471
|
+
}
|
|
1472
|
+
var DEVICE_AGENT_SPAWN_ENV_KEYS;
|
|
1473
|
+
var init_spawn_env2 = __esm({
|
|
1474
|
+
"../core/dist/runtime/spawn-env.js"() {
|
|
1475
|
+
"use strict";
|
|
1476
|
+
DEVICE_AGENT_SPAWN_ENV_KEYS = {
|
|
1477
|
+
/**
|
|
1478
|
+
* Agent Gateway base URL. Used by device-agent for `/bootstrap`,
|
|
1479
|
+
* bridge config discovery, and Message Service token exchange.
|
|
1480
|
+
*/
|
|
1481
|
+
AGENT_GATEWAY_URL: "AGENT_GATEWAY_URL"
|
|
922
1482
|
};
|
|
923
1483
|
}
|
|
924
1484
|
});
|
|
@@ -933,6 +1493,7 @@ var init_device = __esm({
|
|
|
933
1493
|
init_keypair();
|
|
934
1494
|
init_device_setup();
|
|
935
1495
|
init_progress();
|
|
1496
|
+
init_spawn_env2();
|
|
936
1497
|
deviceRuntime = {
|
|
937
1498
|
agentFramework() {
|
|
938
1499
|
return "device";
|
|
@@ -1008,10 +1569,7 @@ var init_device = __esm({
|
|
|
1008
1569
|
const logDir = p.joinPath(beeoHome(), "logs");
|
|
1009
1570
|
await p.mkdir(logDir);
|
|
1010
1571
|
const logFile = p.joinPath(logDir, `device-${serial}.log`);
|
|
1011
|
-
const env = {};
|
|
1012
|
-
if (agentGatewayUrl) {
|
|
1013
|
-
env.AGENT_GATEWAY_URL = agentGatewayUrl;
|
|
1014
|
-
}
|
|
1572
|
+
const env = buildDeviceAgentSpawnEnv({ agentGatewayUrl });
|
|
1015
1573
|
const result = await p.spawn(cmd, args, {
|
|
1016
1574
|
detached: true,
|
|
1017
1575
|
stdoutFile: logFile,
|
|
@@ -1199,7 +1757,9 @@ async function downloadManagedBinary(cfg, target, progress) {
|
|
|
1199
1757
|
if (!data)
|
|
1200
1758
|
return null;
|
|
1201
1759
|
const digestUrl = `${url}.sha256`;
|
|
1760
|
+
const allowUnverified = (process.env?.BEEOS_ALLOW_UNVERIFIED_SIDECAR ?? "").toLowerCase() === "1" || (process.env?.BEEOS_ALLOW_UNVERIFIED_SIDECAR ?? "").toLowerCase() === "true";
|
|
1202
1761
|
let expected = null;
|
|
1762
|
+
let digestFetchError = null;
|
|
1203
1763
|
try {
|
|
1204
1764
|
const digResp = await p.fetch(digestUrl);
|
|
1205
1765
|
if (digResp.ok) {
|
|
@@ -1207,18 +1767,35 @@ async function downloadManagedBinary(cfg, target, progress) {
|
|
|
1207
1767
|
const first = text.split(/\s+/)[0] ?? "";
|
|
1208
1768
|
if (/^[a-f0-9]{64}$/i.test(first))
|
|
1209
1769
|
expected = first.toLowerCase();
|
|
1770
|
+
else
|
|
1771
|
+
digestFetchError = `malformed .sha256 body: ${text.slice(0, 80)}`;
|
|
1772
|
+
} else {
|
|
1773
|
+
digestFetchError = `HTTP ${digResp.status}`;
|
|
1210
1774
|
}
|
|
1211
|
-
} catch {
|
|
1775
|
+
} catch (e) {
|
|
1776
|
+
digestFetchError = e instanceof Error ? e.message : String(e);
|
|
1212
1777
|
}
|
|
1213
1778
|
if (expected) {
|
|
1214
1779
|
const actual = createHash2("sha256").update(data).digest("hex");
|
|
1215
1780
|
if (actual !== expected) {
|
|
1216
1781
|
progress.onStatus(`${cfg.name} checksum mismatch (expected ${expected.slice(0, 12)}\u2026, got ${actual.slice(0, 12)}\u2026) \u2014 aborting install.`);
|
|
1217
|
-
|
|
1782
|
+
throw new BeeosError({
|
|
1783
|
+
code: "sidecar_checksum_mismatch",
|
|
1784
|
+
message: `${cfg.name} archive integrity check failed at ${url}`,
|
|
1785
|
+
hint: "The downloaded archive does not match the publisher's checksum. Possible causes: MITM proxy rewriting the asset, a corrupted CDN cache, or a malicious replacement. Retry once; if it persists, file a bug at the sidecar's release repo.",
|
|
1786
|
+
details: { sidecar: cfg.name, url, expected, actual }
|
|
1787
|
+
});
|
|
1218
1788
|
}
|
|
1219
1789
|
progress.onStatus(`${cfg.name} checksum ok (${expected.slice(0, 12)}\u2026).`);
|
|
1790
|
+
} else if (allowUnverified) {
|
|
1791
|
+
progress.onStatus(`${cfg.name}: no .sha256 sidecar at ${digestUrl} (${digestFetchError ?? "unavailable"}) \u2014 BEEOS_ALLOW_UNVERIFIED_SIDECAR=1 set, continuing without integrity check (NOT recommended).`);
|
|
1220
1792
|
} else {
|
|
1221
|
-
|
|
1793
|
+
throw new BeeosError({
|
|
1794
|
+
code: "sidecar_checksum_missing",
|
|
1795
|
+
message: `${cfg.name}: no .sha256 sidecar at ${digestUrl}` + (digestFetchError ? ` (${digestFetchError})` : ""),
|
|
1796
|
+
hint: "The publisher must ship a .sha256 alongside every release asset (cargo-dist does this by default). If you are on a locked-down network that blocks .sha256 URLs, set BEEOS_ALLOW_UNVERIFIED_SIDECAR=1 to opt out \u2014 but understand you are accepting an unverified binary.",
|
|
1797
|
+
details: { sidecar: cfg.name, archiveUrl: url, digestUrl }
|
|
1798
|
+
});
|
|
1222
1799
|
}
|
|
1223
1800
|
const binDir = p.joinPath(beeoHome(), "bin");
|
|
1224
1801
|
await p.mkdir(binDir);
|
|
@@ -1286,6 +1863,7 @@ var init_cargo_dist = __esm({
|
|
|
1286
1863
|
"use strict";
|
|
1287
1864
|
init_platform_adapter();
|
|
1288
1865
|
init_paths();
|
|
1866
|
+
init_errors();
|
|
1289
1867
|
}
|
|
1290
1868
|
});
|
|
1291
1869
|
|
|
@@ -1549,7 +2127,7 @@ async function writeDeviceAgentConfig(input) {
|
|
|
1549
2127
|
if (p.platform() !== "win32") {
|
|
1550
2128
|
await p.chmod(file, 384);
|
|
1551
2129
|
}
|
|
1552
|
-
return file;
|
|
2130
|
+
return { filePath: file, payload };
|
|
1553
2131
|
}
|
|
1554
2132
|
async function persistAgentConfigBestEffort(input) {
|
|
1555
2133
|
const reporter = input.reporter ?? noopReporter;
|
|
@@ -1563,7 +2141,7 @@ async function persistAgentConfigBestEffort(input) {
|
|
|
1563
2141
|
if (result.keyMissing) {
|
|
1564
2142
|
reporter.onStatus(` VLM API key not configured for instance ${input.instanceId} \u2014 set AROUTER_API_KEY in the dashboard, then run \`beeos device refresh-config\`.`);
|
|
1565
2143
|
}
|
|
1566
|
-
|
|
2144
|
+
return await writeDeviceAgentConfig({
|
|
1567
2145
|
instanceId: input.instanceId,
|
|
1568
2146
|
vlmApiKey: result.vlmApiKey,
|
|
1569
2147
|
vlmModel: result.vlmModel,
|
|
@@ -1573,7 +2151,6 @@ async function persistAgentConfigBestEffort(input) {
|
|
|
1573
2151
|
...input.mobileUse ? { mobileUse: input.mobileUse } : {},
|
|
1574
2152
|
...input.grounder ? { grounder: input.grounder } : {}
|
|
1575
2153
|
});
|
|
1576
|
-
return file;
|
|
1577
2154
|
} catch (e) {
|
|
1578
2155
|
reporter.onStatus(` VLM config not fetched (${e.message}). Agent will run without ARouter \u2014 pass \`--vlm-api-key\` for standalone testing or run \`beeos device refresh-config\` later.`);
|
|
1579
2156
|
return null;
|
|
@@ -1657,6 +2234,9 @@ function hasLocalDetection(driver) {
|
|
|
1657
2234
|
function hasStartupDiagnostics(driver) {
|
|
1658
2235
|
return typeof driver.diagnoseStartup === "function";
|
|
1659
2236
|
}
|
|
2237
|
+
function hasPreflightConflictCheck(driver) {
|
|
2238
|
+
return typeof driver.preflightConflictCheck === "function";
|
|
2239
|
+
}
|
|
1660
2240
|
var init_driver = __esm({
|
|
1661
2241
|
"../core/dist/agent/driver.js"() {
|
|
1662
2242
|
"use strict";
|
|
@@ -1728,10 +2308,16 @@ async function downloadAgent(npmPackage, requestedVersion, agentFramework, progr
|
|
|
1728
2308
|
}
|
|
1729
2309
|
return dest;
|
|
1730
2310
|
}
|
|
2311
|
+
function pluginVersionPin() {
|
|
2312
|
+
const env = globalThis.process?.env;
|
|
2313
|
+
const fromEnv = env?.BEEOS_BEEOS_CLAW_VERSION?.trim();
|
|
2314
|
+
return fromEnv && fromEnv.length > 0 ? fromEnv : void 0;
|
|
2315
|
+
}
|
|
1731
2316
|
async function downloadPlugin(pluginPackage, agentFramework, progress, installedVersion) {
|
|
1732
2317
|
const p = getPlatformAdapter();
|
|
1733
2318
|
const info = await fetchNpmPackageInfo(pluginPackage);
|
|
1734
|
-
const
|
|
2319
|
+
const pin = pluginVersionPin();
|
|
2320
|
+
const version = pin ?? info["dist-tags"]["latest"];
|
|
1735
2321
|
if (!version)
|
|
1736
2322
|
throw new Error("no 'latest' tag for plugin");
|
|
1737
2323
|
if (installedVersion && semverGte(installedVersion, version)) {
|
|
@@ -1852,13 +2438,22 @@ async function locateManagedBinary() {
|
|
|
1852
2438
|
}
|
|
1853
2439
|
async function downloadManagedOpenclaw(reporter, versionOverride) {
|
|
1854
2440
|
reporter.onStatus(`Downloading ${OPENCLAW_NPM_PACKAGE}...`);
|
|
1855
|
-
await downloadAgent(OPENCLAW_NPM_PACKAGE, versionOverride, OPENCLAW_ID, reporter);
|
|
2441
|
+
await downloadAgent(OPENCLAW_NPM_PACKAGE, resolveOpenclawVersion(versionOverride), OPENCLAW_ID, reporter);
|
|
1856
2442
|
const binary = await locateManagedBinary();
|
|
1857
2443
|
if (!binary) {
|
|
1858
2444
|
throw new Error("openclaw binary not found after download \u2014 expected it under ~/.beeos/agents/openclaw/versions/current/");
|
|
1859
2445
|
}
|
|
1860
2446
|
return binary;
|
|
1861
2447
|
}
|
|
2448
|
+
function resolveOpenclawVersion(explicit) {
|
|
2449
|
+
if (explicit && explicit.trim().length > 0)
|
|
2450
|
+
return explicit.trim();
|
|
2451
|
+
const env = globalThis.process?.env;
|
|
2452
|
+
const fromEnv = env?.BEEOS_OPENCLAW_VERSION?.trim();
|
|
2453
|
+
if (fromEnv && fromEnv.length > 0)
|
|
2454
|
+
return fromEnv;
|
|
2455
|
+
return void 0;
|
|
2456
|
+
}
|
|
1862
2457
|
var init_download = __esm({
|
|
1863
2458
|
"../core/dist/openclaw/download.js"() {
|
|
1864
2459
|
"use strict";
|
|
@@ -1887,6 +2482,10 @@ async function readLocalPluginVersion(agentHome2) {
|
|
|
1887
2482
|
}
|
|
1888
2483
|
return null;
|
|
1889
2484
|
}
|
|
2485
|
+
async function isPluginInstalled(agentHome2) {
|
|
2486
|
+
const p = getPlatformAdapter();
|
|
2487
|
+
return p.exists(p.joinPath(agentHome2, "extensions", "beeos-claw"));
|
|
2488
|
+
}
|
|
1890
2489
|
async function findAgent(driver) {
|
|
1891
2490
|
const managed = await locateManagedBinary();
|
|
1892
2491
|
if (managed) {
|
|
@@ -2442,12 +3041,20 @@ function fallbackSystemHome() {
|
|
|
2442
3041
|
async function isOpenclawGatewayRunning() {
|
|
2443
3042
|
return getPlatformAdapter().tcpProbe(OPENCLAW_GATEWAY_HOST, OPENCLAW_GATEWAY_PORT, 500);
|
|
2444
3043
|
}
|
|
3044
|
+
function describeError2(e) {
|
|
3045
|
+
if (e instanceof Error) {
|
|
3046
|
+
const msg = e.message.split(/\r?\n/, 1)[0]?.trim();
|
|
3047
|
+
return msg && msg.length > 0 ? msg : e.name;
|
|
3048
|
+
}
|
|
3049
|
+
return String(e);
|
|
3050
|
+
}
|
|
2445
3051
|
var OpenclawDriver, openClawDriver;
|
|
2446
3052
|
var init_driver2 = __esm({
|
|
2447
3053
|
"../core/dist/openclaw/driver.js"() {
|
|
2448
3054
|
"use strict";
|
|
2449
3055
|
init_platform_adapter();
|
|
2450
3056
|
init_paths();
|
|
3057
|
+
init_detector();
|
|
2451
3058
|
init_constants();
|
|
2452
3059
|
init_config();
|
|
2453
3060
|
init_detect_local();
|
|
@@ -2461,10 +3068,12 @@ var init_driver2 = __esm({
|
|
|
2461
3068
|
displayName = OPENCLAW_DISPLAY_NAME;
|
|
2462
3069
|
async launch(ctx, reporter) {
|
|
2463
3070
|
const p = getPlatformAdapter();
|
|
3071
|
+
const warnings = [];
|
|
2464
3072
|
const picked = await pickLocation(ctx, reporter);
|
|
2465
3073
|
try {
|
|
2466
3074
|
await ensureOpenclawPlugin(picked.home, picked.binary, reporter);
|
|
2467
|
-
} catch {
|
|
3075
|
+
} catch (e) {
|
|
3076
|
+
warnings.push(`plugin: failed to register beeos-claw plugin into ${picked.home} (${describeError2(e)}); the gateway will start but BeeOS-specific tools may not be available. Re-run \`beeos doctor\`.`);
|
|
2468
3077
|
}
|
|
2469
3078
|
const gatewayToken = await resolveGatewayToken(picked.home, picked.isSystemHome);
|
|
2470
3079
|
if (picked.isSystemHome)
|
|
@@ -2491,14 +3100,16 @@ var init_driver2 = __esm({
|
|
|
2491
3100
|
} else if (picked.isSystemHome) {
|
|
2492
3101
|
try {
|
|
2493
3102
|
await configurePluginViaCli(configCtx);
|
|
2494
|
-
} catch {
|
|
3103
|
+
} catch (e) {
|
|
3104
|
+
warnings.push(`live-reconfigure: failed to update beeos-claw plugin section in running ${picked.home} (${describeError2(e)}); the foreign gateway may still be serving an older plugin config. Stop it and re-run \`beeos start openclaw --force\` to force a clean register.`);
|
|
2495
3105
|
}
|
|
2496
3106
|
try {
|
|
2497
3107
|
await restartGatewayViaCli(configCtx);
|
|
2498
|
-
} catch {
|
|
3108
|
+
} catch (e) {
|
|
3109
|
+
warnings.push(`live-reconfigure: failed to restart running gateway after re-configuring (${describeError2(e)}); changes may not take effect until the gateway is restarted manually.`);
|
|
2499
3110
|
}
|
|
2500
3111
|
}
|
|
2501
|
-
|
|
3112
|
+
const spec = buildOpenclawServiceSpec({
|
|
2502
3113
|
agentBinary: picked.binary,
|
|
2503
3114
|
agentHome: picked.home,
|
|
2504
3115
|
agentGatewayUrl: ctx.agentGatewayUrl,
|
|
@@ -2506,6 +3117,7 @@ var init_driver2 = __esm({
|
|
|
2506
3117
|
isSystemHome: picked.isSystemHome,
|
|
2507
3118
|
envOverrides: ctx.envOverrides
|
|
2508
3119
|
});
|
|
3120
|
+
return { spec, warnings };
|
|
2509
3121
|
}
|
|
2510
3122
|
async detectLocal() {
|
|
2511
3123
|
return detectLocalOpenclaw();
|
|
@@ -2513,6 +3125,34 @@ var init_driver2 = __esm({
|
|
|
2513
3125
|
async diagnoseStartup(logPath) {
|
|
2514
3126
|
return diagnoseOpenclawStartup(logPath);
|
|
2515
3127
|
}
|
|
3128
|
+
/**
|
|
3129
|
+
* P0-C of the install-link review: previously this probe lived in
|
|
3130
|
+
* `commands/start.ts` with hard-coded "OpenClaw gateway port" copy
|
|
3131
|
+
* and `identifyGateway()` defaults — the abstraction promise that
|
|
3132
|
+
* "a new framework = a new registry entry" was broken because
|
|
3133
|
+
* adding e.g. Cline / Roo would have triggered a misleading
|
|
3134
|
+
* OpenClaw-flavoured error. The probe is now a driver-owned
|
|
3135
|
+
* capability; the CLI just routes the structured report into a
|
|
3136
|
+
* generic `gateway_port_conflict` BeeosError.
|
|
3137
|
+
*/
|
|
3138
|
+
async preflightConflictCheck(ctx) {
|
|
3139
|
+
const probe = await identifyGateway({ expectedFingerprint: ctx.fingerprint });
|
|
3140
|
+
switch (probe.state) {
|
|
3141
|
+
case "unreachable":
|
|
3142
|
+
return { state: "unreachable" };
|
|
3143
|
+
case "own":
|
|
3144
|
+
return { state: "ours" };
|
|
3145
|
+
case "foreign":
|
|
3146
|
+
return {
|
|
3147
|
+
state: "foreign",
|
|
3148
|
+
detail: `a different beeos-claw gateway (fingerprint ${probe.fingerprint.slice(0, 16)}\u2026, plugin ${probe.pluginVersion || "?"})`
|
|
3149
|
+
};
|
|
3150
|
+
case "unknown":
|
|
3151
|
+
return { state: "unknown", reason: probe.reason };
|
|
3152
|
+
default:
|
|
3153
|
+
return { state: "unknown", reason: `unhandled probe state` };
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
2516
3156
|
};
|
|
2517
3157
|
openClawDriver = new OpenclawDriver();
|
|
2518
3158
|
}
|
|
@@ -2573,23 +3213,27 @@ async function detectOpenclaw() {
|
|
|
2573
3213
|
const location = await findAgent(openClawDriver);
|
|
2574
3214
|
const running = await getPlatformAdapter().tcpProbe(OPENCLAW_GATEWAY_HOST, OPENCLAW_GATEWAY_PORT, 500);
|
|
2575
3215
|
if (location.type === "managed") {
|
|
3216
|
+
const pluginInstalled = await isPluginInstalled(location.home).catch(() => false);
|
|
2576
3217
|
return {
|
|
2577
3218
|
found: true,
|
|
2578
3219
|
source: "managed",
|
|
2579
3220
|
binary: location.binary,
|
|
2580
3221
|
home: location.home,
|
|
2581
3222
|
version: null,
|
|
2582
|
-
gatewayRunning: running
|
|
3223
|
+
gatewayRunning: running,
|
|
3224
|
+
pluginInstalled
|
|
2583
3225
|
};
|
|
2584
3226
|
}
|
|
2585
3227
|
if (location.type === "system") {
|
|
3228
|
+
const pluginInstalled = await isPluginInstalled(location.home).catch(() => false);
|
|
2586
3229
|
return {
|
|
2587
3230
|
found: true,
|
|
2588
3231
|
source: "system",
|
|
2589
3232
|
binary: location.binary,
|
|
2590
3233
|
home: location.home,
|
|
2591
3234
|
version: location.version || null,
|
|
2592
|
-
gatewayRunning: running
|
|
3235
|
+
gatewayRunning: running,
|
|
3236
|
+
pluginInstalled
|
|
2593
3237
|
};
|
|
2594
3238
|
}
|
|
2595
3239
|
return {
|
|
@@ -2598,7 +3242,8 @@ async function detectOpenclaw() {
|
|
|
2598
3242
|
binary: null,
|
|
2599
3243
|
home: null,
|
|
2600
3244
|
version: null,
|
|
2601
|
-
gatewayRunning: running
|
|
3245
|
+
gatewayRunning: running,
|
|
3246
|
+
pluginInstalled: null
|
|
2602
3247
|
};
|
|
2603
3248
|
}
|
|
2604
3249
|
async function detectDevices() {
|
|
@@ -2669,7 +3314,11 @@ function summarizeExistingInstall(state) {
|
|
|
2669
3314
|
lines.push(`BeeOS home : ${state.beeosHome}${state.beeosHomeExists ? "" : " (not created)"}`);
|
|
2670
3315
|
lines.push(`Identity : ${state.hasIdentity ? state.fingerprint ?? "(keypair present)" : "not created"}`);
|
|
2671
3316
|
lines.push(`Binding : ${state.binding ? `bound \u2192 instance ${state.binding.instance_id}` : "not bound"}`);
|
|
2672
|
-
lines.push(`OpenClaw : ${state.openclaw.found ? `${state.openclaw.source}${state.openclaw.version ? ` v${state.openclaw.version}` : ""} (gateway ${state.openclaw.gatewayRunning ? "running" : "stopped"}
|
|
3317
|
+
lines.push(`OpenClaw : ${state.openclaw.found ? `${state.openclaw.source}${state.openclaw.version ? ` v${state.openclaw.version}` : ""} (gateway ${state.openclaw.gatewayRunning ? "running" : "stopped"}` + // P2-N: surface "installed but plugin missing" so users
|
|
3318
|
+
// who hit a soft-failed launch see the discrepancy on the
|
|
3319
|
+
// first init/doctor pass instead of debugging a generic
|
|
3320
|
+
// "tools not available" later.
|
|
3321
|
+
(state.openclaw.pluginInstalled === false ? ", plugin missing" : "") + `)` : "not installed"}`);
|
|
2673
3322
|
lines.push(`Devices : ${state.devices.entries.length === 0 && state.devices.keyedSerials.length === 0 ? "none attached" : `${state.devices.entries.length} attached, ${state.devices.keyedSerials.length} key(s)`}`);
|
|
2674
3323
|
lines.push(`Supervisor : ${state.supervisor.ipcReachable ? `running (${state.supervisor.targets.length} target(s))` : state.supervisor.targets.length > 0 ? `stopped, ${state.supervisor.targets.length} persisted target(s)` : "not installed"}`);
|
|
2675
3324
|
return lines;
|
|
@@ -2770,6 +3419,7 @@ var init_target_spec = __esm({
|
|
|
2770
3419
|
"use strict";
|
|
2771
3420
|
init_scrcpy_bridge();
|
|
2772
3421
|
init_vnc_bridge();
|
|
3422
|
+
init_spawn_env2();
|
|
2773
3423
|
}
|
|
2774
3424
|
});
|
|
2775
3425
|
|
|
@@ -2969,13 +3619,23 @@ var init_launchd = __esm({
|
|
|
2969
3619
|
await fs.mkdir(path2.dirname(plist), { recursive: true });
|
|
2970
3620
|
await fs.mkdir(path2.dirname(logFile), { recursive: true });
|
|
2971
3621
|
const resolvedSpec = await absolutizeCommand(spec);
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
3622
|
+
const newContent = renderPlist(resolvedSpec, label, logFile);
|
|
3623
|
+
let existingContent = null;
|
|
3624
|
+
try {
|
|
3625
|
+
existingContent = await fs.readFile(plist, "utf-8");
|
|
3626
|
+
} catch {
|
|
3627
|
+
}
|
|
3628
|
+
if (existingContent === newContent) {
|
|
3629
|
+
const existing = await this.status(spec.id);
|
|
3630
|
+
if (existing?.running)
|
|
3631
|
+
return existing;
|
|
3632
|
+
}
|
|
3633
|
+
await fs.writeFile(plist, newContent, { mode: 420 });
|
|
3634
|
+
const uid = process.getuid?.();
|
|
3635
|
+
if (uid !== void 0) {
|
|
2977
3636
|
try {
|
|
2978
3637
|
await execFileP("launchctl", ["bootout", `gui/${uid}/${label}`]);
|
|
3638
|
+
await this.waitForBootout(label, uid);
|
|
2979
3639
|
} catch {
|
|
2980
3640
|
}
|
|
2981
3641
|
try {
|
|
@@ -2998,6 +3658,24 @@ var init_launchd = __esm({
|
|
|
2998
3658
|
const status2 = await this.status(spec.id);
|
|
2999
3659
|
return status2 ?? fallbackStatus(spec, label, logFile, false);
|
|
3000
3660
|
}
|
|
3661
|
+
/**
|
|
3662
|
+
* Poll `launchctl print` until the service is no longer registered
|
|
3663
|
+
* with launchd. `bootout` returns as soon as it sends SIGTERM, but
|
|
3664
|
+
* the old process may still be alive (and holding ports) for the
|
|
3665
|
+
* length of its graceful-shutdown window. `print` exits non-zero
|
|
3666
|
+
* once the job is gone, which is the signal we want.
|
|
3667
|
+
*/
|
|
3668
|
+
async waitForBootout(label, uid, timeoutMs = 3e4) {
|
|
3669
|
+
const deadline = Date.now() + timeoutMs;
|
|
3670
|
+
while (Date.now() < deadline) {
|
|
3671
|
+
try {
|
|
3672
|
+
await execFileP("launchctl", ["print", `gui/${uid}/${label}`]);
|
|
3673
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
3674
|
+
} catch {
|
|
3675
|
+
return;
|
|
3676
|
+
}
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3001
3679
|
async uninstall(id) {
|
|
3002
3680
|
const label = launchdLabel(id);
|
|
3003
3681
|
const plist = this.plistPath(label);
|
|
@@ -3968,6 +4646,7 @@ async function migrateLegacySupervisor(mgr) {
|
|
|
3968
4646
|
const supervisorDir = path6.join(home, "supervisor");
|
|
3969
4647
|
const stateFile = path6.join(supervisorDir, "state.json");
|
|
3970
4648
|
const flagFile = path6.join(home, MIGRATION_FLAG);
|
|
4649
|
+
const lockFile = path6.join(home, MIGRATION_LOCK);
|
|
3971
4650
|
if (fsSync4.existsSync(flagFile)) {
|
|
3972
4651
|
return { ran: false, migrated: 0, errors: [], backupPath: null };
|
|
3973
4652
|
}
|
|
@@ -3978,47 +4657,73 @@ async function migrateLegacySupervisor(mgr) {
|
|
|
3978
4657
|
}
|
|
3979
4658
|
return { ran: false, migrated: 0, errors: [], backupPath: null };
|
|
3980
4659
|
}
|
|
3981
|
-
|
|
3982
|
-
try {
|
|
3983
|
-
await fs5.copyFile(stateFile, backupPath);
|
|
3984
|
-
} catch {
|
|
3985
|
-
}
|
|
3986
|
-
let parsed;
|
|
3987
|
-
try {
|
|
3988
|
-
const raw = await fs5.readFile(stateFile, "utf-8");
|
|
3989
|
-
parsed = JSON.parse(raw);
|
|
3990
|
-
} catch (e) {
|
|
4660
|
+
if (!await acquireLock(lockFile)) {
|
|
3991
4661
|
return {
|
|
3992
4662
|
ran: true,
|
|
3993
4663
|
migrated: 0,
|
|
3994
|
-
errors: [{
|
|
3995
|
-
|
|
4664
|
+
errors: [{
|
|
4665
|
+
id: "<lock>",
|
|
4666
|
+
error: `migration-in-progress.lock held by another beeos process (${lockFile}); rerun once it exits`
|
|
4667
|
+
}],
|
|
4668
|
+
backupPath: null
|
|
3996
4669
|
};
|
|
3997
4670
|
}
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
let migrated = 0;
|
|
4001
|
-
for (const t of targets) {
|
|
4002
|
-
const canonicalId = safeId(t.id);
|
|
4671
|
+
try {
|
|
4672
|
+
const backupPath = `${stateFile}.migration-backup-${Date.now()}`;
|
|
4003
4673
|
try {
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
restart: t.restart ?? "on-failure",
|
|
4012
|
-
label: t.label
|
|
4013
|
-
};
|
|
4014
|
-
await mgr.install(spec);
|
|
4015
|
-
migrated++;
|
|
4674
|
+
await fs5.copyFile(stateFile, backupPath);
|
|
4675
|
+
} catch {
|
|
4676
|
+
}
|
|
4677
|
+
let parsed;
|
|
4678
|
+
try {
|
|
4679
|
+
const raw = await fs5.readFile(stateFile, "utf-8");
|
|
4680
|
+
parsed = JSON.parse(raw);
|
|
4016
4681
|
} catch (e) {
|
|
4017
|
-
|
|
4682
|
+
return {
|
|
4683
|
+
ran: true,
|
|
4684
|
+
migrated: 0,
|
|
4685
|
+
errors: [{ id: "<state.json>", error: `unreadable: ${e}` }],
|
|
4686
|
+
backupPath
|
|
4687
|
+
};
|
|
4018
4688
|
}
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4689
|
+
const targets = parsed.targets ?? [];
|
|
4690
|
+
const errors = [];
|
|
4691
|
+
const installed = [];
|
|
4692
|
+
for (const t of targets) {
|
|
4693
|
+
const canonicalId = safeId(t.id);
|
|
4694
|
+
try {
|
|
4695
|
+
const spec = {
|
|
4696
|
+
id: canonicalId,
|
|
4697
|
+
kind: t.kind,
|
|
4698
|
+
command: t.command,
|
|
4699
|
+
args: t.args ?? [],
|
|
4700
|
+
env: t.env,
|
|
4701
|
+
cwd: t.cwd,
|
|
4702
|
+
restart: t.restart ?? "on-failure",
|
|
4703
|
+
label: t.label
|
|
4704
|
+
};
|
|
4705
|
+
await mgr.install(spec);
|
|
4706
|
+
installed.push(canonicalId);
|
|
4707
|
+
} catch (e) {
|
|
4708
|
+
errors.push({ id: canonicalId, error: e instanceof Error ? e.message : String(e) });
|
|
4709
|
+
break;
|
|
4710
|
+
}
|
|
4711
|
+
}
|
|
4712
|
+
if (errors.length > 0) {
|
|
4713
|
+
for (const id of installed) {
|
|
4714
|
+
try {
|
|
4715
|
+
await mgr.uninstall(id);
|
|
4716
|
+
} catch {
|
|
4717
|
+
}
|
|
4718
|
+
}
|
|
4719
|
+
return {
|
|
4720
|
+
ran: true,
|
|
4721
|
+
migrated: 0,
|
|
4722
|
+
errors,
|
|
4723
|
+
backupPath
|
|
4724
|
+
};
|
|
4725
|
+
}
|
|
4726
|
+
await stopLegacyDaemon();
|
|
4022
4727
|
try {
|
|
4023
4728
|
await fs5.writeFile(flagFile, (/* @__PURE__ */ new Date()).toISOString());
|
|
4024
4729
|
} catch {
|
|
@@ -4027,8 +4732,56 @@ async function migrateLegacySupervisor(mgr) {
|
|
|
4027
4732
|
await fs5.rm(supervisorDir, { recursive: true, force: true });
|
|
4028
4733
|
} catch {
|
|
4029
4734
|
}
|
|
4735
|
+
return { ran: true, migrated: installed.length, errors: [], backupPath };
|
|
4736
|
+
} finally {
|
|
4737
|
+
try {
|
|
4738
|
+
await fs5.unlink(lockFile);
|
|
4739
|
+
} catch {
|
|
4740
|
+
}
|
|
4741
|
+
}
|
|
4742
|
+
}
|
|
4743
|
+
async function acquireLock(lockFile) {
|
|
4744
|
+
try {
|
|
4745
|
+
const handle = await fs5.open(lockFile, "wx");
|
|
4746
|
+
try {
|
|
4747
|
+
await handle.write(`${process.pid}
|
|
4748
|
+
${(/* @__PURE__ */ new Date()).toISOString()}
|
|
4749
|
+
`);
|
|
4750
|
+
} finally {
|
|
4751
|
+
await handle.close();
|
|
4752
|
+
}
|
|
4753
|
+
return true;
|
|
4754
|
+
} catch (e) {
|
|
4755
|
+
if (e.code !== "EEXIST") {
|
|
4756
|
+
return false;
|
|
4757
|
+
}
|
|
4758
|
+
}
|
|
4759
|
+
let stale = false;
|
|
4760
|
+
try {
|
|
4761
|
+
const stat = await fs5.stat(lockFile);
|
|
4762
|
+
stale = Date.now() - stat.mtimeMs > STALE_LOCK_AGE_MS;
|
|
4763
|
+
} catch {
|
|
4764
|
+
stale = true;
|
|
4765
|
+
}
|
|
4766
|
+
if (!stale)
|
|
4767
|
+
return false;
|
|
4768
|
+
try {
|
|
4769
|
+
await fs5.unlink(lockFile);
|
|
4770
|
+
} catch {
|
|
4771
|
+
}
|
|
4772
|
+
try {
|
|
4773
|
+
const handle = await fs5.open(lockFile, "wx");
|
|
4774
|
+
try {
|
|
4775
|
+
await handle.write(`${process.pid}
|
|
4776
|
+
${(/* @__PURE__ */ new Date()).toISOString()}
|
|
4777
|
+
`);
|
|
4778
|
+
} finally {
|
|
4779
|
+
await handle.close();
|
|
4780
|
+
}
|
|
4781
|
+
return true;
|
|
4782
|
+
} catch {
|
|
4783
|
+
return false;
|
|
4030
4784
|
}
|
|
4031
|
-
return { ran: true, migrated, errors, backupPath };
|
|
4032
4785
|
}
|
|
4033
4786
|
async function stopLegacyDaemon() {
|
|
4034
4787
|
if (process.platform === "darwin") {
|
|
@@ -4063,7 +4816,7 @@ async function stopLegacyDaemon() {
|
|
|
4063
4816
|
}
|
|
4064
4817
|
}
|
|
4065
4818
|
}
|
|
4066
|
-
var execFileP5, LEGACY_LABEL, LEGACY_SYSTEMD_UNIT, MIGRATION_FLAG;
|
|
4819
|
+
var execFileP5, LEGACY_LABEL, LEGACY_SYSTEMD_UNIT, MIGRATION_FLAG, MIGRATION_LOCK, STALE_LOCK_AGE_MS;
|
|
4067
4820
|
var init_migrate = __esm({
|
|
4068
4821
|
"../core/dist/services/migrate.js"() {
|
|
4069
4822
|
"use strict";
|
|
@@ -4073,11 +4826,91 @@ var init_migrate = __esm({
|
|
|
4073
4826
|
LEGACY_LABEL = "ai.beeos.supervisor";
|
|
4074
4827
|
LEGACY_SYSTEMD_UNIT = "beeos-supervisor.service";
|
|
4075
4828
|
MIGRATION_FLAG = "migrated-to-services.flag";
|
|
4829
|
+
MIGRATION_LOCK = "migration-in-progress.lock";
|
|
4830
|
+
STALE_LOCK_AGE_MS = 5 * 60 * 1e3;
|
|
4831
|
+
}
|
|
4832
|
+
});
|
|
4833
|
+
|
|
4834
|
+
// ../core/dist/services/tail-logs.js
|
|
4835
|
+
import fs6 from "fs/promises";
|
|
4836
|
+
async function readLastLines(file, n) {
|
|
4837
|
+
let text = "";
|
|
4838
|
+
let size = 0;
|
|
4839
|
+
try {
|
|
4840
|
+
text = await fs6.readFile(file, "utf8");
|
|
4841
|
+
const stat = await fs6.stat(file);
|
|
4842
|
+
size = stat.size;
|
|
4843
|
+
} catch (e) {
|
|
4844
|
+
if (e.code !== "ENOENT")
|
|
4845
|
+
throw e;
|
|
4846
|
+
}
|
|
4847
|
+
if (text === "")
|
|
4848
|
+
return { text: "", size };
|
|
4849
|
+
const trimmed = text.endsWith("\n") ? text.slice(0, -1) : text;
|
|
4850
|
+
const lines = trimmed.split("\n");
|
|
4851
|
+
const sliced = lines.slice(Math.max(0, lines.length - n));
|
|
4852
|
+
return { text: sliced.join("\n") + "\n", size };
|
|
4853
|
+
}
|
|
4854
|
+
async function tailLogFile(opts) {
|
|
4855
|
+
const write = opts.write ?? ((s) => process.stdout.write(s));
|
|
4856
|
+
const prefix = opts.prefix ?? "";
|
|
4857
|
+
const initial = opts.initialLines ?? 50;
|
|
4858
|
+
const pollMs = opts.pollIntervalMs ?? 200;
|
|
4859
|
+
const writePrefixed = (chunk) => {
|
|
4860
|
+
if (chunk.length === 0)
|
|
4861
|
+
return;
|
|
4862
|
+
if (prefix === "") {
|
|
4863
|
+
write(chunk);
|
|
4864
|
+
return;
|
|
4865
|
+
}
|
|
4866
|
+
const endsWithNewline = chunk.endsWith("\n");
|
|
4867
|
+
const lines = (endsWithNewline ? chunk.slice(0, -1) : chunk).split("\n");
|
|
4868
|
+
const out = lines.map((l) => `${prefix}${l}`).join("\n");
|
|
4869
|
+
write(endsWithNewline ? out + "\n" : out);
|
|
4870
|
+
};
|
|
4871
|
+
const initialRead = await readLastLines(opts.logFile, initial);
|
|
4872
|
+
writePrefixed(initialRead.text);
|
|
4873
|
+
if (!opts.follow)
|
|
4874
|
+
return;
|
|
4875
|
+
let lastSize = initialRead.size;
|
|
4876
|
+
while (!opts.signal?.aborted) {
|
|
4877
|
+
await new Promise((resolve) => setTimeout(resolve, pollMs));
|
|
4878
|
+
if (opts.signal?.aborted)
|
|
4879
|
+
break;
|
|
4880
|
+
let stat;
|
|
4881
|
+
try {
|
|
4882
|
+
stat = await fs6.stat(opts.logFile);
|
|
4883
|
+
} catch (e) {
|
|
4884
|
+
if (e.code === "ENOENT") {
|
|
4885
|
+
lastSize = 0;
|
|
4886
|
+
continue;
|
|
4887
|
+
}
|
|
4888
|
+
throw e;
|
|
4889
|
+
}
|
|
4890
|
+
if (stat.size === lastSize)
|
|
4891
|
+
continue;
|
|
4892
|
+
if (stat.size < lastSize) {
|
|
4893
|
+
lastSize = 0;
|
|
4894
|
+
}
|
|
4895
|
+
const handle = await fs6.open(opts.logFile, "r");
|
|
4896
|
+
try {
|
|
4897
|
+
const buf = Buffer.alloc(stat.size - lastSize);
|
|
4898
|
+
const { bytesRead } = await handle.read(buf, 0, buf.length, lastSize);
|
|
4899
|
+
writePrefixed(buf.subarray(0, bytesRead).toString("utf8"));
|
|
4900
|
+
lastSize = lastSize + bytesRead;
|
|
4901
|
+
} finally {
|
|
4902
|
+
await handle.close();
|
|
4903
|
+
}
|
|
4904
|
+
}
|
|
4905
|
+
}
|
|
4906
|
+
var init_tail_logs = __esm({
|
|
4907
|
+
"../core/dist/services/tail-logs.js"() {
|
|
4908
|
+
"use strict";
|
|
4076
4909
|
}
|
|
4077
4910
|
});
|
|
4078
4911
|
|
|
4079
4912
|
// ../core/dist/cli-version.js
|
|
4080
|
-
import
|
|
4913
|
+
import fs7 from "fs";
|
|
4081
4914
|
import path7 from "path";
|
|
4082
4915
|
import { fileURLToPath } from "url";
|
|
4083
4916
|
function getCliVersion(moduleUrl, distTag) {
|
|
@@ -4105,8 +4938,8 @@ function readBakedDistTag() {
|
|
|
4105
4938
|
let dir = here;
|
|
4106
4939
|
for (let i = 0; i < 6; i++) {
|
|
4107
4940
|
const candidate = path7.join(dir, "package.json");
|
|
4108
|
-
if (
|
|
4109
|
-
const raw =
|
|
4941
|
+
if (fs7.existsSync(candidate)) {
|
|
4942
|
+
const raw = fs7.readFileSync(candidate, "utf-8");
|
|
4110
4943
|
const pkg = JSON.parse(raw);
|
|
4111
4944
|
if ((pkg.name === "@beeos-ai/cli" || pkg.name === "beeos") && pkg.beeos && typeof pkg.beeos.distTag === "string") {
|
|
4112
4945
|
return pkg.beeos.distTag;
|
|
@@ -4131,8 +4964,8 @@ function readVersionFromModuleUrl(moduleUrl) {
|
|
|
4131
4964
|
for (let i = 0; i < 6; i++) {
|
|
4132
4965
|
const candidate = path7.join(here, "package.json");
|
|
4133
4966
|
try {
|
|
4134
|
-
if (
|
|
4135
|
-
const raw =
|
|
4967
|
+
if (fs7.existsSync(candidate)) {
|
|
4968
|
+
const raw = fs7.readFileSync(candidate, "utf-8");
|
|
4136
4969
|
const pkg = JSON.parse(raw);
|
|
4137
4970
|
if (typeof pkg.version === "string" && (pkg.name === "@beeos-ai/cli" || pkg.name === "beeos")) {
|
|
4138
4971
|
return pkg.version;
|
|
@@ -4162,17 +4995,21 @@ var init_dist = __esm({
|
|
|
4162
4995
|
init_types();
|
|
4163
4996
|
init_paths();
|
|
4164
4997
|
init_toml();
|
|
4998
|
+
init_spawn_env();
|
|
4165
4999
|
init_binding();
|
|
4166
5000
|
init_gateway_token();
|
|
5001
|
+
init_state();
|
|
4167
5002
|
init_keypair();
|
|
4168
5003
|
init_client();
|
|
4169
5004
|
init_orchestrator();
|
|
4170
5005
|
init_process();
|
|
5006
|
+
init_errors();
|
|
4171
5007
|
init_device_setup();
|
|
4172
5008
|
init_adb_setup();
|
|
4173
5009
|
init_device();
|
|
4174
5010
|
init_scrcpy_bridge();
|
|
4175
5011
|
init_vnc_bridge();
|
|
5012
|
+
init_spawn_env2();
|
|
4176
5013
|
init_agent_config();
|
|
4177
5014
|
init_driver();
|
|
4178
5015
|
init_detector();
|
|
@@ -4187,7 +5024,9 @@ var init_dist = __esm({
|
|
|
4187
5024
|
init_factory();
|
|
4188
5025
|
init_ids();
|
|
4189
5026
|
init_migrate();
|
|
5027
|
+
init_tail_logs();
|
|
4190
5028
|
init_cli_version();
|
|
5029
|
+
init_upgrade();
|
|
4191
5030
|
}
|
|
4192
5031
|
});
|
|
4193
5032
|
|
|
@@ -4266,6 +5105,34 @@ async function notifyFleetReloadBestEffort(baseUrl = FLEET_STATUS_BASE_URL) {
|
|
|
4266
5105
|
return "unknown";
|
|
4267
5106
|
}
|
|
4268
5107
|
}
|
|
5108
|
+
async function notifyFleetRestartDeviceBestEffort(serial, baseUrl = FLEET_STATUS_BASE_URL) {
|
|
5109
|
+
try {
|
|
5110
|
+
const res = await fetch(
|
|
5111
|
+
`${baseUrl}/fleet/devices/${encodeURIComponent(serial)}/restart`,
|
|
5112
|
+
{
|
|
5113
|
+
method: "POST",
|
|
5114
|
+
signal: AbortSignal.timeout(FLEET_NOTIFY_TIMEOUT_MS)
|
|
5115
|
+
}
|
|
5116
|
+
);
|
|
5117
|
+
return res.ok ? "ok" : "unknown";
|
|
5118
|
+
} catch (e) {
|
|
5119
|
+
if (isConnectionRefused(e)) return "not_running";
|
|
5120
|
+
return "unknown";
|
|
5121
|
+
}
|
|
5122
|
+
}
|
|
5123
|
+
async function notifyFleetShutdownBestEffort(baseUrl = FLEET_STATUS_BASE_URL) {
|
|
5124
|
+
try {
|
|
5125
|
+
const res = await fetch(`${baseUrl}/fleet/shutdown`, {
|
|
5126
|
+
method: "POST",
|
|
5127
|
+
signal: AbortSignal.timeout(FLEET_SHUTDOWN_TIMEOUT_MS)
|
|
5128
|
+
});
|
|
5129
|
+
if (res.status === 501) return "unknown";
|
|
5130
|
+
return res.ok ? "ok" : "unknown";
|
|
5131
|
+
} catch (e) {
|
|
5132
|
+
if (isConnectionRefused(e)) return "not_running";
|
|
5133
|
+
return "unknown";
|
|
5134
|
+
}
|
|
5135
|
+
}
|
|
4269
5136
|
function isConnectionRefused(e) {
|
|
4270
5137
|
const code = extractErrorCode(e);
|
|
4271
5138
|
return code === "ECONNREFUSED" || code === "ECONNRESET";
|
|
@@ -4290,12 +5157,13 @@ function printFleetNotRunningHint() {
|
|
|
4290
5157
|
console.log(" device-agent fleet start # foreground (debug-friendly)");
|
|
4291
5158
|
console.log("");
|
|
4292
5159
|
}
|
|
4293
|
-
var FLEET_STATUS_BASE_URL, FLEET_NOTIFY_TIMEOUT_MS;
|
|
5160
|
+
var FLEET_STATUS_BASE_URL, FLEET_NOTIFY_TIMEOUT_MS, FLEET_SHUTDOWN_TIMEOUT_MS;
|
|
4294
5161
|
var init_fleet_notify = __esm({
|
|
4295
5162
|
"src/commands/device/fleet-notify.ts"() {
|
|
4296
5163
|
"use strict";
|
|
4297
5164
|
FLEET_STATUS_BASE_URL = "http://127.0.0.1:7950";
|
|
4298
5165
|
FLEET_NOTIFY_TIMEOUT_MS = 1e3;
|
|
5166
|
+
FLEET_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
4299
5167
|
}
|
|
4300
5168
|
});
|
|
4301
5169
|
|
|
@@ -4355,7 +5223,7 @@ async function removeTargetsForSerial(mgr, serial) {
|
|
|
4355
5223
|
} catch {
|
|
4356
5224
|
}
|
|
4357
5225
|
}
|
|
4358
|
-
var
|
|
5226
|
+
var init_state2 = __esm({
|
|
4359
5227
|
"src/commands/device/state.ts"() {
|
|
4360
5228
|
"use strict";
|
|
4361
5229
|
init_dist();
|
|
@@ -4365,6 +5233,37 @@ var init_state = __esm({
|
|
|
4365
5233
|
// src/commands/device/attach.ts
|
|
4366
5234
|
import { spawn as spawn2 } from "child_process";
|
|
4367
5235
|
import readline from "readline";
|
|
5236
|
+
function resolvePerDeviceAgentGatewayUrl(cfg, override) {
|
|
5237
|
+
if (override === void 0) return resolveAgentGatewayUrl(cfg);
|
|
5238
|
+
const trimmed = override.trim();
|
|
5239
|
+
if (trimmed === "") {
|
|
5240
|
+
throw new BeeosError({
|
|
5241
|
+
code: "invalid_argument",
|
|
5242
|
+
message: "--agent-gateway-url cannot be empty.",
|
|
5243
|
+
hint: "Pass an http(s) URL, e.g. --agent-gateway-url https://agent-gw.staging.beeos.ai"
|
|
5244
|
+
});
|
|
5245
|
+
}
|
|
5246
|
+
let parsed;
|
|
5247
|
+
try {
|
|
5248
|
+
parsed = new URL(trimmed);
|
|
5249
|
+
} catch {
|
|
5250
|
+
throw new BeeosError({
|
|
5251
|
+
code: "invalid_argument",
|
|
5252
|
+
message: `--agent-gateway-url is not a valid URL: ${trimmed}`,
|
|
5253
|
+
hint: "Pass an http(s) URL, e.g. --agent-gateway-url https://agent-gw.staging.beeos.ai",
|
|
5254
|
+
details: { value: trimmed }
|
|
5255
|
+
});
|
|
5256
|
+
}
|
|
5257
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
5258
|
+
throw new BeeosError({
|
|
5259
|
+
code: "invalid_argument",
|
|
5260
|
+
message: `--agent-gateway-url must use http(s); got ${parsed.protocol}`,
|
|
5261
|
+
hint: "Pass an http(s) URL, e.g. --agent-gateway-url https://agent-gw.staging.beeos.ai",
|
|
5262
|
+
details: { value: trimmed, protocol: parsed.protocol }
|
|
5263
|
+
});
|
|
5264
|
+
}
|
|
5265
|
+
return trimmed;
|
|
5266
|
+
}
|
|
4368
5267
|
async function attach(options) {
|
|
4369
5268
|
const cfg = await loadOrCreateConfig();
|
|
4370
5269
|
const reporter = new CliReporter();
|
|
@@ -4388,17 +5287,19 @@ async function attach(options) {
|
|
|
4388
5287
|
return;
|
|
4389
5288
|
}
|
|
4390
5289
|
const mgr = await getServiceManager();
|
|
5290
|
+
let prevInstanceId = null;
|
|
4391
5291
|
await withDeviceLock(async () => {
|
|
4392
5292
|
const state = await loadDeviceState();
|
|
4393
5293
|
const existingIdx = state.devices.findIndex((d) => d.serial === serial);
|
|
4394
5294
|
if (existingIdx >= 0) {
|
|
5295
|
+
prevInstanceId = state.devices[existingIdx].instance_id ?? null;
|
|
4395
5296
|
await removeTargetsForSerial(mgr, serial);
|
|
4396
5297
|
state.devices.splice(existingIdx, 1);
|
|
4397
5298
|
}
|
|
4398
5299
|
const httpPort = nextHttpPort(state, cfg.device.http_port);
|
|
4399
5300
|
const keyFile = deviceRuntime.deviceKeyPath(serial);
|
|
4400
|
-
const agentGatewayUrl =
|
|
4401
|
-
await persistAgentConfigBestEffort({
|
|
5301
|
+
const agentGatewayUrl = resolvePerDeviceAgentGatewayUrl(cfg, options.agentGatewayUrl);
|
|
5302
|
+
const persistedAgentConfig = await persistAgentConfigBestEffort({
|
|
4402
5303
|
agentGatewayUrl,
|
|
4403
5304
|
keyFile,
|
|
4404
5305
|
instanceId,
|
|
@@ -4413,8 +5314,12 @@ async function attach(options) {
|
|
|
4413
5314
|
withVideo,
|
|
4414
5315
|
vncHost: options.vncHost,
|
|
4415
5316
|
vncPort: options.vncPort,
|
|
4416
|
-
vncPassword: options.vncPassword
|
|
5317
|
+
vncPassword: options.vncPassword,
|
|
5318
|
+
agentGatewayUrl
|
|
4417
5319
|
});
|
|
5320
|
+
const fp = fingerprintFromB64(pubkeyB64);
|
|
5321
|
+
const boundAt = Math.floor(Date.now() / 1e3);
|
|
5322
|
+
const backend = options.vncHost ? void 0 : "adb";
|
|
4418
5323
|
state.devices.push({
|
|
4419
5324
|
serial,
|
|
4420
5325
|
name,
|
|
@@ -4428,11 +5333,37 @@ async function attach(options) {
|
|
|
4428
5333
|
// path through fleet → mcp). When self-attach for
|
|
4429
5334
|
// macOS/Linux/Windows lands, this field will be set
|
|
4430
5335
|
// accordingly (`macos`/`linux`/`windows`).
|
|
4431
|
-
backend
|
|
5336
|
+
backend,
|
|
4432
5337
|
vnc_host: options.vncHost,
|
|
4433
|
-
vnc_port: options.vncPort
|
|
5338
|
+
vnc_port: options.vncPort,
|
|
5339
|
+
// P0-D: pin the resolved Agent Gateway URL on the entry so
|
|
5340
|
+
// the fleet supervisor reuses *this* URL on every restart
|
|
5341
|
+
// instead of re-resolving from the (possibly drifting) config
|
|
5342
|
+
// — and so multi-region staging users can attach devices to
|
|
5343
|
+
// different gateways without splitting fleets.
|
|
5344
|
+
agent_gateway_url: agentGatewayUrl
|
|
4434
5345
|
});
|
|
4435
5346
|
await saveDeviceState(state);
|
|
5347
|
+
const stateEntry = {
|
|
5348
|
+
kind: "device",
|
|
5349
|
+
instance_id: instanceId,
|
|
5350
|
+
bound_at: boundAt,
|
|
5351
|
+
fingerprint: fp,
|
|
5352
|
+
agent_gateway_url: agentGatewayUrl,
|
|
5353
|
+
serial,
|
|
5354
|
+
name,
|
|
5355
|
+
key_file: keyFile,
|
|
5356
|
+
http_port: httpPort,
|
|
5357
|
+
video_mode: bridgeInfo.mode,
|
|
5358
|
+
...backend ? { backend } : {},
|
|
5359
|
+
...options.vncHost ? { vnc_host: options.vncHost } : {},
|
|
5360
|
+
...options.vncPort ? { vnc_port: options.vncPort } : {},
|
|
5361
|
+
...persistedAgentConfig ? { agent_config: persistedAgentConfig.payload } : {}
|
|
5362
|
+
};
|
|
5363
|
+
await mutateStateBestEffort(
|
|
5364
|
+
(s) => upsertDeviceAgent(s, withCarriedForwardAgentConfig(s, stateEntry)),
|
|
5365
|
+
{ authority: "devices.json", subject: serial }
|
|
5366
|
+
);
|
|
4436
5367
|
console.log(`Attached device ${serial} (${name}) \u2014 instance ${instanceId}`);
|
|
4437
5368
|
console.log(` local http: :${httpPort} (if enabled)`);
|
|
4438
5369
|
console.log(` logs: device-agent fleet logs ${serial}`);
|
|
@@ -4448,6 +5379,14 @@ async function attach(options) {
|
|
|
4448
5379
|
throw e;
|
|
4449
5380
|
}
|
|
4450
5381
|
});
|
|
5382
|
+
const isRebind = prevInstanceId !== null && prevInstanceId !== instanceId;
|
|
5383
|
+
if (isRebind) {
|
|
5384
|
+
const outcome = await notifyFleetRestartDeviceBestEffort(serial);
|
|
5385
|
+
if (outcome === "not_running") {
|
|
5386
|
+
await maybeNotifyFleetWithHint(cfg);
|
|
5387
|
+
}
|
|
5388
|
+
return;
|
|
5389
|
+
}
|
|
4451
5390
|
await maybeNotifyFleetWithHint(cfg);
|
|
4452
5391
|
}
|
|
4453
5392
|
async function attachAll(cfg, reporter, withVideo, options) {
|
|
@@ -4459,9 +5398,13 @@ async function attachAll(cfg, reporter, withVideo, options) {
|
|
|
4459
5398
|
console.log(`Discovered ${devices.length} device(s) via adb.`);
|
|
4460
5399
|
await deviceRuntime.ensureAgent(reporter);
|
|
4461
5400
|
reporter.stop();
|
|
4462
|
-
const agentGatewayUrl =
|
|
5401
|
+
const agentGatewayUrl = resolvePerDeviceAgentGatewayUrl(cfg, options.agentGatewayUrl);
|
|
4463
5402
|
const mgr = await getServiceManager();
|
|
4464
5403
|
const preState = await loadDeviceState();
|
|
5404
|
+
const prevInstanceIdBySerial = /* @__PURE__ */ new Map();
|
|
5405
|
+
for (const d of preState.devices) {
|
|
5406
|
+
if (d.instance_id) prevInstanceIdBySerial.set(d.serial, d.instance_id);
|
|
5407
|
+
}
|
|
4465
5408
|
const simulatedEntries = preState.devices.map((d) => ({ ...d }));
|
|
4466
5409
|
function reserveHttpPort(serial) {
|
|
4467
5410
|
const port = nextHttpPort({ devices: simulatedEntries }, cfg.device.http_port);
|
|
@@ -4488,7 +5431,8 @@ async function attachAll(cfg, reporter, withVideo, options) {
|
|
|
4488
5431
|
serial: device.serial,
|
|
4489
5432
|
instanceId,
|
|
4490
5433
|
keyFile: deviceRuntime.deviceKeyPath(device.serial),
|
|
4491
|
-
httpPort
|
|
5434
|
+
httpPort,
|
|
5435
|
+
fingerprint: fingerprintFromB64(pubkeyB64)
|
|
4492
5436
|
});
|
|
4493
5437
|
} catch (e) {
|
|
4494
5438
|
console.error(` Failed to bind ${device.serial}: ${e}`);
|
|
@@ -4511,18 +5455,62 @@ async function attachAll(cfg, reporter, withVideo, options) {
|
|
|
4511
5455
|
})
|
|
4512
5456
|
)
|
|
4513
5457
|
);
|
|
5458
|
+
const reboundSerials = [];
|
|
4514
5459
|
await withDeviceLock(async () => {
|
|
4515
5460
|
const state = await loadDeviceState();
|
|
4516
5461
|
const { committed, failures } = commitAttachResults(state, commits);
|
|
4517
5462
|
for (const f of failures) console.error(`Failed to attach ${f.serial}: ${f.error}`);
|
|
5463
|
+
for (const c of committed) {
|
|
5464
|
+
const prev = prevInstanceIdBySerial.get(c.serial);
|
|
5465
|
+
if (prev && prev !== c.entry.instance_id) reboundSerials.push(c.serial);
|
|
5466
|
+
}
|
|
4518
5467
|
for (const c of committed) {
|
|
4519
5468
|
console.log(
|
|
4520
5469
|
`Attached device ${c.serial} \u2014 instance ${c.entry.instance_id}` + (c.videoMode !== "none" ? ` (video: ${c.videoMode})` : "")
|
|
4521
5470
|
);
|
|
4522
5471
|
}
|
|
4523
5472
|
await saveDeviceState(state);
|
|
5473
|
+
for (const c of committed) {
|
|
5474
|
+
const e = c.entry;
|
|
5475
|
+
const mirror = commits.find(
|
|
5476
|
+
(x) => x.ok && x.serial === c.serial
|
|
5477
|
+
).stateMirror;
|
|
5478
|
+
const stateEntry = {
|
|
5479
|
+
kind: "device",
|
|
5480
|
+
instance_id: e.instance_id ?? "",
|
|
5481
|
+
bound_at: mirror.boundAt,
|
|
5482
|
+
fingerprint: mirror.fingerprint,
|
|
5483
|
+
agent_gateway_url: e.agent_gateway_url && e.agent_gateway_url !== "" ? e.agent_gateway_url : mirror.agentGatewayUrl,
|
|
5484
|
+
serial: e.serial,
|
|
5485
|
+
name: e.name,
|
|
5486
|
+
key_file: e.key_file,
|
|
5487
|
+
http_port: e.http_port,
|
|
5488
|
+
...e.video_mode ? { video_mode: e.video_mode } : {},
|
|
5489
|
+
...e.backend ? { backend: e.backend } : {},
|
|
5490
|
+
...e.vnc_host ? { vnc_host: e.vnc_host } : {},
|
|
5491
|
+
...e.vnc_port ? { vnc_port: e.vnc_port } : {},
|
|
5492
|
+
...mirror.agentConfig ? { agent_config: mirror.agentConfig } : {}
|
|
5493
|
+
};
|
|
5494
|
+
await mutateStateBestEffort(
|
|
5495
|
+
(s) => upsertDeviceAgent(s, withCarriedForwardAgentConfig(s, stateEntry)),
|
|
5496
|
+
{ authority: "devices.json", subject: e.serial }
|
|
5497
|
+
);
|
|
5498
|
+
}
|
|
4524
5499
|
console.log(`Attached ${committed.length} device(s); supervision via device-agent fleet.`);
|
|
4525
5500
|
});
|
|
5501
|
+
if (reboundSerials.length > 0) {
|
|
5502
|
+
let anyNotRunning = false;
|
|
5503
|
+
for (const serial of reboundSerials) {
|
|
5504
|
+
const outcome = await notifyFleetRestartDeviceBestEffort(serial);
|
|
5505
|
+
if (outcome === "not_running") anyNotRunning = true;
|
|
5506
|
+
}
|
|
5507
|
+
if (anyNotRunning) {
|
|
5508
|
+
await maybeNotifyFleetWithHint(cfg);
|
|
5509
|
+
} else if (reboundSerials.length < commits.filter((c) => c.ok).length) {
|
|
5510
|
+
await maybeNotifyFleetWithHint(cfg);
|
|
5511
|
+
}
|
|
5512
|
+
return;
|
|
5513
|
+
}
|
|
4526
5514
|
await maybeNotifyFleetWithHint(cfg);
|
|
4527
5515
|
}
|
|
4528
5516
|
function commitAttachResults(state, commits) {
|
|
@@ -4542,10 +5530,10 @@ function commitAttachResults(state, commits) {
|
|
|
4542
5530
|
}
|
|
4543
5531
|
async function runAttachStage2(params) {
|
|
4544
5532
|
const { bind, cfg, mgr, reporter, agentGatewayUrl, withVideo, options } = params;
|
|
4545
|
-
const { serial, instanceId, keyFile, httpPort } = bind;
|
|
5533
|
+
const { serial, instanceId, keyFile, httpPort, fingerprint: fp } = bind;
|
|
4546
5534
|
try {
|
|
4547
5535
|
await removeTargetsForSerial(mgr, serial);
|
|
4548
|
-
await persistAgentConfigBestEffort({
|
|
5536
|
+
const persistedAgentConfig = await persistAgentConfigBestEffort({
|
|
4549
5537
|
agentGatewayUrl,
|
|
4550
5538
|
keyFile,
|
|
4551
5539
|
instanceId,
|
|
@@ -4559,7 +5547,8 @@ async function runAttachStage2(params) {
|
|
|
4559
5547
|
withVideo,
|
|
4560
5548
|
vncHost: options.vncHost,
|
|
4561
5549
|
vncPort: options.vncPort,
|
|
4562
|
-
vncPassword: options.vncPassword
|
|
5550
|
+
vncPassword: options.vncPassword,
|
|
5551
|
+
agentGatewayUrl
|
|
4563
5552
|
});
|
|
4564
5553
|
const entry = {
|
|
4565
5554
|
serial,
|
|
@@ -4570,9 +5559,25 @@ async function runAttachStage2(params) {
|
|
|
4570
5559
|
video_mode: bridgeInfo.mode,
|
|
4571
5560
|
backend: options.vncHost ? void 0 : "adb",
|
|
4572
5561
|
vnc_host: options.vncHost,
|
|
4573
|
-
vnc_port: options.vncPort
|
|
5562
|
+
vnc_port: options.vncPort,
|
|
5563
|
+
// P0-D: see attach() above — every new entry self-pins to the
|
|
5564
|
+
// resolved Agent Gateway URL so the fleet supervisor (and any
|
|
5565
|
+
// future per-device override flows) honour the binding's
|
|
5566
|
+
// origin region.
|
|
5567
|
+
agent_gateway_url: agentGatewayUrl
|
|
5568
|
+
};
|
|
5569
|
+
return {
|
|
5570
|
+
ok: true,
|
|
5571
|
+
serial,
|
|
5572
|
+
entry,
|
|
5573
|
+
videoMode: bridgeInfo.mode,
|
|
5574
|
+
stateMirror: {
|
|
5575
|
+
agentGatewayUrl,
|
|
5576
|
+
fingerprint: fp,
|
|
5577
|
+
boundAt: Math.floor(Date.now() / 1e3),
|
|
5578
|
+
agentConfig: persistedAgentConfig?.payload ?? null
|
|
5579
|
+
}
|
|
4574
5580
|
};
|
|
4575
|
-
return { ok: true, serial, entry, videoMode: bridgeInfo.mode };
|
|
4576
5581
|
} catch (e) {
|
|
4577
5582
|
await rollbackDeviceBind(cfg.platform.api_url, instanceId);
|
|
4578
5583
|
return { ok: false, serial, error: e };
|
|
@@ -4684,7 +5689,7 @@ async function runDeviceAgentFleetEnable(cfg) {
|
|
|
4684
5689
|
}
|
|
4685
5690
|
async function registerVideoBridge(_mgr, reporter, params) {
|
|
4686
5691
|
if (!params.withVideo) return { mode: "none" };
|
|
4687
|
-
const agentGatewayUrl = resolveAgentGatewayUrl(params.cfg);
|
|
5692
|
+
const agentGatewayUrl = params.agentGatewayUrl ?? resolveAgentGatewayUrl(params.cfg);
|
|
4688
5693
|
if (params.vncHost) {
|
|
4689
5694
|
const binary2 = await ensureBridgeBinaryDegraded(
|
|
4690
5695
|
vncBridgeRuntime.ensureInstalled.bind(vncBridgeRuntime),
|
|
@@ -4737,23 +5742,80 @@ async function ensureBridgeBinaryDegraded(fn, reporter, label) {
|
|
|
4737
5742
|
return null;
|
|
4738
5743
|
}
|
|
4739
5744
|
}
|
|
5745
|
+
function withCarriedForwardAgentConfig(state, entry) {
|
|
5746
|
+
if (entry.agent_config) return entry;
|
|
5747
|
+
const previous = state.agents.find(
|
|
5748
|
+
(a) => a.kind === "device" && a.serial === entry.serial
|
|
5749
|
+
);
|
|
5750
|
+
if (!previous?.agent_config) return entry;
|
|
5751
|
+
return { ...entry, agent_config: previous.agent_config };
|
|
5752
|
+
}
|
|
4740
5753
|
async function ensureAdbAvailable(reporter) {
|
|
4741
5754
|
const existing = await findAdb();
|
|
4742
5755
|
if (existing) return;
|
|
4743
5756
|
if (process.env.BEEOS_SKIP_ADB_INSTALL === "1") {
|
|
4744
|
-
throw new
|
|
4745
|
-
"
|
|
4746
|
-
|
|
5757
|
+
throw new BeeosError({
|
|
5758
|
+
code: "adb_install_skipped",
|
|
5759
|
+
message: "adb not found on PATH and BEEOS_SKIP_ADB_INSTALL=1 is set.",
|
|
5760
|
+
hint: "Install Android platform-tools manually and re-run, or unset BEEOS_SKIP_ADB_INSTALL."
|
|
5761
|
+
});
|
|
5762
|
+
}
|
|
5763
|
+
const decision = detectAdbLicenseDecision();
|
|
5764
|
+
if (decision === "rejected") {
|
|
5765
|
+
throw new BeeosError({
|
|
5766
|
+
code: "adb_install_rejected",
|
|
5767
|
+
message: "BEEOS_REJECT_ADB_LICENSE=1 is set \u2014 adb auto-install refused.",
|
|
5768
|
+
hint: `Pre-install Android platform-tools and point $BEEOS_ADB_BIN at it, or unset BEEOS_REJECT_ADB_LICENSE and review the license at ${ADB_LICENSE_URL}.`,
|
|
5769
|
+
details: { licenseUrl: ADB_LICENSE_URL }
|
|
5770
|
+
});
|
|
5771
|
+
}
|
|
5772
|
+
if (decision === "unknown") {
|
|
5773
|
+
const proceed = await confirmAdbLicensePrompt();
|
|
5774
|
+
if (!proceed) {
|
|
5775
|
+
throw new BeeosError({
|
|
5776
|
+
code: "adb_install_declined",
|
|
5777
|
+
message: "adb auto-install declined at the license prompt.",
|
|
5778
|
+
hint: "Pre-install Android platform-tools manually and re-run, or set BEEOS_ACCEPT_ADB_LICENSE=1 in your environment."
|
|
5779
|
+
});
|
|
5780
|
+
}
|
|
4747
5781
|
}
|
|
4748
5782
|
console.log("adb not found \u2014 downloading Android platform-tools (one-time, ~10MB)...");
|
|
4749
5783
|
try {
|
|
4750
5784
|
await ensureAdb(reporter, { autoInstall: true });
|
|
4751
5785
|
} catch (e) {
|
|
4752
|
-
throw new
|
|
4753
|
-
|
|
4754
|
-
|
|
5786
|
+
throw new BeeosError({
|
|
5787
|
+
code: "adb_install_failed",
|
|
5788
|
+
message: `Failed to install adb automatically: ${String(e)}`,
|
|
5789
|
+
hint: "Workarounds:\n \u2022 macOS: brew install android-platform-tools\n \u2022 Linux: apt install android-tools-adb (or equivalent)\n \u2022 Windows: install Android SDK Platform-Tools from https://developer.android.com/tools/releases/platform-tools\n \u2022 Or point $BEEOS_ADB_BIN at an existing adb binary.",
|
|
5790
|
+
cause: e
|
|
5791
|
+
});
|
|
5792
|
+
}
|
|
5793
|
+
}
|
|
5794
|
+
async function confirmAdbLicensePrompt() {
|
|
5795
|
+
const isTty = Boolean(process.stdout.isTTY);
|
|
5796
|
+
if (!isTty) {
|
|
5797
|
+
console.log(
|
|
5798
|
+
` Android platform-tools license: ${ADB_LICENSE_URL}
|
|
5799
|
+
Non-interactive shell \u2014 auto-accepting. Set BEEOS_ACCEPT_ADB_LICENSE=1
|
|
5800
|
+
in your environment to silence this banner.`
|
|
4755
5801
|
);
|
|
5802
|
+
return true;
|
|
4756
5803
|
}
|
|
5804
|
+
console.log(
|
|
5805
|
+
`
|
|
5806
|
+
Android SDK Platform-Tools (adb) is licensed under the Android SDK License Agreement:
|
|
5807
|
+
${ADB_LICENSE_URL}
|
|
5808
|
+
Set BEEOS_ACCEPT_ADB_LICENSE=1 in your environment to skip this prompt next time.
|
|
5809
|
+
`
|
|
5810
|
+
);
|
|
5811
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
5812
|
+
const ans = await new Promise((resolve) => {
|
|
5813
|
+
rl.question("Agree to the Android SDK license and download platform-tools? [Y/n]: ", (a) => {
|
|
5814
|
+
rl.close();
|
|
5815
|
+
resolve(a);
|
|
5816
|
+
});
|
|
5817
|
+
});
|
|
5818
|
+
return /^(y(es)?|)$/i.test(ans.trim());
|
|
4757
5819
|
}
|
|
4758
5820
|
var init_attach = __esm({
|
|
4759
5821
|
"src/commands/device/attach.ts"() {
|
|
@@ -4762,7 +5824,7 @@ var init_attach = __esm({
|
|
|
4762
5824
|
init_progress2();
|
|
4763
5825
|
init_fallback_banner();
|
|
4764
5826
|
init_fleet_notify();
|
|
4765
|
-
|
|
5827
|
+
init_state2();
|
|
4766
5828
|
}
|
|
4767
5829
|
});
|
|
4768
5830
|
|
|
@@ -4814,7 +5876,7 @@ var init_detach = __esm({
|
|
|
4814
5876
|
"use strict";
|
|
4815
5877
|
init_dist();
|
|
4816
5878
|
init_fleet_notify();
|
|
4817
|
-
|
|
5879
|
+
init_state2();
|
|
4818
5880
|
}
|
|
4819
5881
|
});
|
|
4820
5882
|
|
|
@@ -4856,7 +5918,7 @@ var init_list = __esm({
|
|
|
4856
5918
|
"src/commands/device/list.ts"() {
|
|
4857
5919
|
"use strict";
|
|
4858
5920
|
init_dist();
|
|
4859
|
-
|
|
5921
|
+
init_state2();
|
|
4860
5922
|
}
|
|
4861
5923
|
});
|
|
4862
5924
|
|
|
@@ -4892,14 +5954,49 @@ var init_exec = __esm({
|
|
|
4892
5954
|
"src/commands/device/exec.ts"() {
|
|
4893
5955
|
"use strict";
|
|
4894
5956
|
init_dist();
|
|
4895
|
-
|
|
5957
|
+
init_state2();
|
|
4896
5958
|
}
|
|
4897
5959
|
});
|
|
4898
5960
|
|
|
4899
5961
|
// src/commands/device/upgrade.ts
|
|
4900
5962
|
async function upgrade(options = {}) {
|
|
4901
5963
|
const reporter = new CliReporter();
|
|
4902
|
-
await
|
|
5964
|
+
const outcome = await upgradeBeeosSuite({
|
|
5965
|
+
packages: [NPM_PKGS.DEVICE_AGENT, NPM_PKGS.DEVICE_MCP_SERVER],
|
|
5966
|
+
progress: reporter
|
|
5967
|
+
});
|
|
5968
|
+
reporter.stop();
|
|
5969
|
+
if (outcome.anyFailed) {
|
|
5970
|
+
const failed = outcome.packages.find((p) => p.failed);
|
|
5971
|
+
console.error(
|
|
5972
|
+
`device-agent upgrade had errors: ${failed?.failed ?? "unknown"}
|
|
5973
|
+
Manual fix:
|
|
5974
|
+
npm i -g @beeos-ai/device-agent @beeos-ai/device-mcp-server`
|
|
5975
|
+
);
|
|
5976
|
+
}
|
|
5977
|
+
for (const pkg of outcome.packages) {
|
|
5978
|
+
if (pkg.changed) {
|
|
5979
|
+
console.log(` ${pkg.pkg}: ${pkg.before ?? "(none)"} \u2192 ${pkg.after}`);
|
|
5980
|
+
} else if (!pkg.failed) {
|
|
5981
|
+
console.log(` ${pkg.pkg}: ${pkg.after ?? "(absent)"} (already current)`);
|
|
5982
|
+
}
|
|
5983
|
+
}
|
|
5984
|
+
if (outcome.anyChanged) {
|
|
5985
|
+
const reloadOutcome = await notifyFleetShutdownBestEffort();
|
|
5986
|
+
switch (reloadOutcome) {
|
|
5987
|
+
case "ok":
|
|
5988
|
+
console.log(" Fleet asked to restart \u2014 launchd will bring it up with the new code.");
|
|
5989
|
+
break;
|
|
5990
|
+
case "not_running":
|
|
5991
|
+
console.log(" Fleet is not running \u2014 nothing to restart.");
|
|
5992
|
+
break;
|
|
5993
|
+
case "unknown":
|
|
5994
|
+
console.log(
|
|
5995
|
+
" Fleet restart could not be confirmed; if you see stale behaviour run\n launchctl kickstart -k gui/$(id -u)/ai.beeos.device-fleet # macOS\n or stop & start the fleet manually."
|
|
5996
|
+
);
|
|
5997
|
+
break;
|
|
5998
|
+
}
|
|
5999
|
+
}
|
|
4903
6000
|
if (options.bridges !== false) {
|
|
4904
6001
|
try {
|
|
4905
6002
|
await scrcpyBridgeRuntime.upgrade(reporter);
|
|
@@ -4913,11 +6010,12 @@ async function upgrade(options = {}) {
|
|
|
4913
6010
|
}
|
|
4914
6011
|
}
|
|
4915
6012
|
}
|
|
4916
|
-
var
|
|
6013
|
+
var init_upgrade2 = __esm({
|
|
4917
6014
|
"src/commands/device/upgrade.ts"() {
|
|
4918
6015
|
"use strict";
|
|
4919
6016
|
init_dist();
|
|
4920
6017
|
init_progress2();
|
|
6018
|
+
init_fleet_notify();
|
|
4921
6019
|
}
|
|
4922
6020
|
});
|
|
4923
6021
|
|
|
@@ -4931,7 +6029,6 @@ async function refreshConfig(options = {}) {
|
|
|
4931
6029
|
console.log("No matching devices in ~/.beeos/devices.json \u2014 nothing to refresh.");
|
|
4932
6030
|
return;
|
|
4933
6031
|
}
|
|
4934
|
-
const mgr = await getServiceManager();
|
|
4935
6032
|
let okCount = 0;
|
|
4936
6033
|
let failCount = 0;
|
|
4937
6034
|
for (const entry of targets) {
|
|
@@ -4949,27 +6046,44 @@ async function refreshConfig(options = {}) {
|
|
|
4949
6046
|
failCount++;
|
|
4950
6047
|
continue;
|
|
4951
6048
|
}
|
|
4952
|
-
const
|
|
6049
|
+
const result = await persistAgentConfigBestEffort({
|
|
4953
6050
|
agentGatewayUrl,
|
|
4954
6051
|
keyFile: entry.key_file,
|
|
4955
6052
|
instanceId: entry.instance_id,
|
|
4956
6053
|
reporter
|
|
4957
6054
|
});
|
|
4958
|
-
if (!
|
|
6055
|
+
if (!result) {
|
|
4959
6056
|
console.error(
|
|
4960
6057
|
` Refresh failed for ${entry.serial} (see warning above) \u2014 config file left untouched.`
|
|
4961
6058
|
);
|
|
4962
6059
|
failCount++;
|
|
4963
6060
|
continue;
|
|
4964
6061
|
}
|
|
4965
|
-
console.log(` Wrote ${
|
|
4966
|
-
|
|
4967
|
-
|
|
6062
|
+
console.log(` Wrote ${result.filePath}`);
|
|
6063
|
+
const instanceId = entry.instance_id;
|
|
6064
|
+
const payload = result.payload;
|
|
6065
|
+
await mutateStateBestEffort(
|
|
6066
|
+
(state) => {
|
|
6067
|
+
const idx = state.agents.findIndex(
|
|
6068
|
+
(a) => a.kind === "device" && a.instance_id === instanceId
|
|
6069
|
+
);
|
|
6070
|
+
if (idx < 0) return null;
|
|
6071
|
+
const next = { ...state.agents[idx], agent_config: payload };
|
|
6072
|
+
const agents = state.agents.slice();
|
|
6073
|
+
agents[idx] = next;
|
|
6074
|
+
return { ...state, agents };
|
|
6075
|
+
},
|
|
6076
|
+
{ authority: "Per-instance config.json", subject: instanceId }
|
|
6077
|
+
);
|
|
6078
|
+
const outcome = await notifyFleetRestartDeviceBestEffort(entry.serial);
|
|
6079
|
+
if (outcome === "ok") {
|
|
4968
6080
|
console.log(` Restarted device-agent for ${entry.serial}`);
|
|
4969
|
-
}
|
|
4970
|
-
console.
|
|
4971
|
-
`
|
|
6081
|
+
} else if (outcome === "not_running") {
|
|
6082
|
+
console.log(
|
|
6083
|
+
` fleet not running for ${entry.serial} \u2014 config will load on next start`
|
|
4972
6084
|
);
|
|
6085
|
+
} else {
|
|
6086
|
+
console.error(` fleet restart for ${entry.serial} returned an error`);
|
|
4973
6087
|
}
|
|
4974
6088
|
okCount++;
|
|
4975
6089
|
}
|
|
@@ -5000,7 +6114,8 @@ var init_refresh_config = __esm({
|
|
|
5000
6114
|
"use strict";
|
|
5001
6115
|
init_dist();
|
|
5002
6116
|
init_progress2();
|
|
5003
|
-
|
|
6117
|
+
init_fleet_notify();
|
|
6118
|
+
init_state2();
|
|
5004
6119
|
}
|
|
5005
6120
|
});
|
|
5006
6121
|
|
|
@@ -5022,9 +6137,9 @@ var init_device2 = __esm({
|
|
|
5022
6137
|
init_detach();
|
|
5023
6138
|
init_list();
|
|
5024
6139
|
init_exec();
|
|
5025
|
-
|
|
6140
|
+
init_upgrade2();
|
|
5026
6141
|
init_refresh_config();
|
|
5027
|
-
|
|
6142
|
+
init_state2();
|
|
5028
6143
|
}
|
|
5029
6144
|
});
|
|
5030
6145
|
|
|
@@ -5037,7 +6152,7 @@ import {
|
|
|
5037
6152
|
execFile as execFile6,
|
|
5038
6153
|
spawn as nodeSpawn
|
|
5039
6154
|
} from "child_process";
|
|
5040
|
-
import
|
|
6155
|
+
import fs8 from "fs";
|
|
5041
6156
|
import fsp from "fs/promises";
|
|
5042
6157
|
import net from "net";
|
|
5043
6158
|
import os5 from "os";
|
|
@@ -5144,11 +6259,11 @@ var NodePlatformAdapter = class {
|
|
|
5144
6259
|
let stderrFd;
|
|
5145
6260
|
if (options?.stdoutFile) {
|
|
5146
6261
|
await fsp.mkdir(path8.dirname(options.stdoutFile), { recursive: true });
|
|
5147
|
-
stdoutFd =
|
|
6262
|
+
stdoutFd = fs8.openSync(options.stdoutFile, "a");
|
|
5148
6263
|
}
|
|
5149
6264
|
if (options?.stderrFile) {
|
|
5150
6265
|
await fsp.mkdir(path8.dirname(options.stderrFile), { recursive: true });
|
|
5151
|
-
stderrFd = options.stderrFile === options.stdoutFile ? stdoutFd :
|
|
6266
|
+
stderrFd = options.stderrFile === options.stdoutFile ? stdoutFd : fs8.openSync(options.stderrFile, "a");
|
|
5152
6267
|
}
|
|
5153
6268
|
const child = nodeSpawn(cmd, args, {
|
|
5154
6269
|
cwd: options?.cwd,
|
|
@@ -5165,8 +6280,8 @@ var NodePlatformAdapter = class {
|
|
|
5165
6280
|
if (options?.detached) {
|
|
5166
6281
|
child.unref();
|
|
5167
6282
|
}
|
|
5168
|
-
if (stdoutFd != null)
|
|
5169
|
-
if (stderrFd != null && stderrFd !== stdoutFd)
|
|
6283
|
+
if (stdoutFd != null) fs8.closeSync(stdoutFd);
|
|
6284
|
+
if (stderrFd != null && stderrFd !== stdoutFd) fs8.closeSync(stderrFd);
|
|
5170
6285
|
const pid = child.pid;
|
|
5171
6286
|
if (pid == null) {
|
|
5172
6287
|
throw new Error(`Failed to spawn process: ${cmd} ${args.join(" ")}`);
|
|
@@ -5248,6 +6363,17 @@ init_dist();
|
|
|
5248
6363
|
init_progress2();
|
|
5249
6364
|
init_fallback_banner();
|
|
5250
6365
|
import os6 from "os";
|
|
6366
|
+
|
|
6367
|
+
// src/json-envelope.ts
|
|
6368
|
+
init_dist();
|
|
6369
|
+
function jsonOk(data) {
|
|
6370
|
+
return { ok: true, data };
|
|
6371
|
+
}
|
|
6372
|
+
function emitJsonEnvelope(env) {
|
|
6373
|
+
console.log(JSON.stringify(env, null, 2));
|
|
6374
|
+
}
|
|
6375
|
+
|
|
6376
|
+
// src/commands/start.ts
|
|
5251
6377
|
async function run(agentFramework, options) {
|
|
5252
6378
|
const p = getPlatformAdapter();
|
|
5253
6379
|
await ensureDirs();
|
|
@@ -5256,9 +6382,12 @@ async function run(agentFramework, options) {
|
|
|
5256
6382
|
const descriptor = frameworkById(agentFramework);
|
|
5257
6383
|
if (!descriptor || descriptor.status !== "available") {
|
|
5258
6384
|
const avail = availableFrameworks().map((f) => f.id).join(", ");
|
|
5259
|
-
throw new
|
|
5260
|
-
|
|
5261
|
-
|
|
6385
|
+
throw new BeeosError({
|
|
6386
|
+
code: "framework_unavailable",
|
|
6387
|
+
message: `Agent framework '${agentFramework}' is not available.`,
|
|
6388
|
+
hint: `Available frameworks: ${avail}.`,
|
|
6389
|
+
details: { requested: agentFramework, available: avail.split(/,\s*/) }
|
|
6390
|
+
});
|
|
5262
6391
|
}
|
|
5263
6392
|
const driver = descriptor.driver;
|
|
5264
6393
|
const identity = await loadOrCreateIdentity();
|
|
@@ -5266,12 +6395,17 @@ async function run(agentFramework, options) {
|
|
|
5266
6395
|
const pubkey = publicKeyB64(identity);
|
|
5267
6396
|
const keyFile = p.joinPath(beeoHome(), "identity", "keypair.json");
|
|
5268
6397
|
const agentGatewayUrl = resolveAgentGatewayUrl(cfg);
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
6398
|
+
if (hasPreflightConflictCheck(driver) && !options.force) {
|
|
6399
|
+
const report = await driver.preflightConflictCheck({ fingerprint: fp });
|
|
6400
|
+
if (report.state === "foreign" || report.state === "unknown") {
|
|
6401
|
+
const detail = report.state === "foreign" ? report.detail : `an unrecognised process (${report.reason})`;
|
|
6402
|
+
throw new BeeosError({
|
|
6403
|
+
code: "gateway_port_conflict",
|
|
6404
|
+
message: `${driver.displayName} port is already in use by ${detail}.`,
|
|
6405
|
+
hint: `Refusing to install over it \u2014 clobbering would make both agents fight for the same port and leak your fingerprint to the foreign process. Run \`beeos doctor\` to diagnose, or re-run with \`--force\` to terminate the existing listener.`,
|
|
6406
|
+
details: { driver: driver.id, state: report.state }
|
|
6407
|
+
});
|
|
6408
|
+
}
|
|
5275
6409
|
}
|
|
5276
6410
|
reporter.onStatus(`Preparing ${driver.displayName}`);
|
|
5277
6411
|
const ctx = {
|
|
@@ -5281,29 +6415,11 @@ async function run(agentFramework, options) {
|
|
|
5281
6415
|
locationPreference: "auto",
|
|
5282
6416
|
versionOverride: options.version
|
|
5283
6417
|
};
|
|
5284
|
-
const
|
|
6418
|
+
const launchOutcome = await driver.launch(ctx, reporter);
|
|
6419
|
+
const spec = launchOutcome.spec;
|
|
6420
|
+
const launchWarnings = launchOutcome.warnings;
|
|
5285
6421
|
reporter.stop();
|
|
5286
|
-
const mgr = await getServiceManager();
|
|
5287
|
-
maybePrintFallbackBanner(mgr.kind);
|
|
5288
|
-
const status2 = await mgr.install(spec);
|
|
5289
|
-
if (spec.healthcheck) {
|
|
5290
|
-
try {
|
|
5291
|
-
await waitForHealthcheck(spec.healthcheck, 45e3);
|
|
5292
|
-
} catch (err) {
|
|
5293
|
-
if (err instanceof HealthcheckTimeoutError && hasStartupDiagnostics(driver)) {
|
|
5294
|
-
const diag = await driver.diagnoseStartup(serviceLogPath(driver.id));
|
|
5295
|
-
if (diag) {
|
|
5296
|
-
throw new Error(
|
|
5297
|
-
`${diag.reason}
|
|
5298
|
-
${diag.hints.map((h) => ` \u2022 ${h}`).join("\n")}`
|
|
5299
|
-
);
|
|
5300
|
-
}
|
|
5301
|
-
}
|
|
5302
|
-
throw err;
|
|
5303
|
-
}
|
|
5304
|
-
}
|
|
5305
6422
|
const hostname = buildHostname();
|
|
5306
|
-
const gatewayPid = status2.pid ?? void 0;
|
|
5307
6423
|
const cachedBinding = await loadBindingInfo();
|
|
5308
6424
|
const outcome = await bindAgent({
|
|
5309
6425
|
apiUrl: cfg.platform.api_url,
|
|
@@ -5318,52 +6434,94 @@ ${diag.hints.map((h) => ` \u2022 ${h}`).join("\n")}`
|
|
|
5318
6434
|
instance_id: cachedBinding.instance_id
|
|
5319
6435
|
} : null
|
|
5320
6436
|
});
|
|
5321
|
-
if (outcome.status === "bound_offline") {
|
|
5322
|
-
emit(options.json, {
|
|
5323
|
-
status: "bound_offline",
|
|
5324
|
-
public_key: pubkey,
|
|
5325
|
-
fingerprint: fp,
|
|
5326
|
-
gateway_pid: gatewayPid,
|
|
5327
|
-
agent_gateway_url: agentGatewayUrl,
|
|
5328
|
-
instance_id: outcome.instanceId
|
|
5329
|
-
}, `Agent running (offline, cached instance: ${outcome.instanceId})`);
|
|
5330
|
-
return;
|
|
5331
|
-
}
|
|
5332
6437
|
if (outcome.status === "pending_expired") {
|
|
5333
|
-
throw new
|
|
5334
|
-
|
|
5335
|
-
|
|
6438
|
+
throw new BeeosError({
|
|
6439
|
+
code: "bind_pending_expired",
|
|
6440
|
+
message: "Bind approval timed out before the dashboard confirmed it.",
|
|
6441
|
+
hint: "Re-run `beeos start` and approve the new request in the dashboard.",
|
|
6442
|
+
details: { framework: agentFramework }
|
|
6443
|
+
});
|
|
5336
6444
|
}
|
|
6445
|
+
const isOffline = outcome.status === "bound_offline";
|
|
5337
6446
|
const boundInstanceId = outcome.instanceId;
|
|
5338
|
-
|
|
5339
|
-
await saveBindingInfo({
|
|
5340
|
-
fingerprint: fp,
|
|
5341
|
-
instance_id: boundInstanceId,
|
|
5342
|
-
bound_at: Math.floor(Date.now() / 1e3),
|
|
5343
|
-
framework: agentFramework
|
|
5344
|
-
});
|
|
5345
|
-
emit(options.json, {
|
|
5346
|
-
status: "bound",
|
|
5347
|
-
public_key: pubkey,
|
|
5348
|
-
fingerprint: fp,
|
|
5349
|
-
gateway_pid: gatewayPid,
|
|
5350
|
-
agent_gateway_url: agentGatewayUrl,
|
|
5351
|
-
instance_id: boundInstanceId
|
|
5352
|
-
}, `Agent bound to instance: ${boundInstanceId}`);
|
|
5353
|
-
} catch (e) {
|
|
6447
|
+
if (!isOffline) {
|
|
5354
6448
|
try {
|
|
5355
|
-
|
|
5356
|
-
|
|
6449
|
+
const boundAt = Math.floor(Date.now() / 1e3);
|
|
6450
|
+
await saveBindingInfo({
|
|
6451
|
+
fingerprint: fp,
|
|
6452
|
+
instance_id: boundInstanceId,
|
|
6453
|
+
bound_at: boundAt,
|
|
6454
|
+
framework: agentFramework
|
|
6455
|
+
});
|
|
6456
|
+
const entry = {
|
|
6457
|
+
kind: "openclaw",
|
|
6458
|
+
framework: agentFramework,
|
|
6459
|
+
instance_id: boundInstanceId,
|
|
6460
|
+
bound_at: boundAt,
|
|
6461
|
+
fingerprint: fp,
|
|
6462
|
+
agent_gateway_url: agentGatewayUrl
|
|
6463
|
+
};
|
|
6464
|
+
await mutateStateBestEffort(
|
|
6465
|
+
(state) => upsertOpenClawAgent(state, entry),
|
|
6466
|
+
{ authority: "binding.json" }
|
|
6467
|
+
);
|
|
6468
|
+
} catch (e) {
|
|
6469
|
+
try {
|
|
6470
|
+
await deleteInstance(cfg.platform.api_url, boundInstanceId);
|
|
6471
|
+
} catch {
|
|
6472
|
+
}
|
|
6473
|
+
try {
|
|
6474
|
+
await removeBindingInfo();
|
|
6475
|
+
} catch {
|
|
6476
|
+
}
|
|
6477
|
+
throw new BeeosError({
|
|
6478
|
+
code: "bind_post_bind_failed",
|
|
6479
|
+
message: `Agent bound to instance ${boundInstanceId} but post-bind step failed.`,
|
|
6480
|
+
hint: `Best-effort cleanup attempted; if the instance is still visible in the dashboard, delete it manually. Underlying error: ${e instanceof Error ? e.message : String(e)}`,
|
|
6481
|
+
cause: e,
|
|
6482
|
+
details: { instance_id: boundInstanceId }
|
|
6483
|
+
});
|
|
5357
6484
|
}
|
|
6485
|
+
}
|
|
6486
|
+
const isRebind = !isOffline && cachedBinding !== null && cachedBinding.instance_id !== boundInstanceId;
|
|
6487
|
+
const mgr = await getServiceManager();
|
|
6488
|
+
maybePrintFallbackBanner(mgr.kind);
|
|
6489
|
+
let status2 = await mgr.install(spec);
|
|
6490
|
+
if (isRebind) {
|
|
6491
|
+
await mgr.restart(spec.id);
|
|
6492
|
+
const refreshed = await mgr.status(spec.id);
|
|
6493
|
+
if (refreshed) status2 = refreshed;
|
|
6494
|
+
}
|
|
6495
|
+
if (spec.healthcheck) {
|
|
5358
6496
|
try {
|
|
5359
|
-
await
|
|
5360
|
-
} catch {
|
|
6497
|
+
await waitForHealthcheck(spec.healthcheck, 45e3);
|
|
6498
|
+
} catch (err) {
|
|
6499
|
+
if (err instanceof HealthcheckTimeoutError && hasStartupDiagnostics(driver)) {
|
|
6500
|
+
const diag = await driver.diagnoseStartup(serviceLogPath(driver.id));
|
|
6501
|
+
if (diag) {
|
|
6502
|
+
throw new BeeosError({
|
|
6503
|
+
code: "service_install_failed",
|
|
6504
|
+
message: diag.reason,
|
|
6505
|
+
hint: diag.hints.length > 0 ? diag.hints.map((h) => `\u2022 ${h}`).join("\n ") : void 0,
|
|
6506
|
+
cause: err,
|
|
6507
|
+
details: { driver: driver.id }
|
|
6508
|
+
});
|
|
6509
|
+
}
|
|
6510
|
+
}
|
|
6511
|
+
throw err;
|
|
5361
6512
|
}
|
|
5362
|
-
throw new Error(
|
|
5363
|
-
`Agent bound to instance ${boundInstanceId} but post-bind step failed. Attempted best-effort cleanup; if the instance is still visible in the dashboard, delete it manually. Underlying error: ${e}`,
|
|
5364
|
-
{ cause: e }
|
|
5365
|
-
);
|
|
5366
6513
|
}
|
|
6514
|
+
const gatewayPid = status2.pid ?? void 0;
|
|
6515
|
+
emit(options.json, {
|
|
6516
|
+
status: isOffline ? "bound_offline" : "bound",
|
|
6517
|
+
public_key: pubkey,
|
|
6518
|
+
fingerprint: fp,
|
|
6519
|
+
gateway_pid: gatewayPid,
|
|
6520
|
+
agent_gateway_url: agentGatewayUrl,
|
|
6521
|
+
instance_id: boundInstanceId,
|
|
6522
|
+
warnings: launchWarnings.length > 0 ? [...launchWarnings] : void 0
|
|
6523
|
+
}, isOffline ? `Agent running (offline, cached instance: ${boundInstanceId})` : `Agent bound to instance: ${boundInstanceId}`);
|
|
6524
|
+
if (!options.json) printLaunchWarnings(launchWarnings);
|
|
5367
6525
|
}
|
|
5368
6526
|
function buildHostname() {
|
|
5369
6527
|
const machine = os6.hostname();
|
|
@@ -5372,11 +6530,22 @@ function buildHostname() {
|
|
|
5372
6530
|
}
|
|
5373
6531
|
function emit(json, payload, human) {
|
|
5374
6532
|
if (json) {
|
|
5375
|
-
|
|
6533
|
+
emitJsonEnvelope(jsonOk(payload));
|
|
5376
6534
|
} else if (human) {
|
|
5377
6535
|
console.log(human);
|
|
5378
6536
|
}
|
|
5379
6537
|
}
|
|
6538
|
+
function printLaunchWarnings(warnings) {
|
|
6539
|
+
if (warnings.length === 0) return;
|
|
6540
|
+
console.log("");
|
|
6541
|
+
console.log("Warnings:");
|
|
6542
|
+
for (const w of warnings) {
|
|
6543
|
+
console.log(` ! ${w}`);
|
|
6544
|
+
}
|
|
6545
|
+
console.log(
|
|
6546
|
+
" Run `beeos doctor` for a full health report."
|
|
6547
|
+
);
|
|
6548
|
+
}
|
|
5380
6549
|
|
|
5381
6550
|
// src/commands/stop.ts
|
|
5382
6551
|
init_dist();
|
|
@@ -5395,30 +6564,56 @@ async function run2(agentFramework) {
|
|
|
5395
6564
|
|
|
5396
6565
|
// src/commands/status.ts
|
|
5397
6566
|
init_dist();
|
|
5398
|
-
async function run3() {
|
|
6567
|
+
async function run3(options = {}) {
|
|
5399
6568
|
const home = beeoHome();
|
|
5400
6569
|
const cfg = await loadOrCreateConfig();
|
|
5401
|
-
console.log(`BeeOS Home: ${home}`);
|
|
5402
|
-
console.log(`API URL: ${cfg.platform.api_url}`);
|
|
5403
|
-
console.log(`Agent Gateway: ${cfg.platform.agent_gateway_url}`);
|
|
5404
|
-
console.log("");
|
|
5405
6570
|
const p = getPlatformAdapter();
|
|
5406
6571
|
const fpPath = p.joinPath(home, "identity", "fingerprint");
|
|
6572
|
+
let fingerprint2 = null;
|
|
5407
6573
|
if (await p.exists(fpPath)) {
|
|
5408
6574
|
const fp = (await p.readFile(fpPath)).trim();
|
|
5409
|
-
|
|
5410
|
-
} else {
|
|
5411
|
-
console.log("Identity: not yet created");
|
|
6575
|
+
if (fp) fingerprint2 = fp;
|
|
5412
6576
|
}
|
|
5413
|
-
console.log("");
|
|
5414
6577
|
const mgr = await getServiceManager();
|
|
5415
|
-
const
|
|
5416
|
-
console.log(`Service manager: ${mgr.kind}${reason ? ` (${reason})` : ""}`);
|
|
6578
|
+
const fallbackReason2 = activeFallbackReason();
|
|
5417
6579
|
let services = [];
|
|
5418
6580
|
try {
|
|
5419
6581
|
services = await mgr.list();
|
|
5420
6582
|
} catch {
|
|
5421
6583
|
}
|
|
6584
|
+
if (options.json) {
|
|
6585
|
+
emitJsonEnvelope(
|
|
6586
|
+
jsonOk({
|
|
6587
|
+
beeos_home: home,
|
|
6588
|
+
api_url: cfg.platform.api_url,
|
|
6589
|
+
agent_gateway_url: cfg.platform.agent_gateway_url,
|
|
6590
|
+
fingerprint: fingerprint2,
|
|
6591
|
+
service_manager: {
|
|
6592
|
+
kind: mgr.kind,
|
|
6593
|
+
fallback_reason: fallbackReason2 ?? null
|
|
6594
|
+
},
|
|
6595
|
+
services: services.map((s) => ({
|
|
6596
|
+
id: s.id,
|
|
6597
|
+
installed: s.installed,
|
|
6598
|
+
running: s.running,
|
|
6599
|
+
pid: s.pid ?? null,
|
|
6600
|
+
last_exit_code: s.lastExitCode ?? null
|
|
6601
|
+
}))
|
|
6602
|
+
})
|
|
6603
|
+
);
|
|
6604
|
+
return;
|
|
6605
|
+
}
|
|
6606
|
+
console.log(`BeeOS Home: ${home}`);
|
|
6607
|
+
console.log(`API URL: ${cfg.platform.api_url}`);
|
|
6608
|
+
console.log(`Agent Gateway: ${cfg.platform.agent_gateway_url}`);
|
|
6609
|
+
console.log("");
|
|
6610
|
+
if (fingerprint2) {
|
|
6611
|
+
console.log(`Identity fingerprint: ${fingerprint2}`);
|
|
6612
|
+
} else {
|
|
6613
|
+
console.log("Identity: not yet created");
|
|
6614
|
+
}
|
|
6615
|
+
console.log("");
|
|
6616
|
+
console.log(`Service manager: ${mgr.kind}${fallbackReason2 ? ` (${fallbackReason2})` : ""}`);
|
|
5422
6617
|
if (services.length === 0) {
|
|
5423
6618
|
console.log(" (no services registered)");
|
|
5424
6619
|
} else {
|
|
@@ -5435,20 +6630,30 @@ function formatService(s) {
|
|
|
5435
6630
|
// src/commands/update.ts
|
|
5436
6631
|
init_dist();
|
|
5437
6632
|
init_progress2();
|
|
5438
|
-
async function run4(agentFramework) {
|
|
6633
|
+
async function run4(agentFramework, options = {}) {
|
|
5439
6634
|
if (agentFramework === "device") {
|
|
5440
6635
|
const reporter = new CliReporter();
|
|
5441
6636
|
await deviceRuntime.update(reporter);
|
|
5442
6637
|
reporter.stop();
|
|
5443
|
-
|
|
6638
|
+
if (options.json) {
|
|
6639
|
+
emitJsonEnvelope(jsonOk({ framework: "device", action: "updated" }));
|
|
6640
|
+
} else {
|
|
6641
|
+
console.log("device updated");
|
|
6642
|
+
}
|
|
5444
6643
|
return;
|
|
5445
6644
|
}
|
|
5446
6645
|
const descriptor = frameworkById(agentFramework);
|
|
5447
6646
|
if (!descriptor || descriptor.status !== "available") {
|
|
5448
6647
|
const avail = availableFrameworks().map((f) => f.id).join(", ");
|
|
5449
|
-
throw new
|
|
5450
|
-
|
|
5451
|
-
|
|
6648
|
+
throw new BeeosError({
|
|
6649
|
+
code: "framework_unavailable",
|
|
6650
|
+
message: `Unknown agent framework '${agentFramework}'.`,
|
|
6651
|
+
hint: avail.length > 0 ? `Available frameworks: ${avail}.` : "No frameworks are registered \u2014 reinstall the CLI.",
|
|
6652
|
+
details: {
|
|
6653
|
+
requested: agentFramework,
|
|
6654
|
+
available: avail.length > 0 ? avail.split(/,\s*/) : []
|
|
6655
|
+
}
|
|
6656
|
+
});
|
|
5452
6657
|
}
|
|
5453
6658
|
const mgr = await getServiceManager();
|
|
5454
6659
|
const services = await mgr.list().catch(() => []);
|
|
@@ -5457,7 +6662,11 @@ async function run4(agentFramework) {
|
|
|
5457
6662
|
await run2(agentFramework);
|
|
5458
6663
|
}
|
|
5459
6664
|
await run(agentFramework, { force: true });
|
|
5460
|
-
|
|
6665
|
+
if (options.json) {
|
|
6666
|
+
emitJsonEnvelope(jsonOk({ framework: agentFramework, action: "updated" }));
|
|
6667
|
+
} else {
|
|
6668
|
+
console.log(`${agentFramework} updated`);
|
|
6669
|
+
}
|
|
5461
6670
|
}
|
|
5462
6671
|
|
|
5463
6672
|
// src/commands/bind-status.ts
|
|
@@ -5472,21 +6681,39 @@ async function run5(bindId, options) {
|
|
|
5472
6681
|
if (await p.exists(fpPath)) {
|
|
5473
6682
|
const fp = (await p.readFile(fpPath)).trim();
|
|
5474
6683
|
if (fp) {
|
|
6684
|
+
const boundAt = Math.floor(Date.now() / 1e3);
|
|
6685
|
+
const cached2 = await safeLoadBindingInfo();
|
|
6686
|
+
const framework = cached2?.framework?.trim() || "openclaw";
|
|
5475
6687
|
await saveBindingInfo({
|
|
5476
6688
|
fingerprint: fp,
|
|
5477
6689
|
instance_id: resp.instance_id,
|
|
5478
|
-
bound_at:
|
|
6690
|
+
bound_at: boundAt,
|
|
6691
|
+
framework
|
|
5479
6692
|
});
|
|
6693
|
+
const entry = {
|
|
6694
|
+
kind: "openclaw",
|
|
6695
|
+
framework,
|
|
6696
|
+
instance_id: resp.instance_id,
|
|
6697
|
+
bound_at: boundAt,
|
|
6698
|
+
fingerprint: fp,
|
|
6699
|
+
agent_gateway_url: resolveAgentGatewayUrl(cfg)
|
|
6700
|
+
};
|
|
6701
|
+
await mutateStateBestEffort(
|
|
6702
|
+
(state) => upsertOpenClawAgent(state, entry),
|
|
6703
|
+
{ authority: "binding.json" }
|
|
6704
|
+
);
|
|
5480
6705
|
}
|
|
5481
6706
|
}
|
|
5482
6707
|
} catch {
|
|
5483
6708
|
}
|
|
5484
6709
|
}
|
|
5485
6710
|
if (options.json) {
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
|
|
6711
|
+
emitJsonEnvelope(
|
|
6712
|
+
jsonOk({
|
|
6713
|
+
status: resp.status,
|
|
6714
|
+
instance_id: resp.instance_id ?? null
|
|
6715
|
+
})
|
|
6716
|
+
);
|
|
5490
6717
|
} else {
|
|
5491
6718
|
console.log(`Bind status: ${resp.status}`);
|
|
5492
6719
|
if (resp.instance_id) {
|
|
@@ -5494,6 +6721,13 @@ async function run5(bindId, options) {
|
|
|
5494
6721
|
}
|
|
5495
6722
|
}
|
|
5496
6723
|
}
|
|
6724
|
+
async function safeLoadBindingInfo() {
|
|
6725
|
+
try {
|
|
6726
|
+
return await loadBindingInfo();
|
|
6727
|
+
} catch {
|
|
6728
|
+
return null;
|
|
6729
|
+
}
|
|
6730
|
+
}
|
|
5497
6731
|
|
|
5498
6732
|
// src/index.ts
|
|
5499
6733
|
init_device2();
|
|
@@ -5504,7 +6738,7 @@ import readline2 from "readline";
|
|
|
5504
6738
|
|
|
5505
6739
|
// src/commands/doctor.ts
|
|
5506
6740
|
init_dist();
|
|
5507
|
-
import
|
|
6741
|
+
import fs9 from "fs/promises";
|
|
5508
6742
|
import path9 from "path";
|
|
5509
6743
|
var LOG_SIZE_WARN_BYTES = 50 * 1024 * 1024;
|
|
5510
6744
|
async function run6(options) {
|
|
@@ -5521,7 +6755,8 @@ async function run6(options) {
|
|
|
5521
6755
|
}
|
|
5522
6756
|
const resolvedAgentGatewayUrl = resolveAgentGatewayUrl(cfg);
|
|
5523
6757
|
const agentGatewayHealth = await probeAgentGateway(resolvedAgentGatewayUrl);
|
|
5524
|
-
const tools = await collectToolStatus(
|
|
6758
|
+
const tools = await collectToolStatus();
|
|
6759
|
+
const shimReport = await collectShimReport();
|
|
5525
6760
|
const warnings = [];
|
|
5526
6761
|
const hints = [];
|
|
5527
6762
|
if (!state.hasIdentity) {
|
|
@@ -5530,6 +6765,14 @@ async function run6(options) {
|
|
|
5530
6765
|
if (state.openclaw.gatewayRunning && !state.openclaw.found) {
|
|
5531
6766
|
warnings.push("port 18789 is in use but no OpenClaw install detected \u2014 another app may conflict");
|
|
5532
6767
|
}
|
|
6768
|
+
if (state.openclaw.found && state.openclaw.pluginInstalled === false) {
|
|
6769
|
+
warnings.push(
|
|
6770
|
+
`OpenClaw is installed at ${state.openclaw.home ?? "?"} but the beeos-claw plugin is not registered. BeeOS-specific tools may be unavailable to agents until this is fixed.`
|
|
6771
|
+
);
|
|
6772
|
+
hints.push(
|
|
6773
|
+
"re-run `beeos start openclaw --force` to re-install the plugin, or check ~/.beeos/logs/services/openclaw.log for the original failure"
|
|
6774
|
+
);
|
|
6775
|
+
}
|
|
5533
6776
|
if (state.devices.entries.length > 0 && services.length === 0) {
|
|
5534
6777
|
warnings.push(
|
|
5535
6778
|
"devices tracked via legacy devices.json \u2014 no OS services registered. Re-run `beeos device attach` to migrate them to the OS service manager."
|
|
@@ -5572,9 +6815,19 @@ async function run6(options) {
|
|
|
5572
6815
|
"adb not found \u2014 `beeos device attach` will download Android platform-tools on first use"
|
|
5573
6816
|
);
|
|
5574
6817
|
}
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
6818
|
+
for (const e of shimReport.entries) {
|
|
6819
|
+
if (e.outcome === "outdated") {
|
|
6820
|
+
warnings.push(
|
|
6821
|
+
`${e.pkg}: installed ${e.installed ?? "(missing)"}, npm latest ${e.latest}.`
|
|
6822
|
+
);
|
|
6823
|
+
} else if (e.outcome === "missing") {
|
|
6824
|
+
warnings.push(`${e.pkg}: not installed globally \u2014 run 'beeos init' to install.`);
|
|
6825
|
+
} else if (e.outcome === "error") {
|
|
6826
|
+
}
|
|
6827
|
+
}
|
|
6828
|
+
if (shimReport.entries.some((e) => e.outcome === "outdated" || e.outcome === "missing")) {
|
|
6829
|
+
hints.push(
|
|
6830
|
+
"run `beeos init` and pick option [1] to upgrade CLI + agents to the latest npm release"
|
|
5578
6831
|
);
|
|
5579
6832
|
}
|
|
5580
6833
|
const bloatedLogs = await checkLogFileSizes();
|
|
@@ -5585,29 +6838,26 @@ async function run6(options) {
|
|
|
5585
6838
|
hints.push(`truncate safely: : > ${l.path}`);
|
|
5586
6839
|
}
|
|
5587
6840
|
if (options.json) {
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
services
|
|
5602
|
-
},
|
|
5603
|
-
agentGateway: agentGatewayHealth,
|
|
5604
|
-
tools,
|
|
5605
|
-
warnings,
|
|
5606
|
-
hints
|
|
6841
|
+
emitJsonEnvelope(
|
|
6842
|
+
jsonOk({
|
|
6843
|
+
platform: p.platform(),
|
|
6844
|
+
arch: process.arch,
|
|
6845
|
+
node: process.version,
|
|
6846
|
+
beeosHome: state.beeosHome,
|
|
6847
|
+
config: cfg,
|
|
6848
|
+
state,
|
|
6849
|
+
serviceManager: {
|
|
6850
|
+
kind: mgr.kind,
|
|
6851
|
+
available: serviceCheck.available,
|
|
6852
|
+
fallbackReason: fallbackReason2,
|
|
6853
|
+
services
|
|
5607
6854
|
},
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
6855
|
+
agentGateway: agentGatewayHealth,
|
|
6856
|
+
tools,
|
|
6857
|
+
npmShims: shimReport,
|
|
6858
|
+
warnings,
|
|
6859
|
+
hints
|
|
6860
|
+
})
|
|
5611
6861
|
);
|
|
5612
6862
|
return;
|
|
5613
6863
|
}
|
|
@@ -5631,10 +6881,19 @@ async function run6(options) {
|
|
|
5631
6881
|
console.log(` - ${s.id.padEnd(28)} ${status2}${pid}`);
|
|
5632
6882
|
}
|
|
5633
6883
|
console.log(` adb : ${tools.adb.path ?? "not found"}`);
|
|
5634
|
-
console.log(` python3 : ${tools.python.path ?? "not found"}`);
|
|
5635
6884
|
console.log(` scrcpy-bridge: ${tools.scrcpyBridge.path ?? "not installed (auto on attach)"}`);
|
|
5636
6885
|
console.log(` vnc-bridge : ${tools.vncBridge.path ?? "not installed (auto on --vnc-host)"}`);
|
|
5637
6886
|
console.log("");
|
|
6887
|
+
console.log(" npm shims:");
|
|
6888
|
+
for (const e of shimReport.entries) {
|
|
6889
|
+
const marker = shimMarker(e.outcome);
|
|
6890
|
+
const installed = e.installed ?? "(missing)";
|
|
6891
|
+
const latest = e.latest ?? "(unknown)";
|
|
6892
|
+
console.log(
|
|
6893
|
+
` ${marker} ${e.pkg.padEnd(34)} installed=${installed.padEnd(8)} npm=${latest}`
|
|
6894
|
+
);
|
|
6895
|
+
}
|
|
6896
|
+
console.log("");
|
|
5638
6897
|
if (warnings.length > 0) {
|
|
5639
6898
|
console.log(" Warnings:");
|
|
5640
6899
|
for (const w of warnings) {
|
|
@@ -5652,38 +6911,57 @@ async function run6(options) {
|
|
|
5652
6911
|
console.log("");
|
|
5653
6912
|
}
|
|
5654
6913
|
}
|
|
5655
|
-
async function collectToolStatus(
|
|
5656
|
-
const [adbPath,
|
|
6914
|
+
async function collectToolStatus() {
|
|
6915
|
+
const [adbPath, scrcpyPath, vncPath] = await Promise.all([
|
|
5657
6916
|
findAdb().catch(() => null),
|
|
5658
|
-
findPython(p).catch(() => null),
|
|
5659
6917
|
scrcpyBridgeRuntime.findBinary().catch(() => null),
|
|
5660
6918
|
vncBridgeRuntime.findBinary().catch(() => null)
|
|
5661
6919
|
]);
|
|
5662
6920
|
return {
|
|
5663
6921
|
adb: { path: adbPath },
|
|
5664
|
-
python: { path: pythonPath },
|
|
5665
6922
|
scrcpyBridge: { path: scrcpyPath },
|
|
5666
6923
|
vncBridge: { path: vncPath }
|
|
5667
6924
|
};
|
|
5668
6925
|
}
|
|
5669
|
-
async function
|
|
5670
|
-
const
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
const
|
|
5674
|
-
|
|
5675
|
-
|
|
6926
|
+
async function collectShimReport() {
|
|
6927
|
+
const sources = readPinSourcesFromEnv();
|
|
6928
|
+
const entries = await Promise.all(
|
|
6929
|
+
ALL_BEEOS_PKGS.map(async (pkg) => {
|
|
6930
|
+
const spec = resolveInstallSpec(pkg, sources);
|
|
6931
|
+
const [installed, latest] = await Promise.all([
|
|
6932
|
+
npmGlobalVersion(pkg).catch(() => null),
|
|
6933
|
+
npmRegistryVersion(spec).catch(() => null)
|
|
6934
|
+
]);
|
|
6935
|
+
let outcome;
|
|
6936
|
+
if (latest === null) outcome = "error";
|
|
6937
|
+
else if (installed === null) outcome = "missing";
|
|
6938
|
+
else if (installed === latest) outcome = "ok";
|
|
6939
|
+
else outcome = "outdated";
|
|
6940
|
+
if (pkg === NPM_PKGS.CLI && installed === null) {
|
|
6941
|
+
outcome = "missing";
|
|
5676
6942
|
}
|
|
5677
|
-
|
|
5678
|
-
}
|
|
6943
|
+
return { pkg, installed, latest, spec, outcome };
|
|
6944
|
+
})
|
|
6945
|
+
);
|
|
6946
|
+
return { entries };
|
|
6947
|
+
}
|
|
6948
|
+
function shimMarker(outcome) {
|
|
6949
|
+
switch (outcome) {
|
|
6950
|
+
case "ok":
|
|
6951
|
+
return "\u2713";
|
|
6952
|
+
case "outdated":
|
|
6953
|
+
return "\u2191";
|
|
6954
|
+
case "missing":
|
|
6955
|
+
return "\u2717";
|
|
6956
|
+
case "error":
|
|
6957
|
+
return "?";
|
|
5679
6958
|
}
|
|
5680
|
-
return null;
|
|
5681
6959
|
}
|
|
5682
6960
|
async function checkLogFileSizes() {
|
|
5683
6961
|
const dir = path9.join(beeoHome(), "logs", "services");
|
|
5684
6962
|
let entries;
|
|
5685
6963
|
try {
|
|
5686
|
-
entries = await
|
|
6964
|
+
entries = await fs9.readdir(dir);
|
|
5687
6965
|
} catch {
|
|
5688
6966
|
return [];
|
|
5689
6967
|
}
|
|
@@ -5692,7 +6970,7 @@ async function checkLogFileSizes() {
|
|
|
5692
6970
|
if (!name.endsWith(".log")) continue;
|
|
5693
6971
|
const full = path9.join(dir, name);
|
|
5694
6972
|
try {
|
|
5695
|
-
const st = await
|
|
6973
|
+
const st = await fs9.stat(full);
|
|
5696
6974
|
if (st.isFile() && st.size >= LOG_SIZE_WARN_BYTES) {
|
|
5697
6975
|
bloated.push({ path: full, size: st.size });
|
|
5698
6976
|
}
|
|
@@ -5761,6 +7039,7 @@ function formatAgentGatewayStatus(h) {
|
|
|
5761
7039
|
}
|
|
5762
7040
|
|
|
5763
7041
|
// src/commands/init.ts
|
|
7042
|
+
init_progress2();
|
|
5764
7043
|
init_fallback_banner();
|
|
5765
7044
|
async function run7(options) {
|
|
5766
7045
|
await ensureDirs();
|
|
@@ -5791,7 +7070,11 @@ async function run7(options) {
|
|
|
5791
7070
|
return;
|
|
5792
7071
|
}
|
|
5793
7072
|
if (decision === "upgrade") {
|
|
5794
|
-
|
|
7073
|
+
if (await shouldUpgradeBeeosSuite(options)) {
|
|
7074
|
+
const exited = await upgradeAndMaybeExit(options);
|
|
7075
|
+
if (exited) return;
|
|
7076
|
+
}
|
|
7077
|
+
console.log("Ensuring OpenClaw is running...\n");
|
|
5795
7078
|
}
|
|
5796
7079
|
if (decision === "rebind-new-key") {
|
|
5797
7080
|
await rotateIdentity();
|
|
@@ -5801,9 +7084,12 @@ async function run7(options) {
|
|
|
5801
7084
|
const descriptor = frameworkById(frameworkId);
|
|
5802
7085
|
if (!descriptor || descriptor.status !== "available") {
|
|
5803
7086
|
const avail = availableFrameworks().map((f) => f.id).join(", ");
|
|
5804
|
-
throw new
|
|
5805
|
-
|
|
5806
|
-
|
|
7087
|
+
throw new BeeosError({
|
|
7088
|
+
code: "framework_unavailable",
|
|
7089
|
+
message: `Agent framework '${frameworkId}' is not available. Available: ${avail}.`,
|
|
7090
|
+
hint: avail.length > 0 ? `Re-run with --framework <one-of-the-above>` : "No frameworks are registered \u2014 reinstall the CLI.",
|
|
7091
|
+
details: { requested: frameworkId, available: availableFrameworks().map((f) => f.id) }
|
|
7092
|
+
});
|
|
5807
7093
|
}
|
|
5808
7094
|
await run(descriptor.id, {
|
|
5809
7095
|
force: true,
|
|
@@ -5951,6 +7237,60 @@ function prompt(question) {
|
|
|
5951
7237
|
});
|
|
5952
7238
|
});
|
|
5953
7239
|
}
|
|
7240
|
+
async function shouldUpgradeBeeosSuite(options) {
|
|
7241
|
+
if (options.headless) return false;
|
|
7242
|
+
if (process.env.BEEOS_INIT_SKIP_NPM_UPGRADE === "1") return false;
|
|
7243
|
+
return true;
|
|
7244
|
+
}
|
|
7245
|
+
async function upgradeAndMaybeExit(options) {
|
|
7246
|
+
if (options.json) {
|
|
7247
|
+
return false;
|
|
7248
|
+
}
|
|
7249
|
+
console.log("Checking for newer @beeos-ai/cli + agents on npm...");
|
|
7250
|
+
const reporter = new CliReporter();
|
|
7251
|
+
let outcome;
|
|
7252
|
+
try {
|
|
7253
|
+
outcome = await upgradeBeeosSuite({
|
|
7254
|
+
packages: ALL_BEEOS_PKGS,
|
|
7255
|
+
progress: reporter
|
|
7256
|
+
});
|
|
7257
|
+
} catch (e) {
|
|
7258
|
+
reporter.stop();
|
|
7259
|
+
console.log(
|
|
7260
|
+
` (upgrade skipped \u2014 ${e instanceof Error ? e.message : String(e)})
|
|
7261
|
+
Tip: re-run \`npm install -g @beeos-ai/cli @beeos-ai/device-agent @beeos-ai/device-mcp-server\` manually.`
|
|
7262
|
+
);
|
|
7263
|
+
return false;
|
|
7264
|
+
}
|
|
7265
|
+
reporter.stop();
|
|
7266
|
+
if (outcome.anyFailed) {
|
|
7267
|
+
const failed = outcome.packages.find((p) => p.failed)?.failed ?? "unknown error";
|
|
7268
|
+
console.log(` \u26A0 npm install failed: ${failed}`);
|
|
7269
|
+
console.log(
|
|
7270
|
+
" Manual fix:\n npm i -g @beeos-ai/cli @beeos-ai/device-agent @beeos-ai/device-mcp-server\n"
|
|
7271
|
+
);
|
|
7272
|
+
return false;
|
|
7273
|
+
}
|
|
7274
|
+
if (!outcome.anyChanged) {
|
|
7275
|
+
console.log(" All BeeOS npm packages are up to date.\n");
|
|
7276
|
+
return false;
|
|
7277
|
+
}
|
|
7278
|
+
for (const pkg of outcome.packages) {
|
|
7279
|
+
if (pkg.changed) {
|
|
7280
|
+
console.log(` ${pkg.pkg}: ${pkg.before ?? "(none)"} \u2192 ${pkg.after}`);
|
|
7281
|
+
}
|
|
7282
|
+
}
|
|
7283
|
+
const cliEntry = outcome.packages.find((p) => p.pkg === NPM_PKGS.CLI);
|
|
7284
|
+
if (cliEntry?.changed) {
|
|
7285
|
+
console.log("");
|
|
7286
|
+
console.log(" CLI itself was upgraded \u2014 please re-run `beeos init` to continue");
|
|
7287
|
+
console.log(" with the new code. The current process is still running the old version.");
|
|
7288
|
+
console.log("");
|
|
7289
|
+
return true;
|
|
7290
|
+
}
|
|
7291
|
+
console.log(" Agents updated. Continuing init...\n");
|
|
7292
|
+
return false;
|
|
7293
|
+
}
|
|
5954
7294
|
|
|
5955
7295
|
// src/commands/service.ts
|
|
5956
7296
|
init_dist();
|
|
@@ -5959,18 +7299,14 @@ async function install(options) {
|
|
|
5959
7299
|
const check = await mgr.selfCheck();
|
|
5960
7300
|
const reason = activeFallbackReason();
|
|
5961
7301
|
if (options.json) {
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
},
|
|
5971
|
-
null,
|
|
5972
|
-
2
|
|
5973
|
-
)
|
|
7302
|
+
emitJsonEnvelope(
|
|
7303
|
+
jsonOk({
|
|
7304
|
+
available: check.available,
|
|
7305
|
+
manager: mgr.kind,
|
|
7306
|
+
fallbackReason: reason,
|
|
7307
|
+
warnings: check.warnings,
|
|
7308
|
+
hints: check.hints
|
|
7309
|
+
})
|
|
5974
7310
|
);
|
|
5975
7311
|
return;
|
|
5976
7312
|
}
|
|
@@ -6004,7 +7340,7 @@ async function uninstall(options) {
|
|
|
6004
7340
|
}
|
|
6005
7341
|
}
|
|
6006
7342
|
if (options.json) {
|
|
6007
|
-
|
|
7343
|
+
emitJsonEnvelope(jsonOk({ removed, errors }));
|
|
6008
7344
|
return;
|
|
6009
7345
|
}
|
|
6010
7346
|
if (removed.length === 0 && errors.length === 0) {
|
|
@@ -6023,12 +7359,8 @@ async function status(options) {
|
|
|
6023
7359
|
} catch {
|
|
6024
7360
|
}
|
|
6025
7361
|
if (options.json) {
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
{ manager: mgr.kind, fallbackReason: reason, services },
|
|
6029
|
-
null,
|
|
6030
|
-
2
|
|
6031
|
-
)
|
|
7362
|
+
emitJsonEnvelope(
|
|
7363
|
+
jsonOk({ manager: mgr.kind, fallbackReason: reason, services })
|
|
6032
7364
|
);
|
|
6033
7365
|
return;
|
|
6034
7366
|
}
|
|
@@ -6051,13 +7383,15 @@ async function run8(options) {
|
|
|
6051
7383
|
const mgr = await getServiceManager();
|
|
6052
7384
|
const mig = await migrateLegacySupervisor(mgr);
|
|
6053
7385
|
if (options.json) {
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
7386
|
+
emitJsonEnvelope(
|
|
7387
|
+
jsonOk({
|
|
7388
|
+
ran: mig.ran,
|
|
7389
|
+
migrated: mig.migrated,
|
|
7390
|
+
backupPath: mig.backupPath ?? null,
|
|
7391
|
+
errors: mig.errors,
|
|
7392
|
+
serviceManager: mgr.kind
|
|
7393
|
+
})
|
|
7394
|
+
);
|
|
6061
7395
|
return;
|
|
6062
7396
|
}
|
|
6063
7397
|
if (!mig.ran) {
|
|
@@ -6079,6 +7413,98 @@ async function run8(options) {
|
|
|
6079
7413
|
}
|
|
6080
7414
|
}
|
|
6081
7415
|
|
|
7416
|
+
// src/commands/logs.ts
|
|
7417
|
+
init_dist();
|
|
7418
|
+
function resolveLogTarget(input, services) {
|
|
7419
|
+
const exact = services.find((s) => s.id === input);
|
|
7420
|
+
if (exact) {
|
|
7421
|
+
return { id: exact.id, logFile: exact.logFile, matchedFrom: "exact" };
|
|
7422
|
+
}
|
|
7423
|
+
const safe = safeId(input);
|
|
7424
|
+
const safeMatch = services.find((s) => s.id === safe);
|
|
7425
|
+
if (safeMatch) {
|
|
7426
|
+
return { id: safeMatch.id, logFile: safeMatch.logFile, matchedFrom: "safeId" };
|
|
7427
|
+
}
|
|
7428
|
+
const heuristicId = `device-agent-${safe}`;
|
|
7429
|
+
const heuristic = services.find((s) => s.id === heuristicId);
|
|
7430
|
+
if (heuristic) {
|
|
7431
|
+
return { id: heuristic.id, logFile: heuristic.logFile, matchedFrom: "adbSerialHeuristic" };
|
|
7432
|
+
}
|
|
7433
|
+
return {
|
|
7434
|
+
id: safe,
|
|
7435
|
+
logFile: serviceLogPath(safe),
|
|
7436
|
+
matchedFrom: "fallback"
|
|
7437
|
+
};
|
|
7438
|
+
}
|
|
7439
|
+
async function run9(idArg, options) {
|
|
7440
|
+
const mgr = await getServiceManager();
|
|
7441
|
+
let services = [];
|
|
7442
|
+
try {
|
|
7443
|
+
services = await mgr.list();
|
|
7444
|
+
} catch {
|
|
7445
|
+
}
|
|
7446
|
+
if (!idArg) {
|
|
7447
|
+
if (options.json) {
|
|
7448
|
+
emitJsonEnvelope(
|
|
7449
|
+
jsonOk({
|
|
7450
|
+
manager: mgr.kind,
|
|
7451
|
+
services: services.map((s) => ({ id: s.id, logFile: s.logFile }))
|
|
7452
|
+
})
|
|
7453
|
+
);
|
|
7454
|
+
return;
|
|
7455
|
+
}
|
|
7456
|
+
if (services.length === 0) {
|
|
7457
|
+
console.log("No registered BeeOS services. Start one with `beeos start <agent>` or `beeos device attach`.");
|
|
7458
|
+
return;
|
|
7459
|
+
}
|
|
7460
|
+
console.log(`Service manager: ${mgr.kind}`);
|
|
7461
|
+
console.log("Available services:");
|
|
7462
|
+
for (const s of services) {
|
|
7463
|
+
console.log(` ${s.id.padEnd(28)} ${s.logFile}`);
|
|
7464
|
+
}
|
|
7465
|
+
console.log("\nUsage: beeos logs <id> [-f] [-n <lines>]");
|
|
7466
|
+
return;
|
|
7467
|
+
}
|
|
7468
|
+
const target = resolveLogTarget(idArg, services);
|
|
7469
|
+
if (options.json) {
|
|
7470
|
+
emitJsonEnvelope(
|
|
7471
|
+
jsonOk({
|
|
7472
|
+
input: idArg,
|
|
7473
|
+
resolved: {
|
|
7474
|
+
id: target.id,
|
|
7475
|
+
logFile: target.logFile,
|
|
7476
|
+
matchedFrom: target.matchedFrom
|
|
7477
|
+
}
|
|
7478
|
+
})
|
|
7479
|
+
);
|
|
7480
|
+
return;
|
|
7481
|
+
}
|
|
7482
|
+
if (target.matchedFrom === "fallback") {
|
|
7483
|
+
console.error(
|
|
7484
|
+
`! No registered service matched "${idArg}". Falling back to canonical path:
|
|
7485
|
+
${target.logFile}
|
|
7486
|
+
(the file may not exist yet if the service has never run.)`
|
|
7487
|
+
);
|
|
7488
|
+
}
|
|
7489
|
+
const ac = new AbortController();
|
|
7490
|
+
const onSigint = () => {
|
|
7491
|
+
ac.abort();
|
|
7492
|
+
};
|
|
7493
|
+
if (options.follow) {
|
|
7494
|
+
process.on("SIGINT", onSigint);
|
|
7495
|
+
}
|
|
7496
|
+
try {
|
|
7497
|
+
await tailLogFile({
|
|
7498
|
+
logFile: target.logFile,
|
|
7499
|
+
initialLines: options.lines ?? 50,
|
|
7500
|
+
follow: !!options.follow,
|
|
7501
|
+
signal: ac.signal
|
|
7502
|
+
});
|
|
7503
|
+
} finally {
|
|
7504
|
+
if (options.follow) process.off("SIGINT", onSigint);
|
|
7505
|
+
}
|
|
7506
|
+
}
|
|
7507
|
+
|
|
6082
7508
|
// src/index.ts
|
|
6083
7509
|
setPlatformAdapter(new NodePlatformAdapter());
|
|
6084
7510
|
var program = new Command();
|
|
@@ -6087,15 +7513,18 @@ program.name("beeos").version(cliVersion.display).description("BeeOS \u2014 run
|
|
|
6087
7513
|
program.command("init").description("One-shot install + bind flow (default entry from the curl installer)").option("--framework <name>", "Agent framework to install (default: openclaw)").option("--yes", "Non-interactive \u2014 accept the default action", false).option("--json", "Output machine-readable JSON (implies --yes)", false).option("--no-browser", "Don't auto-open a browser for bind confirmation").option("--headless", "Never open a browser (use terminal QR only)", false).option("--skip-service-prompt", "Skip the system-service install prompt", false).option("--device", "Jump straight into `beeos device attach` instead", false).action((opts) => run7(opts));
|
|
6088
7514
|
program.command("doctor").description("Inspect local BeeOS state (install, binding, services, warnings)").option("--json", "Output machine-readable JSON", false).action((opts) => run6(opts));
|
|
6089
7515
|
program.command("migrate").description("Migrate legacy Node supervisor state to OS-native services (one-shot)").option("--json", "Output machine-readable JSON", false).action((opts) => run8(opts));
|
|
7516
|
+
program.command("logs").description(
|
|
7517
|
+
"Tail logs for a BeeOS service. Pass an ADB serial or service id; omit to list every registered service's log file path."
|
|
7518
|
+
).argument("[id]", "Service id (e.g. `device-agent-<serial>`) or raw ADB serial").option("-f, --follow", "Stream new log lines (Ctrl-C to exit)", false).option("-n, --lines <n>", "Initial trailing lines to print (default 50)", (v) => Number(v)).option("--json", "Output machine-readable JSON (resolves the path; does not stream)", false).action((id, opts) => run9(id, opts));
|
|
6090
7519
|
var serviceCmd = program.command("service").description("Inspect and manage the OS-native service manager (launchd / systemd --user / Task Scheduler)");
|
|
6091
7520
|
serviceCmd.command("install").description("Verify the OS service manager is available (per-target services install automatically)").option("--json", "Output machine-readable JSON", false).action((opts) => install(opts));
|
|
6092
7521
|
serviceCmd.command("uninstall").description("Uninstall every BeeOS-managed OS service").option("--json", "Output machine-readable JSON", false).action((opts) => uninstall(opts));
|
|
6093
7522
|
serviceCmd.command("status").description("List every BeeOS-managed OS service").option("--json", "Output machine-readable JSON", false).action((opts) => status(opts));
|
|
6094
7523
|
program.command("start").description("Install and start an agent").argument("<agent_type>", "Agent type to start (e.g. openclaw)").option("--version <version>", "Pin to a specific version instead of latest").option("--force", "Skip confirmation prompts", false).option("--json", "Output machine-readable JSON to stdout (implies --force)", false).option("--no-browser", "Don't auto-open browser for bind confirmation").action((agentFramework, options) => run(agentFramework, options));
|
|
6095
7524
|
program.command("stop").description("Stop a running agent").argument("<agent_type>", "Agent type to stop").action((agentFramework) => run2(agentFramework));
|
|
6096
|
-
program.command("status").description("Show status of managed agents").action(run3);
|
|
6097
|
-
program.command("update").description("Update an agent to the latest version").argument("<agent_type>", "Agent type to update").action((agentFramework) => run4(agentFramework));
|
|
6098
|
-
program.command("bind-status").description("Check agent binding status").argument("<bind_id>", "Bind session ID returned by `beeos start --json`").option("--json", "Output machine-readable JSON", false).action(run5);
|
|
7525
|
+
program.command("status").description("Show status of managed agents").option("--json", "Output machine-readable JSON envelope ({ok,data})", false).action((opts) => run3(opts));
|
|
7526
|
+
program.command("update").description("Update an agent to the latest version").argument("<agent_type>", "Agent type to update").option("--json", "Output machine-readable JSON envelope ({ok,data})", false).action((agentFramework, opts) => run4(agentFramework, opts));
|
|
7527
|
+
program.command("bind-status").description("Check agent binding status").argument("<bind_id>", "Bind session ID returned by `beeos start --json`").option("--json", "Output machine-readable JSON envelope ({ok,data})", false).action(run5);
|
|
6099
7528
|
var deviceCmd = program.command("device").description("Manage device agents (Android/Desktop/ChromeOS)");
|
|
6100
7529
|
deviceCmd.command("attach").description("Attach an ADB-connected device as an AI agent").option("--serial <serial>", "ADB device serial number").option("--name <name>", "Human-readable device name").option("--all", "Attach all connected ADB devices", false).option(
|
|
6101
7530
|
"--no-video",
|
|
@@ -6103,7 +7532,10 @@ deviceCmd.command("attach").description("Attach an ADB-connected device as an AI
|
|
|
6103
7532
|
).option(
|
|
6104
7533
|
"--vnc-host <host>",
|
|
6105
7534
|
"Use vnc-bridge against a VNC server at this host (desktop/Linux/macOS). Implies video mode = vnc instead of scrcpy."
|
|
6106
|
-
).option("--vnc-port <port>", "VNC TCP port (default 5900)", (v) => Number(v)).option("--vnc-password <password>", "VNC password (forwarded via env)").
|
|
7535
|
+
).option("--vnc-port <port>", "VNC TCP port (default 5900)", (v) => Number(v)).option("--vnc-password <password>", "VNC password (forwarded via env)").option(
|
|
7536
|
+
"--agent-gateway-url <url>",
|
|
7537
|
+
"Override the Agent Gateway URL for THIS device only (advanced; multi-region staging). Persists to the device entry so the fleet supervisor reuses it on every restart. Falls back to the global config when omitted."
|
|
7538
|
+
).action(attach);
|
|
6107
7539
|
deviceCmd.command("detach").description("Detach a device agent").option("--serial <serial>", "ADB device serial number").option("--all", "Detach all devices", false).action(detach);
|
|
6108
7540
|
deviceCmd.command("list").description("List attached devices").option("--local", "Only show locally running device agents", false).action(list);
|
|
6109
7541
|
deviceCmd.command("exec").description("Send a natural language command to a device").option("--serial <serial>", "ADB device serial number").argument("<prompt>", "The command/prompt to execute").action(exec);
|
|
@@ -6112,6 +7544,15 @@ deviceCmd.command("refresh-config").description(
|
|
|
6112
7544
|
"Re-fetch this device's VLM/ARouter config from Agent Gateway and rewrite ~/.beeos/<instance_id>.config.json (use after dashboard changes)."
|
|
6113
7545
|
).option("--serial <serial>", "ADB device serial number").option("--all", "Refresh every attached device", false).action(refreshConfig);
|
|
6114
7546
|
program.parseAsync(process.argv).catch((err) => {
|
|
7547
|
+
const wantsJson = process.argv.includes("--json");
|
|
7548
|
+
if (isBeeosError(err)) {
|
|
7549
|
+
if (wantsJson) {
|
|
7550
|
+
console.log(JSON.stringify(toJsonFailurePayload(err), null, 2));
|
|
7551
|
+
} else {
|
|
7552
|
+
console.error(formatHumanError(err));
|
|
7553
|
+
}
|
|
7554
|
+
process.exit(1);
|
|
7555
|
+
}
|
|
6115
7556
|
console.error(err.message);
|
|
6116
7557
|
process.exit(1);
|
|
6117
7558
|
});
|