@beeos-ai/cli 1.1.2 → 1.1.3
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 +245 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6583,6 +6583,60 @@ function printFleetNotRunningHint() {
|
|
|
6583
6583
|
console.log(" device-agent fleet start # foreground (debug-friendly)");
|
|
6584
6584
|
console.log("");
|
|
6585
6585
|
}
|
|
6586
|
+
async function getFleetDeviceSnapshotBestEffort(serial, baseUrl = FLEET_STATUS_BASE_URL) {
|
|
6587
|
+
let res;
|
|
6588
|
+
try {
|
|
6589
|
+
res = await fetch(
|
|
6590
|
+
`${baseUrl}/fleet/devices/${encodeURIComponent(serial)}`,
|
|
6591
|
+
{
|
|
6592
|
+
method: "GET",
|
|
6593
|
+
signal: AbortSignal.timeout(FLEET_NOTIFY_TIMEOUT_MS)
|
|
6594
|
+
}
|
|
6595
|
+
);
|
|
6596
|
+
} catch {
|
|
6597
|
+
return null;
|
|
6598
|
+
}
|
|
6599
|
+
if (!res.ok) {
|
|
6600
|
+
return null;
|
|
6601
|
+
}
|
|
6602
|
+
let body;
|
|
6603
|
+
try {
|
|
6604
|
+
body = await res.json();
|
|
6605
|
+
} catch {
|
|
6606
|
+
return null;
|
|
6607
|
+
}
|
|
6608
|
+
return parseFleetDeviceSnapshot(body);
|
|
6609
|
+
}
|
|
6610
|
+
function parseFleetDeviceSnapshot(raw) {
|
|
6611
|
+
if (typeof raw !== "object" || raw === null) return null;
|
|
6612
|
+
const r = raw;
|
|
6613
|
+
if (typeof r.serial !== "string" || r.serial.length === 0) return null;
|
|
6614
|
+
if (!isFleetDeviceState(r.state)) return null;
|
|
6615
|
+
return {
|
|
6616
|
+
serial: r.serial,
|
|
6617
|
+
state: r.state,
|
|
6618
|
+
instanceId: typeof r.instanceId === "string" ? r.instanceId : null,
|
|
6619
|
+
mcpPid: typeof r.mcpPid === "number" ? r.mcpPid : null,
|
|
6620
|
+
daemonPid: typeof r.daemonPid === "number" ? r.daemonPid : null,
|
|
6621
|
+
sinceMs: typeof r.sinceMs === "number" ? r.sinceMs : 0
|
|
6622
|
+
};
|
|
6623
|
+
}
|
|
6624
|
+
function isFleetDeviceState(v) {
|
|
6625
|
+
switch (v) {
|
|
6626
|
+
case "adb_offline":
|
|
6627
|
+
case "adb_unauthorized":
|
|
6628
|
+
case "awaiting_bind":
|
|
6629
|
+
case "spawning":
|
|
6630
|
+
case "healthy":
|
|
6631
|
+
case "grace":
|
|
6632
|
+
case "degraded":
|
|
6633
|
+
case "gone":
|
|
6634
|
+
case "crashed":
|
|
6635
|
+
return true;
|
|
6636
|
+
default:
|
|
6637
|
+
return false;
|
|
6638
|
+
}
|
|
6639
|
+
}
|
|
6586
6640
|
var FLEET_STATUS_BASE_URL, FLEET_NOTIFY_TIMEOUT_MS, FLEET_SHUTDOWN_TIMEOUT_MS;
|
|
6587
6641
|
var init_fleet_notify = __esm({
|
|
6588
6642
|
"src/commands/device/fleet-notify.ts"() {
|
|
@@ -6855,6 +6909,169 @@ var init_instance_picker = __esm({
|
|
|
6855
6909
|
}
|
|
6856
6910
|
});
|
|
6857
6911
|
|
|
6912
|
+
// src/commands/device/wait-for-fleet-healthy.ts
|
|
6913
|
+
import ora2 from "ora";
|
|
6914
|
+
async function waitForFleetHealthy(opts) {
|
|
6915
|
+
const timeoutMs = resolveTimeoutMs(opts.timeoutMs);
|
|
6916
|
+
if (timeoutMs <= 0) {
|
|
6917
|
+
return { kind: "skipped", reason: "opt_out" };
|
|
6918
|
+
}
|
|
6919
|
+
const intervalMs = opts.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
6920
|
+
const useSpinner = (opts.spinner ?? true) && Boolean(process.stdout?.isTTY);
|
|
6921
|
+
const reporter = useSpinner ? new SpinnerReporter() : new BareReporter();
|
|
6922
|
+
reporter.start(opts.serial, timeoutMs);
|
|
6923
|
+
const startedAt = Date.now();
|
|
6924
|
+
let nullStreak = 0;
|
|
6925
|
+
let lastSnapshot = null;
|
|
6926
|
+
try {
|
|
6927
|
+
while (true) {
|
|
6928
|
+
const snapshot = await getFleetDeviceSnapshotBestEffort(
|
|
6929
|
+
opts.serial,
|
|
6930
|
+
opts.fleetBaseUrl
|
|
6931
|
+
);
|
|
6932
|
+
const elapsedMs = Date.now() - startedAt;
|
|
6933
|
+
if (snapshot === null) {
|
|
6934
|
+
nullStreak += 1;
|
|
6935
|
+
if (nullStreak >= FLEET_DEAD_NULL_THRESHOLD) {
|
|
6936
|
+
reporter.skipFleetDown();
|
|
6937
|
+
return { kind: "skipped", reason: "fleet_not_running" };
|
|
6938
|
+
}
|
|
6939
|
+
} else {
|
|
6940
|
+
nullStreak = 0;
|
|
6941
|
+
lastSnapshot = snapshot;
|
|
6942
|
+
if (snapshot.state === "healthy") {
|
|
6943
|
+
reporter.online(snapshot, elapsedMs);
|
|
6944
|
+
return { kind: "online", elapsedMs, snapshot };
|
|
6945
|
+
}
|
|
6946
|
+
reporter.update(snapshot, elapsedMs);
|
|
6947
|
+
}
|
|
6948
|
+
if (elapsedMs + intervalMs >= timeoutMs) {
|
|
6949
|
+
reporter.timeout(lastSnapshot, elapsedMs);
|
|
6950
|
+
return { kind: "timeout", elapsedMs, lastSnapshot };
|
|
6951
|
+
}
|
|
6952
|
+
await sleep6(intervalMs);
|
|
6953
|
+
}
|
|
6954
|
+
} finally {
|
|
6955
|
+
reporter.dispose();
|
|
6956
|
+
}
|
|
6957
|
+
}
|
|
6958
|
+
function resolveTimeoutMs(optsTimeoutMs) {
|
|
6959
|
+
const envRaw = process.env.BEEOS_AGENT_WAIT_TIMEOUT_MS;
|
|
6960
|
+
if (envRaw !== void 0 && envRaw !== "") {
|
|
6961
|
+
const parsed = Number(envRaw);
|
|
6962
|
+
if (Number.isFinite(parsed) && parsed >= 0) return parsed;
|
|
6963
|
+
}
|
|
6964
|
+
if (process.env.BEEOS_NO_WAIT_AGENT === "1") return 0;
|
|
6965
|
+
if (optsTimeoutMs !== void 0) return optsTimeoutMs;
|
|
6966
|
+
return DEFAULT_TIMEOUT_MS2;
|
|
6967
|
+
}
|
|
6968
|
+
function sleep6(ms) {
|
|
6969
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
6970
|
+
}
|
|
6971
|
+
function formatSec(ms) {
|
|
6972
|
+
return `${(ms / 1e3).toFixed(1)}s`;
|
|
6973
|
+
}
|
|
6974
|
+
function formatOnline(snapshot, elapsedMs) {
|
|
6975
|
+
const mcp = snapshot.mcpPid ?? "?";
|
|
6976
|
+
const daemon = snapshot.daemonPid ?? "?";
|
|
6977
|
+
return `Device fleet healthy: mcp_pid=${mcp} daemon_pid=${daemon} (${formatSec(elapsedMs)})`;
|
|
6978
|
+
}
|
|
6979
|
+
function formatTimeout(serial, lastSnapshot, elapsedMs) {
|
|
6980
|
+
const state = lastSnapshot?.state ?? "unknown";
|
|
6981
|
+
const daemonPid = lastSnapshot?.daemonPid ?? null;
|
|
6982
|
+
return `Waiting for ${serial} healthy timed out after ${formatSec(elapsedMs)} \u2014 current state: ${state}, daemon_pid=${daemonPid ?? "null"}`;
|
|
6983
|
+
}
|
|
6984
|
+
var DEFAULT_TIMEOUT_MS2, DEFAULT_INTERVAL_MS, FLEET_DEAD_NULL_THRESHOLD, SpinnerReporter, BareReporter;
|
|
6985
|
+
var init_wait_for_fleet_healthy = __esm({
|
|
6986
|
+
"src/commands/device/wait-for-fleet-healthy.ts"() {
|
|
6987
|
+
"use strict";
|
|
6988
|
+
init_fleet_notify();
|
|
6989
|
+
DEFAULT_TIMEOUT_MS2 = 3e4;
|
|
6990
|
+
DEFAULT_INTERVAL_MS = 1e3;
|
|
6991
|
+
FLEET_DEAD_NULL_THRESHOLD = 3;
|
|
6992
|
+
SpinnerReporter = class {
|
|
6993
|
+
spinner = null;
|
|
6994
|
+
serial = "";
|
|
6995
|
+
start(serial, timeoutMs) {
|
|
6996
|
+
this.serial = serial;
|
|
6997
|
+
this.spinner = ora2({
|
|
6998
|
+
text: `Waiting for device-agent fleet to mark ${serial} healthy (timeout=${formatSec(timeoutMs)})...`,
|
|
6999
|
+
stream: process.stderr
|
|
7000
|
+
}).start();
|
|
7001
|
+
}
|
|
7002
|
+
update(snapshot, elapsedMs) {
|
|
7003
|
+
if (!this.spinner) return;
|
|
7004
|
+
this.spinner.text = `Waiting for ${this.serial} healthy \u2014 current state=${snapshot.state} (${formatSec(elapsedMs)})`;
|
|
7005
|
+
}
|
|
7006
|
+
online(snapshot, elapsedMs) {
|
|
7007
|
+
if (!this.spinner) return;
|
|
7008
|
+
this.spinner.succeed(formatOnline(snapshot, elapsedMs));
|
|
7009
|
+
this.spinner = null;
|
|
7010
|
+
console.log(
|
|
7011
|
+
" Agent may take ~1-2s more to register on dashboard."
|
|
7012
|
+
);
|
|
7013
|
+
}
|
|
7014
|
+
timeout(lastSnapshot, elapsedMs) {
|
|
7015
|
+
if (!this.spinner) return;
|
|
7016
|
+
this.spinner.warn(formatTimeout(this.serial, lastSnapshot, elapsedMs));
|
|
7017
|
+
this.spinner = null;
|
|
7018
|
+
console.log(
|
|
7019
|
+
` See ~/.beeos/logs/services/device-agent-${this.serial}.log for details.`
|
|
7020
|
+
);
|
|
7021
|
+
}
|
|
7022
|
+
skipFleetDown() {
|
|
7023
|
+
if (!this.spinner) return;
|
|
7024
|
+
this.spinner.info(
|
|
7025
|
+
"Skipped fleet wait \u2014 supervisor not running. Run `beeos start` or `beeos doctor`."
|
|
7026
|
+
);
|
|
7027
|
+
this.spinner = null;
|
|
7028
|
+
}
|
|
7029
|
+
dispose() {
|
|
7030
|
+
if (this.spinner) {
|
|
7031
|
+
this.spinner.stop();
|
|
7032
|
+
this.spinner = null;
|
|
7033
|
+
}
|
|
7034
|
+
}
|
|
7035
|
+
};
|
|
7036
|
+
BareReporter = class {
|
|
7037
|
+
serial = "";
|
|
7038
|
+
lastState = null;
|
|
7039
|
+
start(serial, timeoutMs) {
|
|
7040
|
+
this.serial = serial;
|
|
7041
|
+
console.log(
|
|
7042
|
+
`Waiting for device-agent fleet to mark ${serial} healthy (timeout=${formatSec(timeoutMs)})...`
|
|
7043
|
+
);
|
|
7044
|
+
}
|
|
7045
|
+
update(snapshot, elapsedMs) {
|
|
7046
|
+
if (snapshot.state === this.lastState) return;
|
|
7047
|
+
this.lastState = snapshot.state;
|
|
7048
|
+
console.log(
|
|
7049
|
+
`[${formatSec(elapsedMs)}] ${this.serial} state=${snapshot.state}`
|
|
7050
|
+
);
|
|
7051
|
+
}
|
|
7052
|
+
online(snapshot, elapsedMs) {
|
|
7053
|
+
console.log(`\u2713 ${formatOnline(snapshot, elapsedMs)}`);
|
|
7054
|
+
console.log(
|
|
7055
|
+
" Agent may take ~1-2s more to register on dashboard."
|
|
7056
|
+
);
|
|
7057
|
+
}
|
|
7058
|
+
timeout(lastSnapshot, elapsedMs) {
|
|
7059
|
+
console.log(`\u26A0 ${formatTimeout(this.serial, lastSnapshot, elapsedMs)}`);
|
|
7060
|
+
console.log(
|
|
7061
|
+
` See ~/.beeos/logs/services/device-agent-${this.serial}.log for details.`
|
|
7062
|
+
);
|
|
7063
|
+
}
|
|
7064
|
+
skipFleetDown() {
|
|
7065
|
+
console.log(
|
|
7066
|
+
"Skipped fleet wait \u2014 supervisor not running. Run `beeos start` or `beeos doctor`."
|
|
7067
|
+
);
|
|
7068
|
+
}
|
|
7069
|
+
dispose() {
|
|
7070
|
+
}
|
|
7071
|
+
};
|
|
7072
|
+
}
|
|
7073
|
+
});
|
|
7074
|
+
|
|
6858
7075
|
// src/commands/device/state.ts
|
|
6859
7076
|
import lockfile from "proper-lockfile";
|
|
6860
7077
|
function deviceAgentTargetId(serial) {
|
|
@@ -7101,10 +7318,17 @@ async function attach(options) {
|
|
|
7101
7318
|
const outcome = await notifyFleetRestartDeviceBestEffort(serial);
|
|
7102
7319
|
if (outcome === "not_running") {
|
|
7103
7320
|
await maybeNotifyFleetWithHint(cfg);
|
|
7321
|
+
} else {
|
|
7322
|
+
await maybeWaitForFleetHealthy(serial, options);
|
|
7104
7323
|
}
|
|
7105
7324
|
return;
|
|
7106
7325
|
}
|
|
7107
7326
|
await maybeNotifyFleetWithHint(cfg);
|
|
7327
|
+
await maybeWaitForFleetHealthy(serial, options);
|
|
7328
|
+
}
|
|
7329
|
+
async function maybeWaitForFleetHealthy(serial, options) {
|
|
7330
|
+
if (options.wait === false) return;
|
|
7331
|
+
await waitForFleetHealthy({ serial });
|
|
7108
7332
|
}
|
|
7109
7333
|
async function attachAll(cfg, reporter, withVideo, options) {
|
|
7110
7334
|
const all = await deviceRuntime.listAdbDevices();
|
|
@@ -7230,6 +7454,7 @@ async function attachAll(cfg, reporter, withVideo, options) {
|
|
|
7230
7454
|
}
|
|
7231
7455
|
console.log(`Attached ${committed.length} device(s); supervision via device-agent fleet.`);
|
|
7232
7456
|
});
|
|
7457
|
+
const committedSerials = commits.filter((c) => c.ok).map((c) => c.serial);
|
|
7233
7458
|
if (reboundSerials.length > 0) {
|
|
7234
7459
|
let anyNotRunning = false;
|
|
7235
7460
|
for (const serial of reboundSerials) {
|
|
@@ -7238,12 +7463,27 @@ async function attachAll(cfg, reporter, withVideo, options) {
|
|
|
7238
7463
|
}
|
|
7239
7464
|
if (anyNotRunning) {
|
|
7240
7465
|
await maybeNotifyFleetWithHint(cfg);
|
|
7241
|
-
} else if (reboundSerials.length <
|
|
7466
|
+
} else if (reboundSerials.length < committedSerials.length) {
|
|
7242
7467
|
await maybeNotifyFleetWithHint(cfg);
|
|
7243
7468
|
}
|
|
7469
|
+
await maybeWaitForFleetHealthyMany(committedSerials, options);
|
|
7244
7470
|
return;
|
|
7245
7471
|
}
|
|
7246
7472
|
await maybeNotifyFleetWithHint(cfg);
|
|
7473
|
+
await maybeWaitForFleetHealthyMany(committedSerials, options);
|
|
7474
|
+
}
|
|
7475
|
+
async function maybeWaitForFleetHealthyMany(serials, options) {
|
|
7476
|
+
if (options.wait === false || serials.length === 0) return;
|
|
7477
|
+
await Promise.allSettled(
|
|
7478
|
+
serials.map(
|
|
7479
|
+
(s) => (
|
|
7480
|
+
// Bare mode: ora doesn't compose with parallel callers cleanly,
|
|
7481
|
+
// so we route every serial through the line-per-state-change
|
|
7482
|
+
// reporter even on a TTY.
|
|
7483
|
+
waitForFleetHealthy({ serial: s, spinner: false })
|
|
7484
|
+
)
|
|
7485
|
+
)
|
|
7486
|
+
);
|
|
7247
7487
|
}
|
|
7248
7488
|
function commitAttachResults(state, commits) {
|
|
7249
7489
|
const committed = [];
|
|
@@ -7558,6 +7798,7 @@ var init_attach = __esm({
|
|
|
7558
7798
|
init_instance_picker();
|
|
7559
7799
|
init_fallback_banner();
|
|
7560
7800
|
init_fleet_notify();
|
|
7801
|
+
init_wait_for_fleet_healthy();
|
|
7561
7802
|
init_state2();
|
|
7562
7803
|
}
|
|
7563
7804
|
});
|
|
@@ -9932,6 +10173,9 @@ deviceCmd.command("attach").description("Attach an ADB-connected device as an AI
|
|
|
9932
10173
|
).option(
|
|
9933
10174
|
"--agent-gateway-url <url>",
|
|
9934
10175
|
"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."
|
|
10176
|
+
).option(
|
|
10177
|
+
"--no-wait",
|
|
10178
|
+
"Skip waiting for the fleet supervisor to mark the device healthy after bind. By default, attach polls fleet on 127.0.0.1:7950 for up to 30s (BEEOS_AGENT_WAIT_TIMEOUT_MS overrides) so terminal exit aligns with dashboard agent-online status. Set this for CI/scripts that don't care about post-bind dashboard sync."
|
|
9935
10179
|
).action(attach);
|
|
9936
10180
|
deviceCmd.command("detach").description("Detach a device agent").option("--serial <serial>", "ADB device serial number").option("--all", "Detach all devices", false).option(
|
|
9937
10181
|
"--rotate-identity",
|