aegis-desktop 1.0.13 → 1.0.15

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 (96) hide show
  1. package/SOURCE-OFFER.txt +9 -0
  2. package/bin/aegis.js +1 -32
  3. package/lib/account-key.js +1 -56
  4. package/lib/auto.js +1 -430
  5. package/lib/avatar/companion.js +1 -698
  6. package/lib/avatar/cosmetics.js +1 -532
  7. package/lib/avatar/events.js +1 -344
  8. package/lib/avatar/holder-turn.js +1 -350
  9. package/lib/avatar/holders.js +1 -612
  10. package/lib/avatar/identity.js +1 -641
  11. package/lib/avatar/level.js +1 -368
  12. package/lib/avatar/mirror.js +1 -524
  13. package/lib/avatar/persona.js +1 -388
  14. package/lib/avatar/profile.js +1 -977
  15. package/lib/avatar/register.js +1 -307
  16. package/lib/avatar/store.js +1 -472
  17. package/lib/avatar/tiers.js +1 -38
  18. package/lib/avatar/transfer.js +1 -415
  19. package/lib/avatar/turn.js +1 -216
  20. package/lib/avatar/voice.js +1 -328
  21. package/lib/avatar/xp.js +1 -404
  22. package/lib/deep-link.js +1 -120
  23. package/lib/fingerprint.js +2 -347
  24. package/lib/google-connect.js +1 -169
  25. package/lib/local/agents.js +1 -105
  26. package/lib/local/autonomous.js +1 -1045
  27. package/lib/local/claude-pro.js +1 -993
  28. package/lib/local/context.js +1 -95
  29. package/lib/local/engine.js +1 -3228
  30. package/lib/local/entitlement.js +1 -654
  31. package/lib/local/git-scope.js +1 -228
  32. package/lib/local/local.js +1 -2135
  33. package/lib/local/prompt.js +1 -275
  34. package/lib/local/queue.js +1 -560
  35. package/lib/local/session-rounds.js +1 -208
  36. package/lib/local/shell.js +1 -680
  37. package/lib/local/telemetry.js +1 -470
  38. package/lib/local/tools.js +1 -1238
  39. package/lib/local/turn-guard.js +1 -300
  40. package/lib/local/worktree-lock.js +1 -235
  41. package/lib/provider-key-mirror.js +1 -252
  42. package/lib/quick-launcher.js +1 -84
  43. package/lib/settings.js +1 -890
  44. package/lib/sync/memory-queue.js +1 -60
  45. package/lib/sync/persist-gate.js +1 -148
  46. package/lib/sync/sessions.js +1 -106
  47. package/lib/window-state.js +1 -66
  48. package/main.js +3 -4390
  49. package/package.json +3 -5
  50. package/preload.js +3 -628
  51. package/renderer/app.js +1 -6560
  52. package/renderer/avatar/assemble.js +1 -335
  53. package/renderer/avatar/avatar.js +1 -380
  54. package/renderer/avatar/cards.js +1 -141
  55. package/renderer/avatar/hud.js +1 -206
  56. package/renderer/avatar/machine.js +1 -213
  57. package/renderer/avatar/pane.js +1 -439
  58. package/renderer/avatar/parts.js +1 -437
  59. package/renderer/budget.js +1 -168
  60. package/renderer/companion.js +1 -223
  61. package/renderer/diff.js +1 -359
  62. package/renderer/diffview.js +1 -336
  63. package/renderer/fingerprint.js +3 -720
  64. package/renderer/markdown.js +1 -168
  65. package/renderer/provider-toolbar.js +1 -134
  66. package/renderer/providers.js +1 -259
  67. package/renderer/quick.js +1 -191
  68. package/renderer/stream-policy.js +1 -306
  69. package/renderer/transcript-view.js +1 -323
  70. package/renderer/usage.js +1 -1239
  71. package/vendor/aegis.js +1 -2054
  72. package/vendor/attest.js +1 -442
  73. package/vendor/authorship-key.js +1 -410
  74. package/vendor/auto-provision.js +1 -134
  75. package/vendor/brain-catalog.js +1 -268
  76. package/vendor/byok-catalog.js +1 -230
  77. package/vendor/credentials.js +1 -494
  78. package/vendor/device-fingerprint.js +1 -141
  79. package/vendor/drive-connect.js +1 -85
  80. package/vendor/drive-upload.js +1 -174
  81. package/vendor/env-file.js +1 -1273
  82. package/vendor/fingerprint.js +1 -1064
  83. package/vendor/foreign-memory.js +1 -669
  84. package/vendor/google-signin.js +1 -125
  85. package/vendor/hooks.js +1 -430
  86. package/vendor/model-discovery.js +1 -330
  87. package/vendor/platform.js +1 -937
  88. package/vendor/prefs.js +1 -351
  89. package/vendor/presence-constants.js +1 -107
  90. package/vendor/presence.js +1 -438
  91. package/vendor/record-vault.js +3 -1055
  92. package/vendor/session-store.js +1 -548
  93. package/vendor/topup.js +1 -297
  94. package/vendor/touch.js +1 -458
  95. package/vendor/update.js +1 -704
  96. package/vendor/user-models.js +1 -381
