@amd-gaia/agent-ui 0.25.4-rc.2 → 0.25.4-rc.4
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/bin/gaia-ui.cjs +2 -23
- package/dist/assets/AdvancedSettings-DoIDd09m.js +1 -0
- package/dist/assets/ConnectorsPane-CK3h7C7E.js +1 -0
- package/dist/assets/ConnectorsPane-CzLYXGMl.css +1 -0
- package/dist/assets/DocumentLibrary-BWvkvxTu.js +4 -0
- package/dist/assets/FileBrowser-DhqWGirJ.js +4 -0
- package/dist/assets/MemoryDashboard-BF-o8284.js +1 -0
- package/dist/assets/MobileAccessModal-51t2kQaJ.js +2 -0
- package/dist/assets/MobileAccessModal-BkVDm3YG.css +1 -0
- package/dist/assets/ScheduleManager-2xjZsnLK.js +2 -0
- package/dist/assets/api-BzlFpP4T.js +4 -0
- package/dist/assets/apiBase-BMkGbdXt.js +1 -0
- package/dist/assets/index-C_Cx5IlD.css +1 -0
- package/dist/assets/index-hAcJijAU.js +78 -0
- package/dist/assets/{zap-BdhDcjhB.js → zap-D7aLu_f0.js} +1 -1
- package/dist/icons/apple-touch-icon.png +0 -0
- package/dist/icons/icon-192.png +0 -0
- package/dist/icons/icon-512.png +0 -0
- package/dist/icons/icon-maskable-192.png +0 -0
- package/dist/icons/icon-maskable-512.png +0 -0
- package/dist/index.html +8 -3
- package/dist/manifest.json +16 -0
- package/dist/sw.js +49 -0
- package/main-safety-net.cjs +106 -8
- package/main.cjs +139 -159
- package/package.json +2 -1
- package/preload.cjs +16 -9
- package/services/auto-updater.cjs +780 -216
- package/services/backend-installer.cjs +233 -344
- package/services/engine-install.cjs +142 -0
- package/services/index-query.cjs +7 -1
- package/services/quit-guard.cjs +10 -3
- package/services/tray-manager.cjs +12 -7
- package/dist/assets/AdvancedSettings-B4MY5XPH.js +0 -1
- package/dist/assets/ConnectorsPane-B71QL-Kl.js +0 -1
- package/dist/assets/ConnectorsPane-Dzu2DRTc.css +0 -1
- package/dist/assets/DocumentLibrary-c78Y6arz.js +0 -4
- package/dist/assets/FileBrowser-DwEPODjV.js +0 -4
- package/dist/assets/MemoryDashboard-BQhg3t2B.js +0 -1
- package/dist/assets/MobileAccessModal-DuVy9F1u.css +0 -1
- package/dist/assets/MobileAccessModal-RI4HtcYe.js +0 -2
- package/dist/assets/ScheduleManager-tcyjbGY9.js +0 -2
- package/dist/assets/apiBase-DRtaTd8u.js +0 -1
- package/dist/assets/index-C9zLn8zo.css +0 -1
- package/dist/assets/index-CKOG_8jk.js +0 -78
- package/services/backend-installer-progress-dialog.cjs +0 -510
|
@@ -16,10 +16,12 @@
|
|
|
16
16
|
* - ensureUv() → Promise<void>
|
|
17
17
|
* - installBackend(opts) → Promise<void>
|
|
18
18
|
* - ensureBackend(opts) → Promise<string> (returns gaia bin path)
|
|
19
|
+
* - isBackendCurrent() → boolean (nothing to install or refresh)
|
|
19
20
|
* - getInstalledVersion(gaiaBin) → string | null
|
|
20
21
|
* - findGaiaBin() → string | null
|
|
21
22
|
* - getState() / setState() → state machine helpers
|
|
22
23
|
* - getLogPath() / getStatePath() → path helpers
|
|
24
|
+
* - readLogTail(maxLines) → string[]
|
|
23
25
|
* - runPreChecks(opts) → Promise<PreCheckResult>
|
|
24
26
|
* - STATES → state name constants
|
|
25
27
|
*
|
|
@@ -54,10 +56,16 @@ const GAIA_PYTHON_BIN = IS_WINDOWS
|
|
|
54
56
|
|
|
55
57
|
const STATE_FILE = path.join(GAIA_HOME, "electron-install-state.json");
|
|
56
58
|
const LOG_FILE = path.join(GAIA_HOME, "electron-install.log");
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
59
|
+
// hub/installer.py writes this once the flagship is installed; first-run setup installs it.
|
|
60
|
+
const FLAGSHIP_SENTINEL = path.join(GAIA_HOME, "agents", "gaia", ".installed");
|
|
61
|
+
/** `gaia init --agent-only` kept the installed agent: the hub couldn't be reached. */
|
|
62
|
+
const AGENT_KEPT_EXIT = 5;
|
|
63
|
+
|
|
64
|
+
// Room for what this installer writes: Python 3.12, uv's wheel cache and the
|
|
65
|
+
// `amd-gaia[ui]` venv (0.3 GB on macOS arm64 for 0.25.1rc5, under 1 GB in
|
|
66
|
+
// all). Models are not installed here; first-run setup checks the disk for
|
|
67
|
+
// the download the user picks.
|
|
68
|
+
const MIN_DISK_SPACE_BYTES = 2 * 1024 * 1024 * 1024; // 2 GB
|
|
61
69
|
const NETWORK_CHECK_HOSTS = Object.freeze([
|
|
62
70
|
"https://pypi.org/simple/",
|
|
63
71
|
"https://astral.sh",
|
|
@@ -131,24 +139,30 @@ const STAGES = Object.freeze({
|
|
|
131
139
|
ENSURE_UV: "ensure-uv",
|
|
132
140
|
CREATE_VENV: "create-venv",
|
|
133
141
|
INSTALL_PACKAGE: "install-package",
|
|
134
|
-
GAIA_INIT: "gaia-init",
|
|
135
142
|
VERIFY: "verify",
|
|
143
|
+
// After the package, and outside the weighted bar: it has no percent to report.
|
|
144
|
+
AGENT_REFRESH: "agent-refresh",
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Why an install failed, for what the app tells the user to do about it.
|
|
148
|
+
const FAILURE_REASONS = Object.freeze({
|
|
149
|
+
DISK: "disk",
|
|
150
|
+
NETWORK: "network",
|
|
151
|
+
TLS: "tls",
|
|
152
|
+
LOCKED: "locked",
|
|
136
153
|
});
|
|
137
154
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const
|
|
141
|
-
"Waiting for your answer: GAIA found a Lemonade Server already running " +
|
|
142
|
-
"and is asking whether to use it. Setup continues once you choose.";
|
|
155
|
+
// Stage an older installer recorded while it ran `gaia init`; the package was
|
|
156
|
+
// already in place by then.
|
|
157
|
+
const LEGACY_SETUP_STAGE = "gaia-init";
|
|
143
158
|
|
|
144
159
|
// Weight each stage contributes to the overall 0-100 progress.
|
|
145
160
|
// Sum must equal 100.
|
|
146
161
|
const STAGE_WEIGHTS = {
|
|
147
162
|
[STAGES.PRE_CHECKS]: 2,
|
|
148
163
|
[STAGES.ENSURE_UV]: 8,
|
|
149
|
-
[STAGES.CREATE_VENV]:
|
|
150
|
-
[STAGES.INSTALL_PACKAGE]:
|
|
151
|
-
[STAGES.GAIA_INIT]: 28,
|
|
164
|
+
[STAGES.CREATE_VENV]: 15,
|
|
165
|
+
[STAGES.INSTALL_PACKAGE]: 73,
|
|
152
166
|
[STAGES.VERIFY]: 2,
|
|
153
167
|
};
|
|
154
168
|
|
|
@@ -157,7 +171,6 @@ const STAGE_ORDER = [
|
|
|
157
171
|
STAGES.ENSURE_UV,
|
|
158
172
|
STAGES.CREATE_VENV,
|
|
159
173
|
STAGES.INSTALL_PACKAGE,
|
|
160
|
-
STAGES.GAIA_INIT,
|
|
161
174
|
STAGES.VERIFY,
|
|
162
175
|
];
|
|
163
176
|
|
|
@@ -175,10 +188,6 @@ let logStream = null;
|
|
|
175
188
|
*/
|
|
176
189
|
let logRotatedThisSession = false;
|
|
177
190
|
|
|
178
|
-
function isTruthyEnv(value) {
|
|
179
|
-
return /^(1|true|yes|on)$/i.test(String(value || ""));
|
|
180
|
-
}
|
|
181
|
-
|
|
182
191
|
function ensureGaiaHome() {
|
|
183
192
|
try {
|
|
184
193
|
if (!fs.existsSync(GAIA_HOME)) {
|
|
@@ -321,6 +330,16 @@ function getStatePath() {
|
|
|
321
330
|
return STATE_FILE;
|
|
322
331
|
}
|
|
323
332
|
|
|
333
|
+
/** The end of the install log, or nothing when no install has written one. */
|
|
334
|
+
function readLogTail(maxLines = 40) {
|
|
335
|
+
try {
|
|
336
|
+
return fs.readFileSync(LOG_FILE, "utf8").split(/\r?\n/).filter(Boolean).slice(-maxLines);
|
|
337
|
+
} catch (err) {
|
|
338
|
+
if (err.code === "ENOENT") return [];
|
|
339
|
+
throw err;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
324
343
|
// ── State machine ────────────────────────────────────────────────────────────
|
|
325
344
|
|
|
326
345
|
/**
|
|
@@ -396,7 +415,8 @@ function computeOverallPercent(stage, withinStagePercent) {
|
|
|
396
415
|
function makeProgressReporter(onProgress) {
|
|
397
416
|
const safe = typeof onProgress === "function" ? onProgress : () => {};
|
|
398
417
|
return function report(stage, withinStagePercent, message) {
|
|
399
|
-
const percent =
|
|
418
|
+
const percent =
|
|
419
|
+
withinStagePercent === null ? null : computeOverallPercent(stage, withinStagePercent);
|
|
400
420
|
try {
|
|
401
421
|
safe(stage, percent, message || "");
|
|
402
422
|
} catch (err) {
|
|
@@ -405,148 +425,6 @@ function makeProgressReporter(onProgress) {
|
|
|
405
425
|
};
|
|
406
426
|
}
|
|
407
427
|
|
|
408
|
-
// ── gaia init progress (`gaia init --progress-json`) ─────────────────────────
|
|
409
|
-
|
|
410
|
-
// Where the plan and the end of the downloads land in the GAIA_INIT slice; the
|
|
411
|
-
// rest of the slice is Lemonade setup before and verification after.
|
|
412
|
-
const INIT_PLAN_PERCENT = 5;
|
|
413
|
-
const INIT_DOWNLOADED_PERCENT = 95;
|
|
414
|
-
|
|
415
|
-
/**
|
|
416
|
-
* Read one line of `gaia init --progress-json` output. Returns `{ event }` for
|
|
417
|
-
* a progress event, `{ error }` for a line that looks like one but is not, and
|
|
418
|
-
* null for human output.
|
|
419
|
-
*/
|
|
420
|
-
function parseInitEventLine(line) {
|
|
421
|
-
const text = String(line || "").trim();
|
|
422
|
-
if (!text.startsWith("{")) return null;
|
|
423
|
-
let value;
|
|
424
|
-
try {
|
|
425
|
-
value = JSON.parse(text);
|
|
426
|
-
} catch (err) {
|
|
427
|
-
return { error: err.message };
|
|
428
|
-
}
|
|
429
|
-
if (!value || typeof value !== "object" || typeof value.event !== "string") {
|
|
430
|
-
return { error: 'no "event" field' };
|
|
431
|
-
}
|
|
432
|
-
return { event: value };
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
function formatGB(bytes) {
|
|
436
|
-
return `${(Math.max(0, bytes || 0) / 1e9).toFixed(1)} GB`;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
function formatSpeed(bytesPerSecond) {
|
|
440
|
-
return `${(bytesPerSecond / 1e6).toFixed(1)} MB/s`;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
function formatTimeLeft(seconds) {
|
|
444
|
-
if (seconds < 60) return "less than a minute left";
|
|
445
|
-
const minutes = Math.round(seconds / 60);
|
|
446
|
-
if (minutes < 60) return `about ${minutes} min left`;
|
|
447
|
-
return `about ${Math.floor(minutes / 60)} h ${minutes % 60} min left`;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
/**
|
|
451
|
-
* Map `gaia init --progress-json` events onto the GAIA_INIT slice of the bar.
|
|
452
|
-
* `report(withinStagePercent, message)` receives a percent that never goes
|
|
453
|
-
* back. `onLine` returns true for the lines too frequent to log.
|
|
454
|
-
*/
|
|
455
|
-
function createInitProgress(report, logFn = log) {
|
|
456
|
-
const models = new Map();
|
|
457
|
-
let percent = 0;
|
|
458
|
-
|
|
459
|
-
const advance = (within, message) => {
|
|
460
|
-
percent = Math.max(percent, Math.round(within));
|
|
461
|
-
report(percent, message);
|
|
462
|
-
};
|
|
463
|
-
const totals = () => {
|
|
464
|
-
let done = 0;
|
|
465
|
-
let total = 0;
|
|
466
|
-
for (const m of models.values()) {
|
|
467
|
-
total += m.total;
|
|
468
|
-
done += Math.min(m.done, m.total);
|
|
469
|
-
}
|
|
470
|
-
return { done, total };
|
|
471
|
-
};
|
|
472
|
-
const downloadPercent = () => {
|
|
473
|
-
const { done, total } = totals();
|
|
474
|
-
const share = total > 0 ? done / total : 0;
|
|
475
|
-
return INIT_PLAN_PERCENT + (INIT_DOWNLOADED_PERCENT - INIT_PLAN_PERCENT) * share;
|
|
476
|
-
};
|
|
477
|
-
const model = (id) => {
|
|
478
|
-
if (!models.has(id)) models.set(id, { done: 0, total: 0 });
|
|
479
|
-
return models.get(id);
|
|
480
|
-
};
|
|
481
|
-
|
|
482
|
-
function handle(event) {
|
|
483
|
-
switch (event.event) {
|
|
484
|
-
case "step":
|
|
485
|
-
advance(percent, `Step ${event.step} of ${event.total}: ${event.message}`);
|
|
486
|
-
return true;
|
|
487
|
-
case "plan":
|
|
488
|
-
for (const m of event.models || []) {
|
|
489
|
-
if (!m.downloaded) model(m.id).total = m.bytes_total || 0;
|
|
490
|
-
}
|
|
491
|
-
advance(
|
|
492
|
-
INIT_PLAN_PERCENT,
|
|
493
|
-
event.bytes_total > 0
|
|
494
|
-
? `Downloading ${formatGB(event.bytes_total)} of models`
|
|
495
|
-
: "Models already downloaded"
|
|
496
|
-
);
|
|
497
|
-
return true;
|
|
498
|
-
case "progress": {
|
|
499
|
-
const m = model(event.model);
|
|
500
|
-
m.done = event.bytes_done || 0;
|
|
501
|
-
m.total = Math.max(event.bytes_total || 0, m.done);
|
|
502
|
-
const parts = [
|
|
503
|
-
`Downloading ${event.model} (${event.index} of ${event.count}): ` +
|
|
504
|
-
`${formatGB(m.done)} of ${formatGB(m.total)}`,
|
|
505
|
-
];
|
|
506
|
-
if (event.speed_bps > 0) {
|
|
507
|
-
const { done, total } = totals();
|
|
508
|
-
parts.push(
|
|
509
|
-
`at ${formatSpeed(event.speed_bps)}, ` +
|
|
510
|
-
formatTimeLeft((total - done) / event.speed_bps)
|
|
511
|
-
);
|
|
512
|
-
}
|
|
513
|
-
advance(downloadPercent(), parts.join(" "));
|
|
514
|
-
return true;
|
|
515
|
-
}
|
|
516
|
-
case "model_done": {
|
|
517
|
-
const m = model(event.model);
|
|
518
|
-
m.done = m.total;
|
|
519
|
-
advance(downloadPercent(), `${event.model} ready`);
|
|
520
|
-
return true;
|
|
521
|
-
}
|
|
522
|
-
case "done":
|
|
523
|
-
advance(
|
|
524
|
-
event.ok ? INIT_DOWNLOADED_PERCENT : percent,
|
|
525
|
-
event.ok ? "Models downloaded, finishing setup" : "Model download did not finish"
|
|
526
|
-
);
|
|
527
|
-
return true;
|
|
528
|
-
case "error":
|
|
529
|
-
advance(percent, String(event.message || "gaia init reported an error").trim());
|
|
530
|
-
return true;
|
|
531
|
-
default:
|
|
532
|
-
return false;
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
function onLine(line) {
|
|
537
|
-
const parsed = parseInitEventLine(line);
|
|
538
|
-
if (!parsed) return false;
|
|
539
|
-
if (parsed.error) {
|
|
540
|
-
logFn(`Malformed gaia init progress line (${parsed.error}): ${line}`);
|
|
541
|
-
return false;
|
|
542
|
-
}
|
|
543
|
-
handle(parsed.event);
|
|
544
|
-
return parsed.event.event === "progress";
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
return { onLine, handle };
|
|
548
|
-
}
|
|
549
|
-
|
|
550
428
|
// ── Command helpers ──────────────────────────────────────────────────────────
|
|
551
429
|
|
|
552
430
|
/**
|
|
@@ -1078,15 +956,24 @@ async function runPreChecks(opts = {}) {
|
|
|
1078
956
|
// ── uv install ───────────────────────────────────────────────────────────────
|
|
1079
957
|
|
|
1080
958
|
class InstallError extends Error {
|
|
1081
|
-
constructor(message, { stage, code, suggestion } = {}) {
|
|
959
|
+
constructor(message, { stage, code, suggestion, reason } = {}) {
|
|
1082
960
|
super(message);
|
|
1083
961
|
this.name = "InstallError";
|
|
1084
962
|
this.stage = stage || null;
|
|
1085
963
|
this.code = code || null;
|
|
1086
964
|
this.suggestion = suggestion || null;
|
|
965
|
+
this.reason = reason || null;
|
|
1087
966
|
}
|
|
1088
967
|
}
|
|
1089
968
|
|
|
969
|
+
/** Which FAILURE_REASONS a uv failure's output shows, or null. */
|
|
970
|
+
function failureReason(output) {
|
|
971
|
+
if (isFileLockedError(output)) return FAILURE_REASONS.LOCKED;
|
|
972
|
+
if (isTlsCertError(output)) return FAILURE_REASONS.TLS;
|
|
973
|
+
if (isTransientNetworkError(output)) return FAILURE_REASONS.NETWORK;
|
|
974
|
+
return null;
|
|
975
|
+
}
|
|
976
|
+
|
|
1090
977
|
/**
|
|
1091
978
|
* Which `extraResources` subdirectory holds the bundled uv for this host.
|
|
1092
979
|
* Returns null for platforms we don't yet ship a binary for (falls through
|
|
@@ -1545,6 +1432,11 @@ function resolveBackendVersion(opts = {}) {
|
|
|
1545
1432
|
}
|
|
1546
1433
|
}
|
|
1547
1434
|
|
|
1435
|
+
/** The version the app ships with, whatever backend this run was asked to install. */
|
|
1436
|
+
function appVersion() {
|
|
1437
|
+
return JSON.parse(fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8")).version;
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1548
1440
|
/**
|
|
1549
1441
|
* The PEP 440 spelling of the app's npm version: `0.25.0-rc.1` -> `0.25.0rc1`.
|
|
1550
1442
|
* pip pins and `gaia --version` use this form; a final version is unchanged.
|
|
@@ -1554,142 +1446,16 @@ function toPep440(version) {
|
|
|
1554
1446
|
return version.replace(/^(\d+\.\d+\.\d+)-rc\.(\d+)$/, "$1rc$2");
|
|
1555
1447
|
}
|
|
1556
1448
|
|
|
1557
|
-
function shouldSkipGaiaInit(opts) {
|
|
1558
|
-
return Boolean(opts.skipGaiaInit) || isTruthyEnv(process.env.GAIA_SKIP_GAIA_INIT);
|
|
1559
|
-
}
|
|
1560
|
-
|
|
1561
|
-
/**
|
|
1562
|
-
* Run `gaia init` for the profile the app's own setup check asks about
|
|
1563
|
-
* (setup_runner.PROFILE), so the first launch finds setup done instead of
|
|
1564
|
-
* running it a second time.
|
|
1565
|
-
*
|
|
1566
|
-
* `chooseLemonade` is asked about a Lemonade Server already running here; see
|
|
1567
|
-
* lemonadeInitArgs.
|
|
1568
|
-
*
|
|
1569
|
-
* Returns `{ init }`, "done", "failed" or "skipped", plus `initError` when it
|
|
1570
|
-
* failed. The state file records both: a backend whose setup never finished is
|
|
1571
|
-
* not installed, so the next launch runs it, and the app shows the error.
|
|
1572
|
-
*/
|
|
1573
|
-
async function runGaiaInit(gaiaBin, report, version, skipGaiaInit, chooseLemonade) {
|
|
1574
|
-
if (skipGaiaInit) {
|
|
1575
|
-
log("Skipping gaia init (skipGaiaInit=true or GAIA_SKIP_GAIA_INIT set)");
|
|
1576
|
-
return { init: "skipped" };
|
|
1577
|
-
}
|
|
1578
|
-
setState(STATES.INSTALLING, { stage: STAGES.GAIA_INIT, version });
|
|
1579
|
-
report(STAGES.GAIA_INIT, 0, GAIA_INIT_MESSAGE);
|
|
1580
|
-
const lemonadeArgs = await lemonadeInitArgs(gaiaBin, chooseLemonade, runCommand, report);
|
|
1581
|
-
const progress = createInitProgress((within, message) =>
|
|
1582
|
-
report(STAGES.GAIA_INIT, within, message)
|
|
1583
|
-
);
|
|
1584
|
-
const initResult = await runCommand(
|
|
1585
|
-
gaiaBin,
|
|
1586
|
-
["init", "--profile", "gaia", "--yes", "--skip-webui-build", "--progress-json", ...lemonadeArgs],
|
|
1587
|
-
// Unwrapped, so an error line reaches initFailureReason whole.
|
|
1588
|
-
{ stageLabel: "gaia-init", env: { ...process.env, COLUMNS: "1000" }, onLine: progress.onLine }
|
|
1589
|
-
);
|
|
1590
|
-
if (initResult.code !== 0) {
|
|
1591
|
-
// Non-fatal: the app opens and its start screen shows initError, and the
|
|
1592
|
-
// next launch runs setup again, because the state file records the failure.
|
|
1593
|
-
log(
|
|
1594
|
-
`Warning: gaia init exited with code ${initResult.code}. Continuing; setup runs again next launch.`
|
|
1595
|
-
);
|
|
1596
|
-
report(STAGES.GAIA_INIT, 100, "Lemonade Server setup did not finish");
|
|
1597
|
-
return { init: "failed", initError: initFailureReason(initResult) };
|
|
1598
|
-
}
|
|
1599
|
-
report(STAGES.GAIA_INIT, 100, "Lemonade Server setup complete");
|
|
1600
|
-
return { init: "done" };
|
|
1601
|
-
}
|
|
1602
|
-
|
|
1603
|
-
/** Why `gaia init` failed, in its own words: its last ❌ line, else its last line. */
|
|
1604
|
-
function initFailureReason({ code, stdout, stderr }) {
|
|
1605
|
-
const lines = `${stdout || ""}\n${stderr || ""}`
|
|
1606
|
-
.split(/\r?\n/)
|
|
1607
|
-
.map((line) => line.trim())
|
|
1608
|
-
// --progress-json event lines are for the progress bar, not the user.
|
|
1609
|
-
.filter((line) => line && !line.startsWith('{"event"'));
|
|
1610
|
-
const marked = lines.filter((line) => line.startsWith("❌"));
|
|
1611
|
-
const reason = marked.length
|
|
1612
|
-
? marked[marked.length - 1].replace(/^❌\s*/, "")
|
|
1613
|
-
: lines[lines.length - 1];
|
|
1614
|
-
return reason || `gaia init exited with code ${code} and printed nothing.`;
|
|
1615
|
-
}
|
|
1616
|
-
|
|
1617
|
-
/**
|
|
1618
|
-
* Run a `gaia ... --json` command and parse what it printed.
|
|
1619
|
-
* Returns undefined after logging a warning that names the failure.
|
|
1620
|
-
*/
|
|
1621
|
-
async function readGaiaJson(run, gaiaBin, args) {
|
|
1622
|
-
const shown = `gaia ${args.join(" ")}`;
|
|
1623
|
-
const result = await run(gaiaBin, args, { stageLabel: "lemonade" });
|
|
1624
|
-
if (result.code !== 0) {
|
|
1625
|
-
log(`Warning: ${shown} exited with code ${result.code}: ${(result.stderr || result.stdout || "").trim().slice(-400)}`);
|
|
1626
|
-
return undefined;
|
|
1627
|
-
}
|
|
1628
|
-
const lines = (result.stdout || "").trim().split(/\r?\n/);
|
|
1629
|
-
try {
|
|
1630
|
-
return JSON.parse(lines[lines.length - 1]);
|
|
1631
|
-
} catch (err) {
|
|
1632
|
-
log(`Warning: ${shown} printed no JSON (${err.message}): ${(result.stdout || "").trim().slice(-400)}`);
|
|
1633
|
-
return undefined;
|
|
1634
|
-
}
|
|
1635
|
-
}
|
|
1636
|
-
|
|
1637
|
-
/**
|
|
1638
|
-
* Extra `gaia init` args for a Lemonade Server the user already runs.
|
|
1639
|
-
*
|
|
1640
|
-
* Asks `chooseLemonade(found)` only on a first setup (nothing saved, no
|
|
1641
|
-
* LEMONADE_BASE_URL) when a usable server answers. "external" adds
|
|
1642
|
-
* `--lemonade-url <url>`, "gaia" adds `--gaia-lemonade`, null adds nothing:
|
|
1643
|
-
* init then sets up GAIA's own and prints how to switch, as it always has.
|
|
1644
|
-
* A server that needs a key or is too old is left to the app's setup screen.
|
|
1645
|
-
*
|
|
1646
|
-
* `report` is the progress reporter: while the question is open the progress
|
|
1647
|
-
* window says setup is waiting on the user, then goes back to GAIA_INIT_MESSAGE.
|
|
1648
|
-
*/
|
|
1649
|
-
async function lemonadeInitArgs(gaiaBin, chooseLemonade, run = runCommand, report = () => {}) {
|
|
1650
|
-
if (typeof chooseLemonade !== "function") return [];
|
|
1651
|
-
const unchanged = "gaia init will set up GAIA's own Lemonade Server; to use yours later run `gaia lemonade connect <url>`.";
|
|
1652
|
-
const status = await readGaiaJson(run, gaiaBin, ["lemonade", "status", "--json"]);
|
|
1653
|
-
if (status === undefined || status === null) {
|
|
1654
|
-
log(`Warning: could not read which Lemonade Server GAIA uses; ${unchanged}`);
|
|
1655
|
-
return [];
|
|
1656
|
-
}
|
|
1657
|
-
if (status.saved_mode != null || status.source === "env") return [];
|
|
1658
|
-
const found = await readGaiaJson(run, gaiaBin, ["lemonade", "detect", "--json"]);
|
|
1659
|
-
if (found === undefined) {
|
|
1660
|
-
log(`Warning: could not look for a Lemonade Server already running; ${unchanged}`);
|
|
1661
|
-
return [];
|
|
1662
|
-
}
|
|
1663
|
-
if (!found) return [];
|
|
1664
|
-
if (found.status !== "ok") {
|
|
1665
|
-
log(`Found a Lemonade Server at ${found.url} (${found.status}); the app's setup screen offers it.`);
|
|
1666
|
-
return [];
|
|
1667
|
-
}
|
|
1668
|
-
let answer;
|
|
1669
|
-
report(STAGES.GAIA_INIT, 0, WAITING_FOR_LEMONADE_CHOICE_MESSAGE);
|
|
1670
|
-
try {
|
|
1671
|
-
answer = await chooseLemonade(found);
|
|
1672
|
-
} catch (err) {
|
|
1673
|
-
log(`Warning: could not ask whether to use the Lemonade Server at ${found.url}: ${err.message}; ${unchanged}`);
|
|
1674
|
-
return [];
|
|
1675
|
-
} finally {
|
|
1676
|
-
report(STAGES.GAIA_INIT, 0, GAIA_INIT_MESSAGE);
|
|
1677
|
-
}
|
|
1678
|
-
log(`Lemonade Server at ${found.url}: user chose ${answer || "not to answer"}`);
|
|
1679
|
-
if (answer === "external") return ["--lemonade-url", found.url];
|
|
1680
|
-
if (answer === "gaia") return ["--gaia-lemonade"];
|
|
1681
|
-
return [];
|
|
1682
|
-
}
|
|
1683
|
-
|
|
1684
1449
|
/**
|
|
1685
1450
|
* Install the GAIA Python backend from scratch.
|
|
1686
1451
|
*
|
|
1452
|
+
* Installs no model and runs no `gaia init`: the Lemonade Server and the models
|
|
1453
|
+
* are set up by the app's first-run setup, where the user chooses cloud or
|
|
1454
|
+
* local and sees what downloads.
|
|
1455
|
+
*
|
|
1687
1456
|
* opts:
|
|
1688
1457
|
* - onProgress(stage, percent, message)
|
|
1689
1458
|
* - version: string — override the pinned version
|
|
1690
|
-
* - skipGaiaInit: boolean — skip `gaia init` (for testing)
|
|
1691
|
-
* - chooseLemonade(found): Promise<"external"|"gaia"|null> — asked when a
|
|
1692
|
-
* Lemonade Server already runs here; see lemonadeInitArgs
|
|
1693
1459
|
*
|
|
1694
1460
|
* Throws InstallError on failure. The state file is updated to reflect
|
|
1695
1461
|
* the current stage so a subsequent launch can recover.
|
|
@@ -1707,7 +1473,6 @@ async function installBackend(opts = {}) {
|
|
|
1707
1473
|
const pipPackage = localWheel
|
|
1708
1474
|
? `${localWheel}[ui]`
|
|
1709
1475
|
: `amd-gaia[ui]==${toPep440(version)}`;
|
|
1710
|
-
const skipGaiaInit = shouldSkipGaiaInit(opts);
|
|
1711
1476
|
|
|
1712
1477
|
log("================================================");
|
|
1713
1478
|
log(" Installing GAIA backend");
|
|
@@ -1775,6 +1540,7 @@ async function installBackend(opts = {}) {
|
|
|
1775
1540
|
{
|
|
1776
1541
|
stage: STAGES.CREATE_VENV,
|
|
1777
1542
|
code: venvResult.code,
|
|
1543
|
+
reason: failureReason(`${venvResult.stdout || ""}\n${venvResult.stderr || ""}`),
|
|
1778
1544
|
suggestion: `Try creating it manually:\n ${manual}\nThen restart GAIA.`,
|
|
1779
1545
|
}
|
|
1780
1546
|
);
|
|
@@ -1804,6 +1570,21 @@ async function installBackend(opts = {}) {
|
|
|
1804
1570
|
// would otherwise fail the whole bootstrap (and block a release). File-lock
|
|
1805
1571
|
// failures (Windows os-error-32) are NOT retried — they need user action.
|
|
1806
1572
|
let installResult;
|
|
1573
|
+
if (!localWheel && isBackendPrefetched(version)) {
|
|
1574
|
+
report(STAGES.INSTALL_PACKAGE, 0, `Installing ${pipPackage} from the downloaded update`);
|
|
1575
|
+
const offline = await runCommand(
|
|
1576
|
+
"uv",
|
|
1577
|
+
["pip", "install", pipPackage, "--offline", "--compile-bytecode", "--python", GAIA_PYTHON_BIN],
|
|
1578
|
+
{ stageLabel: "pip" }
|
|
1579
|
+
);
|
|
1580
|
+
fs.rmSync(_prefetchMarker(toPep440(version)), { force: true });
|
|
1581
|
+
if (offline.code === 0) {
|
|
1582
|
+
installResult = offline;
|
|
1583
|
+
} else {
|
|
1584
|
+
log(`The prefetched backend is no longer all in uv's cache (${_lastLines(offline)}) — installing from PyPI`);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
const installedOffline = Boolean(installResult);
|
|
1807
1588
|
let useNativeTls = false;
|
|
1808
1589
|
let nativeTlsAttempted = false;
|
|
1809
1590
|
// The TLS branch flips useNativeTls and `continue`s, consuming an iteration.
|
|
@@ -1811,7 +1592,7 @@ async function installBackend(opts = {}) {
|
|
|
1811
1592
|
// so the promised --native-tls retry actually runs (issue #1693 review).
|
|
1812
1593
|
for (
|
|
1813
1594
|
let attempt = 1;
|
|
1814
|
-
attempt <= INSTALL_MAX_ATTEMPTS || (useNativeTls && !nativeTlsAttempted);
|
|
1595
|
+
!installedOffline && (attempt <= INSTALL_MAX_ATTEMPTS || (useNativeTls && !nativeTlsAttempted));
|
|
1815
1596
|
attempt++
|
|
1816
1597
|
) {
|
|
1817
1598
|
const attemptArgs = useNativeTls ? [...pipArgs, "--native-tls"] : pipArgs;
|
|
@@ -1871,6 +1652,14 @@ async function installBackend(opts = {}) {
|
|
|
1871
1652
|
: "") +
|
|
1872
1653
|
"Ask IT to install the proxy's root CA in your system's trusted certificate store, then relaunch GAIA. " +
|
|
1873
1654
|
"See https://amd-gaia.ai/docs/quickstart#cli-install";
|
|
1655
|
+
} else if (isTransientNetworkError(output)) {
|
|
1656
|
+
// The log file keeps pip's output; the box says what to do about it.
|
|
1657
|
+
throw new InstallError("GAIA couldn't reach the internet to finish installing.", {
|
|
1658
|
+
stage: STAGES.INSTALL_PACKAGE,
|
|
1659
|
+
code: installResult.code,
|
|
1660
|
+
suggestion:
|
|
1661
|
+
"Check your connection, then choose Retry. If it keeps failing, the log file below says why. Your chats and settings are safe.",
|
|
1662
|
+
});
|
|
1874
1663
|
} else {
|
|
1875
1664
|
suggestion = `Try installing manually:\n uv pip install ${pipPackage} --python ${
|
|
1876
1665
|
IS_WINDOWS ? `${GAIA_VENV_DISPLAY}/Scripts/python.exe` : `${GAIA_VENV_DISPLAY}/bin/python`
|
|
@@ -1891,6 +1680,7 @@ async function installBackend(opts = {}) {
|
|
|
1891
1680
|
{
|
|
1892
1681
|
stage: STAGES.INSTALL_PACKAGE,
|
|
1893
1682
|
code: installResult.code,
|
|
1683
|
+
reason: failureReason(output),
|
|
1894
1684
|
suggestion,
|
|
1895
1685
|
}
|
|
1896
1686
|
);
|
|
@@ -1907,16 +1697,7 @@ async function installBackend(opts = {}) {
|
|
|
1907
1697
|
}
|
|
1908
1698
|
report(STAGES.INSTALL_PACKAGE, 100, "GAIA package installed");
|
|
1909
1699
|
|
|
1910
|
-
// Stage 4:
|
|
1911
|
-
const setup = await runGaiaInit(
|
|
1912
|
-
GAIA_BIN,
|
|
1913
|
-
report,
|
|
1914
|
-
version,
|
|
1915
|
-
skipGaiaInit,
|
|
1916
|
-
opts.chooseLemonade
|
|
1917
|
-
);
|
|
1918
|
-
|
|
1919
|
-
// Stage 5: verify
|
|
1700
|
+
// Stage 4: verify
|
|
1920
1701
|
setState(STATES.INSTALLING, { stage: STAGES.VERIFY, version });
|
|
1921
1702
|
report(STAGES.VERIFY, 0, "Verifying installation");
|
|
1922
1703
|
|
|
@@ -1974,12 +1755,141 @@ async function installBackend(opts = {}) {
|
|
|
1974
1755
|
log(`Shim creation check failed: ${err.message}`);
|
|
1975
1756
|
}
|
|
1976
1757
|
|
|
1977
|
-
setState(STATES.READY, { stage: null, version, installedVersion
|
|
1758
|
+
setState(STATES.READY, { stage: null, version, installedVersion });
|
|
1978
1759
|
log("Backend install complete");
|
|
1979
1760
|
}
|
|
1980
1761
|
|
|
1762
|
+
// ── Agent refresh ────────────────────────────────────────────────────────────
|
|
1763
|
+
|
|
1764
|
+
/**
|
|
1765
|
+
* Update the flagship to this release, without Lemonade or models. A PC with
|
|
1766
|
+
* no flagship is skipped: first-run setup installs it.
|
|
1767
|
+
*
|
|
1768
|
+
* The stage stays recorded until this succeeds, so an update that failed or was
|
|
1769
|
+
* quit part way through refreshes again on the next launch.
|
|
1770
|
+
*/
|
|
1771
|
+
async function refreshAgent(gaiaBin, opts = {}) {
|
|
1772
|
+
const version = resolveBackendVersion(opts);
|
|
1773
|
+
if (!fs.existsSync(FLAGSHIP_SENTINEL)) {
|
|
1774
|
+
log("No GAIA agent installed yet — first-run setup installs it");
|
|
1775
|
+
// Nothing to refresh: a refresh recorded earlier is done, not pending forever.
|
|
1776
|
+
setState(STATES.READY, { version, installedVersion: getInstalledVersion(gaiaBin) });
|
|
1777
|
+
return;
|
|
1778
|
+
}
|
|
1779
|
+
setState(STATES.INSTALLING, { stage: STAGES.AGENT_REFRESH, version });
|
|
1780
|
+
makeProgressReporter(opts.onProgress)(STAGES.AGENT_REFRESH, null, "Updating the GAIA agent");
|
|
1781
|
+
|
|
1782
|
+
const result = await runCommand(gaiaBin, ["init", "--agent-only"], {
|
|
1783
|
+
stageLabel: "agent",
|
|
1784
|
+
env: { ...process.env, COLUMNS: "1000" },
|
|
1785
|
+
});
|
|
1786
|
+
const lastLine = `${result.stderr || ""}`.trim().split(/\r?\n/).pop();
|
|
1787
|
+
if (result.code === AGENT_KEPT_EXIT) {
|
|
1788
|
+
// Offline: the working agent stays, and the next launch tries the update again.
|
|
1789
|
+
log(`Kept the installed GAIA agent; the update runs again next launch: ${lastLine}`);
|
|
1790
|
+
setState(STATES.PARTIAL, { stage: STAGES.AGENT_REFRESH, version, message: lastLine });
|
|
1791
|
+
return;
|
|
1792
|
+
}
|
|
1793
|
+
if (result.code !== 0) {
|
|
1794
|
+
throw new InstallError(
|
|
1795
|
+
lastLine ? lastLine.replace(/^Error:\s*/, "") : `The GAIA agent update exited ${result.code}.`,
|
|
1796
|
+
{ stage: STAGES.AGENT_REFRESH, code: result.code }
|
|
1797
|
+
);
|
|
1798
|
+
}
|
|
1799
|
+
setState(STATES.READY, { version, installedVersion: getInstalledVersion(gaiaBin) });
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
// ── Backend prefetch for an app update ──────────────────────────────────────
|
|
1803
|
+
|
|
1804
|
+
const PREFETCH_DIR = path.join(GAIA_HOME, "cache", "backend-prefetch");
|
|
1805
|
+
|
|
1806
|
+
function _prefetchMarker(pep440) {
|
|
1807
|
+
return path.join(PREFETCH_DIR, `${pep440}.ready`);
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
/** True when an earlier prefetch left everything `version` needs in uv's cache. */
|
|
1811
|
+
function isBackendPrefetched(version) {
|
|
1812
|
+
return Boolean(version) && fs.existsSync(_prefetchMarker(toPep440(version)));
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
/**
|
|
1816
|
+
* Before an app update restarts, put the backend it will need into uv's cache
|
|
1817
|
+
* by installing it into a throwaway environment. The first launch then
|
|
1818
|
+
* installs offline, so it neither waits on nor depends on PyPI.
|
|
1819
|
+
* Throws with uv's own words when the download fails.
|
|
1820
|
+
*/
|
|
1821
|
+
async function prefetchBackend(appVersion, { isPackaged } = {}) {
|
|
1822
|
+
if (process.env.GAIA_LOCAL_WHEEL) {
|
|
1823
|
+
log("GAIA_LOCAL_WHEEL is set: the next launch installs that wheel, so there is nothing to prefetch");
|
|
1824
|
+
return;
|
|
1825
|
+
}
|
|
1826
|
+
const version = toPep440(appVersion);
|
|
1827
|
+
const pipPackage = `amd-gaia[ui]==${version}`;
|
|
1828
|
+
const workDir = path.join(PREFETCH_DIR, version);
|
|
1829
|
+
const venv = path.join(workDir, "venv");
|
|
1830
|
+
const python = IS_WINDOWS ? path.join(venv, "Scripts", "python.exe") : path.join(venv, "bin", "python");
|
|
1831
|
+
log(`Prefetching ${pipPackage} for the update`);
|
|
1832
|
+
await ensureUv({ isPackaged });
|
|
1833
|
+
_removeScratchVenvs();
|
|
1834
|
+
fs.mkdirSync(workDir, { recursive: true });
|
|
1835
|
+
try {
|
|
1836
|
+
const venvResult = await runCommand("uv", ["venv", venv, "--python", "3.12"], { stageLabel: "prefetch" });
|
|
1837
|
+
if (venvResult.code !== 0) {
|
|
1838
|
+
throw new Error(`uv venv exited ${venvResult.code}: ${_lastLines(venvResult)}`);
|
|
1839
|
+
}
|
|
1840
|
+
const args = ["pip", "install", pipPackage, "--refresh", "--python", python];
|
|
1841
|
+
let result = await runCommand("uv", args, { stageLabel: "prefetch" });
|
|
1842
|
+
if (result.code !== 0 && isTlsCertError(`${result.stdout || ""}\n${result.stderr || ""}`)) {
|
|
1843
|
+
result = await runCommand("uv", [...args, "--native-tls"], { stageLabel: "prefetch" });
|
|
1844
|
+
}
|
|
1845
|
+
if (result.code !== 0) {
|
|
1846
|
+
throw new Error(`Couldn't download ${pipPackage} (uv exit ${result.code}): ${_lastLines(result)}`);
|
|
1847
|
+
}
|
|
1848
|
+
} finally {
|
|
1849
|
+
fs.rmSync(workDir, { recursive: true, force: true });
|
|
1850
|
+
}
|
|
1851
|
+
fs.writeFileSync(_prefetchMarker(version), new Date().toISOString());
|
|
1852
|
+
log(`Prefetched ${pipPackage}: the next launch installs it offline`);
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
/** Scratch venvs a quit cut short; the .ready markers beside them stay. */
|
|
1856
|
+
function _removeScratchVenvs() {
|
|
1857
|
+
let entries = [];
|
|
1858
|
+
try {
|
|
1859
|
+
entries = fs.readdirSync(PREFETCH_DIR, { withFileTypes: true });
|
|
1860
|
+
} catch (err) {
|
|
1861
|
+
if (err.code !== "ENOENT") throw err;
|
|
1862
|
+
}
|
|
1863
|
+
for (const entry of entries) {
|
|
1864
|
+
if (entry.isDirectory()) fs.rmSync(path.join(PREFETCH_DIR, entry.name), { recursive: true, force: true });
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
function _lastLines({ stdout, stderr }) {
|
|
1869
|
+
return `${stdout || ""}\n${stderr || ""}`
|
|
1870
|
+
.split("\n")
|
|
1871
|
+
.map((line) => line.trimEnd())
|
|
1872
|
+
.filter(Boolean)
|
|
1873
|
+
.slice(-5)
|
|
1874
|
+
.join(" | ");
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1981
1877
|
// ── Version-aware ensure ─────────────────────────────────────────────────────
|
|
1982
1878
|
|
|
1879
|
+
/**
|
|
1880
|
+
* Whether the backend is finished at the app's version. A recorded failure or
|
|
1881
|
+
* unfinished stage says no, so it reaches ensureBackend and the user.
|
|
1882
|
+
*/
|
|
1883
|
+
function isBackendCurrent() {
|
|
1884
|
+
// A local wheel is reinstalled on every launch, as ensureBackend says.
|
|
1885
|
+
if (process.env.GAIA_LOCAL_WHEEL) return false;
|
|
1886
|
+
const bin = findGaiaBin();
|
|
1887
|
+
if (!bin) return false;
|
|
1888
|
+
const prev = getState();
|
|
1889
|
+
if (prev && prev.state !== STATES.READY) return false;
|
|
1890
|
+
return getInstalledVersion(bin) === toPep440(appVersion());
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1983
1893
|
/**
|
|
1984
1894
|
* The installed backend version, or null when it is not installed.
|
|
1985
1895
|
* For the app's own venv this reads the package record instead of running
|
|
@@ -2049,8 +1959,6 @@ function venvPackageVersion() {
|
|
|
2049
1959
|
* opts:
|
|
2050
1960
|
* - onProgress(stage, percent, message)
|
|
2051
1961
|
* - version: override the pinned version
|
|
2052
|
-
* - skipGaiaInit: bool
|
|
2053
|
-
* - chooseLemonade: see installBackend
|
|
2054
1962
|
* - allowPartialRestart: bool (default true) — restart from scratch if
|
|
2055
1963
|
* the state file indicates a `partial` install.
|
|
2056
1964
|
*
|
|
@@ -2097,6 +2005,7 @@ async function ensureBackend(opts = {}) {
|
|
|
2097
2005
|
}.`,
|
|
2098
2006
|
{
|
|
2099
2007
|
stage: STAGES.PRE_CHECKS,
|
|
2008
|
+
reason: FAILURE_REASONS.DISK,
|
|
2100
2009
|
suggestion: `Free at least ${requiredMb} MB at ${path.dirname(
|
|
2101
2010
|
GAIA_HOME
|
|
2102
2011
|
)} and try again.`,
|
|
@@ -2132,47 +2041,28 @@ async function ensureBackend(opts = {}) {
|
|
|
2132
2041
|
// reinstall from the local wheel so CI gets a fresh install each run.
|
|
2133
2042
|
const expectedVersion = resolveBackendVersion(opts);
|
|
2134
2043
|
const existingBin = findGaiaBin();
|
|
2135
|
-
// Installed means
|
|
2136
|
-
//
|
|
2044
|
+
// Installed means the package finished installing. No state file at all
|
|
2045
|
+
// is an install from before it existed.
|
|
2137
2046
|
const prev = preChecks.previousState;
|
|
2138
|
-
const setupFinished =
|
|
2139
|
-
!prev || (prev.state === STATES.READY && prev.init !== "failed");
|
|
2140
2047
|
const packageFinished =
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2048
|
+
!prev ||
|
|
2049
|
+
prev.state === STATES.READY ||
|
|
2050
|
+
prev.stage === LEGACY_SETUP_STAGE ||
|
|
2051
|
+
prev.stage === STAGES.VERIFY ||
|
|
2052
|
+
prev.stage === STAGES.AGENT_REFRESH;
|
|
2146
2053
|
if (existingBin) {
|
|
2147
2054
|
const installedVersion = getInstalledVersion(existingBin);
|
|
2148
2055
|
const versionMatches =
|
|
2149
2056
|
expectedVersion !== null && installedVersion === toPep440(expectedVersion);
|
|
2150
|
-
if (versionMatches &&
|
|
2151
|
-
|
|
2152
|
-
`GAIA ${installedVersion} is installed but its setup did not finish ` +
|
|
2153
|
-
`(state=${prev.state}, stage=${prev.stage || "-"}) — running gaia init`
|
|
2154
|
-
);
|
|
2155
|
-
const report = makeProgressReporter(opts.onProgress);
|
|
2156
|
-
const setup = await runGaiaInit(
|
|
2157
|
-
existingBin,
|
|
2158
|
-
report,
|
|
2159
|
-
expectedVersion,
|
|
2160
|
-
shouldSkipGaiaInit(opts),
|
|
2161
|
-
opts.chooseLemonade
|
|
2162
|
-
);
|
|
2163
|
-
setState(STATES.READY, { version: expectedVersion, installedVersion, ...setup });
|
|
2164
|
-
report(STAGES.VERIFY, 100, `GAIA ${installedVersion} ready`);
|
|
2057
|
+
if (versionMatches && packageFinished && prev && prev.stage === STAGES.AGENT_REFRESH) {
|
|
2058
|
+
await refreshAgent(existingBin, opts);
|
|
2165
2059
|
return existingBin;
|
|
2166
2060
|
}
|
|
2167
|
-
if (versionMatches &&
|
|
2061
|
+
if (versionMatches && packageFinished) {
|
|
2168
2062
|
log(
|
|
2169
2063
|
`GAIA backend already installed at version ${installedVersion} — nothing to do`
|
|
2170
2064
|
);
|
|
2171
|
-
setState(STATES.READY, {
|
|
2172
|
-
version: expectedVersion,
|
|
2173
|
-
installedVersion,
|
|
2174
|
-
init: prev ? prev.init : undefined,
|
|
2175
|
-
});
|
|
2065
|
+
setState(STATES.READY, { version: expectedVersion, installedVersion });
|
|
2176
2066
|
// Tell the UI we are instantly ready.
|
|
2177
2067
|
const report = makeProgressReporter(opts.onProgress);
|
|
2178
2068
|
report(STAGES.VERIFY, 100, `GAIA ${installedVersion} ready`);
|
|
@@ -2205,6 +2095,7 @@ async function ensureBackend(opts = {}) {
|
|
|
2205
2095
|
throw err;
|
|
2206
2096
|
}
|
|
2207
2097
|
|
|
2098
|
+
await refreshAgent(verified, opts);
|
|
2208
2099
|
return verified;
|
|
2209
2100
|
} catch (err) {
|
|
2210
2101
|
if (err instanceof InstallError) {
|
|
@@ -2232,11 +2123,12 @@ module.exports = {
|
|
|
2232
2123
|
ensureUv,
|
|
2233
2124
|
installBackend,
|
|
2234
2125
|
ensureBackend,
|
|
2126
|
+
isBackendCurrent,
|
|
2127
|
+
prefetchBackend,
|
|
2128
|
+
isBackendPrefetched,
|
|
2235
2129
|
getInstalledVersion,
|
|
2236
2130
|
findGaiaBin,
|
|
2237
2131
|
toPep440,
|
|
2238
|
-
lemonadeInitArgs,
|
|
2239
|
-
initFailureReason,
|
|
2240
2132
|
|
|
2241
2133
|
// Pre-checks
|
|
2242
2134
|
runPreChecks,
|
|
@@ -2255,9 +2147,6 @@ module.exports = {
|
|
|
2255
2147
|
setState,
|
|
2256
2148
|
clearState,
|
|
2257
2149
|
|
|
2258
|
-
// gaia init progress
|
|
2259
|
-
parseInitEventLine,
|
|
2260
|
-
createInitProgress,
|
|
2261
2150
|
computeOverallPercent,
|
|
2262
2151
|
|
|
2263
2152
|
// Logging
|
|
@@ -2267,12 +2156,12 @@ module.exports = {
|
|
|
2267
2156
|
logError,
|
|
2268
2157
|
getLogPath,
|
|
2269
2158
|
getStatePath,
|
|
2159
|
+
readLogTail,
|
|
2270
2160
|
|
|
2271
2161
|
// Constants
|
|
2272
2162
|
STATES,
|
|
2273
2163
|
STAGES,
|
|
2274
|
-
|
|
2275
|
-
WAITING_FOR_LEMONADE_CHOICE_MESSAGE,
|
|
2164
|
+
FAILURE_REASONS,
|
|
2276
2165
|
GAIA_HOME,
|
|
2277
2166
|
GAIA_VENV,
|
|
2278
2167
|
GAIA_BIN,
|