@beeos-ai/cli 1.0.12 → 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 +1752 -315
- 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;
|
|
@@ -2681,13 +3330,13 @@ function inferInitChoices(state) {
|
|
|
2681
3330
|
if (state.hasIdentity && state.binding) {
|
|
2682
3331
|
return {
|
|
2683
3332
|
defaultDecision: "upgrade",
|
|
2684
|
-
options: ["upgrade", "rebind-
|
|
3333
|
+
options: ["upgrade", "rebind-new-key", "skip"]
|
|
2685
3334
|
};
|
|
2686
3335
|
}
|
|
2687
3336
|
if (state.hasIdentity) {
|
|
2688
3337
|
return {
|
|
2689
|
-
defaultDecision: "
|
|
2690
|
-
options: ["upgrade", "rebind-
|
|
3338
|
+
defaultDecision: "upgrade",
|
|
3339
|
+
options: ["upgrade", "rebind-new-key", "skip"]
|
|
2691
3340
|
};
|
|
2692
3341
|
}
|
|
2693
3342
|
return { defaultDecision: "fresh", options: ["fresh", "skip"] };
|
|
@@ -2697,9 +3346,7 @@ function initDecisionLabel(decision) {
|
|
|
2697
3346
|
case "fresh":
|
|
2698
3347
|
return "Install + bind";
|
|
2699
3348
|
case "upgrade":
|
|
2700
|
-
return "Upgrade CLI & agents (keep binding)";
|
|
2701
|
-
case "rebind-keep-key":
|
|
2702
|
-
return "Re-bind (keep existing Ed25519 key)";
|
|
3349
|
+
return "Upgrade CLI & agents (keep binding & key)";
|
|
2703
3350
|
case "rebind-new-key":
|
|
2704
3351
|
return "Re-bind (rotate Ed25519 key)";
|
|
2705
3352
|
case "skip":
|
|
@@ -2772,6 +3419,7 @@ var init_target_spec = __esm({
|
|
|
2772
3419
|
"use strict";
|
|
2773
3420
|
init_scrcpy_bridge();
|
|
2774
3421
|
init_vnc_bridge();
|
|
3422
|
+
init_spawn_env2();
|
|
2775
3423
|
}
|
|
2776
3424
|
});
|
|
2777
3425
|
|
|
@@ -2971,13 +3619,23 @@ var init_launchd = __esm({
|
|
|
2971
3619
|
await fs.mkdir(path2.dirname(plist), { recursive: true });
|
|
2972
3620
|
await fs.mkdir(path2.dirname(logFile), { recursive: true });
|
|
2973
3621
|
const resolvedSpec = await absolutizeCommand(spec);
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
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?.();
|
|
2978
3635
|
if (uid !== void 0) {
|
|
2979
3636
|
try {
|
|
2980
3637
|
await execFileP("launchctl", ["bootout", `gui/${uid}/${label}`]);
|
|
3638
|
+
await this.waitForBootout(label, uid);
|
|
2981
3639
|
} catch {
|
|
2982
3640
|
}
|
|
2983
3641
|
try {
|
|
@@ -3000,6 +3658,24 @@ var init_launchd = __esm({
|
|
|
3000
3658
|
const status2 = await this.status(spec.id);
|
|
3001
3659
|
return status2 ?? fallbackStatus(spec, label, logFile, false);
|
|
3002
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
|
+
}
|
|
3003
3679
|
async uninstall(id) {
|
|
3004
3680
|
const label = launchdLabel(id);
|
|
3005
3681
|
const plist = this.plistPath(label);
|
|
@@ -3970,6 +4646,7 @@ async function migrateLegacySupervisor(mgr) {
|
|
|
3970
4646
|
const supervisorDir = path6.join(home, "supervisor");
|
|
3971
4647
|
const stateFile = path6.join(supervisorDir, "state.json");
|
|
3972
4648
|
const flagFile = path6.join(home, MIGRATION_FLAG);
|
|
4649
|
+
const lockFile = path6.join(home, MIGRATION_LOCK);
|
|
3973
4650
|
if (fsSync4.existsSync(flagFile)) {
|
|
3974
4651
|
return { ran: false, migrated: 0, errors: [], backupPath: null };
|
|
3975
4652
|
}
|
|
@@ -3980,47 +4657,73 @@ async function migrateLegacySupervisor(mgr) {
|
|
|
3980
4657
|
}
|
|
3981
4658
|
return { ran: false, migrated: 0, errors: [], backupPath: null };
|
|
3982
4659
|
}
|
|
3983
|
-
|
|
3984
|
-
try {
|
|
3985
|
-
await fs5.copyFile(stateFile, backupPath);
|
|
3986
|
-
} catch {
|
|
3987
|
-
}
|
|
3988
|
-
let parsed;
|
|
3989
|
-
try {
|
|
3990
|
-
const raw = await fs5.readFile(stateFile, "utf-8");
|
|
3991
|
-
parsed = JSON.parse(raw);
|
|
3992
|
-
} catch (e) {
|
|
4660
|
+
if (!await acquireLock(lockFile)) {
|
|
3993
4661
|
return {
|
|
3994
4662
|
ran: true,
|
|
3995
4663
|
migrated: 0,
|
|
3996
|
-
errors: [{
|
|
3997
|
-
|
|
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
|
|
3998
4669
|
};
|
|
3999
4670
|
}
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
let migrated = 0;
|
|
4003
|
-
for (const t of targets) {
|
|
4004
|
-
const canonicalId = safeId(t.id);
|
|
4671
|
+
try {
|
|
4672
|
+
const backupPath = `${stateFile}.migration-backup-${Date.now()}`;
|
|
4005
4673
|
try {
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
restart: t.restart ?? "on-failure",
|
|
4014
|
-
label: t.label
|
|
4015
|
-
};
|
|
4016
|
-
await mgr.install(spec);
|
|
4017
|
-
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);
|
|
4018
4681
|
} catch (e) {
|
|
4019
|
-
|
|
4682
|
+
return {
|
|
4683
|
+
ran: true,
|
|
4684
|
+
migrated: 0,
|
|
4685
|
+
errors: [{ id: "<state.json>", error: `unreadable: ${e}` }],
|
|
4686
|
+
backupPath
|
|
4687
|
+
};
|
|
4020
4688
|
}
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
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();
|
|
4024
4727
|
try {
|
|
4025
4728
|
await fs5.writeFile(flagFile, (/* @__PURE__ */ new Date()).toISOString());
|
|
4026
4729
|
} catch {
|
|
@@ -4029,8 +4732,56 @@ async function migrateLegacySupervisor(mgr) {
|
|
|
4029
4732
|
await fs5.rm(supervisorDir, { recursive: true, force: true });
|
|
4030
4733
|
} catch {
|
|
4031
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;
|
|
4032
4784
|
}
|
|
4033
|
-
return { ran: true, migrated, errors, backupPath };
|
|
4034
4785
|
}
|
|
4035
4786
|
async function stopLegacyDaemon() {
|
|
4036
4787
|
if (process.platform === "darwin") {
|
|
@@ -4065,7 +4816,7 @@ async function stopLegacyDaemon() {
|
|
|
4065
4816
|
}
|
|
4066
4817
|
}
|
|
4067
4818
|
}
|
|
4068
|
-
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;
|
|
4069
4820
|
var init_migrate = __esm({
|
|
4070
4821
|
"../core/dist/services/migrate.js"() {
|
|
4071
4822
|
"use strict";
|
|
@@ -4075,11 +4826,91 @@ var init_migrate = __esm({
|
|
|
4075
4826
|
LEGACY_LABEL = "ai.beeos.supervisor";
|
|
4076
4827
|
LEGACY_SYSTEMD_UNIT = "beeos-supervisor.service";
|
|
4077
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";
|
|
4078
4909
|
}
|
|
4079
4910
|
});
|
|
4080
4911
|
|
|
4081
4912
|
// ../core/dist/cli-version.js
|
|
4082
|
-
import
|
|
4913
|
+
import fs7 from "fs";
|
|
4083
4914
|
import path7 from "path";
|
|
4084
4915
|
import { fileURLToPath } from "url";
|
|
4085
4916
|
function getCliVersion(moduleUrl, distTag) {
|
|
@@ -4107,8 +4938,8 @@ function readBakedDistTag() {
|
|
|
4107
4938
|
let dir = here;
|
|
4108
4939
|
for (let i = 0; i < 6; i++) {
|
|
4109
4940
|
const candidate = path7.join(dir, "package.json");
|
|
4110
|
-
if (
|
|
4111
|
-
const raw =
|
|
4941
|
+
if (fs7.existsSync(candidate)) {
|
|
4942
|
+
const raw = fs7.readFileSync(candidate, "utf-8");
|
|
4112
4943
|
const pkg = JSON.parse(raw);
|
|
4113
4944
|
if ((pkg.name === "@beeos-ai/cli" || pkg.name === "beeos") && pkg.beeos && typeof pkg.beeos.distTag === "string") {
|
|
4114
4945
|
return pkg.beeos.distTag;
|
|
@@ -4133,8 +4964,8 @@ function readVersionFromModuleUrl(moduleUrl) {
|
|
|
4133
4964
|
for (let i = 0; i < 6; i++) {
|
|
4134
4965
|
const candidate = path7.join(here, "package.json");
|
|
4135
4966
|
try {
|
|
4136
|
-
if (
|
|
4137
|
-
const raw =
|
|
4967
|
+
if (fs7.existsSync(candidate)) {
|
|
4968
|
+
const raw = fs7.readFileSync(candidate, "utf-8");
|
|
4138
4969
|
const pkg = JSON.parse(raw);
|
|
4139
4970
|
if (typeof pkg.version === "string" && (pkg.name === "@beeos-ai/cli" || pkg.name === "beeos")) {
|
|
4140
4971
|
return pkg.version;
|
|
@@ -4164,17 +4995,21 @@ var init_dist = __esm({
|
|
|
4164
4995
|
init_types();
|
|
4165
4996
|
init_paths();
|
|
4166
4997
|
init_toml();
|
|
4998
|
+
init_spawn_env();
|
|
4167
4999
|
init_binding();
|
|
4168
5000
|
init_gateway_token();
|
|
5001
|
+
init_state();
|
|
4169
5002
|
init_keypair();
|
|
4170
5003
|
init_client();
|
|
4171
5004
|
init_orchestrator();
|
|
4172
5005
|
init_process();
|
|
5006
|
+
init_errors();
|
|
4173
5007
|
init_device_setup();
|
|
4174
5008
|
init_adb_setup();
|
|
4175
5009
|
init_device();
|
|
4176
5010
|
init_scrcpy_bridge();
|
|
4177
5011
|
init_vnc_bridge();
|
|
5012
|
+
init_spawn_env2();
|
|
4178
5013
|
init_agent_config();
|
|
4179
5014
|
init_driver();
|
|
4180
5015
|
init_detector();
|
|
@@ -4189,7 +5024,9 @@ var init_dist = __esm({
|
|
|
4189
5024
|
init_factory();
|
|
4190
5025
|
init_ids();
|
|
4191
5026
|
init_migrate();
|
|
5027
|
+
init_tail_logs();
|
|
4192
5028
|
init_cli_version();
|
|
5029
|
+
init_upgrade();
|
|
4193
5030
|
}
|
|
4194
5031
|
});
|
|
4195
5032
|
|
|
@@ -4268,6 +5105,34 @@ async function notifyFleetReloadBestEffort(baseUrl = FLEET_STATUS_BASE_URL) {
|
|
|
4268
5105
|
return "unknown";
|
|
4269
5106
|
}
|
|
4270
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
|
+
}
|
|
4271
5136
|
function isConnectionRefused(e) {
|
|
4272
5137
|
const code = extractErrorCode(e);
|
|
4273
5138
|
return code === "ECONNREFUSED" || code === "ECONNRESET";
|
|
@@ -4292,12 +5157,13 @@ function printFleetNotRunningHint() {
|
|
|
4292
5157
|
console.log(" device-agent fleet start # foreground (debug-friendly)");
|
|
4293
5158
|
console.log("");
|
|
4294
5159
|
}
|
|
4295
|
-
var FLEET_STATUS_BASE_URL, FLEET_NOTIFY_TIMEOUT_MS;
|
|
5160
|
+
var FLEET_STATUS_BASE_URL, FLEET_NOTIFY_TIMEOUT_MS, FLEET_SHUTDOWN_TIMEOUT_MS;
|
|
4296
5161
|
var init_fleet_notify = __esm({
|
|
4297
5162
|
"src/commands/device/fleet-notify.ts"() {
|
|
4298
5163
|
"use strict";
|
|
4299
5164
|
FLEET_STATUS_BASE_URL = "http://127.0.0.1:7950";
|
|
4300
5165
|
FLEET_NOTIFY_TIMEOUT_MS = 1e3;
|
|
5166
|
+
FLEET_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
4301
5167
|
}
|
|
4302
5168
|
});
|
|
4303
5169
|
|
|
@@ -4357,7 +5223,7 @@ async function removeTargetsForSerial(mgr, serial) {
|
|
|
4357
5223
|
} catch {
|
|
4358
5224
|
}
|
|
4359
5225
|
}
|
|
4360
|
-
var
|
|
5226
|
+
var init_state2 = __esm({
|
|
4361
5227
|
"src/commands/device/state.ts"() {
|
|
4362
5228
|
"use strict";
|
|
4363
5229
|
init_dist();
|
|
@@ -4367,6 +5233,37 @@ var init_state = __esm({
|
|
|
4367
5233
|
// src/commands/device/attach.ts
|
|
4368
5234
|
import { spawn as spawn2 } from "child_process";
|
|
4369
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
|
+
}
|
|
4370
5267
|
async function attach(options) {
|
|
4371
5268
|
const cfg = await loadOrCreateConfig();
|
|
4372
5269
|
const reporter = new CliReporter();
|
|
@@ -4390,17 +5287,19 @@ async function attach(options) {
|
|
|
4390
5287
|
return;
|
|
4391
5288
|
}
|
|
4392
5289
|
const mgr = await getServiceManager();
|
|
5290
|
+
let prevInstanceId = null;
|
|
4393
5291
|
await withDeviceLock(async () => {
|
|
4394
5292
|
const state = await loadDeviceState();
|
|
4395
5293
|
const existingIdx = state.devices.findIndex((d) => d.serial === serial);
|
|
4396
5294
|
if (existingIdx >= 0) {
|
|
5295
|
+
prevInstanceId = state.devices[existingIdx].instance_id ?? null;
|
|
4397
5296
|
await removeTargetsForSerial(mgr, serial);
|
|
4398
5297
|
state.devices.splice(existingIdx, 1);
|
|
4399
5298
|
}
|
|
4400
5299
|
const httpPort = nextHttpPort(state, cfg.device.http_port);
|
|
4401
5300
|
const keyFile = deviceRuntime.deviceKeyPath(serial);
|
|
4402
|
-
const agentGatewayUrl =
|
|
4403
|
-
await persistAgentConfigBestEffort({
|
|
5301
|
+
const agentGatewayUrl = resolvePerDeviceAgentGatewayUrl(cfg, options.agentGatewayUrl);
|
|
5302
|
+
const persistedAgentConfig = await persistAgentConfigBestEffort({
|
|
4404
5303
|
agentGatewayUrl,
|
|
4405
5304
|
keyFile,
|
|
4406
5305
|
instanceId,
|
|
@@ -4415,8 +5314,12 @@ async function attach(options) {
|
|
|
4415
5314
|
withVideo,
|
|
4416
5315
|
vncHost: options.vncHost,
|
|
4417
5316
|
vncPort: options.vncPort,
|
|
4418
|
-
vncPassword: options.vncPassword
|
|
5317
|
+
vncPassword: options.vncPassword,
|
|
5318
|
+
agentGatewayUrl
|
|
4419
5319
|
});
|
|
5320
|
+
const fp = fingerprintFromB64(pubkeyB64);
|
|
5321
|
+
const boundAt = Math.floor(Date.now() / 1e3);
|
|
5322
|
+
const backend = options.vncHost ? void 0 : "adb";
|
|
4420
5323
|
state.devices.push({
|
|
4421
5324
|
serial,
|
|
4422
5325
|
name,
|
|
@@ -4430,11 +5333,37 @@ async function attach(options) {
|
|
|
4430
5333
|
// path through fleet → mcp). When self-attach for
|
|
4431
5334
|
// macOS/Linux/Windows lands, this field will be set
|
|
4432
5335
|
// accordingly (`macos`/`linux`/`windows`).
|
|
4433
|
-
backend
|
|
5336
|
+
backend,
|
|
4434
5337
|
vnc_host: options.vncHost,
|
|
4435
|
-
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
|
|
4436
5345
|
});
|
|
4437
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
|
+
);
|
|
4438
5367
|
console.log(`Attached device ${serial} (${name}) \u2014 instance ${instanceId}`);
|
|
4439
5368
|
console.log(` local http: :${httpPort} (if enabled)`);
|
|
4440
5369
|
console.log(` logs: device-agent fleet logs ${serial}`);
|
|
@@ -4450,6 +5379,14 @@ async function attach(options) {
|
|
|
4450
5379
|
throw e;
|
|
4451
5380
|
}
|
|
4452
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
|
+
}
|
|
4453
5390
|
await maybeNotifyFleetWithHint(cfg);
|
|
4454
5391
|
}
|
|
4455
5392
|
async function attachAll(cfg, reporter, withVideo, options) {
|
|
@@ -4461,9 +5398,13 @@ async function attachAll(cfg, reporter, withVideo, options) {
|
|
|
4461
5398
|
console.log(`Discovered ${devices.length} device(s) via adb.`);
|
|
4462
5399
|
await deviceRuntime.ensureAgent(reporter);
|
|
4463
5400
|
reporter.stop();
|
|
4464
|
-
const agentGatewayUrl =
|
|
5401
|
+
const agentGatewayUrl = resolvePerDeviceAgentGatewayUrl(cfg, options.agentGatewayUrl);
|
|
4465
5402
|
const mgr = await getServiceManager();
|
|
4466
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
|
+
}
|
|
4467
5408
|
const simulatedEntries = preState.devices.map((d) => ({ ...d }));
|
|
4468
5409
|
function reserveHttpPort(serial) {
|
|
4469
5410
|
const port = nextHttpPort({ devices: simulatedEntries }, cfg.device.http_port);
|
|
@@ -4490,7 +5431,8 @@ async function attachAll(cfg, reporter, withVideo, options) {
|
|
|
4490
5431
|
serial: device.serial,
|
|
4491
5432
|
instanceId,
|
|
4492
5433
|
keyFile: deviceRuntime.deviceKeyPath(device.serial),
|
|
4493
|
-
httpPort
|
|
5434
|
+
httpPort,
|
|
5435
|
+
fingerprint: fingerprintFromB64(pubkeyB64)
|
|
4494
5436
|
});
|
|
4495
5437
|
} catch (e) {
|
|
4496
5438
|
console.error(` Failed to bind ${device.serial}: ${e}`);
|
|
@@ -4513,18 +5455,62 @@ async function attachAll(cfg, reporter, withVideo, options) {
|
|
|
4513
5455
|
})
|
|
4514
5456
|
)
|
|
4515
5457
|
);
|
|
5458
|
+
const reboundSerials = [];
|
|
4516
5459
|
await withDeviceLock(async () => {
|
|
4517
5460
|
const state = await loadDeviceState();
|
|
4518
5461
|
const { committed, failures } = commitAttachResults(state, commits);
|
|
4519
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
|
+
}
|
|
4520
5467
|
for (const c of committed) {
|
|
4521
5468
|
console.log(
|
|
4522
5469
|
`Attached device ${c.serial} \u2014 instance ${c.entry.instance_id}` + (c.videoMode !== "none" ? ` (video: ${c.videoMode})` : "")
|
|
4523
5470
|
);
|
|
4524
5471
|
}
|
|
4525
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
|
+
}
|
|
4526
5499
|
console.log(`Attached ${committed.length} device(s); supervision via device-agent fleet.`);
|
|
4527
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
|
+
}
|
|
4528
5514
|
await maybeNotifyFleetWithHint(cfg);
|
|
4529
5515
|
}
|
|
4530
5516
|
function commitAttachResults(state, commits) {
|
|
@@ -4544,10 +5530,10 @@ function commitAttachResults(state, commits) {
|
|
|
4544
5530
|
}
|
|
4545
5531
|
async function runAttachStage2(params) {
|
|
4546
5532
|
const { bind, cfg, mgr, reporter, agentGatewayUrl, withVideo, options } = params;
|
|
4547
|
-
const { serial, instanceId, keyFile, httpPort } = bind;
|
|
5533
|
+
const { serial, instanceId, keyFile, httpPort, fingerprint: fp } = bind;
|
|
4548
5534
|
try {
|
|
4549
5535
|
await removeTargetsForSerial(mgr, serial);
|
|
4550
|
-
await persistAgentConfigBestEffort({
|
|
5536
|
+
const persistedAgentConfig = await persistAgentConfigBestEffort({
|
|
4551
5537
|
agentGatewayUrl,
|
|
4552
5538
|
keyFile,
|
|
4553
5539
|
instanceId,
|
|
@@ -4561,7 +5547,8 @@ async function runAttachStage2(params) {
|
|
|
4561
5547
|
withVideo,
|
|
4562
5548
|
vncHost: options.vncHost,
|
|
4563
5549
|
vncPort: options.vncPort,
|
|
4564
|
-
vncPassword: options.vncPassword
|
|
5550
|
+
vncPassword: options.vncPassword,
|
|
5551
|
+
agentGatewayUrl
|
|
4565
5552
|
});
|
|
4566
5553
|
const entry = {
|
|
4567
5554
|
serial,
|
|
@@ -4572,9 +5559,25 @@ async function runAttachStage2(params) {
|
|
|
4572
5559
|
video_mode: bridgeInfo.mode,
|
|
4573
5560
|
backend: options.vncHost ? void 0 : "adb",
|
|
4574
5561
|
vnc_host: options.vncHost,
|
|
4575
|
-
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
|
+
}
|
|
4576
5580
|
};
|
|
4577
|
-
return { ok: true, serial, entry, videoMode: bridgeInfo.mode };
|
|
4578
5581
|
} catch (e) {
|
|
4579
5582
|
await rollbackDeviceBind(cfg.platform.api_url, instanceId);
|
|
4580
5583
|
return { ok: false, serial, error: e };
|
|
@@ -4686,7 +5689,7 @@ async function runDeviceAgentFleetEnable(cfg) {
|
|
|
4686
5689
|
}
|
|
4687
5690
|
async function registerVideoBridge(_mgr, reporter, params) {
|
|
4688
5691
|
if (!params.withVideo) return { mode: "none" };
|
|
4689
|
-
const agentGatewayUrl = resolveAgentGatewayUrl(params.cfg);
|
|
5692
|
+
const agentGatewayUrl = params.agentGatewayUrl ?? resolveAgentGatewayUrl(params.cfg);
|
|
4690
5693
|
if (params.vncHost) {
|
|
4691
5694
|
const binary2 = await ensureBridgeBinaryDegraded(
|
|
4692
5695
|
vncBridgeRuntime.ensureInstalled.bind(vncBridgeRuntime),
|
|
@@ -4739,23 +5742,80 @@ async function ensureBridgeBinaryDegraded(fn, reporter, label) {
|
|
|
4739
5742
|
return null;
|
|
4740
5743
|
}
|
|
4741
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
|
+
}
|
|
4742
5753
|
async function ensureAdbAvailable(reporter) {
|
|
4743
5754
|
const existing = await findAdb();
|
|
4744
5755
|
if (existing) return;
|
|
4745
5756
|
if (process.env.BEEOS_SKIP_ADB_INSTALL === "1") {
|
|
4746
|
-
throw new
|
|
4747
|
-
"
|
|
4748
|
-
|
|
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
|
+
}
|
|
4749
5781
|
}
|
|
4750
5782
|
console.log("adb not found \u2014 downloading Android platform-tools (one-time, ~10MB)...");
|
|
4751
5783
|
try {
|
|
4752
5784
|
await ensureAdb(reporter, { autoInstall: true });
|
|
4753
5785
|
} catch (e) {
|
|
4754
|
-
throw new
|
|
4755
|
-
|
|
4756
|
-
|
|
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.`
|
|
4757
5801
|
);
|
|
5802
|
+
return true;
|
|
4758
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());
|
|
4759
5819
|
}
|
|
4760
5820
|
var init_attach = __esm({
|
|
4761
5821
|
"src/commands/device/attach.ts"() {
|
|
@@ -4764,7 +5824,7 @@ var init_attach = __esm({
|
|
|
4764
5824
|
init_progress2();
|
|
4765
5825
|
init_fallback_banner();
|
|
4766
5826
|
init_fleet_notify();
|
|
4767
|
-
|
|
5827
|
+
init_state2();
|
|
4768
5828
|
}
|
|
4769
5829
|
});
|
|
4770
5830
|
|
|
@@ -4816,7 +5876,7 @@ var init_detach = __esm({
|
|
|
4816
5876
|
"use strict";
|
|
4817
5877
|
init_dist();
|
|
4818
5878
|
init_fleet_notify();
|
|
4819
|
-
|
|
5879
|
+
init_state2();
|
|
4820
5880
|
}
|
|
4821
5881
|
});
|
|
4822
5882
|
|
|
@@ -4858,7 +5918,7 @@ var init_list = __esm({
|
|
|
4858
5918
|
"src/commands/device/list.ts"() {
|
|
4859
5919
|
"use strict";
|
|
4860
5920
|
init_dist();
|
|
4861
|
-
|
|
5921
|
+
init_state2();
|
|
4862
5922
|
}
|
|
4863
5923
|
});
|
|
4864
5924
|
|
|
@@ -4894,14 +5954,49 @@ var init_exec = __esm({
|
|
|
4894
5954
|
"src/commands/device/exec.ts"() {
|
|
4895
5955
|
"use strict";
|
|
4896
5956
|
init_dist();
|
|
4897
|
-
|
|
5957
|
+
init_state2();
|
|
4898
5958
|
}
|
|
4899
5959
|
});
|
|
4900
5960
|
|
|
4901
5961
|
// src/commands/device/upgrade.ts
|
|
4902
5962
|
async function upgrade(options = {}) {
|
|
4903
5963
|
const reporter = new CliReporter();
|
|
4904
|
-
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
|
+
}
|
|
4905
6000
|
if (options.bridges !== false) {
|
|
4906
6001
|
try {
|
|
4907
6002
|
await scrcpyBridgeRuntime.upgrade(reporter);
|
|
@@ -4915,11 +6010,12 @@ async function upgrade(options = {}) {
|
|
|
4915
6010
|
}
|
|
4916
6011
|
}
|
|
4917
6012
|
}
|
|
4918
|
-
var
|
|
6013
|
+
var init_upgrade2 = __esm({
|
|
4919
6014
|
"src/commands/device/upgrade.ts"() {
|
|
4920
6015
|
"use strict";
|
|
4921
6016
|
init_dist();
|
|
4922
6017
|
init_progress2();
|
|
6018
|
+
init_fleet_notify();
|
|
4923
6019
|
}
|
|
4924
6020
|
});
|
|
4925
6021
|
|
|
@@ -4933,7 +6029,6 @@ async function refreshConfig(options = {}) {
|
|
|
4933
6029
|
console.log("No matching devices in ~/.beeos/devices.json \u2014 nothing to refresh.");
|
|
4934
6030
|
return;
|
|
4935
6031
|
}
|
|
4936
|
-
const mgr = await getServiceManager();
|
|
4937
6032
|
let okCount = 0;
|
|
4938
6033
|
let failCount = 0;
|
|
4939
6034
|
for (const entry of targets) {
|
|
@@ -4951,27 +6046,44 @@ async function refreshConfig(options = {}) {
|
|
|
4951
6046
|
failCount++;
|
|
4952
6047
|
continue;
|
|
4953
6048
|
}
|
|
4954
|
-
const
|
|
6049
|
+
const result = await persistAgentConfigBestEffort({
|
|
4955
6050
|
agentGatewayUrl,
|
|
4956
6051
|
keyFile: entry.key_file,
|
|
4957
6052
|
instanceId: entry.instance_id,
|
|
4958
6053
|
reporter
|
|
4959
6054
|
});
|
|
4960
|
-
if (!
|
|
6055
|
+
if (!result) {
|
|
4961
6056
|
console.error(
|
|
4962
6057
|
` Refresh failed for ${entry.serial} (see warning above) \u2014 config file left untouched.`
|
|
4963
6058
|
);
|
|
4964
6059
|
failCount++;
|
|
4965
6060
|
continue;
|
|
4966
6061
|
}
|
|
4967
|
-
console.log(` Wrote ${
|
|
4968
|
-
|
|
4969
|
-
|
|
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") {
|
|
4970
6080
|
console.log(` Restarted device-agent for ${entry.serial}`);
|
|
4971
|
-
}
|
|
4972
|
-
console.
|
|
4973
|
-
`
|
|
6081
|
+
} else if (outcome === "not_running") {
|
|
6082
|
+
console.log(
|
|
6083
|
+
` fleet not running for ${entry.serial} \u2014 config will load on next start`
|
|
4974
6084
|
);
|
|
6085
|
+
} else {
|
|
6086
|
+
console.error(` fleet restart for ${entry.serial} returned an error`);
|
|
4975
6087
|
}
|
|
4976
6088
|
okCount++;
|
|
4977
6089
|
}
|
|
@@ -5002,7 +6114,8 @@ var init_refresh_config = __esm({
|
|
|
5002
6114
|
"use strict";
|
|
5003
6115
|
init_dist();
|
|
5004
6116
|
init_progress2();
|
|
5005
|
-
|
|
6117
|
+
init_fleet_notify();
|
|
6118
|
+
init_state2();
|
|
5006
6119
|
}
|
|
5007
6120
|
});
|
|
5008
6121
|
|
|
@@ -5024,9 +6137,9 @@ var init_device2 = __esm({
|
|
|
5024
6137
|
init_detach();
|
|
5025
6138
|
init_list();
|
|
5026
6139
|
init_exec();
|
|
5027
|
-
|
|
6140
|
+
init_upgrade2();
|
|
5028
6141
|
init_refresh_config();
|
|
5029
|
-
|
|
6142
|
+
init_state2();
|
|
5030
6143
|
}
|
|
5031
6144
|
});
|
|
5032
6145
|
|
|
@@ -5039,7 +6152,7 @@ import {
|
|
|
5039
6152
|
execFile as execFile6,
|
|
5040
6153
|
spawn as nodeSpawn
|
|
5041
6154
|
} from "child_process";
|
|
5042
|
-
import
|
|
6155
|
+
import fs8 from "fs";
|
|
5043
6156
|
import fsp from "fs/promises";
|
|
5044
6157
|
import net from "net";
|
|
5045
6158
|
import os5 from "os";
|
|
@@ -5146,11 +6259,11 @@ var NodePlatformAdapter = class {
|
|
|
5146
6259
|
let stderrFd;
|
|
5147
6260
|
if (options?.stdoutFile) {
|
|
5148
6261
|
await fsp.mkdir(path8.dirname(options.stdoutFile), { recursive: true });
|
|
5149
|
-
stdoutFd =
|
|
6262
|
+
stdoutFd = fs8.openSync(options.stdoutFile, "a");
|
|
5150
6263
|
}
|
|
5151
6264
|
if (options?.stderrFile) {
|
|
5152
6265
|
await fsp.mkdir(path8.dirname(options.stderrFile), { recursive: true });
|
|
5153
|
-
stderrFd = options.stderrFile === options.stdoutFile ? stdoutFd :
|
|
6266
|
+
stderrFd = options.stderrFile === options.stdoutFile ? stdoutFd : fs8.openSync(options.stderrFile, "a");
|
|
5154
6267
|
}
|
|
5155
6268
|
const child = nodeSpawn(cmd, args, {
|
|
5156
6269
|
cwd: options?.cwd,
|
|
@@ -5167,8 +6280,8 @@ var NodePlatformAdapter = class {
|
|
|
5167
6280
|
if (options?.detached) {
|
|
5168
6281
|
child.unref();
|
|
5169
6282
|
}
|
|
5170
|
-
if (stdoutFd != null)
|
|
5171
|
-
if (stderrFd != null && stderrFd !== stdoutFd)
|
|
6283
|
+
if (stdoutFd != null) fs8.closeSync(stdoutFd);
|
|
6284
|
+
if (stderrFd != null && stderrFd !== stdoutFd) fs8.closeSync(stderrFd);
|
|
5172
6285
|
const pid = child.pid;
|
|
5173
6286
|
if (pid == null) {
|
|
5174
6287
|
throw new Error(`Failed to spawn process: ${cmd} ${args.join(" ")}`);
|
|
@@ -5250,6 +6363,17 @@ init_dist();
|
|
|
5250
6363
|
init_progress2();
|
|
5251
6364
|
init_fallback_banner();
|
|
5252
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
|
|
5253
6377
|
async function run(agentFramework, options) {
|
|
5254
6378
|
const p = getPlatformAdapter();
|
|
5255
6379
|
await ensureDirs();
|
|
@@ -5258,9 +6382,12 @@ async function run(agentFramework, options) {
|
|
|
5258
6382
|
const descriptor = frameworkById(agentFramework);
|
|
5259
6383
|
if (!descriptor || descriptor.status !== "available") {
|
|
5260
6384
|
const avail = availableFrameworks().map((f) => f.id).join(", ");
|
|
5261
|
-
throw new
|
|
5262
|
-
|
|
5263
|
-
|
|
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
|
+
});
|
|
5264
6391
|
}
|
|
5265
6392
|
const driver = descriptor.driver;
|
|
5266
6393
|
const identity = await loadOrCreateIdentity();
|
|
@@ -5268,12 +6395,17 @@ async function run(agentFramework, options) {
|
|
|
5268
6395
|
const pubkey = publicKeyB64(identity);
|
|
5269
6396
|
const keyFile = p.joinPath(beeoHome(), "identity", "keypair.json");
|
|
5270
6397
|
const agentGatewayUrl = resolveAgentGatewayUrl(cfg);
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
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
|
+
}
|
|
5277
6409
|
}
|
|
5278
6410
|
reporter.onStatus(`Preparing ${driver.displayName}`);
|
|
5279
6411
|
const ctx = {
|
|
@@ -5283,29 +6415,11 @@ async function run(agentFramework, options) {
|
|
|
5283
6415
|
locationPreference: "auto",
|
|
5284
6416
|
versionOverride: options.version
|
|
5285
6417
|
};
|
|
5286
|
-
const
|
|
6418
|
+
const launchOutcome = await driver.launch(ctx, reporter);
|
|
6419
|
+
const spec = launchOutcome.spec;
|
|
6420
|
+
const launchWarnings = launchOutcome.warnings;
|
|
5287
6421
|
reporter.stop();
|
|
5288
|
-
const mgr = await getServiceManager();
|
|
5289
|
-
maybePrintFallbackBanner(mgr.kind);
|
|
5290
|
-
const status2 = await mgr.install(spec);
|
|
5291
|
-
if (spec.healthcheck) {
|
|
5292
|
-
try {
|
|
5293
|
-
await waitForHealthcheck(spec.healthcheck, 45e3);
|
|
5294
|
-
} catch (err) {
|
|
5295
|
-
if (err instanceof HealthcheckTimeoutError && hasStartupDiagnostics(driver)) {
|
|
5296
|
-
const diag = await driver.diagnoseStartup(serviceLogPath(driver.id));
|
|
5297
|
-
if (diag) {
|
|
5298
|
-
throw new Error(
|
|
5299
|
-
`${diag.reason}
|
|
5300
|
-
${diag.hints.map((h) => ` \u2022 ${h}`).join("\n")}`
|
|
5301
|
-
);
|
|
5302
|
-
}
|
|
5303
|
-
}
|
|
5304
|
-
throw err;
|
|
5305
|
-
}
|
|
5306
|
-
}
|
|
5307
6422
|
const hostname = buildHostname();
|
|
5308
|
-
const gatewayPid = status2.pid ?? void 0;
|
|
5309
6423
|
const cachedBinding = await loadBindingInfo();
|
|
5310
6424
|
const outcome = await bindAgent({
|
|
5311
6425
|
apiUrl: cfg.platform.api_url,
|
|
@@ -5320,52 +6434,94 @@ ${diag.hints.map((h) => ` \u2022 ${h}`).join("\n")}`
|
|
|
5320
6434
|
instance_id: cachedBinding.instance_id
|
|
5321
6435
|
} : null
|
|
5322
6436
|
});
|
|
5323
|
-
if (outcome.status === "bound_offline") {
|
|
5324
|
-
emit(options.json, {
|
|
5325
|
-
status: "bound_offline",
|
|
5326
|
-
public_key: pubkey,
|
|
5327
|
-
fingerprint: fp,
|
|
5328
|
-
gateway_pid: gatewayPid,
|
|
5329
|
-
agent_gateway_url: agentGatewayUrl,
|
|
5330
|
-
instance_id: outcome.instanceId
|
|
5331
|
-
}, `Agent running (offline, cached instance: ${outcome.instanceId})`);
|
|
5332
|
-
return;
|
|
5333
|
-
}
|
|
5334
6437
|
if (outcome.status === "pending_expired") {
|
|
5335
|
-
throw new
|
|
5336
|
-
|
|
5337
|
-
|
|
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
|
+
});
|
|
5338
6444
|
}
|
|
6445
|
+
const isOffline = outcome.status === "bound_offline";
|
|
5339
6446
|
const boundInstanceId = outcome.instanceId;
|
|
5340
|
-
|
|
5341
|
-
await saveBindingInfo({
|
|
5342
|
-
fingerprint: fp,
|
|
5343
|
-
instance_id: boundInstanceId,
|
|
5344
|
-
bound_at: Math.floor(Date.now() / 1e3),
|
|
5345
|
-
framework: agentFramework
|
|
5346
|
-
});
|
|
5347
|
-
emit(options.json, {
|
|
5348
|
-
status: "bound",
|
|
5349
|
-
public_key: pubkey,
|
|
5350
|
-
fingerprint: fp,
|
|
5351
|
-
gateway_pid: gatewayPid,
|
|
5352
|
-
agent_gateway_url: agentGatewayUrl,
|
|
5353
|
-
instance_id: boundInstanceId
|
|
5354
|
-
}, `Agent bound to instance: ${boundInstanceId}`);
|
|
5355
|
-
} catch (e) {
|
|
6447
|
+
if (!isOffline) {
|
|
5356
6448
|
try {
|
|
5357
|
-
|
|
5358
|
-
|
|
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
|
+
});
|
|
5359
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) {
|
|
5360
6496
|
try {
|
|
5361
|
-
await
|
|
5362
|
-
} 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;
|
|
5363
6512
|
}
|
|
5364
|
-
throw new Error(
|
|
5365
|
-
`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}`,
|
|
5366
|
-
{ cause: e }
|
|
5367
|
-
);
|
|
5368
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);
|
|
5369
6525
|
}
|
|
5370
6526
|
function buildHostname() {
|
|
5371
6527
|
const machine = os6.hostname();
|
|
@@ -5374,11 +6530,22 @@ function buildHostname() {
|
|
|
5374
6530
|
}
|
|
5375
6531
|
function emit(json, payload, human) {
|
|
5376
6532
|
if (json) {
|
|
5377
|
-
|
|
6533
|
+
emitJsonEnvelope(jsonOk(payload));
|
|
5378
6534
|
} else if (human) {
|
|
5379
6535
|
console.log(human);
|
|
5380
6536
|
}
|
|
5381
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
|
+
}
|
|
5382
6549
|
|
|
5383
6550
|
// src/commands/stop.ts
|
|
5384
6551
|
init_dist();
|
|
@@ -5397,30 +6564,56 @@ async function run2(agentFramework) {
|
|
|
5397
6564
|
|
|
5398
6565
|
// src/commands/status.ts
|
|
5399
6566
|
init_dist();
|
|
5400
|
-
async function run3() {
|
|
6567
|
+
async function run3(options = {}) {
|
|
5401
6568
|
const home = beeoHome();
|
|
5402
6569
|
const cfg = await loadOrCreateConfig();
|
|
5403
|
-
console.log(`BeeOS Home: ${home}`);
|
|
5404
|
-
console.log(`API URL: ${cfg.platform.api_url}`);
|
|
5405
|
-
console.log(`Agent Gateway: ${cfg.platform.agent_gateway_url}`);
|
|
5406
|
-
console.log("");
|
|
5407
6570
|
const p = getPlatformAdapter();
|
|
5408
6571
|
const fpPath = p.joinPath(home, "identity", "fingerprint");
|
|
6572
|
+
let fingerprint2 = null;
|
|
5409
6573
|
if (await p.exists(fpPath)) {
|
|
5410
6574
|
const fp = (await p.readFile(fpPath)).trim();
|
|
5411
|
-
|
|
5412
|
-
} else {
|
|
5413
|
-
console.log("Identity: not yet created");
|
|
6575
|
+
if (fp) fingerprint2 = fp;
|
|
5414
6576
|
}
|
|
5415
|
-
console.log("");
|
|
5416
6577
|
const mgr = await getServiceManager();
|
|
5417
|
-
const
|
|
5418
|
-
console.log(`Service manager: ${mgr.kind}${reason ? ` (${reason})` : ""}`);
|
|
6578
|
+
const fallbackReason2 = activeFallbackReason();
|
|
5419
6579
|
let services = [];
|
|
5420
6580
|
try {
|
|
5421
6581
|
services = await mgr.list();
|
|
5422
6582
|
} catch {
|
|
5423
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})` : ""}`);
|
|
5424
6617
|
if (services.length === 0) {
|
|
5425
6618
|
console.log(" (no services registered)");
|
|
5426
6619
|
} else {
|
|
@@ -5437,20 +6630,30 @@ function formatService(s) {
|
|
|
5437
6630
|
// src/commands/update.ts
|
|
5438
6631
|
init_dist();
|
|
5439
6632
|
init_progress2();
|
|
5440
|
-
async function run4(agentFramework) {
|
|
6633
|
+
async function run4(agentFramework, options = {}) {
|
|
5441
6634
|
if (agentFramework === "device") {
|
|
5442
6635
|
const reporter = new CliReporter();
|
|
5443
6636
|
await deviceRuntime.update(reporter);
|
|
5444
6637
|
reporter.stop();
|
|
5445
|
-
|
|
6638
|
+
if (options.json) {
|
|
6639
|
+
emitJsonEnvelope(jsonOk({ framework: "device", action: "updated" }));
|
|
6640
|
+
} else {
|
|
6641
|
+
console.log("device updated");
|
|
6642
|
+
}
|
|
5446
6643
|
return;
|
|
5447
6644
|
}
|
|
5448
6645
|
const descriptor = frameworkById(agentFramework);
|
|
5449
6646
|
if (!descriptor || descriptor.status !== "available") {
|
|
5450
6647
|
const avail = availableFrameworks().map((f) => f.id).join(", ");
|
|
5451
|
-
throw new
|
|
5452
|
-
|
|
5453
|
-
|
|
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
|
+
});
|
|
5454
6657
|
}
|
|
5455
6658
|
const mgr = await getServiceManager();
|
|
5456
6659
|
const services = await mgr.list().catch(() => []);
|
|
@@ -5459,7 +6662,11 @@ async function run4(agentFramework) {
|
|
|
5459
6662
|
await run2(agentFramework);
|
|
5460
6663
|
}
|
|
5461
6664
|
await run(agentFramework, { force: true });
|
|
5462
|
-
|
|
6665
|
+
if (options.json) {
|
|
6666
|
+
emitJsonEnvelope(jsonOk({ framework: agentFramework, action: "updated" }));
|
|
6667
|
+
} else {
|
|
6668
|
+
console.log(`${agentFramework} updated`);
|
|
6669
|
+
}
|
|
5463
6670
|
}
|
|
5464
6671
|
|
|
5465
6672
|
// src/commands/bind-status.ts
|
|
@@ -5474,21 +6681,39 @@ async function run5(bindId, options) {
|
|
|
5474
6681
|
if (await p.exists(fpPath)) {
|
|
5475
6682
|
const fp = (await p.readFile(fpPath)).trim();
|
|
5476
6683
|
if (fp) {
|
|
6684
|
+
const boundAt = Math.floor(Date.now() / 1e3);
|
|
6685
|
+
const cached2 = await safeLoadBindingInfo();
|
|
6686
|
+
const framework = cached2?.framework?.trim() || "openclaw";
|
|
5477
6687
|
await saveBindingInfo({
|
|
5478
6688
|
fingerprint: fp,
|
|
5479
6689
|
instance_id: resp.instance_id,
|
|
5480
|
-
bound_at:
|
|
6690
|
+
bound_at: boundAt,
|
|
6691
|
+
framework
|
|
5481
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
|
+
);
|
|
5482
6705
|
}
|
|
5483
6706
|
}
|
|
5484
6707
|
} catch {
|
|
5485
6708
|
}
|
|
5486
6709
|
}
|
|
5487
6710
|
if (options.json) {
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
6711
|
+
emitJsonEnvelope(
|
|
6712
|
+
jsonOk({
|
|
6713
|
+
status: resp.status,
|
|
6714
|
+
instance_id: resp.instance_id ?? null
|
|
6715
|
+
})
|
|
6716
|
+
);
|
|
5492
6717
|
} else {
|
|
5493
6718
|
console.log(`Bind status: ${resp.status}`);
|
|
5494
6719
|
if (resp.instance_id) {
|
|
@@ -5496,6 +6721,13 @@ async function run5(bindId, options) {
|
|
|
5496
6721
|
}
|
|
5497
6722
|
}
|
|
5498
6723
|
}
|
|
6724
|
+
async function safeLoadBindingInfo() {
|
|
6725
|
+
try {
|
|
6726
|
+
return await loadBindingInfo();
|
|
6727
|
+
} catch {
|
|
6728
|
+
return null;
|
|
6729
|
+
}
|
|
6730
|
+
}
|
|
5499
6731
|
|
|
5500
6732
|
// src/index.ts
|
|
5501
6733
|
init_device2();
|
|
@@ -5506,7 +6738,7 @@ import readline2 from "readline";
|
|
|
5506
6738
|
|
|
5507
6739
|
// src/commands/doctor.ts
|
|
5508
6740
|
init_dist();
|
|
5509
|
-
import
|
|
6741
|
+
import fs9 from "fs/promises";
|
|
5510
6742
|
import path9 from "path";
|
|
5511
6743
|
var LOG_SIZE_WARN_BYTES = 50 * 1024 * 1024;
|
|
5512
6744
|
async function run6(options) {
|
|
@@ -5523,7 +6755,8 @@ async function run6(options) {
|
|
|
5523
6755
|
}
|
|
5524
6756
|
const resolvedAgentGatewayUrl = resolveAgentGatewayUrl(cfg);
|
|
5525
6757
|
const agentGatewayHealth = await probeAgentGateway(resolvedAgentGatewayUrl);
|
|
5526
|
-
const tools = await collectToolStatus(
|
|
6758
|
+
const tools = await collectToolStatus();
|
|
6759
|
+
const shimReport = await collectShimReport();
|
|
5527
6760
|
const warnings = [];
|
|
5528
6761
|
const hints = [];
|
|
5529
6762
|
if (!state.hasIdentity) {
|
|
@@ -5532,6 +6765,14 @@ async function run6(options) {
|
|
|
5532
6765
|
if (state.openclaw.gatewayRunning && !state.openclaw.found) {
|
|
5533
6766
|
warnings.push("port 18789 is in use but no OpenClaw install detected \u2014 another app may conflict");
|
|
5534
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
|
+
}
|
|
5535
6776
|
if (state.devices.entries.length > 0 && services.length === 0) {
|
|
5536
6777
|
warnings.push(
|
|
5537
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."
|
|
@@ -5574,9 +6815,19 @@ async function run6(options) {
|
|
|
5574
6815
|
"adb not found \u2014 `beeos device attach` will download Android platform-tools on first use"
|
|
5575
6816
|
);
|
|
5576
6817
|
}
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
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"
|
|
5580
6831
|
);
|
|
5581
6832
|
}
|
|
5582
6833
|
const bloatedLogs = await checkLogFileSizes();
|
|
@@ -5587,29 +6838,26 @@ async function run6(options) {
|
|
|
5587
6838
|
hints.push(`truncate safely: : > ${l.path}`);
|
|
5588
6839
|
}
|
|
5589
6840
|
if (options.json) {
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
services
|
|
5604
|
-
},
|
|
5605
|
-
agentGateway: agentGatewayHealth,
|
|
5606
|
-
tools,
|
|
5607
|
-
warnings,
|
|
5608
|
-
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
|
|
5609
6854
|
},
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
6855
|
+
agentGateway: agentGatewayHealth,
|
|
6856
|
+
tools,
|
|
6857
|
+
npmShims: shimReport,
|
|
6858
|
+
warnings,
|
|
6859
|
+
hints
|
|
6860
|
+
})
|
|
5613
6861
|
);
|
|
5614
6862
|
return;
|
|
5615
6863
|
}
|
|
@@ -5633,10 +6881,19 @@ async function run6(options) {
|
|
|
5633
6881
|
console.log(` - ${s.id.padEnd(28)} ${status2}${pid}`);
|
|
5634
6882
|
}
|
|
5635
6883
|
console.log(` adb : ${tools.adb.path ?? "not found"}`);
|
|
5636
|
-
console.log(` python3 : ${tools.python.path ?? "not found"}`);
|
|
5637
6884
|
console.log(` scrcpy-bridge: ${tools.scrcpyBridge.path ?? "not installed (auto on attach)"}`);
|
|
5638
6885
|
console.log(` vnc-bridge : ${tools.vncBridge.path ?? "not installed (auto on --vnc-host)"}`);
|
|
5639
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("");
|
|
5640
6897
|
if (warnings.length > 0) {
|
|
5641
6898
|
console.log(" Warnings:");
|
|
5642
6899
|
for (const w of warnings) {
|
|
@@ -5654,38 +6911,57 @@ async function run6(options) {
|
|
|
5654
6911
|
console.log("");
|
|
5655
6912
|
}
|
|
5656
6913
|
}
|
|
5657
|
-
async function collectToolStatus(
|
|
5658
|
-
const [adbPath,
|
|
6914
|
+
async function collectToolStatus() {
|
|
6915
|
+
const [adbPath, scrcpyPath, vncPath] = await Promise.all([
|
|
5659
6916
|
findAdb().catch(() => null),
|
|
5660
|
-
findPython(p).catch(() => null),
|
|
5661
6917
|
scrcpyBridgeRuntime.findBinary().catch(() => null),
|
|
5662
6918
|
vncBridgeRuntime.findBinary().catch(() => null)
|
|
5663
6919
|
]);
|
|
5664
6920
|
return {
|
|
5665
6921
|
adb: { path: adbPath },
|
|
5666
|
-
python: { path: pythonPath },
|
|
5667
6922
|
scrcpyBridge: { path: scrcpyPath },
|
|
5668
6923
|
vncBridge: { path: vncPath }
|
|
5669
6924
|
};
|
|
5670
6925
|
}
|
|
5671
|
-
async function
|
|
5672
|
-
const
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
const
|
|
5676
|
-
|
|
5677
|
-
|
|
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";
|
|
5678
6942
|
}
|
|
5679
|
-
|
|
5680
|
-
}
|
|
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 "?";
|
|
5681
6958
|
}
|
|
5682
|
-
return null;
|
|
5683
6959
|
}
|
|
5684
6960
|
async function checkLogFileSizes() {
|
|
5685
6961
|
const dir = path9.join(beeoHome(), "logs", "services");
|
|
5686
6962
|
let entries;
|
|
5687
6963
|
try {
|
|
5688
|
-
entries = await
|
|
6964
|
+
entries = await fs9.readdir(dir);
|
|
5689
6965
|
} catch {
|
|
5690
6966
|
return [];
|
|
5691
6967
|
}
|
|
@@ -5694,7 +6970,7 @@ async function checkLogFileSizes() {
|
|
|
5694
6970
|
if (!name.endsWith(".log")) continue;
|
|
5695
6971
|
const full = path9.join(dir, name);
|
|
5696
6972
|
try {
|
|
5697
|
-
const st = await
|
|
6973
|
+
const st = await fs9.stat(full);
|
|
5698
6974
|
if (st.isFile() && st.size >= LOG_SIZE_WARN_BYTES) {
|
|
5699
6975
|
bloated.push({ path: full, size: st.size });
|
|
5700
6976
|
}
|
|
@@ -5763,6 +7039,7 @@ function formatAgentGatewayStatus(h) {
|
|
|
5763
7039
|
}
|
|
5764
7040
|
|
|
5765
7041
|
// src/commands/init.ts
|
|
7042
|
+
init_progress2();
|
|
5766
7043
|
init_fallback_banner();
|
|
5767
7044
|
async function run7(options) {
|
|
5768
7045
|
await ensureDirs();
|
|
@@ -5793,21 +7070,26 @@ async function run7(options) {
|
|
|
5793
7070
|
return;
|
|
5794
7071
|
}
|
|
5795
7072
|
if (decision === "upgrade") {
|
|
5796
|
-
|
|
7073
|
+
if (await shouldUpgradeBeeosSuite(options)) {
|
|
7074
|
+
const exited = await upgradeAndMaybeExit(options);
|
|
7075
|
+
if (exited) return;
|
|
7076
|
+
}
|
|
7077
|
+
console.log("Ensuring OpenClaw is running...\n");
|
|
5797
7078
|
}
|
|
5798
7079
|
if (decision === "rebind-new-key") {
|
|
5799
7080
|
await rotateIdentity();
|
|
5800
|
-
}
|
|
5801
|
-
if (decision === "rebind-keep-key" || decision === "rebind-new-key") {
|
|
5802
7081
|
await removeBindingInfo();
|
|
5803
7082
|
}
|
|
5804
7083
|
const frameworkId = await decideFramework(state, decision, options);
|
|
5805
7084
|
const descriptor = frameworkById(frameworkId);
|
|
5806
7085
|
if (!descriptor || descriptor.status !== "available") {
|
|
5807
7086
|
const avail = availableFrameworks().map((f) => f.id).join(", ");
|
|
5808
|
-
throw new
|
|
5809
|
-
|
|
5810
|
-
|
|
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
|
+
});
|
|
5811
7093
|
}
|
|
5812
7094
|
await run(descriptor.id, {
|
|
5813
7095
|
force: true,
|
|
@@ -5851,7 +7133,7 @@ async function decideFramework(state, decision, options) {
|
|
|
5851
7133
|
if (options.framework && options.framework.trim()) {
|
|
5852
7134
|
return options.framework.trim();
|
|
5853
7135
|
}
|
|
5854
|
-
if (state.binding &&
|
|
7136
|
+
if (state.binding && decision === "upgrade") {
|
|
5855
7137
|
const persisted = state.binding.framework?.trim();
|
|
5856
7138
|
if (persisted) return persisted;
|
|
5857
7139
|
return defaultFrameworkId();
|
|
@@ -5955,6 +7237,60 @@ function prompt(question) {
|
|
|
5955
7237
|
});
|
|
5956
7238
|
});
|
|
5957
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
|
+
}
|
|
5958
7294
|
|
|
5959
7295
|
// src/commands/service.ts
|
|
5960
7296
|
init_dist();
|
|
@@ -5963,18 +7299,14 @@ async function install(options) {
|
|
|
5963
7299
|
const check = await mgr.selfCheck();
|
|
5964
7300
|
const reason = activeFallbackReason();
|
|
5965
7301
|
if (options.json) {
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
},
|
|
5975
|
-
null,
|
|
5976
|
-
2
|
|
5977
|
-
)
|
|
7302
|
+
emitJsonEnvelope(
|
|
7303
|
+
jsonOk({
|
|
7304
|
+
available: check.available,
|
|
7305
|
+
manager: mgr.kind,
|
|
7306
|
+
fallbackReason: reason,
|
|
7307
|
+
warnings: check.warnings,
|
|
7308
|
+
hints: check.hints
|
|
7309
|
+
})
|
|
5978
7310
|
);
|
|
5979
7311
|
return;
|
|
5980
7312
|
}
|
|
@@ -6008,7 +7340,7 @@ async function uninstall(options) {
|
|
|
6008
7340
|
}
|
|
6009
7341
|
}
|
|
6010
7342
|
if (options.json) {
|
|
6011
|
-
|
|
7343
|
+
emitJsonEnvelope(jsonOk({ removed, errors }));
|
|
6012
7344
|
return;
|
|
6013
7345
|
}
|
|
6014
7346
|
if (removed.length === 0 && errors.length === 0) {
|
|
@@ -6027,12 +7359,8 @@ async function status(options) {
|
|
|
6027
7359
|
} catch {
|
|
6028
7360
|
}
|
|
6029
7361
|
if (options.json) {
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
{ manager: mgr.kind, fallbackReason: reason, services },
|
|
6033
|
-
null,
|
|
6034
|
-
2
|
|
6035
|
-
)
|
|
7362
|
+
emitJsonEnvelope(
|
|
7363
|
+
jsonOk({ manager: mgr.kind, fallbackReason: reason, services })
|
|
6036
7364
|
);
|
|
6037
7365
|
return;
|
|
6038
7366
|
}
|
|
@@ -6055,13 +7383,15 @@ async function run8(options) {
|
|
|
6055
7383
|
const mgr = await getServiceManager();
|
|
6056
7384
|
const mig = await migrateLegacySupervisor(mgr);
|
|
6057
7385
|
if (options.json) {
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
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
|
+
);
|
|
6065
7395
|
return;
|
|
6066
7396
|
}
|
|
6067
7397
|
if (!mig.ran) {
|
|
@@ -6083,6 +7413,98 @@ async function run8(options) {
|
|
|
6083
7413
|
}
|
|
6084
7414
|
}
|
|
6085
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
|
+
|
|
6086
7508
|
// src/index.ts
|
|
6087
7509
|
setPlatformAdapter(new NodePlatformAdapter());
|
|
6088
7510
|
var program = new Command();
|
|
@@ -6091,15 +7513,18 @@ program.name("beeos").version(cliVersion.display).description("BeeOS \u2014 run
|
|
|
6091
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));
|
|
6092
7514
|
program.command("doctor").description("Inspect local BeeOS state (install, binding, services, warnings)").option("--json", "Output machine-readable JSON", false).action((opts) => run6(opts));
|
|
6093
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));
|
|
6094
7519
|
var serviceCmd = program.command("service").description("Inspect and manage the OS-native service manager (launchd / systemd --user / Task Scheduler)");
|
|
6095
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));
|
|
6096
7521
|
serviceCmd.command("uninstall").description("Uninstall every BeeOS-managed OS service").option("--json", "Output machine-readable JSON", false).action((opts) => uninstall(opts));
|
|
6097
7522
|
serviceCmd.command("status").description("List every BeeOS-managed OS service").option("--json", "Output machine-readable JSON", false).action((opts) => status(opts));
|
|
6098
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));
|
|
6099
7524
|
program.command("stop").description("Stop a running agent").argument("<agent_type>", "Agent type to stop").action((agentFramework) => run2(agentFramework));
|
|
6100
|
-
program.command("status").description("Show status of managed agents").action(run3);
|
|
6101
|
-
program.command("update").description("Update an agent to the latest version").argument("<agent_type>", "Agent type to update").action((agentFramework) => run4(agentFramework));
|
|
6102
|
-
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);
|
|
6103
7528
|
var deviceCmd = program.command("device").description("Manage device agents (Android/Desktop/ChromeOS)");
|
|
6104
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(
|
|
6105
7530
|
"--no-video",
|
|
@@ -6107,7 +7532,10 @@ deviceCmd.command("attach").description("Attach an ADB-connected device as an AI
|
|
|
6107
7532
|
).option(
|
|
6108
7533
|
"--vnc-host <host>",
|
|
6109
7534
|
"Use vnc-bridge against a VNC server at this host (desktop/Linux/macOS). Implies video mode = vnc instead of scrcpy."
|
|
6110
|
-
).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);
|
|
6111
7539
|
deviceCmd.command("detach").description("Detach a device agent").option("--serial <serial>", "ADB device serial number").option("--all", "Detach all devices", false).action(detach);
|
|
6112
7540
|
deviceCmd.command("list").description("List attached devices").option("--local", "Only show locally running device agents", false).action(list);
|
|
6113
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);
|
|
@@ -6116,6 +7544,15 @@ deviceCmd.command("refresh-config").description(
|
|
|
6116
7544
|
"Re-fetch this device's VLM/ARouter config from Agent Gateway and rewrite ~/.beeos/<instance_id>.config.json (use after dashboard changes)."
|
|
6117
7545
|
).option("--serial <serial>", "ADB device serial number").option("--all", "Refresh every attached device", false).action(refreshConfig);
|
|
6118
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
|
+
}
|
|
6119
7556
|
console.error(err.message);
|
|
6120
7557
|
process.exit(1);
|
|
6121
7558
|
});
|