@@ -0,0 +1,9 @@
1
+ AEGIS Desktop (aegis-desktop) — obfuscated build.
2
+
3
+ This build is obfuscated (minified and mangled) object code of an
4
+ AGPL-3.0-or-later program. The corresponding source is:
5
+ https://github.com/aegisinfo/aegiscode-plugin
6
+ at the commit this tree was built from; see LICENSE for the full terms.
7
+
8
+ Obfuscation does not alter the licence. If you received this artifact you
9
+ are entitled to the source above.
package/bin/aegis.js CHANGED
@@ -1,35 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
  // SPDX-License-Identifier: AGPL-3.0-or-later
3
3
  // Copyright (C) 2026 Niklas Borneklint — AEGIS Code (aegiscode) / Aegiscloud.
4
- // AEGIS Code (aegiscode) — source: https://github.com/aegisinfo/aegiscode-plugin
5
- // Conveying a modified copy, or running one as a network service, owes the
6
- // corresponding source under this licence (§5/§13) — see LICENSING.md.
7
- 'use strict';
8
-
9
- /**
10
- * `aegis` CLI entry point for the `aegis-desktop` npm package: launches the
11
- * same Electron app as `npm start` in this repo, but from a global install
12
- * where there is no sibling `client/` dir — main.js's require('../client/
13
- * aegis.js') fails there and it falls back to the vendored copy staged by
14
- * predist at publish time (prepublishOnly), which is the intended path.
15
- */
16
-
17
- const path = require('node:path');
18
- const { spawn } = require('node:child_process');
19
-
20
- const appDir = path.join(__dirname, '..');
21
-
22
- if (process.argv.includes('--version') || process.argv.includes('-v')) {
23
- console.log(require(path.join(appDir, 'package.json')).version);
24
- process.exit(0);
25
- }
26
-
27
- const electronPath = require('electron');
28
- const child = spawn(electronPath, [appDir, ...process.argv.slice(2)], {
29
- stdio: 'inherit',
30
- });
31
- child.on('error', (err) => {
32
- console.error('Failed to launch AEGIS Desktop:', err.message);
33
- process.exit(1);
34
- });
35
- child.on('close', (code) => process.exit(code == null ? 0 : code));
4
+ "use strict";const path=require("node:path"),{spawn:spawn}=require("node:child_process"),appDir=path.join(__dirname,"..");(process.argv.includes("--version")||process.argv.includes("-v"))&&(console.log(require(path.join(appDir,"package.json")).version),process.exit(0));const electronPath=require("electron"),child=spawn(electronPath,[appDir,...process.argv.slice(2)],{stdio:"inherit"});child.on("error",(e=>{console.error("Failed to launch AEGIS Desktop:",e.message),process.exit(1)})),child.on("close",(e=>process.exit(null==e?0:e)));
@@ -1,60 +1,5 @@
1
1
  // SPDX-License-Identifier: AGPL-3.0-or-later
2
2
  // Copyright (C) 2026 Niklas Borneklint — AEGIS Code (aegiscode) / Aegiscloud.
3
- // AEGIS Code (aegiscode) — source: https://github.com/aegisinfo/aegiscode-plugin
4
- // Conveying a modified copy, or running one as a network service, owes the
5
- // corresponding source under this licence (§5/§13) — see LICENSING.md.
6
3
  // SPDX-License-Identifier: AGPL-3.0-or-later
