aegis-desktop 0.8.2 → 0.8.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 +66 -4
  2. package/package.json +1 -1
package/main.js CHANGED
@@ -508,6 +508,50 @@ function isSafeExternalUrl(url) {
508
508
  }
509
509
  }
510
510
 
511
+ /**
512
+ * Whether this machine holds a provider route of the user's OWN — the "bring
513
+ * your own key" half of the welcome block's two pitches.
514
+ *
515
+ * The AEGIS Cloud key is `aegis.apiKey` and is reported separately as
516
+ * `keyConfigured`; a BYOK provider row never appears there (it is written by
517
+ * the Provider settings card through settings.set(provider, {baseURL, key})),
518
+ * so it needs its own reader. A row counts once it carries either a key (the
519
+ * pasted-provider case) or a baseURL (the point-it-at-a-local-model case) —
520
+ * both are the user having answered the question the block was asking.
521
+ *
522
+ * `settings.list()` goes through `get()`, which is the same reader the IPC
523
+ * bridge uses to answer the renderer — it never puts the plaintext key on a
524
+ * row (that would put it one `list()` call away from crossing IPC), only the
525
+ * `configured` boolean it already derived from the key. Reading `row.key`
526
+ * here was always reading a field `get()` doesn't emit, so a pasted-key-only
527
+ * provider row (no baseURL — the common case: OpenAI, Anthropic, DeepSeek)
528
+ * never counted and the block kept nagging a user who had already connected
529
+ * one. Verified live: `store.set('openai', { key })` then `list()` — the row
530
+ * carries `configured: true` and no `key` property at all.
531
+ *
532
+ * Reserved namespaces are excluded for the same reason createModelDispatch
533
+ * filters them: they are the store's own bookkeeping (the AEGIS key, the
534
+ * memory-persist preference), not a provider the user configured.
535
+ *
536
+ * Failure reads as "no", which keeps the block visible — the same
537
+ * fail-toward-showing-it bias the CLI's connectNeeded() documents. Showing a
538
+ * block the user has outgrown is a nag; hiding one a new install needed is a
539
+ * dead end, and only the latter loses the user.
540
+ */
541
+ function providerRouteConfigured(engine) {
542
+ try {
543
+ const list = engine && engine.settings && engine.settings.list;
544
+ if (typeof list !== 'function') return false;
545
+ return (list.call(engine.settings) || []).some((row) => {
546
+ if (!row || !row.provider || isReservedNamespace(row.provider)) return false;
547
+ const baseURL = typeof row.baseURL === 'string' ? row.baseURL.trim() : '';
548
+ return Boolean(row.configured || baseURL);
549
+ });
550
+ } catch {
551
+ return false;
552
+ }
553
+ }
554
+
511
555
  /**
512
556
  * Pure mapping: IPC payload -> shared-client call. No Electron types here, so
513
557
  * tests can drive it with a stub client and a fake ipcMain. `openExternal` is
@@ -515,7 +559,17 @@ function isSafeExternalUrl(url) {
515
559
  * reject), injected by bootstrap() so this module still needs no `electron`
516
560
  * import to stay unit-testable.
517
561
  */
518
- function createIpcDispatch(aegis, dir, persistApiKey, openExternal) {
562
+ function createIpcDispatch(aegis, dir, persistApiKey, openExternal, providerConfigured) {
563
+ // `providerConfigured` is an injected thunk (bootstrap() passes one that reads
564
+ // the settings store) so this module keeps its no-Electron, unit-testable
565
+ // shape; absent in tests and older call sites, where it reads as "no".
566
+ const hasOwnProviderKey = () => {
567
+ try {
568
+ return Boolean(typeof providerConfigured === 'function' && providerConfigured());
569
+ } catch {
570
+ return false;
571
+ }
572
+ };
519
573
  const openExternalFn = openExternal || (() => Promise.reject(new Error('no opener configured')));
520
574
  const dispatch = {
521
575
  status: () => ({
@@ -524,6 +578,12 @@ function createIpcDispatch(aegis, dir, persistApiKey, openExternal) {
524
578
  apiBase: aegis.apiBase,
525
579
  keyConfigured: Boolean(aegis.apiKey),
526
580
  keyMask: maskKey(aegis.apiKey),
581
+ // The welcome block pitches TWO routes (bring your own key, or AEGIS
582
+ // Cloud) and must retire once the user has taken either one. A BYOK
583
+ // provider key never touches aegis.apiKey, so without this the block
584
+ // kept re-pitching "bring your own key" at someone who just did — see
585
+ // applyWelcomeConnect() in renderer/app.js.
586
+ providerConfigured: hasOwnProviderKey(),
527
587
  }),
528
588
 
529
589
  // In-app API key entry (plan rebuild): replace the live client key and
@@ -628,8 +688,8 @@ function withReplyNotify(promise, event, onReplyFinished) {
628
688
  * tests, where the safe no-op default in createIpcDispatch takes over.
629
689
  * `onReplyFinished` is likewise bootstrap()-only: it fires the native
630
690
  * "reply ready" notification when the window is unfocused/hidden. */
631
- function registerIpc(ipcMain, aegis, dir, persistApiKey, openExternal, onReplyFinished) {
632
- const dispatch = createIpcDispatch(aegis, dir, persistApiKey, openExternal);
691
+ function registerIpc(ipcMain, aegis, dir, persistApiKey, openExternal, onReplyFinished, providerConfigured) {
692
+ const dispatch = createIpcDispatch(aegis, dir, persistApiKey, openExternal, providerConfigured);
633
693
  for (const [name, handler] of Object.entries(dispatch)) {
634
694
  if (name === 'chatCompletion') {
635
695
  // Streaming render (D2.1): when the renderer asks for stream, SSE deltas
@@ -2378,7 +2438,8 @@ function bootstrap() {
2378
2438
  dataDir,
2379
2439
  persistApiKey,
2380
2440
  (url) => shell.openExternal(url),
2381
- notifyReplyIfUnfocused
2441
+ notifyReplyIfUnfocused,
2442
+ () => providerRouteConfigured(engine)
2382
2443
  );
2383
2444
  registerModelIpc(
2384
2445
  ipcMain,
@@ -2755,6 +2816,7 @@ if (electron && electron.app) {
2755
2816
  module.exports = {
2756
2817
  createIpcDispatch,
2757
2818
  registerIpc,
2819
+ providerRouteConfigured,
2758
2820
  IPC_PREFIX,
2759
2821
  MODEL_PREFIX,
2760
2822
  SYNC_PREFIX,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "aegis-desktop",
3
3
  "productName": "AEGIS Desktop",
4
- "version": "0.8.2",
4
+ "version": "0.8.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",