aegis-desktop 1.0.5 → 1.0.7
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/lib/account-key.js +9 -12
- package/main.js +24 -10
- package/package.json +1 -1
- package/vendor/aegis.js +3 -2
- package/vendor/auto-provision.js +24 -7
- package/vendor/device-fingerprint.js +1 -8
package/lib/account-key.js
CHANGED
|
@@ -8,15 +8,7 @@
|
|
|
8
8
|
* Pure: Electron, the store and the provisioner are injected.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
function
|
|
12
|
-
const err = result && result.error;
|
|
13
|
-
if (!err) return 'the server returned no account key';
|
|
14
|
-
if (err.status === 429) return 'too many sign-ups from this network — try again later';
|
|
15
|
-
if (!err.status || err.status === 408 || err.status >= 500) return 'could not reach aegiscloud.org';
|
|
16
|
-
return `the server refused the request (HTTP ${err.status})`;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function createAccountKey({ aegis, credentials, persistKey, provision, onChange }) {
|
|
11
|
+
function createAccountKey({ aegis, credentials, persistKey, provision, describeFailure, onChange }) {
|
|
20
12
|
let failure = null;
|
|
21
13
|
let inFlight = null;
|
|
22
14
|
|
|
@@ -26,7 +18,7 @@ function createAccountKey({ aegis, credentials, persistKey, provision, onChange
|
|
|
26
18
|
}
|
|
27
19
|
|
|
28
20
|
function recordStartup(result) {
|
|
29
|
-
failure = (result.key || aegis.apiKey) ? null :
|
|
21
|
+
failure = (result.key || aegis.apiKey) ? null : describeFailure(result);
|
|
30
22
|
onChange(status());
|
|
31
23
|
}
|
|
32
24
|
|
|
@@ -39,7 +31,7 @@ function createAccountKey({ aegis, credentials, persistKey, provision, onChange
|
|
|
39
31
|
persistKey(result.key);
|
|
40
32
|
failure = null;
|
|
41
33
|
} else {
|
|
42
|
-
failure =
|
|
34
|
+
failure = describeFailure(result);
|
|
43
35
|
}
|
|
44
36
|
const state = status();
|
|
45
37
|
onChange(state);
|
|
@@ -55,4 +47,9 @@ function createAccountKey({ aegis, credentials, persistKey, provision, onChange
|
|
|
55
47
|
return { status, recordStartup, registerAccount };
|
|
56
48
|
}
|
|
57
49
|
|
|
58
|
-
|
|
50
|
+
// A key this host saved on purpose outranks one minted for a fresh device at startup.
|
|
51
|
+
function startupKeyOverride({ mintedKey, hostKey }) {
|
|
52
|
+
return mintedKey && hostKey && hostKey !== mintedKey ? hostKey : null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = { createAccountKey, startupKeyOverride };
|
package/main.js
CHANGED
|
@@ -152,7 +152,7 @@ try {
|
|
|
152
152
|
// backed by transport-only modules in desktop/lib/. No brain logic enters here.
|
|
153
153
|
const os = require('node:os');
|
|
154
154
|
const { createLocalEngine } = require('./lib/local/engine.js');
|
|
155
|
-
const { createAccountKey } = require('./lib/account-key.js');
|
|
155
|
+
const { createAccountKey, startupKeyOverride } = require('./lib/account-key.js');
|
|
156
156
|
const { createSettingsStore, isReservedNamespace } = require('./lib/settings.js');
|
|
157
157
|
const sessionStore = require('./lib/sync/sessions.js');
|
|
158
158
|
const memoryQueue = require('./lib/sync/memory-queue.js');
|
|
@@ -3330,10 +3330,12 @@ function bootstrap() {
|
|
|
3330
3330
|
aegis,
|
|
3331
3331
|
credentials,
|
|
3332
3332
|
persistKey: (key) => settings.setAegisKey(key),
|
|
3333
|
-
provision: autoProvision.autoProvisionApiKey,
|
|
3333
|
+
provision: (client, creds) => autoProvision.autoProvisionApiKey(client, creds, { app: 'desktop' }),
|
|
3334
|
+
describeFailure: autoProvision.provisionFailureText,
|
|
3334
3335
|
onChange: (state) => broadcastAccountStatus(state),
|
|
3335
3336
|
});
|
|
3336
|
-
const
|
|
3337
|
+
const keyPresentAtStart = Boolean(aegis.apiKey);
|
|
3338
|
+
const earlyKeyProvision = autoProvision.autoProvisionApiKey(aegis, credentials, { app: 'desktop' }).then((result) => {
|
|
3337
3339
|
accountKey.recordStartup(result);
|
|
3338
3340
|
if (!result.key && !aegis.apiKey) {
|
|
3339
3341
|
const reason =
|
|
@@ -3910,13 +3912,25 @@ function bootstrap() {
|
|
|
3910
3912
|
// registration attempt, and earlyKeyProvision's own .then() already
|
|
3911
3913
|
// warns on failure, so there is nothing further to report if it
|
|
3912
3914
|
// didn't land.
|
|
3913
|
-
await earlyKeyProvision;
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3915
|
+
const early = await earlyKeyProvision;
|
|
3916
|
+
// A key minted just now only means "no key was saved yet" — a key the user
|
|
3917
|
+
// saved in Settings before the shared store mirrored it would otherwise be
|
|
3918
|
+
// overwritten by the device's fresh account on this first start.
|
|
3919
|
+
const override = startupKeyOverride({
|
|
3920
|
+
mintedKey: keyPresentAtStart ? '' : early.key,
|
|
3921
|
+
hostKey: credentials.normalizeApiKey(settings.aegisRawKey()),
|
|
3922
|
+
});
|
|
3923
|
+
if (override) {
|
|
3924
|
+
credentials.saveApiKey(override);
|
|
3925
|
+
aegis.setApiKey(override);
|
|
3926
|
+
} else {
|
|
3927
|
+
const chosen = credentials.preferNewest(settings.aegisRawKey(), settings.aegisKeySavedAt());
|
|
3928
|
+
if (chosen.key) {
|
|
3929
|
+
aegis.setApiKey(chosen.key);
|
|
3930
|
+
// The shared store is the newer write: bring this host's encrypted copy
|
|
3931
|
+
// up to date so a CLI rotation does not have to be redone in Settings.
|
|
3932
|
+
if (chosen.source === 'shared') settings.setAegisKey(chosen.key);
|
|
3933
|
+
}
|
|
3920
3934
|
}
|
|
3921
3935
|
}
|
|
3922
3936
|
// ── 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.
|
|
4
|
+
"version": "1.0.7",
|
|
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",
|
package/vendor/aegis.js
CHANGED
|
@@ -855,10 +855,11 @@ function createClient(opts = {}) {
|
|
|
855
855
|
* zero balance. Re-registering an already-known fingerprint does NOT
|
|
856
856
|
* re-grant it; the pool balance gate still applies once the allowance is
|
|
857
857
|
* spent or the 30 days lapse. Sends no API key of its own — there isn't
|
|
858
|
-
* one yet.
|
|
858
|
+
* one yet. `opts.app` names the host ('cli' | 'desktop' | 'mcp') so the
|
|
859
|
+
* server files this machine under its real name, not 'other'.
|
|
859
860
|
*/
|
|
860
861
|
async function registerDevice(fingerprint, opts) {
|
|
861
|
-
return apiPost('/api/v1/auth/register-device', { fingerprint }, undefined, opts);
|
|
862
|
+
return apiPost('/api/v1/auth/register-device', { fingerprint, app: opts && opts.app }, undefined, opts);
|
|
862
863
|
}
|
|
863
864
|
|
|
864
865
|
/** Build the OpenAI-style messages array from either a full history or the
|
package/vendor/auto-provision.js
CHANGED
|
@@ -43,30 +43,47 @@ function isTransientRegisterError(err) {
|
|
|
43
43
|
return !status || status === 408 || status >= 500;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
// Why automatic registration gave up, in words a host can show the user.
|
|
47
|
+
function provisionFailureText(result) {
|
|
48
|
+
const err = result && result.error;
|
|
49
|
+
if (!err) return 'the server returned no account key';
|
|
50
|
+
if (err.status === 429) return 'too many sign-ups from this network — try again later';
|
|
51
|
+
if (err.status >= 500) return `aegiscloud.org failed to register this device: ${err.message}`;
|
|
52
|
+
if (isTransientRegisterError(err)) return 'could not reach aegiscloud.org';
|
|
53
|
+
return `the server refused the request (HTTP ${err.status})`;
|
|
54
|
+
}
|
|
55
|
+
|
|
46
56
|
/**
|
|
47
57
|
* @param {object} aegisClient A client from client/aegis.js (createClient()).
|
|
48
58
|
* @param {object} credentials client/credentials.js (or a compatible double).
|
|
49
|
-
* @param {number[]} [
|
|
50
|
-
*
|
|
59
|
+
* @param {{app?: string, retryDelaysMs?: number[]}} [options] `app` names the host
|
|
60
|
+
* for the server's presence panel; `retryDelaysMs` waits before each retry of a
|
|
61
|
+
* transient failure.
|
|
62
|
+
* @returns {Promise<{ok: boolean, key: string, trialGranted?: boolean, error?: Error}>}
|
|
51
63
|
* `ok` is true only when a usable key was obtained AND persisted to the
|
|
52
64
|
* shared store; `aegisClient.setApiKey()` has already been called on
|
|
53
65
|
* success, so the caller's client is immediately usable either way it
|
|
54
|
-
* inspects `ok`.
|
|
66
|
+
* inspects `ok`. `trialGranted` is false when the server withheld the
|
|
67
|
+
* welcome trial (the account and key are still valid).
|
|
55
68
|
*/
|
|
56
|
-
async function autoProvisionApiKey(aegisClient, credentials,
|
|
69
|
+
async function autoProvisionApiKey(aegisClient, credentials, options = {}) {
|
|
70
|
+
if (Array.isArray(options)) {
|
|
71
|
+
throw new TypeError('autoProvisionApiKey takes { app, retryDelaysMs }; a bare retry array is no longer accepted');
|
|
72
|
+
}
|
|
57
73
|
if (aegisClient.apiKey) return { ok: true, key: aegisClient.apiKey };
|
|
74
|
+
const { app, retryDelaysMs = DEVICE_REGISTER_RETRY_DELAYS_MS } = options;
|
|
58
75
|
let lastError = null;
|
|
59
76
|
for (let attempt = 0; attempt <= retryDelaysMs.length; attempt++) {
|
|
60
77
|
if (attempt > 0) await new Promise((resolve) => setTimeout(resolve, retryDelaysMs[attempt - 1]));
|
|
61
78
|
const controller = new AbortController();
|
|
62
79
|
const timer = setTimeout(() => controller.abort(), DEVICE_REGISTER_TIMEOUT_MS);
|
|
63
80
|
try {
|
|
64
|
-
const data = await aegisClient.registerDevice(machineFingerprint(), { signal: controller.signal });
|
|
81
|
+
const data = await aegisClient.registerDevice(machineFingerprint(), { app, signal: controller.signal });
|
|
65
82
|
const key = data && typeof data.api_key === 'string' ? data.api_key.trim() : '';
|
|
66
83
|
if (!key) return { ok: false, key: '' };
|
|
67
84
|
const saved = credentials.saveApiKey(key);
|
|
68
85
|
aegisClient.setApiKey(key);
|
|
69
|
-
return { ok: !!saved.ok, key };
|
|
86
|
+
return { ok: !!saved.ok, key, trialGranted: data.trial_granted };
|
|
70
87
|
} catch (err) {
|
|
71
88
|
lastError = err;
|
|
72
89
|
if (!isTransientRegisterError(err)) break;
|
|
@@ -77,4 +94,4 @@ async function autoProvisionApiKey(aegisClient, credentials, retryDelaysMs = DEV
|
|
|
77
94
|
return { ok: false, key: '', error: lastError };
|
|
78
95
|
}
|
|
79
96
|
|
|
80
|
-
module.exports = { autoProvisionApiKey, DEVICE_REGISTER_TIMEOUT_MS, DEVICE_REGISTER_RETRY_DELAYS_MS };
|
|
97
|
+
module.exports = { autoProvisionApiKey, provisionFailureText, DEVICE_REGISTER_TIMEOUT_MS, DEVICE_REGISTER_RETRY_DELAYS_MS };
|
|
@@ -111,8 +111,7 @@ function persistedInstallId() {
|
|
|
111
111
|
* 4. `hostname:platform:arch` — last resort, kept only so this never throws.
|
|
112
112
|
*
|
|
113
113
|
* Only step 4 is guessable by a third party; steps 2 and 3 are both per-host
|
|
114
|
-
* unique.
|
|
115
|
-
* abuse review can tell a genuine re-registration from a forged one.
|
|
114
|
+
* unique.
|
|
116
115
|
*
|
|
117
116
|
* @param {string} [overrideId] - test-only override for the raw machine identity.
|
|
118
117
|
*/
|
|
@@ -136,15 +135,9 @@ function machineIdentity(overrideId) {
|
|
|
136
135
|
};
|
|
137
136
|
}
|
|
138
137
|
|
|
139
|
-
/** Which identity class the fingerprint on THIS host is derived from. */
|
|
140
|
-
function machineIdentityKind() {
|
|
141
|
-
return machineIdentity().kind;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
138
|
module.exports = {
|
|
145
139
|
machineFingerprint,
|
|
146
140
|
machineIdentity,
|
|
147
|
-
machineIdentityKind,
|
|
148
141
|
readMachineId,
|
|
149
142
|
persistedInstallId,
|
|
150
143
|
};
|