7
4
  // Copyright (C) 2026 Niklas Borneklint — AEGIS Code (aegiscode) / Aegiscloud.
8
- 'use strict';
9
-
10
- /**
11
- * The desktop's view of whether this machine has an AEGIS account key. When
12
- * startup could not provision one, the banner says why and offers a retry.
13
- * Pure: Electron, the store and the provisioner are injected.
14
- */
15
-
16
- function createAccountKey({ aegis, credentials, persistKey, provision, describeFailure, onChange }) {
17
- let failure = null;
18
- let inFlight = null;
19
-
20
- function status() {
21
- if (aegis.apiKey || !failure) return null;
22
- return { status: 'no-key', reason: failure };
23
- }
24
-
25
- function recordStartup(result) {
26
- failure = (result.key || aegis.apiKey) ? null : describeFailure(result);
27
- onChange(status());
28
- }
29
-
30
- function registerAccount() {
31
- if (!inFlight) {
32
- inFlight = (async () => {
33
- try {
34
- const result = await provision(aegis, credentials);
35
- if (result.key) {
36
- persistKey(result.key);
37
- failure = null;
38
- } else {
39
- failure = describeFailure(result);
40
- }
41
- const state = status();
42
- onChange(state);
43
- return state;
44
- } finally {
45
- inFlight = null;
46
- }
47
- })();
48
- }
49
- return inFlight;
50
- }
51
-
52
- return { status, recordStartup, registerAccount };
53
- }
54
-
55
- // A key this host saved on purpose outranks one minted for a fresh device at startup.
56
- function startupKeyOverride({ mintedKey, hostKey }) {
57
- return mintedKey && hostKey && hostKey !== mintedKey ? hostKey : null;
58
- }
59
-
60
- module.exports = { createAccountKey, startupKeyOverride };
5
+ "use strict";function createAccountKey({aegis:e,credentials:t,persistKey:n,provision:r,describeFailure:u,onChange:c}){let i=null,s=null;function a(){return e.apiKey||!i?null:{status:"no-key",reason:i}}return{status:a,recordStartup:function(t){i=t.key||e.apiKey?null:u(t),c(a())},registerAccount:function(){return s||(s=(async()=>{try{const s=await r(e,t);s.key?(n(s.key),i=null):i=u(s);const l=a();return c(l),l}finally{s=null}})()),s}}}function startupKeyOverride({mintedKey:e,hostKey:t}){return e&&t&&t!==e?t:null}module.exports={createAccountKey:createAccountKey,startupKeyOverride:startupKeyOverride};
package/lib/auto.js CHANGED
@@ -1,432 +1,3 @@
1
1
  // SPDX-License-Identifier: AGPL-3.0-or-later
2
2
  // Copyright (C) 2026 Niklas Borneklint — AEGIS Code (aegiscode) / Aegiscloud.
