aegis-desktop 1.0.1 → 1.0.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.
Files changed (2) hide show
  1. package/main.js +74 -29
  2. package/package.json +1 -1
package/main.js CHANGED
@@ -3203,6 +3203,29 @@ function buildAppMenu({ app, Menu, BrowserWindow, updateManager }) {
3203
3203
  function bootstrap() {
3204
3204
  const { app, BrowserWindow, ipcMain, safeStorage, shell, Menu, Notification, screen, dialog, globalShortcut } = electron;
3205
3205
 
3206
+ // A container/CI/headless server with no usable GPU crashes Chromium's GPU
3207
+ // process in a retry loop that ends in a FATAL exit ("GPU process isn't
3208
+ // usable. Goodbye.") — and that happens BEFORE app.whenReady() ever fires,
3209
+ // so autoProvisionApiKey() (the free device key + welcome-trial grant)
3210
+ // never runs either: `npm install -g aegis-desktop && aegis` on exactly
3211
+ // that class of machine registered nothing, with no error a user could
3212
+ // act on. Must be called before the app is ready (Electron's own
3213
+ // constraint); software rendering is a fine tradeoff for a text/chat UI
3214
+ // with no WebGL or video.
3215
+ //
3216
+ // disableHardwareAcceleration() alone was not enough on a container whose
3217
+ // Mesa/VA-API/DRI stack is fully broken rather than merely GPU-less:
3218
+ // Chromium still spawned a GPU process for the SwiftShader software path,
3219
+ // and that crash-looped to the same FATAL exit, just after a longer delay
3220
+ // (measured: survived past 15s, died at ~44s). These three switches tell
3221
+ // Chromium to skip GPU-process creation entirely rather than retry it
3222
+ // under software rendering — the documented combination for Electron under
3223
+ // Docker/CI/headless Linux.
3224
+ app.disableHardwareAcceleration();
3225
+ app.commandLine.appendSwitch('disable-gpu');
3226
+ app.commandLine.appendSwitch('disable-software-rasterizer');
3227
+ app.commandLine.appendSwitch('disable-gpu-compositing');
3228
+
3206
3229
  // A second launch (double-clicking the icon again, `aegis` from a second
3207
3230
  // terminal, …) must focus the existing window, not spawn a second process
3208
3231
  // fighting the first over the same on-disk settings/session/queue files —
@@ -3272,6 +3295,45 @@ function bootstrap() {
3272
3295
  dir: dataDir,
3273
3296
  });
3274
3297
 
3298
+ // Resolve/auto-provision the account key as early as possible — BEFORE
3299
+ // app.whenReady(), not inside it. autoProvisionApiKey() is a plain Node
3300
+ // fetch call with no Electron dependency: createClient(credentials.
3301
+ // clientOptions()) above already resolved any existing key (env, the
3302
+ // shared file, the env-file fallback) onto `aegis.apiKey`, so this is a
3303
+ // genuine registerDevice call only on a truly fresh install — the same
3304
+ // one-call-is-enough guarantee autoProvisionApiKey() provides to the CLI
3305
+ // and the MCP plugin. It persists straight to the plain shared
3306
+ // credentials file via credentials.saveApiKey(), not safeStorage.
3307
+ //
3308
+ // Why this can't wait for 'ready': a container/CI host with a broken
3309
+ // graphics stack crashes Chromium's GPU process in a retry loop that can
3310
+ // end in a FATAL exit (see disableHardwareAcceleration above) BEFORE
3311
+ // whenReady() ever resolves — measured here surviving 15s before dying at
3312
+ // 42-44s even with hardware acceleration and GPU compositing disabled.
3313
+ // Gating registration on 'ready' meant that exact class of host — exactly
3314
+ // what `npm install -g aegis-desktop && aegis` on a headless server lands
3315
+ // on — registered nothing and left no trace. Running it here means the
3316
+ // account exists and the key is on disk the moment the process starts,
3317
+ // independent of whether the window ever manages to open.
3318
+ //
3319
+ // Not calling settings.setAegisKey() here: safeStorage is not safe to use
3320
+ // before 'ready' (see migrateLegacyAegisKey's own comment in desktop/lib/
3321
+ // settings.js, the one other place in this codebase that already had to
3322
+ // reason about this). The
3323
+ // existing app.whenReady() block further down already re-reads the shared
3324
+ // store via credentials.preferNewest() and writes the encrypted copy once
3325
+ // ready — this just guarantees there is something there for it to find
3326
+ // even when 'ready' never comes.
3327
+ const earlyKeyProvision = autoProvision.autoProvisionApiKey(aegis, credentials).then((result) => {
3328
+ if (!result.key && !aegis.apiKey) {
3329
+ const reason =
3330
+ (result.error && (result.error.status || result.error.message || String(result.error))) ||
3331
+ 'no key returned';
3332
+ console.warn(`auto-provision: no account key from register-device (${reason})`);
3333
+ }
3334
+ return result;
3335
+ });
3336
+
3275
3337
  // A provider key typed in the Settings pane used to reach ONLY this app's
3276
3338
  // encrypted store, so `aegiscode` (the CLI) and the MCP plugin never saw it:
3277
3339
  // same machine, same provider, key asked for twice, and a key entered here
@@ -3820,41 +3882,24 @@ function bootstrap() {
3820
3882
  // it, and nothing here may shadow an explicit export.
3821
3883
  const envKey = credentials.normalizeApiKey(process.env[credentials.KEY_ENV]);
3822
3884
  if (!envKey) {
3885
+ // Wait for the pre-ready attempt above rather than racing a second
3886
+ // registerDevice call: autoProvisionApiKey() already ran (or is still
3887
+ // running) the instant this process started, specifically so it is
3888
+ // not gated on 'ready' — see the earlyKeyProvision comment above for
3889
+ // why (a container with a broken graphics stack can crash before
3890
+ // 'ready' ever fires). Swallowing a keyless outcome here was how a
3891
+ // machine could sit absent from the admin panel's user list AND its
3892
+ // presence card with no trace anywhere — the server now logs every
3893
+ // registration attempt, and earlyKeyProvision's own .then() already
3894
+ // warns on failure, so there is nothing further to report if it
3895
+ // didn't land.
3896
+ await earlyKeyProvision;
3823
3897
  const chosen = credentials.preferNewest(settings.aegisRawKey(), settings.aegisKeySavedAt());
3824
3898
  if (chosen.key) {
3825
3899
  aegis.setApiKey(chosen.key);
3826
3900
  // The shared store is the newer write: bring this host's encrypted copy
3827
3901
  // up to date so a CLI rotation does not have to be redone in Settings.
3828
3902
  if (chosen.source === 'shared') settings.setAegisKey(chosen.key);
3829
- } else {
3830
- // Nothing configured anywhere (env, this app's encrypted copy, or the
3831
- // shared store): a fresh install. Auto-provision a free device key
3832
- // from aegis1 — same shared helper mcp/server.js and the CLI use, so
3833
- // the desktop app works with no manual sign-in step. A brand-new
3834
- // fingerprint is granted aegis1's 30-day welcome-trial `unlimited`
3835
- // entitlement (token-bank credit + a subscriptions row) in the same
3836
- // register-device call that mints the key — see client/
3837
- // auto-provision.js's header for the aegis1 commit that made this a
3838
- // real grant rather than a badge. Failure (offline, aegis1
3839
- // unreachable) falls through exactly as before: the renderer's
3840
- // normal "not signed in" state handles it, and the next launch tries
3841
- // again.
3842
- const result = await autoProvision.autoProvisionApiKey(aegis, credentials);
3843
- if (result.key) settings.setAegisKey(result.key);
3844
- else {
3845
- // Swallowing this was how a machine could sit keyless — and therefore
3846
- // absent from the admin panel's user list AND its presence card —
3847
- // with no trace anywhere: the app just opened in its normal
3848
- // signed-out state, and the next launch retried with the same
3849
- // invisible outcome. The server now logs every registration attempt;
3850
- // this closes the other half so one launch is diagnosable. A 429 here
3851
- // is not fatal (aegis1 creates the account and withholds the trial),
3852
- // so it must read as "retry", not as a broken install.
3853
- const reason =
3854
- (result.error && (result.error.status || result.error.message || String(result.error))) ||
3855
- 'no key returned';
3856
- console.warn(`auto-provision: no account key from register-device (${reason})`);
3857
- }
3858
3903
  }
3859
3904
  }
3860
3905
  // ── presence beacon ───────────────────────────────────────────────────
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "aegis-desktop",
3
3
  "productName": "AEGIS Desktop",
4
- "version": "1.0.1",
4
+ "version": "1.0.3",
5
5
  "description": "Thin Electron host for AEGIS — a local chat UI over the shared client/aegis.js transport. Ships transport + UI only; engine logic stays server-side.",
6
6
  "author": {
7
7
  "name": "AEGIS Code",