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
@@ -1,382 +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
- * avatar.js — the mount: stage, HUD, pane, and the rules that keep the avatar
10
- * out of everything else's way.
11
- *
12
- * The three modules beside this one are pure or nearly so (`parts.js` is data,
13
- * `assemble.js` builds a string, `hud.js` builds a view model, `machine.js`
14
- * owns the face's state). This file is where they meet the document, and that
15
- * makes it the only place the phase's non-functional promises can be kept or
16
- * broken:
17
- *
18
- * 1. **Never blocks input.** No key listener is registered anywhere in
19
- * `renderer/avatar/`, so the avatar cannot consume a keystroke — Escape
20
- * still reaches the interrupt handler and typing still reaches the
21
- * composer, whatever the avatar is doing. Painting is coalesced to at most
22
- * one write per animation frame (`schedule()`), so a stream that emits
23
- * fifty deltas in a frame still costs one repaint, and every entry point is
24
- * wrapped: a throw inside the avatar is swallowed and reported, never
25
- * propagated into the chat path. `desktop/test/electron-smoke-main.js`
26
- * proves the two behaviours this protects (scroll-hold and interrupt) with
27
- * the avatar mounted and streaming.
28
- *
29
- * 2. **Never claims a level it was not given.** There is no arithmetic on XP
30
- * in this file. It renders whatever projection main hands it, and when main
31
- * has nothing to hand it it says `level unknown` (see hud.js).
32
- *
33
- * 3. **Accessible by construction.** The SVG is `aria-hidden` +
34
- * `focusable="false"` (the assembler sets it), the layer that matters is a
35
- * `role="status"` text readout beside it, the XP bar is `aria-hidden` and
36
- * its numbers live in that readout, and `prefers-reduced-motion` is read
37
- * from the OS and forced through `persona.appliesMotion` — the same
38
- * function the storage layer uses, so a persona cannot opt out of the
39
- * accessibility preference.
40
- *
41
- * Source resolution is deliberately forward-compatible: `window.avatar` is
42
- * used when it exists, and `preview()` exists so the
43
- * harnesses (and the customization pane, which previews edits before anything
44
- * is written) can drive the avatar without it. Both are one code path — the
45
- * preview state is an override layer over the real one, and it is labelled as
46
- * such in the readout's own vocabulary (`source`), never presented as truth.
47
- */
48
-
49
- const pkg = typeof window !== 'undefined' && window.AegisAvatarAssemble
50
- ? {
51
- parts: window.AegisAvatarParts,
52
- assemble: window.AegisAvatarAssemble.assemble,
53
- hud: window.AegisAvatarHud,
54
- machine: window.AegisAvatarMachine,
55
- pane: window.AegisAvatarPane,
56
- persona: window.AegisAvatarPersona,
57
- }
58
- : typeof module !== 'undefined' && module.exports
59
- ? {
60
- parts: require('./parts.js'),
61
- assemble: require('./assemble.js').assemble,
62
- hud: require('./hud.js'),
63
- machine: require('./machine.js'),
64
- pane: require('./pane.js'),
65
- persona: require('../../lib/avatar/persona.js'),
66
- }
67
- : null;
68
-
69
- if (!pkg) throw new Error('avatar/avatar.js: the avatar modules must be loaded first');
70
-
71
- /** How long a quiet `idle` state sits before the face dozes off. */
72
- const IDLE_TIMEOUT_MS = 5 * 60 * 1000;
73
- /** The clock the idle timeout is evaluated on. Not a frame loop — see §6. */
74
- const TICK_INTERVAL_MS = 30000;
75
-
76
- function safe(fn, fallback) {
77
- try {
78
- return fn();
79
- } catch {
80
- return fallback;
81
- }
82
- }
83
-
84
- /**
85
- * The data source. `window.avatar` is the preload bridge the host will expose
86
- * (`state()` returning `{ persona, projection, capabilities, warnings }`); when
87
- * it is missing the avatar renders the honest unknown state rather than
88
- * inventing numbers.
89
- */
90
- function resolveSource(win) {
91
- const bridge = win && win.avatar;
92
- if (bridge && typeof bridge.state === 'function') {
93
- return {
94
- kind: 'main',
95
- reason: '',
96
- state: () => Promise.resolve(bridge.state()),
97
- set: typeof bridge.set === 'function' ? (patch) => Promise.resolve(bridge.set(patch)) : null,
98
- };
99
- }
100
- return {
101
- kind: 'none',
102
- reason: 'main has no avatar surface yet',
103
- state: () => Promise.resolve(null),
104
- set: null,
105
- };
106
- }
107
-
108
- /**
109
- * Mount the avatar.
110
- *
111
- * @param {object} input
112
- * @param {object} input.doc
113
- * @param {object} input.win
114
- * @param {object} input.els element handles (see ELEMENT_IDS in app.js); any
115
- * missing handle is skipped rather than thrown on
116
- * @param {object} [input.source] override the bridge lookup (tests)
117
- * @param {object} [input.persona] starting persona
118
- * @param {object} [input.projection] starting level projection
119
- * @param {object} [input.capabilities] starting capabilities
120
- * @param {number} [input.idleTimeoutMs]
121
- */
122
- function mountAvatar(input = {}) {
123
- const doc = input.doc;
124
- const win = input.win || (typeof window !== 'undefined' ? window : null);
125
- if (!doc || typeof doc.createElement !== 'function') {
126
- throw new Error('mountAvatar: a document is required');
127
- }
128
- const els = input.els || {};
129
- const source = input.source || resolveSource(win);
130
-
131
- const signals = pkg.machine.createAvatarSignals({ idleTimeoutMs: input.idleTimeoutMs || IDLE_TIMEOUT_MS });
132
-
133
- let persona = input.persona && typeof input.persona === 'object'
134
- ? input.persona
135
- : pkg.persona
136
- ? pkg.persona.defaultPersona()
137
- : pkg.parts.defaultPersona();
138
- let projection = input.projection && typeof input.projection === 'object' ? input.projection : null;
139
- let capabilities = input.capabilities && typeof input.capabilities === 'object' ? input.capabilities : null;
140
- let reason = input.reason || source.reason || '';
141
-
142
- const overrides = { persona: null, projection: null, capabilities: null, reason: null, active: false };
143
- let reducedMotion = false;
144
- let frame = null;
145
- let destroyed = false;
146
- let lastPainting = null;
147
- let tickTimer = null;
148
-
149
- // ---- OS preference -----------------------------------------------------
150
- const media = safe(() => (win && typeof win.matchMedia === 'function'
151
- ? win.matchMedia('(prefers-reduced-motion: reduce)')
152
- : null), null);
153
- reducedMotion = Boolean(media && media.matches);
154
- if (media && typeof media.addEventListener === 'function') {
155
- safe(() => media.addEventListener('change', (event) => {
156
- reducedMotion = Boolean(event && 'matches' in event ? event.matches : media.matches);
157
- schedule();
158
- }));
159
- }
160
-
161
- // ---- the customization pane -------------------------------------------
162
- const pane = safe(() => pkg.pane.buildPane({
163
- doc,
164
- persona,
165
- capabilities,
166
- onPatch: (patch) => {
167
- const validated = safe(() => (pkg.persona ? pkg.persona.update(persona, patch).persona : persona), persona);
168
- applyPersona(validated.persona || validated, { repaint: true });
169
- return persist(patch);
170
- },
171
- }), null);
172
- if (pane && els.paneHost && typeof els.paneHost.appendChild === 'function') {
173
- els.paneHost.appendChild(pane.root);
174
- }
175
-
176
- /**
177
- * Persist a patch through the bridge. Returns the shape the pane's status
178
- * line prints, so "saved" is only ever shown for a write that really landed.
179
- */
180
- function persist(patch) {
181
- if (!source.set) return { ok: true, saved: false, reason: source.reason || 'no write surface' };
182
- return source.set(patch).then(
183
- (res) => {
184
- if (res && res.ok === false) return { ok: false, reason: res.reason || 'refused' };
185
- if (res && res.persona) applyPersona(res.persona, { repaint: false });
186
- return { ok: true, saved: true };
187
- },
188
- (err) => ({ ok: false, reason: (err && err.message) || 'write failed' })
189
- );
190
- }
191
-
192
- // ---- painting ----------------------------------------------------------
193
- function schedule() {
194
- if (destroyed) return;
195
- if (frame !== null) return;
196
- const raf = win && typeof win.requestAnimationFrame === 'function' ? win.requestAnimationFrame : null;
197
- if (raf) {
198
- frame = safe(() => raf(paint), null);
199
- if (frame !== null) return;
200
- }
201
- // No rAF (a bare test document): coalesce on the microtask queue instead, so
202
- // the "one repaint per burst" property still holds without a timer.
203
- frame = 0;
204
- Promise.resolve().then(() => {
205
- frame = null;
206
- paint();
207
- });
208
- }
209
-
210
- function effective() {
211
- return {
212
- persona: overrides.active && overrides.persona ? overrides.persona : persona,
213
- projection: overrides.active ? overrides.projection : projection,
214
- capabilities: overrides.active ? overrides.capabilities : capabilities,
215
- reason: overrides.active && overrides.reason !== null ? overrides.reason : reason,
216
- };
217
- }
218
-
219
- function paint() {
220
- if (destroyed) return;
221
- const now = effective();
222
- const assembled = safe(
223
- () => pkg.assemble(now.persona, { expression: signals.expression, reducedMotion }),
224
- null
225
- );
226
- if (!assembled) {
227
- // A manifest that cannot assemble must not leave a stale face claiming to
228
- // be current: blank the stage and say so in the readout.
229
- if (els.stage) els.stage.innerHTML = '';
230
- } else if (els.stage) {
231
- // Generated markup only: ids, geometry and colour roles from the manifest.
232
- // No persona text ever reaches this string (see assemble.js rule 2), so
233
- // there is nothing here for a persona to inject through.
234
- els.stage.innerHTML = assembled.svg;
235
- }
236
-
237
- const model = pkg.hud.hudModel({
238
- persona: now.persona,
239
- projection: now.projection,
240
- capabilities: now.capabilities,
241
- expression: signals.expression,
242
- expressionLabel: signals.label,
243
- reason: now.reason,
244
- });
245
- safe(() => pkg.hud.renderHud(els, model));
246
- if (pane && typeof pane.update === 'function') safe(() => pane.update(now.persona));
247
- lastPainting = {
248
- expression: assembled ? assembled.expression : null,
249
- motion: assembled ? assembled.motion : null,
250
- warnings: assembled ? assembled.warnings.slice() : ['assemble failed'],
251
- missing: assembled ? assembled.missing.slice() : [],
252
- hud: model,
253
- source: overrides.active ? 'preview' : source.kind,
254
- };
255
- return lastPainting;
256
- }
257
-
258
- function applyPersona(next, opts = {}) {
259
- if (!next || typeof next !== 'object') return persona;
260
- const validated = safe(() => (pkg.persona ? pkg.persona.validate(next).persona : next), next);
261
- persona = validated && validated.persona ? validated.persona : validated;
262
- if (opts.repaint !== false) schedule();
263
- return persona;
264
- }
265
-
266
- // ---- reads from main ---------------------------------------------------
267
- function refresh() {
268
- if (destroyed) return Promise.resolve(null);
269
- return Promise.resolve(safe(() => source.state(), null)).then(
270
- (state) => {
271
- if (destroyed || !state || typeof state !== 'object') {
272
- if (!state && !reason) reason = source.reason || 'main has not published a level yet';
273
- schedule();
274
- return null;
275
- }
276
- if (state.persona) applyPersona(state.persona, { repaint: false });
277
- if (state.projection && typeof state.projection === 'object') projection = state.projection;
278
- else if (typeof state.level === 'number') projection = state;
279
- if (state.capabilities && typeof state.capabilities === 'object') capabilities = state.capabilities;
280
- reason = '';
281
- schedule();
282
- return state;
283
- },
284
- (err) => {
285
- reason = `level unavailable — ${(err && err.message) || 'read failed'}`;
286
- schedule();
287
- return null;
288
- }
289
- );
290
- }
291
-
292
- // ---- the face follows the engine ---------------------------------------
293
- function signal(name, meta) {
294
- const out = safe(() => signals.signal(name, meta), null);
295
- if (out) schedule();
296
- return out;
297
- }
298
-
299
- const api = {
300
- get expression() {
301
- return signals.expression;
302
- },
303
- get state() {
304
- return signals.state;
305
- },
306
- get persona() {
307
- return persona;
308
- },
309
- get painting() {
310
- return lastPainting;
311
- },
312
- signal,
313
- turnStart: (meta) => signal('turn.start', meta),
314
- stream: () => signal('turn.stream'),
315
- turnEnd: (meta) => signal('turn.end', meta),
316
- tool: (tool) => signal(pkg.machine.signalForTool(tool), { tool: pkg.machine.toolName(tool) }),
317
- approvalCard: () => signal('approval.card'),
318
- approvalDecided: () => signal('approval.decided'),
319
- queue: () => signal('queue.progress'),
320
- syncOk: () => signal('sync.ok'),
321
- syncError: (meta) => signal('sync.error', meta),
322
- poke: () => signal('poke'),
323
- celebrateDone: () => signal('celebrate.done'),
324
- reset: () => signal('reset'),
325
- tick: (now) => {
326
- const out = safe(() => signals.tick(now), null);
327
- if (out && out.changed) schedule();
328
- return out;
329
- },
330
- on: (fn) => signals.on(fn),
331
- setPersona: (next) => applyPersona(next),
332
- /** Ask main for a fresh projection (a turn that saved memory moved XP). */
333
- refresh,
334
- /**
335
- * The preview seam. Harnesses (the Electron smoke leg and the marketing
336
- * shot driver) and the pane use it to render a persona/level this process
337
- * was not handed by main, and `painting.source` reports it as `preview` so
338
- * the evidence can never read as if main had published it.
339
- */
340
- preview(next = {}) {
341
- overrides.active = true;
342
- if (next.persona) overrides.persona = next.persona;
343
- if ('projection' in next) overrides.projection = next.projection;
344
- if ('capabilities' in next) overrides.capabilities = next.capabilities;
345
- if ('reason' in next) overrides.reason = next.reason === undefined ? null : next.reason;
346
- schedule();
347
- return lastPainting;
348
- },
349
- clearPreview() {
350
- overrides.active = false;
351
- schedule();
352
- return lastPainting;
353
- },
354
- /** The readout text, for harnesses that assert on what a user would read. */
355
- readout() {
356
- return els.readout ? String(els.readout.textContent || '') : '';
357
- },
358
- destroy() {
359
- destroyed = true;
360
- if (tickTimer !== null) safe(() => win.clearInterval(tickTimer));
361
- tickTimer = null;
362
- if (pane && typeof pane.destroy === 'function') safe(() => pane.destroy());
363
- if (els.stage) els.stage.innerHTML = '';
364
- },
365
- };
366
-
367
- // ---- the idle clock ----------------------------------------------------
368
- // One 30s interval for the doze timeout — not a frame loop. §6 of the plan
369
- // spells out why: "pure SVG, no WebGL, no per-frame work". The interval only
370
- // evaluates a comparison; it paints only when the machine actually changes.
371
- if (win && typeof win.setInterval === 'function') {
372
- tickTimer = safe(() => win.setInterval(() => api.tick(Date.now()), TICK_INTERVAL_MS), null);
373
- }
374
-
375
- schedule();
376
- return api;
377
- }
378
-
379
- const API = Object.freeze({ mountAvatar, resolveSource, IDLE_TIMEOUT_MS, TICK_INTERVAL_MS });
380
-
381
- if (typeof module !== 'undefined' && module.exports) module.exports = API;
382
- else if (typeof globalThis !== 'undefined') globalThis.AegisAvatar = API;
3
+ "use strict";const pkg="undefined"!=typeof window&&window.AegisAvatarAssemble?{parts:window.AegisAvatarParts,assemble:window.AegisAvatarAssemble.assemble,hud:window.AegisAvatarHud,machine:window.AegisAvatarMachine,pane:window.AegisAvatarPane,persona:window.AegisAvatarPersona}:"undefined"!=typeof module&&module.exports?{parts:require("./parts.js"),assemble:require("./assemble.js").assemble,hud:require("./hud.js"),machine:require("./machine.js"),pane:require("./pane.js"),persona:require("../../lib/avatar/persona.js")}:null;if(!pkg)throw new Error("avatar/avatar.js: the avatar modules must be loaded first");const IDLE_TIMEOUT_MS=3e5,TICK_INTERVAL_MS=3e4;function safe(e,n){try{return e()}catch{return n}}function resolveSource(e){const n=e&&e.avatar;return n&&"function"==typeof n.state?{kind:"main",reason:"",state:()=>Promise.resolve(n.state()),set:"function"==typeof n.set?e=>Promise.resolve(n.set(e)):null}:{kind:"none",reason:"main has no avatar surface yet",state:()=>Promise.resolve(null),set:null}}function mountAvatar(e={}){const n=e.doc,a=e.win||("undefined"!=typeof window?window:null);if(!n||"function"!=typeof n.createElement)throw new Error("mountAvatar: a document is required");const t=e.els||{},o=e.source||resolveSource(a),r=pkg.machine.createAvatarSignals({idleTimeoutMs:e.idleTimeoutMs||3e5});let s=e.persona&&"object"==typeof e.persona?e.persona:pkg.persona?pkg.persona.defaultPersona():pkg.parts.defaultPersona(),i=e.projection&&"object"==typeof e.projection?e.projection:null,l=e.capabilities&&"object"==typeof e.capabilities?e.capabilities:null,p=e.reason||o.reason||"";const u={persona:null,projection:null,capabilities:null,reason:null,active:!1};let c=!1,d=null,f=!1,m=null,v=null;const g=safe((()=>a&&"function"==typeof a.matchMedia?a.matchMedia("(prefers-reduced-motion: reduce)"):null),null);c=Boolean(g&&g.matches),g&&"function"==typeof g.addEventListener&&safe((()=>g.addEventListener("change",(e=>{c=Boolean(e&&"matches"in e?e.matches:g.matches),h()}))));const b=safe((()=>pkg.pane.buildPane({doc:n,persona:s,capabilities:l,onPatch:e=>{const n=safe((()=>pkg.persona?pkg.persona.update(s,e).persona:s),s);return y(n.persona||n,{repaint:!0}),function(e){return o.set?o.set(e).then((e=>e&&!1===e.ok?{ok:!1,reason:e.reason||"refused"}:(e&&e.persona&&y(e.persona,{repaint:!1}),{ok:!0,saved:!0})),(e=>({ok:!1,reason:e&&e.message||"write failed"}))):{ok:!0,saved:!1,reason:o.reason||"no write surface"}}(e)}})),null);function h(){if(f)return;if(null!==d)return;const e=a&&"function"==typeof a.requestAnimationFrame?a.requestAnimationFrame:null;e&&(d=safe((()=>e(w)),null),null!==d)||(d=0,Promise.resolve().then((()=>{d=null,w()})))}function w(){if(f)return;const e={persona:u.active&&u.persona?u.persona:s,projection:u.active?u.projection:i,capabilities:u.active?u.capabilities:l,reason:u.active&&null!==u.reason?u.reason:p},n=safe((()=>pkg.assemble(e.persona,{expression:r.expression,reducedMotion:c})),null);n?t.stage&&(t.stage.innerHTML=n.svg):t.stage&&(t.stage.innerHTML="");const a=pkg.hud.hudModel({persona:e.persona,projection:e.projection,capabilities:e.capabilities,expression:r.expression,expressionLabel:r.label,reason:e.reason});return safe((()=>pkg.hud.renderHud(t,a))),b&&"function"==typeof b.update&&safe((()=>b.update(e.persona))),m={expression:n?n.expression:null,motion:n?n.motion:null,warnings:n?n.warnings.slice():["assemble failed"],missing:n?n.missing.slice():[],hud:a,source:u.active?"preview":o.kind},m}function y(e,n={}){if(!e||"object"!=typeof e)return s;const a=safe((()=>pkg.persona?pkg.persona.validate(e).persona:e),e);return s=a&&a.persona?a.persona:a,!1!==n.repaint&&h(),s}function k(e,n){const a=safe((()=>r.signal(e,n)),null);return a&&h(),a}b&&t.paneHost&&"function"==typeof t.paneHost.appendChild&&t.paneHost.appendChild(b.root);const A={get expression(){return r.expression},get state(){return r.state},get persona(){return s},get painting(){return m},signal:k,turnStart:e=>k("turn.start",e),stream:()=>k("turn.stream"),turnEnd:e=>k("turn.end",e),tool:e=>k(pkg.machine.signalForTool(e),{tool:pkg.machine.toolName(e)}),approvalCard:()=>k("approval.card"),approvalDecided:()=>k("approval.decided"),queue:()=>k("queue.progress"),syncOk:()=>k("sync.ok"),syncError:e=>k("sync.error",e),poke:()=>k("poke"),celebrateDone:()=>k("celebrate.done"),reset:()=>k("reset"),tick:e=>{const n=safe((()=>r.tick(e)),null);return n&&n.changed&&h(),n},on:e=>r.on(e),setPersona:e=>y(e),refresh:function(){return f?Promise.resolve(null):Promise.resolve(safe((()=>o.state()),null)).then((e=>f||!e||"object"!=typeof e?(e||p||(p=o.reason||"main has not published a level yet"),h(),null):(e.persona&&y(e.persona,{repaint:!1}),e.projection&&"object"==typeof e.projection?i=e.projection:"number"==typeof e.level&&(i=e),e.capabilities&&"object"==typeof e.capabilities&&(l=e.capabilities),p="",h(),e)),(e=>(p=`level unavailable — ${e&&e.message||"read failed"}`,h(),null)))},preview:(e={})=>(u.active=!0,e.persona&&(u.persona=e.persona),"projection"in e&&(u.projection=e.projection),"capabilities"in e&&(u.capabilities=e.capabilities),"reason"in e&&(u.reason=void 0===e.reason?null:e.reason),h(),m),clearPreview:()=>(u.active=!1,h(),m),readout:()=>t.readout?String(t.readout.textContent||""):"",destroy(){f=!0,null!==v&&safe((()=>a.clearInterval(v))),v=null,b&&"function"==typeof b.destroy&&safe((()=>b.destroy())),t.stage&&(t.stage.innerHTML="")}};return a&&"function"==typeof a.setInterval&&(v=safe((()=>a.setInterval((()=>A.tick(Date.now())),3e4)),null)),h(),A}const API=Object.freeze({mountAvatar:mountAvatar,resolveSource:resolveSource,IDLE_TIMEOUT_MS:3e5,TICK_INTERVAL_MS:3e4});"undefined"!=typeof module&&module.exports?module.exports=API:"undefined"!=typeof globalThis&&(globalThis.AegisAvatar=API);
@@ -1,143 +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
- * cards.js — the companion card, rendered in the shape of the tool approval
10
- * card.
11
- *
12
- * Two things make that more than a styling choice:
13
- *
14
- * 1. **It is genuinely the same card.** The class names, the action-row
15
- * structure, the disable-on-first-click behaviour and the "what just
16
- * happened" line are the approval card's, not a lookalike: a companion
17
- * proposal and a tool request read as the same kind of event, because they
18
- * are — an offer that does nothing until the user says yes.
19
- * 2. **It presumes nothing.** The decision is sent to `onDecision(cardId,
20
- * 'accept'|'dismiss')` and the card is dead afterwards. There is no
21
- * "auto-accept after N seconds", no default focus that a stray Enter could
22
- * resolve, and no path from rendering to acting: main's `dispatch()`
23
- * (`lib/avatar/companion.js`) still refuses anything without the ticket the
24
- * accept click produced. Rendering a card cannot cause an effect.
25
- *
26
- * Dependency-free and DOM-injectable (`doc`), so a test drives it without
27
- * jsdom, exactly like `renderer/diffview.js` and `renderer/avatar/assemble.js`.
28
- */
29
-
30
- const COMPANION_CARD_CLASS = 'companion-card';
31
- const APPROVAL_CARD_CLASS = 'approval-card';
32
-
33
- function textEl(doc, className, text) {
34
- const el = doc.createElement('div');
35
- el.className = className;
36
- el.textContent = text;
37
- return el;
38
- }
39
-
40
- /**
41
- * Build one companion card element.
42
- *
43
- * @param {Document} doc the document (injected: this module touches nothing global)
44
- * @param {object} card a card from `companion.createSession().consider(...)`
45
- * — `{id, title, body, facts, actions, behaviour}`
46
- * @param {(cardId: string, decision: 'accept'|'dismiss') => void} onDecision
47
- * @returns {HTMLElement|null} null for a malformed card (never throws)
48
- */
49
- function buildCompanionCard(doc, card, onDecision) {
50
- if (!doc || !card || !card.id) return null;
51
-
52
- const el = doc.createElement('div');
53
- // Both classes: `.approval-card` for the shared look, `.companion-card` so a
54
- // theme or a test can tell a proposal from a tool gate.
55
- el.className = `${APPROVAL_CARD_CLASS} ${COMPANION_CARD_CLASS}`;
56
- el.dataset.companionId = String(card.id);
57
- if (card.behaviour) el.dataset.companionBehaviour = String(card.behaviour);
58
- el.dataset.proactive = card.proactive ? 'true' : 'false';
59
-
60
- el.appendChild(textEl(doc, 'approval-title', String(card.title || card.label || 'Suggestion')));
61
-
62
- if (card.body) el.appendChild(textEl(doc, 'approval-summary', String(card.body)));
63
-
64
- const facts = Array.isArray(card.facts) ? card.facts.filter(Boolean) : [];
65
- if (facts.length) {
66
- const list = doc.createElement('ul');
67
- list.className = 'companion-facts';
68
- for (const fact of facts.slice(0, 6)) {
69
- const li = doc.createElement('li');
70
- li.textContent = String(fact);
71
- list.appendChild(li);
72
- }
73
- el.appendChild(list);
74
- }
75
-
76
- const actions = doc.createElement('div');
77
- actions.className = 'approval-actions';
78
-
79
- // Every button this card made, so resolving it can disable exactly those —
80
- // no `querySelectorAll` on the container (a card is self-contained).
81
- const buttons = [];
82
- let resolved = false;
83
-
84
- // No decision is sent until a click. Anything else (Enter on a focused card,
85
- // a timer, a programmatic "accept") is not a path this card offers.
86
- //
87
- // The `resolved` flag is belt-and-braces alongside `disabled`: a browser will
88
- // not fire a click on a disabled button, but the approval card's promise is
89
- // that one card produces at most one decision, and that must not depend on
90
- // the browser's event dispatch. main's `dispatch()` refuses the replayed
91
- // ticket anyway — three independent layers, none of them trusted alone.
92
- const decide = (action) => {
93
- if (resolved) return;
94
- resolved = true;
95
- for (const btn of buttons) btn.disabled = true;
96
- el.classList.add('resolved');
97
- const tag = textEl(doc, 'approval-decision', String(action.doneLabel || (action.decision === 'accept' ? 'Accepted' : 'Not now')));
98
- el.appendChild(tag);
99
- if (typeof onDecision === 'function') onDecision(String(card.id), action.decision);
100
- };
101
-
102
- for (const action of Array.isArray(card.actions) ? card.actions : []) {
103
- const btn = doc.createElement('button');
104
- btn.type = 'button';
105
- btn.className = `approval-btn ${action.decision === 'accept' ? 'allow' : 'deny'}`;
106
- btn.textContent = String(action.label || action.decision);
107
- btn.dataset.decision = String(action.decision);
108
- btn.addEventListener('click', () => decide(action));
109
- buttons.push(btn);
110
- actions.appendChild(btn);
111
- }
112
-
113
- if (!buttons.length) {
114
- // A card with no actions is a bug in the caller, not something to render
115
- // as a dead end.
116
- return null;
117
- }
118
-
119
- el.appendChild(actions);
120
- return el;
121
- }
122
-
123
- /**
124
- * Mount a card into `container`, reusing the approval card's `.approval-slot`
125
- * so the two can never interleave in the transcript.
126
- */
127
- function mountCompanionCard(doc, container, card, onDecision) {
128
- if (!container || typeof container.appendChild !== 'function') return null;
129
- const el = buildCompanionCard(doc, card, onDecision);
130
- if (!el) return null;
131
- let slot = typeof container.querySelector === 'function' ? container.querySelector('.approval-slot') : null;
132
- if (!slot) {
133
- slot = doc.createElement('div');
134
- slot.className = 'approval-slot';
135
- container.appendChild(slot);
136
- }
137
- slot.appendChild(el);
138
- return el;
139
- }
140
-
141
- if (typeof module !== 'undefined' && module.exports) {
142
- module.exports = { buildCompanionCard, mountCompanionCard, COMPANION_CARD_CLASS, APPROVAL_CARD_CLASS };
143
- }
3
+ "use strict";const COMPANION_CARD_CLASS="companion-card",APPROVAL_CARD_CLASS="approval-card";function textEl(t,e,n){const o=t.createElement("div");return o.className=e,o.textContent=n,o}function buildCompanionCard(t,e,n){if(!t||!e||!e.id)return null;const o=t.createElement("div");o.className=`${APPROVAL_CARD_CLASS} ${COMPANION_CARD_CLASS}`,o.dataset.companionId=String(e.id),e.behaviour&&(o.dataset.companionBehaviour=String(e.behaviour)),o.dataset.proactive=e.proactive?"true":"false",o.appendChild(textEl(t,"approval-title",String(e.title||e.label||"Suggestion"))),e.body&&o.appendChild(textEl(t,"approval-summary",String(e.body)));const a=Array.isArray(e.facts)?e.facts.filter(Boolean):[];if(a.length){const e=t.createElement("ul");e.className="companion-facts";for(const n of a.slice(0,6)){const o=t.createElement("li");o.textContent=String(n),e.appendChild(o)}o.appendChild(e)}const i=t.createElement("div");i.className="approval-actions";const l=[];let r=!1;const d=a=>{if(r)return;r=!0;for(const t of l)t.disabled=!0;o.classList.add("resolved");const i=textEl(t,"approval-decision",String(a.doneLabel||("accept"===a.decision?"Accepted":"Not now")));o.appendChild(i),"function"==typeof n&&n(String(e.id),a.decision)};for(const n of Array.isArray(e.actions)?e.actions:[]){const e=t.createElement("button");e.type="button",e.className="approval-btn "+("accept"===n.decision?"allow":"deny"),e.textContent=String(n.label||n.decision),e.dataset.decision=String(n.decision),e.addEventListener("click",(()=>d(n))),l.push(e),i.appendChild(e)}return l.length?(o.appendChild(i),o):null}function mountCompanionCard(t,e,n,o){if(!e||"function"!=typeof e.appendChild)return null;const a=buildCompanionCard(t,n,o);if(!a)return null;let i="function"==typeof e.querySelector?e.querySelector(".approval-slot"):null;return i||(i=t.createElement("div"),i.className="approval-slot",e.appendChild(i)),i.appendChild(a),a}"undefined"!=typeof module&&module.exports&&(module.exports={buildCompanionCard:buildCompanionCard,mountCompanionCard:mountCompanionCard,COMPANION_CARD_CLASS:COMPANION_CARD_CLASS,APPROVAL_CARD_CLASS:APPROVAL_CARD_CLASS});