3
- // AEGIS Code (aegiscode) — source: https://github.com/aegisinfo/aegiscode-plugin
4
- // Conveying a modified copy, or running one as a network service, owes the
5
- // corresponding source under this licence (§5/§13) — see LICENSING.md.
6
- 'use strict';
7
-
8
- /**
9
- * auto.js — the two opt-in automations, and the rules that make them safe to
10
- * leave switched on.
11
- *
12
- * BACKGROUND. desktop/main.js and desktop/preload.js both stated an absolute
13
- * rule when the queue shipped: "NOTHING DRAINS UNLESS THE USER ASKED. There is
14
- * no interval, no drain at startup, and no drain-on-idle anywhere in this
15
- * file." A click was the only trigger, which made "queue a task and walk away"
16
- * impossible — the one thing an unattended queue is FOR. The user asked for the
17
- * automation (twice: "autonomous queue", then "work in the background
18
- * instead"), so both switches now DEFAULT ON and the sidebar card that used to
19
- * gate them is hidden: draining and sealing happen with nothing on screen.
20
- *
21
- * What keeps that safe is not an off-by-default — it is the guards below, which
22
- * are unchanged and all of them skips rather than drains: an empty queue, a
23
- * drain already in flight, another host holding the lock, and a switch turned
24
- * off since the last tick (re-read EVERY tick, so off stops the very next one,
25
- * whoever wrote it: the settings file, the CLI, a hand edit).
26
- *
27
- * There are exactly two automations, because there are exactly two acts in the
28
- * app that are irreversible-ish and cost something:
29
- *
30
- * createAutoDrain — runs pending queue tasks on a timer. Spend: a full tool
31
- * loop on a real model, in a real checkout. Guarded by the queue's own file
32
- * lock (a second drain is refused by queue.js, not here), by a re-entrancy
33
- * flag, by an empty-queue check, and by a lock-holder check that skips the
34
- * tick instead of racing another host for it.
35
- * createAutoMint — seals the current session as a φ(α) record once the
36
- * session goes QUIET. Not once per turn: a record is about a session, so a
37
- * debounce that resets on every finished turn means one record per session
38
- * that carries all of its turns — human axis and AI axis alike. Publishing
39
- * is a separate flag and is never implied by sealing.
40
- *
41
- * WHAT THIS MODULE DELIBERATELY DOES NOT DO:
42
- * - It does not decide whether it MAY run. `settings.getAutoDrain()` /
43
- * `getAutoMint()` are read on every tick, so turning the toggle off stops
44
- * the very next tick, and the namespaces are reserved
45
- * (settings.js RESERVED_NAMESPACES) so no provider row can forge one.
46
- * - It does not take the drain lock, spawn a worker, or write a record. The
47
- * queue worker owns the lock; lib/fingerprint.js owns the seal. This file
48
- * owns the TIMING and the skips, and calls in.
49
- * - It does not swallow a failure. Every skip and every failure is recorded
50
- * in `state()` with a reason, because an automation that fails silently is
51
- * indistinguishable from one that is switched off.
52
- *
53
- * Timers are injected (`setIntervalFn`/`setTimeoutFn`) so the tests drive the
54
- * cadence without waiting for it, and so this module never keeps a Node process
55
- * alive by itself.
56
- */
57
-
58
- /** How long a session must be quiet before an armed auto-mint seals it. Long
59
- * enough that a normal pause for thought does not file a half-finished
60
- * session, short enough that walking away leaves a record behind. */
61
- const AUTO_MINT_QUIET_DEFAULT_MS = 90_000;
62
-
63
- /** Floor: sealing on every turn boundary would file one record per exchange and
64
- * re-read the whole history each time. */
65
- const AUTO_MINT_QUIET_MIN_MS = 10_000;
66
-
67
- /** Ceiling: an hour of quiet. Beyond that the app is closed anyway. */
68
- const AUTO_MINT_QUIET_MAX_MS = 60 * 60_000;
69
-
70
- function errorText(err) {
71
- return (err && err.message) || String(err);
72
- }
73
-
74
- /**
75
- * The auto-drain timer.
76
- *
77
- * `proceed` is main.js's drain-all entry point (the same function the card's
78
- * "Drain all" button calls) — this module never re-implements the loop, the
79
- * lock, the settle-back-into-the-file write, or the Stop path.
80
- */
81
- function createAutoDrain({
82
- proceed,
83
- settings,
84
- queue,
85
- env = process.env,
86
- log = () => {},
87
- setIntervalFn = setInterval,
88
- clearIntervalFn = clearInterval,
89
- } = {}) {
90
- if (typeof proceed !== 'function') {
91
- throw new Error('createAutoDrain: a proceed() entry point is required');
92
- }
93
- if (!settings || typeof settings.getAutoDrain !== 'function') {
94
- throw new Error('createAutoDrain: the settings store is required');
95
- }
96
-
97
- /** The armed timer, or null. `null` is the OFF state and the only one a
98
- * default install is ever in. */
99
- let timer = null;
100
- /** A tick is mid-flight. A drain takes minutes; without this the next tick
101
- * would stack a second drainAll() on top of the first (queue.js would refuse
102
- * it at the lock, but only after a round trip and a second set of frames). */
103
- let busy = false;
104
- let ticks = 0;
105
- let drains = 0;
106
- let last = null;
107
- let lastSkip = null;
108
-
109
- const record = (entry) => {
110
- last = entry;
111
- log(entry);
112
- return entry;
113
- };
114
-
115
- const skip = (reason, extra) => {
116
- lastSkip = { at: Date.now(), reason, ...(extra || {}) };
117
- return lastSkip;
118
- };
119
-
120
- function state() {
121
- const cfg = settings.autoDrainState();
122
- return {
123
- ...cfg,
124
- armed: Boolean(timer),
125
- busy,
126
- ticks,
127
- drains,
128
- last,
129
- lastSkip,
130
- };
131
- }
132
-
133
- function arm() {
134
- const cfg = settings.getAutoDrain();
135
- // Off is checked BEFORE the "already armed at this cadence" shortcut, or
136
- // switching off would leave the old timer running (harmless — its tick
137
- // skips as 'disabled' — but `armed` would read true in the card, which is
138
- // the kind of lie that makes a user distrust the switch).
139
- if (!cfg.enabled) {
140
- stop();
141
- return state();
142
- }
143
- if (timer && cfg.intervalMs === timer.__intervalMs) return state();
144
- stop();
145
- timer = setIntervalFn(() => {
146
- // Fire and forget: a tick's own failures are recorded in `last`, and an
147
- // unhandled rejection here would take the main process's error handler
148
- // with it. tick() never throws.
149
- tick();
150
- }, cfg.intervalMs);
151
- timer.__intervalMs = cfg.intervalMs;
152
- // A drain must never be the reason a process stays alive; in Electron the
153
- // window is, and in a test the harness is.
154
- if (timer && typeof timer.unref === 'function') timer.unref();
155
- log({ type: 'auto-drain-armed', intervalMs: cfg.intervalMs });
156
- return state();
157
- }
158
-
159
- function stop() {
160
- if (!timer) return state();
161
- clearIntervalFn(timer);
162
- timer = null;
163
- log({ type: 'auto-drain-disarmed' });
164
- return state();
165
- }
166
-
167
- /**
168
- * One decision, and the only place the guards live.
169
- *
170
- * Order matters. `disabled` is first so an armed-then-switched-off timer
171
- * cannot act on a stale config; `busy` before any file read; `empty` before
172
- * the lock so an idle install costs one JSON parse and no lock I/O; and the
173
- * lock-holder check LAST, because taking a lock we would only hand straight
174
- * back is the one skip that has a real cost (it makes another host's drain
175
- * fail its own acquire).
176
- */
177
- async function tick() {
178
- ticks += 1;
179
- const cfg = settings.getAutoDrain();
180
- if (!cfg.enabled) return record({ type: 'skip', reason: 'disabled' });
181
- if (busy) return record({ type: 'skip', reason: 'busy' });
182
- if (!queue || typeof queue.loadQueue !== 'function') {
183
- return record({ type: 'skip', reason: 'no queue access' });
184
- }
185
-
186
- let items;
187
- try {
188
- items = queue.loadQueue(env);
189
- } catch (err) {
190
- return record({ type: 'error', phase: 'read', error: errorText(err) });
191
- }
192
- if (!queue.pendingCount(items)) return record({ type: 'skip', reason: 'empty' });
193
-
194
- // Another host holds the drain (the CLI's `aegiscode autonomous`, a
195
- // systemd unit, another window). queue.js's lock would refuse us anyway —
196
- // this check is so the refusal is a quiet skip rather than a drain attempt
197
- // whose every frame reports "locked".
198
- try {
199
- const holder = queue.lockHolder(env);
200
- if (holder && holder.pid && holder.pid !== process.pid) {
201
- const alive = typeof queue.isAlive === 'function' ? queue.isAlive(holder.pid) : true;
202
- if (alive) return record({ type: 'skip', reason: 'locked', holder });
203
- }
204
- } catch {
205
- // An unreadable lock file is not a reason to skip: queue.js's own
206
- // acquireLock decides, and a stale/unparseable marker is exactly the case
207
- // it reclaims immediately.
208
- }
209
-
210
- busy = true;
211
- try {
212
- const result = await proceed();
213
- if (result && result.locked) {
214
- return record({
215
- type: 'skip',
216
- reason: 'locked',
217
- holder: result.holder || null,
218
- });
219
- }
220
- drains += 1;
221
- return record({
222
- type: 'drain',
223
- ok: !(result && result.ok === false),
224
- ran: (result && result.ran) || [],
225
- reason: (result && result.reason) || null,
226
- });
227
- } catch (err) {
228
- return record({ type: 'error', phase: 'drain', error: errorText(err) });
229
- } finally {
230
- busy = false;
231
- }
232
- }
233
-
234
- return {
235
- state,
236
- /** Read the persisted switch and arm/disarm to match. Called at boot and
237
- * after every toggle — never called by a timer. */
238
- start: arm,
239
- arm,
240
- stop,
241
- tick,
242
- /** The card's toggle. Persists through the settings store, then re-arms,
243
- * so the answer the renderer paints is the answer the timer is running.
244
- * Without the re-arm the switch would persist and do nothing until the next
245
- * restart — the exact silent failure this test file exists to catch. */
246
- set(patch) {
247
- settings.setAutoDrain(patch || {});
248
- arm();
249
- return state();
250
- },
251
- /** Test seam: whatever is armed, for a harness that injects its own timer
252
- * and needs to fire it. */
253
- _timer: () => timer,
254
- };
255
- }
256
-
257
- /**
258
- * The auto-mint (seal-on-quiet) timer.
259
- *
260
- * `mint` is lib/fingerprint.js's own `mint()` — the same call the panel's "Seal
261
- * this session" button makes, reading the same session log through the same
262
- * shared adapter, so an automatic record and a hand-sealed one of the same
263
- * session are the same record.
264
- */
265
- function createAutoMint({
266
- mint,
267
- settings,
268
- log = () => {},
269
- quietMs = AUTO_MINT_QUIET_DEFAULT_MS,
270
- setTimeoutFn = setTimeout,
271
- clearTimeoutFn = clearTimeout,
272
- } = {}) {
273
- if (typeof mint !== 'function') throw new Error('createAutoMint: mint() is required');
274
- if (!settings || typeof settings.getAutoMint !== 'function') {
275
- throw new Error('createAutoMint: the settings store is required');
276
- }
277
-
278
- const quiet = (() => {
279
- const n = Number(quietMs);
280
- if (!Number.isFinite(n)) return AUTO_MINT_QUIET_DEFAULT_MS;
281
- const ms = Math.round(n);
282
- if (ms < AUTO_MINT_QUIET_MIN_MS || ms > AUTO_MINT_QUIET_MAX_MS) {
283
- return AUTO_MINT_QUIET_DEFAULT_MS;
284
- }
285
- return ms;
286
- })();
287
-
288
- /** The pending debounce, or null. */
289
- let timer = null;
290
- /** Turns that arrived since the last seal. The ONLY thing that makes a seal
291
- * due: without it, a quiet period with no new work would re-seal the same
292
- * session over and over, one identical record per quiet minute. */
293
- let pendingTurns = 0;
294
- let busy = false;
295
- let seals = 0;
296
- let failures = 0;
297
- let last = null;
298
- let lastError = null;
299
- let lastSeal = null;
300
-
301
- function state() {
302
- const cfg = settings.autoMintState();
303
- return {
304
- ...cfg,
305
- armed: Boolean(timer),
306
- quietMs: quiet,
307
- pendingTurns,
308
- busy,
309
- seals,
310
- failures,
311
- last,
312
- lastError,
313
- lastSeal,
314
- };
315
- }
316
-
317
- function disarm() {
318
- if (!timer) return;
319
- clearTimeoutFn(timer);
320
- timer = null;
321
- }
322
-
323
- /** A turn finished. Restart the quiet clock; do not seal anything now. */
324
- function note(info) {
325
- const cfg = settings.getAutoMint();
326
- if (!cfg.enabled) {
327
- // Counted even while off, so switching the toggle on mid-session seals a
328
- // session that already has work in it rather than waiting for the next
329
- // turn — which is what a user who just flipped the switch means.
330
- pendingTurns += 1;
331
- return state();
332
- }
333
- pendingTurns += 1;
334
- last = { type: 'turn', at: Date.now(), sessionId: (info && info.sessionId) || null };
335
- disarm();
336
- timer = setTimeoutFn(() => {
337
- timer = null;
338
- flush();
339
- }, quiet);
340
- if (timer && typeof timer.unref === 'function') timer.unref();
341
- return state();
342
- }
343
-
344
- /**
345
- * Seal now, if there is anything to seal. This is what the debounce timer
346
- * calls; it is also the seam a test drives directly instead of waiting.
347
- */
348
- async function flush() {
349
- const cfg = settings.getAutoMint();
350
- if (!cfg.enabled) return { type: 'skip', reason: 'disabled' };
351
- if (busy) return { type: 'skip', reason: 'busy' };
352
- if (!pendingTurns) return { type: 'skip', reason: 'nothing new' };
353
- busy = true;
354
- // Claimed BEFORE the call: a seal that throws must not leave the counter
355
- // armed, or every later quiet period retries a failure that will not heal
356
- // (no key, unreadable history) and files a new error each time.
357
- const claimed = pendingTurns;
358
- pendingTurns = 0;
359
- try {
360
- // `aiSecret` is left undefined on purpose: that makes lib/fingerprint.js
361
- // walk the shared rungs in client/attest.js, so the AI axis is attested
362
- // from `$AEGIS_AI_SECRET` in the app's scope and `aiModel` from the same
363
- // place the CLI resolves it. This host never invents either, and never
364
- // borrows the authorship key for the AI axis (they answer different
365
- // questions: who sealed it, and which mind answered).
366
- const result = await mint({ publish: cfg.publish === true });
367
- if (!result || result.ok === false) {
368
- failures += 1;
369
- lastError = (result && result.error) || 'the seal did not complete';
370
- return (last = { type: 'error', phase: 'seal', error: lastError });
371
- }
372
- seals += 1;
373
- lastError = null;
374
- lastSeal = {
375
- at: Date.now(),
376
- id: (result.record && result.record.id) || null,
377
- out: result.out || null,
378
- turns: claimed,
379
- // Reported separately by lib/fingerprint.js: the seal is the evidence,
380
- // the cloud copy is the convenience, and a failed publish never
381
- // unseals anything.
382
- vaultError: result.vaultError || null,
383
- };
384
- return (last = { type: 'seal', ...lastSeal });
385
- } catch (err) {
386
- failures += 1;
387
- lastError = errorText(err);
388
- return (last = { type: 'error', phase: 'seal', error: lastError });
389
- } finally {
390
- busy = false;
391
- }
392
- }
393
-
394
- return {
395
- state,
396
- note,
397
- flush,
398
- /** Disarm the debounce and forget the pending count — the renderer's
399
- * explicit "seal now" path calls this after sealing, so the record it just
400
- * filed is not filed again by the timer. */
401
- reset() {
402
- disarm();
403
- pendingTurns = 0;
404
- return state();
405
- },
406
- start() {
407
- // Arming at boot means "the switch is on", not "seal immediately": with
408
- // pendingTurns at 0 there is nothing to seal until a turn arrives.
409
- if (!settings.getAutoMint().enabled) disarm();
410
- return state();
411
- },
412
- stop() {
413
- disarm();
414
- return state();
415
- },
416
- set(patch) {
417
- settings.setAutoMint(patch || {});
418
- if (!settings.getAutoMint().enabled) disarm();
419
- return state();
420
- },
421
- /** Test seam: is the debounce armed? */
422
- _timer: () => timer,
423
- };
424
- }
425
-
426
- module.exports = {
427
- createAutoDrain,
428
- createAutoMint,
429
- AUTO_MINT_QUIET_DEFAULT_MS,
430
- AUTO_MINT_QUIET_MIN_MS,
431
- AUTO_MINT_QUIET_MAX_MS,
432
- };
3
+ "use strict";const AUTO_MINT_QUIET_DEFAULT_MS=9e4,AUTO_MINT_QUIET_MIN_MS=1e4,AUTO_MINT_QUIET_MAX_MS=36e5;function errorText(e){return e&&e.message||String(e)}function createAutoDrain({proceed:e,settings:t,queue:r,env:n=process.env,log:o=(()=>{}),setIntervalFn:i=setInterval,clearIntervalFn:u=clearInterval}={}){if("function"!=typeof e)throw new Error("createAutoDrain: a proceed() entry point is required");if(!t||"function"!=typeof t.getAutoDrain)throw new Error("createAutoDrain: the settings store is required");let s=null,a=!1,l=0,c=0,p=null,d=null;const f=e=>(p=e,o(e),e);function y(){return{...t.autoDrainState(),armed:Boolean(s),busy:a,ticks:l,drains:c,last:p,lastSkip:d}}function M(){const e=t.getAutoDrain();return e.enabled?(s&&e.intervalMs===s.__intervalMs||(A(),s=i((()=>{_()}),e.intervalMs),s.__intervalMs=e.intervalMs,s&&"function"==typeof s.unref&&s.unref(),o({type:"auto-drain-armed",intervalMs:e.intervalMs})),y()):(A(),y())}function A(){return s?(u(s),s=null,o({type:"auto-drain-disarmed"}),y()):y()}async function _(){if(l+=1,!t.getAutoDrain().enabled)return f({type:"skip",reason:"disabled"});if(a)return f({type:"skip",reason:"busy"});if(!r||"function"!=typeof r.loadQueue)return f({type:"skip",reason:"no queue access"});let o;try{o=r.loadQueue(n)}catch(e){return f({type:"error",phase:"read",error:errorText(e)})}if(!r.pendingCount(o))return f({type:"skip",reason:"empty"});try{const e=r.lockHolder(n);if(e&&e.pid&&e.pid!==process.pid&&("function"!=typeof r.isAlive||r.isAlive(e.pid)))return f({type:"skip",reason:"locked",holder:e})}catch{}a=!0;try{const t=await e();return t&&t.locked?f({type:"skip",reason:"locked",holder:t.holder||null}):(c+=1,f({type:"drain",ok:!(t&&!1===t.ok),ran:t&&t.ran||[],reason:t&&t.reason||null}))}catch(e){return f({type:"error",phase:"drain",error:errorText(e)})}finally{a=!1}}return{state:y,start:M,arm:M,stop:A,tick:_,set:e=>(t.setAutoDrain(e||{}),M(),y()),_timer:()=>s}}function createAutoMint({mint:e,settings:t,log:r=(()=>{}),quietMs:n=9e4,setTimeoutFn:o=setTimeout,clearTimeoutFn:i=clearTimeout}={}){if("function"!=typeof e)throw new Error("createAutoMint: mint() is required");if(!t||"function"!=typeof t.getAutoMint)throw new Error("createAutoMint: the settings store is required");const u=(()=>{const e=Number(n);if(!Number.isFinite(e))return 9e4;const t=Math.round(e);return t<1e4||t>36e5?9e4:t})();let s=null,a=0,l=!1,c=0,p=0,d=null,f=null,y=null;function M(){return{...t.autoMintState(),armed:Boolean(s),quietMs:u,pendingTurns:a,busy:l,seals:c,failures:p,last:d,lastError:f,lastSeal:y}}function A(){s&&(i(s),s=null)}async function _(){const r=t.getAutoMint();if(!r.enabled)return{type:"skip",reason:"disabled"};if(l)return{type:"skip",reason:"busy"};if(!a)return{type:"skip",reason:"nothing new"};l=!0;const n=a;a=0;try{const t=await e({publish:!0===r.publish});return t&&!1!==t.ok?(c+=1,f=null,y={at:Date.now(),id:t.record&&t.record.id||null,out:t.out||null,turns:n,vaultError:t.vaultError||null},d={type:"seal",...y}):(p+=1,f=t&&t.error||"the seal did not complete",d={type:"error",phase:"seal",error:f})}catch(e){return p+=1,f=errorText(e),d={type:"error",phase:"seal",error:f}}finally{l=!1}}return{state:M,note:function(e){return t.getAutoMint().enabled?(a+=1,d={type:"turn",at:Date.now(),sessionId:e&&e.sessionId||null},A(),s=o((()=>{s=null,_()}),u),s&&"function"==typeof s.unref&&s.unref(),M()):(a+=1,M())},flush:_,reset:()=>(A(),a=0,M()),start:()=>(t.getAutoMint().enabled||A(),M()),stop:()=>(A(),M()),set:e=>(t.setAutoMint(e||{}),t.getAutoMint().enabled||A(),M()),_timer:()=>s}}module.exports={createAutoDrain:createAutoDrain,createAutoMint:createAutoMint,AUTO_MINT_QUIET_DEFAULT_MS:9e4,AUTO_MINT_QUIET_MIN_MS:1e4,AUTO_MINT_QUIET_MAX_MS:36e5};