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/lib/avatar/level.js
CHANGED
|
@@ -1,370 +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
|
-
* level.js — what a level actually buys.
|
|
10
|
-
*
|
|
11
|
-
* RULE ZERO, and the reason this module is a pure function instead of a feature
|
|
12
|
-
* flag soup: **a bonus may only be a knob the engine already takes.** Levelling
|
|
13
|
-
* never invents a code path; it widens the values of parameters turn assembly,
|
|
14
|
-
* the tool host and the companion behaviours already read. Recall breadth is
|
|
15
|
-
* `memorySearch`'s `limit`. The model floor is the model picker's minimum. The
|
|
16
|
-
* queue drain is a tool grant the host already understands. If a proposed bonus
|
|
17
|
-
* cannot be expressed as one of those, it is not a bonus — it is a new feature
|
|
18
|
-
* wearing a level as a disguise.
|
|
19
|
-
*
|
|
20
|
-
* `capabilities(level)` is therefore the ONE object the rest of the app reads,
|
|
21
|
-
* and it is deliberately written so the safety-relevant entries are *not* a
|
|
22
|
-
* function of level at all: `approvals.write`, `approvals.shell`,
|
|
23
|
-
* `approvals.network` and `approvals.offDevice` are literally the same frozen
|
|
24
|
-
* values at every level. That is what makes the carve-outs below assertable
|
|
25
|
-
* rather than merely promised — there is no branch to get wrong.
|
|
26
|
-
*
|
|
27
|
-
* The carve-outs (asserted in `test/avatar-level.test.mjs` AND enforced at
|
|
28
|
-
* runtime by `verifyInvariants`, which main calls at boot):
|
|
29
|
-
*
|
|
30
|
-
* - Approval gates for writes, shell and network are level-independent.
|
|
31
|
-
* Levelling must never widen blast radius.
|
|
32
|
-
* - `memoryWriteback` is always true and never gated on level: memory is the
|
|
33
|
-
* source of XP, so gating it would be a feedback deadlock.
|
|
34
|
-
* - Nothing level-gated may send data off-device (publishing, posting, email).
|
|
35
|
-
* - `AEGIS_*` env overrides win over capabilities for automation and CI — and
|
|
36
|
-
* they too cannot widen write/shell/network approval.
|
|
37
|
-
*
|
|
38
|
-
* Pure: no fs, no Electron, no process access except the `env` the caller
|
|
39
|
-
* passes.
|
|
40
|
-
*/
|
|
41
|
-
|
|
42
|
-
const { TIERS, tierFor, tierIndex } = require('./tiers.js');
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* The four approval classes that are NOT a function of level. Frozen once and
|
|
46
|
-
* shared by every capabilities object, so `caps.approvals.write` is the same
|
|
47
|
-
* string for a brand-new install and a level-40 archivist.
|
|
48
|
-
*/
|
|
49
|
-
const LEVEL_INDEPENDENT_APPROVALS = Object.freeze({
|
|
50
|
-
write: 'ask',
|
|
51
|
-
shell: 'ask',
|
|
52
|
-
network: 'ask',
|
|
53
|
-
offDevice: 'ask',
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
/** Classes that may ever be auto-approved. Writes/shell/network never appear. */
|
|
57
|
-
const AUTO_APPROVABLE_CLASSES = Object.freeze(['read', 'diff']);
|
|
58
|
-
|
|
59
|
-
/** Levels at which each tier's headline bonus lands. Kept next to the tier table. */
|
|
60
|
-
const GATES = Object.freeze({
|
|
61
|
-
hints: 2,
|
|
62
|
-
brief: 5,
|
|
63
|
-
ideaLog: 5,
|
|
64
|
-
autoApproveDiff: 10,
|
|
65
|
-
sessionReflection: 10,
|
|
66
|
-
voice: 10,
|
|
67
|
-
reasoningFloor: 20,
|
|
68
|
-
queueDrain: 20,
|
|
69
|
-
importAssistant: 35,
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Cosmetic unlocks. Cosmetics are the ONLY thing this product may ever sell, so
|
|
74
|
-
* the free/paid line is expressed here as data: an id with a `paid` flag and
|
|
75
|
-
* nothing else. No unlock in this table is allowed to gate XP, a level, recall
|
|
76
|
-
* breadth, or approval friction — `verifyInvariants` checks the *capability*
|
|
77
|
-
* side of that; the table itself only ever lists presentation ids.
|
|
78
|
-
*/
|
|
79
|
-
const UNLOCKS = Object.freeze([
|
|
80
|
-
Object.freeze({ id: 'expression.core', level: 1, kind: 'expressions', paid: false, label: 'Core expressions' }),
|
|
81
|
-
Object.freeze({ id: 'outfit.hoodie', level: 1, kind: 'outfit', paid: false, label: 'Hoodie' }),
|
|
82
|
-
Object.freeze({ id: 'outfit.tshirt', level: 1, kind: 'outfit', paid: false, label: 'T-shirt' }),
|
|
83
|
-
Object.freeze({ id: 'palette.amber', level: 1, kind: 'palette', paid: false, label: 'Amber' }),
|
|
84
|
-
Object.freeze({ id: 'outfit.headphones', level: 5, kind: 'outfit', paid: false, label: 'Headphones' }),
|
|
85
|
-
Object.freeze({ id: 'expression.trusted', level: 5, kind: 'expressions', paid: false, label: 'Trusted expressions' }),
|
|
86
|
-
Object.freeze({ id: 'palette.slate', level: 5, kind: 'palette', paid: false, label: 'Slate' }),
|
|
87
|
-
Object.freeze({ id: 'outfit.layers', level: 5, kind: 'outfit', paid: false, label: 'Outfit layers' }),
|
|
88
|
-
Object.freeze({ id: 'expression.companion', level: 10, kind: 'expressions', paid: false, label: 'Companion expressions' }),
|
|
89
|
-
Object.freeze({ id: 'voice.core', level: 10, kind: 'voice', paid: false, label: 'Voice (local)' }),
|
|
90
|
-
Object.freeze({ id: 'expression.confidant', level: 20, kind: 'expressions', paid: false, label: 'Confidant expressions' }),
|
|
91
|
-
Object.freeze({ id: 'outfit.seasonal', level: 20, kind: 'outfit', paid: true, label: 'Seasonal set' }),
|
|
92
|
-
Object.freeze({ id: 'expression.archivist', level: 35, kind: 'expressions', paid: false, label: 'Archivist expressions' }),
|
|
93
|
-
Object.freeze({ id: 'outfit.archive', level: 35, kind: 'outfit', paid: true, label: 'Archive set' }),
|
|
94
|
-
]);
|
|
95
|
-
|
|
96
|
-
const PROACTIVITY_ORDER = Object.freeze(['off', 'hints', 'brief']);
|
|
97
|
-
|
|
98
|
-
function clampLevel(level) {
|
|
99
|
-
const n = Math.floor(typeof level === 'number' && Number.isFinite(level) ? level : 1);
|
|
100
|
-
return Math.max(1, n);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function atLeast(level, gate) {
|
|
104
|
-
return clampLevel(level) >= gate;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/** Ids unlocked at or below `level`. */
|
|
108
|
-
function unlockedBy(level) {
|
|
109
|
-
const n = clampLevel(level);
|
|
110
|
-
return UNLOCKS.filter((u) => u.level <= n).map((u) => u.id);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/** The next unlock(s) to dangle in the HUD, or null at the top of the table. */
|
|
114
|
-
function nextUnlock(level) {
|
|
115
|
-
const n = clampLevel(level);
|
|
116
|
-
const ahead = UNLOCKS.filter((u) => u.level > n);
|
|
117
|
-
if (!ahead.length) return null;
|
|
118
|
-
const level_ = Math.min(...ahead.map((u) => u.level));
|
|
119
|
-
return {
|
|
120
|
-
level: level_,
|
|
121
|
-
unlocks: ahead.filter((u) => u.level === level_).map((u) => ({ id: u.id, label: u.label, kind: u.kind, paid: u.paid })),
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function lowerProactivity(a, b) {
|
|
126
|
-
const ia = PROACTIVITY_ORDER.indexOf(a);
|
|
127
|
-
const ib = PROACTIVITY_ORDER.indexOf(b);
|
|
128
|
-
if (ia < 0) return b;
|
|
129
|
-
if (ib < 0) return a;
|
|
130
|
-
return PROACTIVITY_ORDER[Math.min(ia, ib)];
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/** The proactivity ceiling a level earns, before the user's preference lowers it. */
|
|
134
|
-
function proactivityCeiling(level) {
|
|
135
|
-
if (atLeast(level, GATES.brief)) return 'brief';
|
|
136
|
-
if (atLeast(level, GATES.hints)) return 'hints';
|
|
137
|
-
return 'off';
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* The one object the rest of the app reads.
|
|
142
|
-
*
|
|
143
|
-
* @param {number} level
|
|
144
|
-
* @param {object} [opts]
|
|
145
|
-
* @param {'off'|'hints'|'brief'} [opts.userProactivity] the user's own setting.
|
|
146
|
-
* A preference can only ever LOWER proactivity — a level is not something the
|
|
147
|
-
* user can talk themselves out of, but a chatty companion is.
|
|
148
|
-
*/
|
|
149
|
-
function capabilities(level, opts = {}) {
|
|
150
|
-
const n = clampLevel(level);
|
|
151
|
-
const tier = tierFor(n);
|
|
152
|
-
const autoApproveClasses = AUTO_APPROVABLE_CLASSES.filter(
|
|
153
|
-
(cls) => cls === 'read' || atLeast(n, GATES.autoApproveDiff),
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
const approvals = Object.freeze({
|
|
157
|
-
read: 'auto',
|
|
158
|
-
diff: autoApproveClasses.includes('diff') ? 'auto' : 'ask',
|
|
159
|
-
...LEVEL_INDEPENDENT_APPROVALS,
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
const proactivity = opts.userProactivity === undefined
|
|
163
|
-
? proactivityCeiling(n)
|
|
164
|
-
: lowerProactivity(proactivityCeiling(n), opts.userProactivity);
|
|
165
|
-
|
|
166
|
-
return Object.freeze({
|
|
167
|
-
level: n,
|
|
168
|
-
tier: tier.name,
|
|
169
|
-
tierIndex: tierIndex(n),
|
|
170
|
-
|
|
171
|
-
// recall breadth — literally memorySearch's limit/token budget
|
|
172
|
-
recallEntries: 4 + Math.floor(n / 2),
|
|
173
|
-
recallTokens: 1500 + 250 * n,
|
|
174
|
-
|
|
175
|
-
// never gated (feedback deadlock) — see header
|
|
176
|
-
memoryWriteback: true,
|
|
177
|
-
|
|
178
|
-
autoApproveClasses: Object.freeze(autoApproveClasses.slice()),
|
|
179
|
-
approvals,
|
|
180
|
-
|
|
181
|
-
toolGrants: Object.freeze(atLeast(n, GATES.queueDrain) ? ['queue.drain'] : []),
|
|
182
|
-
modelFloor: atLeast(n, GATES.reasoningFloor) ? 'reasoning' : null,
|
|
183
|
-
|
|
184
|
-
proactivity,
|
|
185
|
-
proactivityCeiling: proactivityCeiling(n),
|
|
186
|
-
|
|
187
|
-
companion: Object.freeze({
|
|
188
|
-
morningBrief: atLeast(n, GATES.brief),
|
|
189
|
-
ideaLog: atLeast(n, GATES.ideaLog),
|
|
190
|
-
sessionReflection: atLeast(n, GATES.sessionReflection),
|
|
191
|
-
voice: atLeast(n, GATES.voice),
|
|
192
|
-
queueDrain: atLeast(n, GATES.queueDrain),
|
|
193
|
-
importAssistant: atLeast(n, GATES.importAssistant),
|
|
194
|
-
}),
|
|
195
|
-
|
|
196
|
-
cosmeticsUnlocked: Object.freeze(unlockedBy(n)),
|
|
197
|
-
offDeviceGrants: Object.freeze([]),
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// ---------------------------------------------------------------------------
|
|
202
|
-
// carve-outs
|
|
203
|
-
// ---------------------------------------------------------------------------
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Which invariants this capabilities object violates. Empty array = safe.
|
|
207
|
-
* `verifyInvariants` runs this over the whole level range; main runs it at boot
|
|
208
|
-
* so a future edit that gates a write approval on level fails loudly at launch
|
|
209
|
-
* instead of silently weakening the gate.
|
|
210
|
-
*/
|
|
211
|
-
function carveOuts(caps) {
|
|
212
|
-
const violations = [];
|
|
213
|
-
if (!caps || typeof caps !== 'object') return ['capabilities missing'];
|
|
214
|
-
|
|
215
|
-
for (const cls of ['write', 'shell', 'network', 'offDevice']) {
|
|
216
|
-
const got = caps.approvals && caps.approvals[cls];
|
|
217
|
-
if (got !== 'ask') violations.push(`approval for ${cls} must stay "ask", got ${JSON.stringify(got)}`);
|
|
218
|
-
}
|
|
219
|
-
for (const cls of caps.autoApproveClasses || []) {
|
|
220
|
-
if (!AUTO_APPROVABLE_CLASSES.includes(cls)) {
|
|
221
|
-
violations.push(`class ${cls} is not auto-approvable at any level`);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
for (const cls of ['write', 'shell', 'network', 'offDevice']) {
|
|
225
|
-
if ((caps.autoApproveClasses || []).includes(cls)) {
|
|
226
|
-
violations.push(`class ${cls} must never be auto-approved`);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
if (caps.memoryWriteback !== true) {
|
|
230
|
-
violations.push('memoryWriteback must stay true at every level (XP would deadlock)');
|
|
231
|
-
}
|
|
232
|
-
if (!Array.isArray(caps.offDeviceGrants) || caps.offDeviceGrants.length) {
|
|
233
|
-
violations.push('nothing level-gated may send data off-device');
|
|
234
|
-
}
|
|
235
|
-
for (const grant of caps.toolGrants || []) {
|
|
236
|
-
if (/publish|post|email|upload|deploy|broadcast/i.test(String(grant))) {
|
|
237
|
-
violations.push(`tool grant ${grant} reaches off-device`);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
return violations;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/** Throws on the first violation. Used by tests and by main at boot. */
|
|
244
|
-
function assertCarveOuts(caps) {
|
|
245
|
-
const bad = carveOuts(caps);
|
|
246
|
-
if (bad.length) throw new Error(`avatar capabilities violate a carve-out: ${bad.join('; ')}`);
|
|
247
|
-
return caps;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Sweep a level range and assert every invariant, plus monotonicity: a higher
|
|
252
|
-
* level may never grant LESS than a lower one (a bonus that disappears as you
|
|
253
|
-
* level is a bug that reads as a punishment).
|
|
254
|
-
*/
|
|
255
|
-
function verifyInvariants(levels) {
|
|
256
|
-
const list = levels || Array.from({ length: 60 }, (_, i) => i + 1);
|
|
257
|
-
const violations = [];
|
|
258
|
-
let prev = null;
|
|
259
|
-
for (const level of list) {
|
|
260
|
-
const caps = capabilities(level);
|
|
261
|
-
for (const v of carveOuts(caps)) violations.push(`L${level}: ${v}`);
|
|
262
|
-
if (prev) {
|
|
263
|
-
const checks = [
|
|
264
|
-
['recallEntries', (c) => c.recallEntries],
|
|
265
|
-
['recallTokens', (c) => c.recallTokens],
|
|
266
|
-
];
|
|
267
|
-
for (const [name, get] of checks) {
|
|
268
|
-
if (get(caps) < get(prev)) violations.push(`L${level}: ${name} shrank from ${get(prev)} to ${get(caps)}`);
|
|
269
|
-
}
|
|
270
|
-
for (const cls of AUTO_APPROVABLE_CLASSES) {
|
|
271
|
-
const was = prev.autoApproveClasses.includes(cls);
|
|
272
|
-
const now = caps.autoApproveClasses.includes(cls);
|
|
273
|
-
if (was && !now) violations.push(`L${level}: auto-approve for ${cls} was taken away`);
|
|
274
|
-
}
|
|
275
|
-
if (prev.modelFloor === 'reasoning' && caps.modelFloor !== 'reasoning') {
|
|
276
|
-
violations.push(`L${level}: reasoning model floor was taken away`);
|
|
277
|
-
}
|
|
278
|
-
if (PROACTIVITY_ORDER.indexOf(caps.proactivity) < PROACTIVITY_ORDER.indexOf(prev.proactivity)) {
|
|
279
|
-
violations.push(`L${level}: proactivity dropped to ${caps.proactivity}`);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
prev = caps;
|
|
283
|
-
}
|
|
284
|
-
return violations;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// ---------------------------------------------------------------------------
|
|
288
|
-
// env overrides (automation / CI)
|
|
289
|
-
// ---------------------------------------------------------------------------
|
|
290
|
-
|
|
291
|
-
const TRUTHY = new Set(['1', 'true', 'yes', 'on']);
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* `AEGIS_*` wins over capabilities, because a headless CI run must not have its
|
|
295
|
-
* recall breadth decided by someone's XP. Overrides may widen recall and lower
|
|
296
|
-
* proactivity freely; they may NOT touch the level-independent approvals, and an
|
|
297
|
-
* attempt to auto-approve writes/shell/network is ignored (and reported in
|
|
298
|
-
* `ignored`) rather than honoured.
|
|
299
|
-
*/
|
|
300
|
-
function applyEnvOverrides(caps, env) {
|
|
301
|
-
const e = env || {};
|
|
302
|
-
const ignored = [];
|
|
303
|
-
const out = Object.assign({}, caps);
|
|
304
|
-
const int = (v) => {
|
|
305
|
-
const n = Number.parseInt(String(v), 10);
|
|
306
|
-
return Number.isFinite(n) && n > 0 ? n : null;
|
|
307
|
-
};
|
|
308
|
-
|
|
309
|
-
if (e.AEGIS_AVATAR_DISABLE !== undefined && TRUTHY.has(String(e.AEGIS_AVATAR_DISABLE).toLowerCase())) {
|
|
310
|
-
out.proactivity = 'off';
|
|
311
|
-
out.modelFloor = null;
|
|
312
|
-
out.toolGrants = Object.freeze([]);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
const entries = int(e.AEGIS_RECALL_ENTRIES);
|
|
316
|
-
if (entries !== null) out.recallEntries = entries;
|
|
317
|
-
const tokens = int(e.AEGIS_RECALL_TOKENS);
|
|
318
|
-
if (tokens !== null) out.recallTokens = tokens;
|
|
319
|
-
|
|
320
|
-
if (e.AEGIS_PROACTIVITY !== undefined) {
|
|
321
|
-
const want = String(e.AEGIS_PROACTIVITY).toLowerCase();
|
|
322
|
-
if (PROACTIVITY_ORDER.includes(want)) {
|
|
323
|
-
out.proactivity = lowerProactivity(out.proactivity, want);
|
|
324
|
-
} else {
|
|
325
|
-
ignored.push(`AEGIS_PROACTIVITY=${e.AEGIS_PROACTIVITY}`);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
if (e.AEGIS_MODEL_FLOOR !== undefined) {
|
|
330
|
-
const want = String(e.AEGIS_MODEL_FLOOR).toLowerCase();
|
|
331
|
-
if (want === 'reasoning') out.modelFloor = 'reasoning';
|
|
332
|
-
else if (want === 'off' || want === 'none') out.modelFloor = null;
|
|
333
|
-
else ignored.push(`AEGIS_MODEL_FLOOR=${e.AEGIS_MODEL_FLOOR}`);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
if (e.AEGIS_AUTO_APPROVE !== undefined) {
|
|
337
|
-
const want = String(e.AEGIS_AUTO_APPROVE)
|
|
338
|
-
.split(',')
|
|
339
|
-
.map((s) => s.trim().toLowerCase())
|
|
340
|
-
.filter(Boolean);
|
|
341
|
-
const dangerous = want.filter((c) => !AUTO_APPROVABLE_CLASSES.includes(c));
|
|
342
|
-
if (dangerous.length) ignored.push(`AEGIS_AUTO_APPROVE=${dangerous.join(',')}`);
|
|
343
|
-
const safe = want.filter((c) => AUTO_APPROVABLE_CLASSES.includes(c));
|
|
344
|
-
if (safe.length) {
|
|
345
|
-
out.autoApproveClasses = Object.freeze(Array.from(new Set([...(out.autoApproveClasses || []), ...safe])));
|
|
346
|
-
out.approvals = Object.freeze({ ...out.approvals, ...Object.fromEntries(safe.map((c) => [c, 'auto'])) });
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
return { caps: out, ignored };
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
module.exports = {
|
|
354
|
-
LEVEL_INDEPENDENT_APPROVALS,
|
|
355
|
-
AUTO_APPROVABLE_CLASSES,
|
|
356
|
-
GATES,
|
|
357
|
-
UNLOCKS,
|
|
358
|
-
PROACTIVITY_ORDER,
|
|
359
|
-
TIERS,
|
|
360
|
-
tierFor,
|
|
361
|
-
tierIndex,
|
|
362
|
-
capabilities,
|
|
363
|
-
proactivityCeiling,
|
|
364
|
-
unlockedBy,
|
|
365
|
-
nextUnlock,
|
|
366
|
-
carveOuts,
|
|
367
|
-
assertCarveOuts,
|
|
368
|
-
verifyInvariants,
|
|
369
|
-
applyEnvOverrides,
|
|
370
|
-
};
|
|
3
|
+
"use strict";const{TIERS:TIERS,tierFor:tierFor,tierIndex:tierIndex}=require("./tiers.js"),LEVEL_INDEPENDENT_APPROVALS=Object.freeze({write:"ask",shell:"ask",network:"ask",offDevice:"ask"}),AUTO_APPROVABLE_CLASSES=Object.freeze(["read","diff"]),GATES=Object.freeze({hints:2,brief:5,ideaLog:5,autoApproveDiff:10,sessionReflection:10,voice:10,reasoningFloor:20,queueDrain:20,importAssistant:35}),UNLOCKS=Object.freeze([Object.freeze({id:"expression.core",level:1,kind:"expressions",paid:!1,label:"Core expressions"}),Object.freeze({id:"outfit.hoodie",level:1,kind:"outfit",paid:!1,label:"Hoodie"}),Object.freeze({id:"outfit.tshirt",level:1,kind:"outfit",paid:!1,label:"T-shirt"}),Object.freeze({id:"palette.amber",level:1,kind:"palette",paid:!1,label:"Amber"}),Object.freeze({id:"outfit.headphones",level:5,kind:"outfit",paid:!1,label:"Headphones"}),Object.freeze({id:"expression.trusted",level:5,kind:"expressions",paid:!1,label:"Trusted expressions"}),Object.freeze({id:"palette.slate",level:5,kind:"palette",paid:!1,label:"Slate"}),Object.freeze({id:"outfit.layers",level:5,kind:"outfit",paid:!1,label:"Outfit layers"}),Object.freeze({id:"expression.companion",level:10,kind:"expressions",paid:!1,label:"Companion expressions"}),Object.freeze({id:"voice.core",level:10,kind:"voice",paid:!1,label:"Voice (local)"}),Object.freeze({id:"expression.confidant",level:20,kind:"expressions",paid:!1,label:"Confidant expressions"}),Object.freeze({id:"outfit.seasonal",level:20,kind:"outfit",paid:!0,label:"Seasonal set"}),Object.freeze({id:"expression.archivist",level:35,kind:"expressions",paid:!1,label:"Archivist expressions"}),Object.freeze({id:"outfit.archive",level:35,kind:"outfit",paid:!0,label:"Archive set"})]),PROACTIVITY_ORDER=Object.freeze(["off","hints","brief"]);function clampLevel(e){const t=Math.floor("number"==typeof e&&Number.isFinite(e)?e:1);return Math.max(1,t)}function atLeast(e,t){return clampLevel(e)>=t}function unlockedBy(e){const t=clampLevel(e);return UNLOCKS.filter((e=>e.level<=t)).map((e=>e.id))}function nextUnlock(e){const t=clampLevel(e),i=UNLOCKS.filter((e=>e.level>t));if(!i.length)return null;const o=Math.min(...i.map((e=>e.level)));return{level:o,unlocks:i.filter((e=>e.level===o)).map((e=>({id:e.id,label:e.label,kind:e.kind,paid:e.paid})))}}function lowerProactivity(e,t){const i=PROACTIVITY_ORDER.indexOf(e),o=PROACTIVITY_ORDER.indexOf(t);return i<0?t:o<0?e:PROACTIVITY_ORDER[Math.min(i,o)]}function proactivityCeiling(e){return atLeast(e,GATES.brief)?"brief":atLeast(e,GATES.hints)?"hints":"off"}function capabilities(e,t={}){const i=clampLevel(e),o=tierFor(i),r=AUTO_APPROVABLE_CLASSES.filter((e=>"read"===e||atLeast(i,GATES.autoApproveDiff))),a=Object.freeze({read:"auto",diff:r.includes("diff")?"auto":"ask",...LEVEL_INDEPENDENT_APPROVALS}),s=void 0===t.userProactivity?proactivityCeiling(i):lowerProactivity(proactivityCeiling(i),t.userProactivity);return Object.freeze({level:i,tier:o.name,tierIndex:tierIndex(i),recallEntries:4+Math.floor(i/2),recallTokens:1500+250*i,memoryWriteback:!0,autoApproveClasses:Object.freeze(r.slice()),approvals:a,toolGrants:Object.freeze(atLeast(i,GATES.queueDrain)?["queue.drain"]:[]),modelFloor:atLeast(i,GATES.reasoningFloor)?"reasoning":null,proactivity:s,proactivityCeiling:proactivityCeiling(i),companion:Object.freeze({morningBrief:atLeast(i,GATES.brief),ideaLog:atLeast(i,GATES.ideaLog),sessionReflection:atLeast(i,GATES.sessionReflection),voice:atLeast(i,GATES.voice),queueDrain:atLeast(i,GATES.queueDrain),importAssistant:atLeast(i,GATES.importAssistant)}),cosmeticsUnlocked:Object.freeze(unlockedBy(i)),offDeviceGrants:Object.freeze([])})}function carveOuts(e){const t=[];if(!e||"object"!=typeof e)return["capabilities missing"];for(const i of["write","shell","network","offDevice"]){const o=e.approvals&&e.approvals[i];"ask"!==o&&t.push(`approval for ${i} must stay "ask", got ${JSON.stringify(o)}`)}for(const i of e.autoApproveClasses||[])AUTO_APPROVABLE_CLASSES.includes(i)||t.push(`class ${i} is not auto-approvable at any level`);for(const i of["write","shell","network","offDevice"])(e.autoApproveClasses||[]).includes(i)&&t.push(`class ${i} must never be auto-approved`);!0!==e.memoryWriteback&&t.push("memoryWriteback must stay true at every level (XP would deadlock)"),Array.isArray(e.offDeviceGrants)&&!e.offDeviceGrants.length||t.push("nothing level-gated may send data off-device");for(const i of e.toolGrants||[])/publish|post|email|upload|deploy|broadcast/i.test(String(i))&&t.push(`tool grant ${i} reaches off-device`);return t}function assertCarveOuts(e){const t=carveOuts(e);if(t.length)throw new Error(`avatar capabilities violate a carve-out: ${t.join("; ")}`);return e}function verifyInvariants(e){const t=e||Array.from({length:60},((e,t)=>t+1)),i=[];let o=null;for(const e of t){const t=capabilities(e);for(const o of carveOuts(t))i.push(`L${e}: ${o}`);if(o){const r=[["recallEntries",e=>e.recallEntries],["recallTokens",e=>e.recallTokens]];for(const[a,s]of r)s(t)<s(o)&&i.push(`L${e}: ${a} shrank from ${s(o)} to ${s(t)}`);for(const r of AUTO_APPROVABLE_CLASSES){const a=o.autoApproveClasses.includes(r),s=t.autoApproveClasses.includes(r);a&&!s&&i.push(`L${e}: auto-approve for ${r} was taken away`)}"reasoning"===o.modelFloor&&"reasoning"!==t.modelFloor&&i.push(`L${e}: reasoning model floor was taken away`),PROACTIVITY_ORDER.indexOf(t.proactivity)<PROACTIVITY_ORDER.indexOf(o.proactivity)&&i.push(`L${e}: proactivity dropped to ${t.proactivity}`)}o=t}return i}const TRUTHY=new Set(["1","true","yes","on"]);function applyEnvOverrides(e,t){const i=t||{},o=[],r=Object.assign({},e),a=e=>{const t=Number.parseInt(String(e),10);return Number.isFinite(t)&&t>0?t:null};void 0!==i.AEGIS_AVATAR_DISABLE&&TRUTHY.has(String(i.AEGIS_AVATAR_DISABLE).toLowerCase())&&(r.proactivity="off",r.modelFloor=null,r.toolGrants=Object.freeze([]));const s=a(i.AEGIS_RECALL_ENTRIES);null!==s&&(r.recallEntries=s);const n=a(i.AEGIS_RECALL_TOKENS);if(null!==n&&(r.recallTokens=n),void 0!==i.AEGIS_PROACTIVITY){const e=String(i.AEGIS_PROACTIVITY).toLowerCase();PROACTIVITY_ORDER.includes(e)?r.proactivity=lowerProactivity(r.proactivity,e):o.push(`AEGIS_PROACTIVITY=${i.AEGIS_PROACTIVITY}`)}if(void 0!==i.AEGIS_MODEL_FLOOR){const e=String(i.AEGIS_MODEL_FLOOR).toLowerCase();"reasoning"===e?r.modelFloor="reasoning":"off"===e||"none"===e?r.modelFloor=null:o.push(`AEGIS_MODEL_FLOOR=${i.AEGIS_MODEL_FLOOR}`)}if(void 0!==i.AEGIS_AUTO_APPROVE){const e=String(i.AEGIS_AUTO_APPROVE).split(",").map((e=>e.trim().toLowerCase())).filter(Boolean),t=e.filter((e=>!AUTO_APPROVABLE_CLASSES.includes(e)));t.length&&o.push(`AEGIS_AUTO_APPROVE=${t.join(",")}`);const a=e.filter((e=>AUTO_APPROVABLE_CLASSES.includes(e)));a.length&&(r.autoApproveClasses=Object.freeze(Array.from(new Set([...r.autoApproveClasses||[],...a]))),r.approvals=Object.freeze({...r.approvals,...Object.fromEntries(a.map((e=>[e,"auto"])))}))}return{caps:r,ignored:o}}module.exports={LEVEL_INDEPENDENT_APPROVALS:LEVEL_INDEPENDENT_APPROVALS,AUTO_APPROVABLE_CLASSES:AUTO_APPROVABLE_CLASSES,GATES:GATES,UNLOCKS:UNLOCKS,PROACTIVITY_ORDER:PROACTIVITY_ORDER,TIERS:TIERS,tierFor:tierFor,tierIndex:tierIndex,capabilities:capabilities,proactivityCeiling:proactivityCeiling,unlockedBy:unlockedBy,nextUnlock:nextUnlock,carveOuts:carveOuts,assertCarveOuts:assertCarveOuts,verifyInvariants:verifyInvariants,applyEnvOverrides:applyEnvOverrides};
|