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.
- package/SOURCE-OFFER.txt +9 -0
- package/bin/aegis.js +1 -32
- package/lib/account-key.js +1 -56
- package/lib/auto.js +1 -430
- package/lib/avatar/companion.js +1 -698
- package/lib/avatar/cosmetics.js +1 -532
- package/lib/avatar/events.js +1 -344
- package/lib/avatar/holder-turn.js +1 -350
- package/lib/avatar/holders.js +1 -612
- package/lib/avatar/identity.js +1 -641
- package/lib/avatar/level.js +1 -368
- package/lib/avatar/mirror.js +1 -524
- package/lib/avatar/persona.js +1 -388
- package/lib/avatar/profile.js +1 -977
- package/lib/avatar/register.js +1 -307
- package/lib/avatar/store.js +1 -472
- package/lib/avatar/tiers.js +1 -38
- package/lib/avatar/transfer.js +1 -415
- package/lib/avatar/turn.js +1 -216
- package/lib/avatar/voice.js +1 -328
- package/lib/avatar/xp.js +1 -404
- package/lib/deep-link.js +1 -120
- package/lib/fingerprint.js +2 -347
- package/lib/google-connect.js +1 -169
- package/lib/local/agents.js +1 -105
- package/lib/local/autonomous.js +1 -1045
- package/lib/local/claude-pro.js +1 -993
- package/lib/local/context.js +1 -95
- package/lib/local/engine.js +1 -3228
- package/lib/local/entitlement.js +1 -654
- package/lib/local/git-scope.js +1 -228
- package/lib/local/local.js +1 -2135
- package/lib/local/prompt.js +1 -275
- package/lib/local/queue.js +1 -560
- package/lib/local/session-rounds.js +1 -208
- package/lib/local/shell.js +1 -680
- package/lib/local/telemetry.js +1 -470
- package/lib/local/tools.js +1 -1238
- package/lib/local/turn-guard.js +1 -300
- package/lib/local/worktree-lock.js +1 -235
- package/lib/provider-key-mirror.js +1 -252
- package/lib/quick-launcher.js +1 -84
- package/lib/settings.js +1 -890
- package/lib/sync/memory-queue.js +1 -60
- package/lib/sync/persist-gate.js +1 -148
- package/lib/sync/sessions.js +1 -106
- package/lib/window-state.js +1 -66
- package/main.js +3 -4390
- package/package.json +3 -5
- package/preload.js +3 -628
- package/renderer/app.js +1 -6560
- package/renderer/avatar/assemble.js +1 -335
- package/renderer/avatar/avatar.js +1 -380
- package/renderer/avatar/cards.js +1 -141
- package/renderer/avatar/hud.js +1 -206
- package/renderer/avatar/machine.js +1 -213
- package/renderer/avatar/pane.js +1 -439
- package/renderer/avatar/parts.js +1 -437
- package/renderer/budget.js +1 -168
- package/renderer/companion.js +1 -223
- package/renderer/diff.js +1 -359
- package/renderer/diffview.js +1 -336
- package/renderer/fingerprint.js +3 -720
- package/renderer/markdown.js +1 -168
- package/renderer/provider-toolbar.js +1 -134
- package/renderer/providers.js +1 -259
- package/renderer/quick.js +1 -191
- package/renderer/stream-policy.js +1 -306
- package/renderer/transcript-view.js +1 -323
- package/renderer/usage.js +1 -1239
- package/vendor/aegis.js +1 -2054
- package/vendor/attest.js +1 -442
- package/vendor/authorship-key.js +1 -410
- package/vendor/auto-provision.js +1 -134
- package/vendor/brain-catalog.js +1 -268
- package/vendor/byok-catalog.js +1 -230
- package/vendor/credentials.js +1 -494
- package/vendor/device-fingerprint.js +1 -141
- package/vendor/drive-connect.js +1 -85
- package/vendor/drive-upload.js +1 -174
- package/vendor/env-file.js +1 -1273
- package/vendor/fingerprint.js +1 -1064
- package/vendor/foreign-memory.js +1 -669
- package/vendor/google-signin.js +1 -125
- package/vendor/hooks.js +1 -430
- package/vendor/model-discovery.js +1 -330
- package/vendor/platform.js +1 -937
- package/vendor/prefs.js +1 -351
- package/vendor/presence-constants.js +1 -107
- package/vendor/presence.js +1 -438
- package/vendor/record-vault.js +3 -1055
- package/vendor/session-store.js +1 -548
- package/vendor/topup.js +1 -297
- package/vendor/touch.js +1 -458
- package/vendor/update.js +1 -704
- package/vendor/user-models.js +1 -381
package/SOURCE-OFFER.txt
ADDED
|
@@ -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
|
-
|
|
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)));
|
package/lib/account-key.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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};
|