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/renderer/avatar/hud.js
CHANGED
|
@@ -1,208 +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
|
-
* hud.js — the level HUD: XP bar, next unlock, and the honest held state.
|
|
10
|
-
*
|
|
11
|
-
* The HUD is a *renderer*: it draws a projection main handed it and derives
|
|
12
|
-
* nothing. That is not a stylistic preference — §1.5 of the plan says a level is
|
|
13
|
-
* written by main because "a renderer-held counter is a suggestion, not a fact",
|
|
14
|
-
* so `hudModel` has no branch that turns XP into a level. It reads `level`,
|
|
15
|
-
* `progress`, `tier` and `held` off the projection, and when there is no
|
|
16
|
-
* projection it says exactly that:
|
|
17
|
-
*
|
|
18
|
-
* known: false, percent: null, readout: "level unknown — …"
|
|
19
|
-
*
|
|
20
|
-
* The tempting alternative — treat a missing projection as `level 1, 0 XP` —
|
|
21
|
-
* would be a fabricated number on a screen about the user's own progress, and on
|
|
22
|
-
* a real install (main landed, level 12) it would be a lie, not a default. The
|
|
23
|
-
* unknown state costs a line of text and cannot be wrong.
|
|
24
|
-
*
|
|
25
|
-
* The three honesty rules the tests hold it to:
|
|
26
|
-
*
|
|
27
|
-
* 1. **Unknown is not zero.** No projection ⇒ no percentage, no bar fill, and
|
|
28
|
-
* a readout that says so.
|
|
29
|
-
* 2. **Held is labelled.** §1.4: a level never silently regresses while the
|
|
30
|
-
* user watches. When `projection.held` is set the HUD says
|
|
31
|
-
* `level held · reshuffling` and marks the bar, and the next-unlock line
|
|
32
|
-
* says it is waiting for the ledger to settle — because unlocks follow the
|
|
33
|
-
* *settled* level, and claiming a unlock the ledger does not support is the
|
|
34
|
-
* same class of lie as a level that ticks down unannounced.
|
|
35
|
-
* 3. **Every number has a text twin.** The SVG is `aria-hidden`, so the
|
|
36
|
-
* readout below it must carry the whole state as a sentence.
|
|
37
|
-
*
|
|
38
|
-
* Pure (a model) plus one small DOM writer. No timers, no storage.
|
|
39
|
-
*/
|
|
40
|
-
|
|
41
|
-
const pkg = typeof window !== 'undefined' && window.AegisAvatarParts
|
|
42
|
-
? {
|
|
43
|
-
parts: window.AegisAvatarParts,
|
|
44
|
-
level: window.AegisAvatarLevel || null,
|
|
45
|
-
}
|
|
46
|
-
: typeof module !== 'undefined' && module.exports
|
|
47
|
-
? {
|
|
48
|
-
parts: require('./parts.js'),
|
|
49
|
-
level: require('../../lib/avatar/level.js'),
|
|
50
|
-
}
|
|
51
|
-
: { parts: null, level: null };
|
|
52
|
-
|
|
53
|
-
if (!pkg.parts) throw new Error('avatar/hud.js: avatar/parts.js must be loaded first');
|
|
54
|
-
|
|
55
|
-
function num(v, fallback) {
|
|
56
|
-
return typeof v === 'number' && Number.isFinite(v) ? v : fallback;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** `Headphones`, `Seasonal set · pack` — a paid cosmetic says so on the tin. */
|
|
60
|
-
function unlockText(unlock) {
|
|
61
|
-
return unlock.unlocks
|
|
62
|
-
.map((u) => (u.paid ? `${u.label} · pack` : u.label))
|
|
63
|
-
.join(', ');
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Build the view model.
|
|
68
|
-
*
|
|
69
|
-
* @param {object} input
|
|
70
|
-
* @param {object} [input.persona] validated persona (for the name)
|
|
71
|
-
* @param {object} [input.projection] `xp.evaluate()` output, as main reports it
|
|
72
|
-
* @param {object} [input.capabilities] `level.capabilities()` output, for the
|
|
73
|
-
* cosmetics gate (which packs the pane may offer)
|
|
74
|
-
* @param {string} [input.expression] current expression id
|
|
75
|
-
* @param {string} [input.expressionLabel] its human label
|
|
76
|
-
* @param {string} [input.reason] why there is no projection, shown verbatim
|
|
77
|
-
*/
|
|
78
|
-
function hudModel(input = {}) {
|
|
79
|
-
const persona = input.persona && typeof input.persona === 'object' ? input.persona : pkg.parts.defaultPersona();
|
|
80
|
-
const name = (persona.identity && persona.identity.name) || 'Aegis';
|
|
81
|
-
const projection = input.projection && typeof input.projection === 'object' ? input.projection : null;
|
|
82
|
-
const levelNo = projection ? num(projection.level, null) : null;
|
|
83
|
-
const known = levelNo !== null;
|
|
84
|
-
|
|
85
|
-
const expressionLabel = String(input.expressionLabel || input.expression || 'neutral');
|
|
86
|
-
const expressionNote = `expression: ${expressionLabel}`;
|
|
87
|
-
|
|
88
|
-
if (!known) {
|
|
89
|
-
const reason = String(input.reason || 'main has not published a level yet');
|
|
90
|
-
return {
|
|
91
|
-
known: false,
|
|
92
|
-
name,
|
|
93
|
-
level: null,
|
|
94
|
-
tier: null,
|
|
95
|
-
xpIntoLevel: null,
|
|
96
|
-
xpForNext: null,
|
|
97
|
-
percent: null,
|
|
98
|
-
held: false,
|
|
99
|
-
heldNote: '',
|
|
100
|
-
nextUnlock: null,
|
|
101
|
-
nextUnlockText: '',
|
|
102
|
-
unlockNote: '',
|
|
103
|
-
expression: input.expression || 'neutral',
|
|
104
|
-
expressionLabel,
|
|
105
|
-
readout: `${name} · level unknown — ${reason} · ${expressionNote}`,
|
|
106
|
-
ariaLive: 'polite',
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const held = projection.held === true;
|
|
111
|
-
const settled = num(projection.settledLevel, levelNo);
|
|
112
|
-
const tier = projection.tier || call(() => pkg.level.tierFor(levelNo).name, null) || null;
|
|
113
|
-
const xpIntoLevel = num(projection.xpIntoLevel, null);
|
|
114
|
-
const xpForNext = num(projection.xpForNext, null);
|
|
115
|
-
// The bar shows the projection's own progress — never a number this file
|
|
116
|
-
// computed. Clamped only so a malformed projection cannot draw outside the
|
|
117
|
-
// track; the held case is labelled in the readout instead of fudged here.
|
|
118
|
-
const percent = Math.round(Math.min(1, Math.max(0, num(projection.progress, 0))) * 100);
|
|
119
|
-
|
|
120
|
-
// Unlocks follow the settled level, never the held one.
|
|
121
|
-
const unlockLevel = held ? settled : levelNo;
|
|
122
|
-
let next = null;
|
|
123
|
-
try {
|
|
124
|
-
next = pkg.level ? pkg.level.nextUnlock(unlockLevel) : null;
|
|
125
|
-
} catch {
|
|
126
|
-
next = null;
|
|
127
|
-
}
|
|
128
|
-
const nextUnlockText = next ? `next at level ${next.level}: ${unlockText(next)}` : 'every unlock is yours';
|
|
129
|
-
|
|
130
|
-
const parts = [];
|
|
131
|
-
parts.push(`${name} · level ${levelNo}`);
|
|
132
|
-
if (tier) parts.push(tier);
|
|
133
|
-
if (held) parts.push('level held · reshuffling');
|
|
134
|
-
if (!held && xpForNext !== null && xpIntoLevel !== null) {
|
|
135
|
-
parts.push(`${xpIntoLevel} / ${xpForNext} XP to level ${levelNo + 1}`);
|
|
136
|
-
}
|
|
137
|
-
parts.push(nextUnlockText);
|
|
138
|
-
parts.push(expressionNote);
|
|
139
|
-
|
|
140
|
-
return {
|
|
141
|
-
known: true,
|
|
142
|
-
name,
|
|
143
|
-
level: levelNo,
|
|
144
|
-
tier,
|
|
145
|
-
xpIntoLevel,
|
|
146
|
-
xpForNext,
|
|
147
|
-
percent,
|
|
148
|
-
held,
|
|
149
|
-
heldNote: held ? 'level held · reshuffling' : '',
|
|
150
|
-
nextUnlock: next,
|
|
151
|
-
nextUnlockText,
|
|
152
|
-
// The held state makes the unlock line provisional; say so where it is read.
|
|
153
|
-
unlockNote: held ? `held — applies after the ledger settles (level ${settled})` : '',
|
|
154
|
-
expression: input.expression || 'neutral',
|
|
155
|
-
expressionLabel,
|
|
156
|
-
readout: parts.join(' · '),
|
|
157
|
-
ariaLive: 'polite',
|
|
158
|
-
cosmetics: input.capabilities && Array.isArray(input.capabilities.cosmeticsUnlocked)
|
|
159
|
-
? input.capabilities.cosmeticsUnlocked.slice()
|
|
160
|
-
: null,
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function call(fn, fallback) {
|
|
165
|
-
try {
|
|
166
|
-
return fn();
|
|
167
|
-
} catch {
|
|
168
|
-
return fallback;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/** Write a model into DOM handles. Missing handles are skipped, never thrown on. */
|
|
173
|
-
function renderHud(els, model) {
|
|
174
|
-
const e = els || {};
|
|
175
|
-
const set = (node, value) => {
|
|
176
|
-
if (node && typeof value === 'string') node.textContent = value;
|
|
177
|
-
};
|
|
178
|
-
const width = model.percent === null ? '' : `${model.percent}%`;
|
|
179
|
-
if (e.root) {
|
|
180
|
-
e.root.setAttribute('data-known', model.known ? 'true' : 'false');
|
|
181
|
-
e.root.setAttribute('data-held', model.held ? 'true' : 'false');
|
|
182
|
-
}
|
|
183
|
-
set(e.level, model.known ? `L${model.level}` : 'L—');
|
|
184
|
-
set(e.tier, model.tier || '—');
|
|
185
|
-
set(e.readout, model.readout);
|
|
186
|
-
set(e.nextUnlock, model.nextUnlockText);
|
|
187
|
-
set(e.unlockNote, model.unlockNote);
|
|
188
|
-
set(e.expression, model.expressionLabel);
|
|
189
|
-
if (e.fill) {
|
|
190
|
-
e.fill.style.width = width;
|
|
191
|
-
if (e.fill.setAttribute) e.fill.setAttribute('data-held', model.held ? 'true' : 'false');
|
|
192
|
-
}
|
|
193
|
-
if (e.bar) {
|
|
194
|
-
// The bar is decorative: its text twin is the readout, so it is hidden from
|
|
195
|
-
// assistive tech rather than announced twice with different words.
|
|
196
|
-
e.bar.setAttribute('aria-hidden', 'true');
|
|
197
|
-
}
|
|
198
|
-
if (e.heldBadge) {
|
|
199
|
-
e.heldBadge.textContent = model.heldNote;
|
|
200
|
-
e.heldBadge.hidden = !model.held;
|
|
201
|
-
}
|
|
202
|
-
return model;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const API = Object.freeze({ hudModel, renderHud, unlockText });
|
|
206
|
-
|
|
207
|
-
if (typeof module !== 'undefined' && module.exports) module.exports = API;
|
|
208
|
-
else if (typeof globalThis !== 'undefined') globalThis.AegisAvatarHud = API;
|
|
3
|
+
"use strict";const pkg="undefined"!=typeof window&&window.AegisAvatarParts?{parts:window.AegisAvatarParts,level:window.AegisAvatarLevel||null}:"undefined"!=typeof module&&module.exports?{parts:require("./parts.js"),level:require("../../lib/avatar/level.js")}:{parts:null,level:null};if(!pkg.parts)throw new Error("avatar/hud.js: avatar/parts.js must be loaded first");function num(e,l){return"number"==typeof e&&Number.isFinite(e)?e:l}function unlockText(e){return e.unlocks.map((e=>e.paid?`${e.label} · pack`:e.label)).join(", ")}function hudModel(e={}){const l=e.persona&&"object"==typeof e.persona?e.persona:pkg.parts.defaultPersona(),t=l.identity&&l.identity.name||"Aegis",n=e.projection&&"object"==typeof e.projection?e.projection:null,o=n?num(n.level,null):null,r=null!==o,u=String(e.expressionLabel||e.expression||"neutral"),s=`expression: ${u}`;if(!r){const l=String(e.reason||"main has not published a level yet");return{known:!1,name:t,level:null,tier:null,xpIntoLevel:null,xpForNext:null,percent:null,held:!1,heldNote:"",nextUnlock:null,nextUnlockText:"",unlockNote:"",expression:e.expression||"neutral",expressionLabel:u,readout:`${t} · level unknown — ${l} · ${s}`,ariaLive:"polite"}}const i=!0===n.held,a=num(n.settledLevel,o),d=n.tier||call((()=>pkg.level.tierFor(o).name),null)||null,p=num(n.xpIntoLevel,null),c=num(n.xpForNext,null),h=Math.round(100*Math.min(1,Math.max(0,num(n.progress,0)))),x=i?a:o;let v=null;try{v=pkg.level?pkg.level.nextUnlock(x):null}catch{v=null}const f=v?`next at level ${v.level}: ${unlockText(v)}`:"every unlock is yours",k=[];return k.push(`${t} · level ${o}`),d&&k.push(d),i&&k.push("level held · reshuffling"),i||null===c||null===p||k.push(`${p} / ${c} XP to level ${o+1}`),k.push(f),k.push(s),{known:!0,name:t,level:o,tier:d,xpIntoLevel:p,xpForNext:c,percent:h,held:i,heldNote:i?"level held · reshuffling":"",nextUnlock:v,nextUnlockText:f,unlockNote:i?`held — applies after the ledger settles (level ${a})`:"",expression:e.expression||"neutral",expressionLabel:u,readout:k.join(" · "),ariaLive:"polite",cosmetics:e.capabilities&&Array.isArray(e.capabilities.cosmeticsUnlocked)?e.capabilities.cosmeticsUnlocked.slice():null}}function call(e,l){try{return e()}catch{return l}}function renderHud(e,l){const t=e||{},n=(e,l)=>{e&&"string"==typeof l&&(e.textContent=l)},o=null===l.percent?"":`${l.percent}%`;return t.root&&(t.root.setAttribute("data-known",l.known?"true":"false"),t.root.setAttribute("data-held",l.held?"true":"false")),n(t.level,l.known?`L${l.level}`:"L—"),n(t.tier,l.tier||"—"),n(t.readout,l.readout),n(t.nextUnlock,l.nextUnlockText),n(t.unlockNote,l.unlockNote),n(t.expression,l.expressionLabel),t.fill&&(t.fill.style.width=o,t.fill.setAttribute&&t.fill.setAttribute("data-held",l.held?"true":"false")),t.bar&&t.bar.setAttribute("aria-hidden","true"),t.heldBadge&&(t.heldBadge.textContent=l.heldNote,t.heldBadge.hidden=!l.held),l}const API=Object.freeze({hudModel:hudModel,renderHud:renderHud,unlockText:unlockText});"undefined"!=typeof module&&module.exports?module.exports=API:"undefined"!=typeof globalThis&&(globalThis.AegisAvatarHud=API);
|
|
@@ -1,215 +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
|
-
* machine.js — the avatar's face, driven by the engine's real signals.
|
|
10
|
-
*
|
|
11
|
-
* The state machine itself is NOT re-implemented here. `lib/avatar/events.js`
|
|
12
|
-
* is a pure module with no timers and no DOM, and index.html loads that same
|
|
13
|
-
* file into the renderer (its UMD tail publishes `AegisAvatarEvents`), so the
|
|
14
|
-
* face follows the *exact* table the engine's events are described by — not a
|
|
15
|
-
* copy of it that can drift. What this file adds is the only thing the pure
|
|
16
|
-
* module deliberately refuses to know: which real renderer event in this app
|
|
17
|
-
* means which signal.
|
|
18
|
-
*
|
|
19
|
-
* The mapping is the interesting part, and it is one table, testable in
|
|
20
|
-
* isolation:
|
|
21
|
-
*
|
|
22
|
-
* send / setBusy(true) → turn.start
|
|
23
|
-
* a streamed delta → turn.stream
|
|
24
|
-
* setBusy(false) → turn.end {green}
|
|
25
|
-
* a tool frame → tool.read | tool.write | tool.run (by NAME)
|
|
26
|
-
* the approval card → approval.card / approval.decided
|
|
27
|
-
* queue progress → queue.progress
|
|
28
|
-
* connection state → sync.error / sync.ok
|
|
29
|
-
* typing in the composer → poke
|
|
30
|
-
*
|
|
31
|
-
* `signalForTool` is deliberately conservative: a tool this table has never
|
|
32
|
-
* heard of (a new tool, a commissioned pack, an MCP server's verb) reads as
|
|
33
|
-
* `tool.run` — "the agent is doing something" — rather than being mapped to
|
|
34
|
-
* `reading`, which would understate what is happening on the user's machine.
|
|
35
|
-
* Understating activity on the face of the thing doing it is the wrong way to
|
|
36
|
-
* be wrong.
|
|
37
|
-
*
|
|
38
|
-
* Nothing here throws on an unknown signal: `events.js` made unknown signals
|
|
39
|
-
* no-ops precisely so that tomorrow's feature cannot crash the avatar, and the
|
|
40
|
-
* wrapper below preserves that.
|
|
41
|
-
*/
|
|
42
|
-
|
|
43
|
-
'use strict';
|
|
44
|
-
|
|
45
|
-
const pkg = typeof window !== 'undefined' && window.AegisAvatarEvents
|
|
46
|
-
? { events: window.AegisAvatarEvents, parts: window.AegisAvatarParts || null }
|
|
47
|
-
: typeof module !== 'undefined' && module.exports
|
|
48
|
-
? { events: require('../../lib/avatar/events.js'), parts: require('./parts.js') }
|
|
49
|
-
: { events: null, parts: null };
|
|
50
|
-
|
|
51
|
-
if (!pkg.events) throw new Error('avatar/machine.js: lib/avatar/events.js must be loaded first');
|
|
52
|
-
|
|
53
|
-
/** Tool names → the signal the face shows. Order matters: first match wins. */
|
|
54
|
-
const TOOL_SIGNALS = Object.freeze([
|
|
55
|
-
Object.freeze({
|
|
56
|
-
signal: 'tool.write',
|
|
57
|
-
re: /^(write|edit|apply|patch|create|mkdir|append|save|delete|remove|move|rename|memorysave|memory_save)/i,
|
|
58
|
-
}),
|
|
59
|
-
Object.freeze({
|
|
60
|
-
signal: 'tool.read',
|
|
61
|
-
re: /^(read|open|list|ls|glob|grep|search|find|cat|stat|memorysearch|memory_search|memorylist|recall)/i,
|
|
62
|
-
}),
|
|
63
|
-
Object.freeze({
|
|
64
|
-
signal: 'tool.run',
|
|
65
|
-
re: /^(run|shell|bash|exec|spawn|test|build|npm|node|python|fetch|http|curl|websearch|browser)/i,
|
|
66
|
-
}),
|
|
67
|
-
]);
|
|
68
|
-
|
|
69
|
-
/** The tool name a frame is about, whichever shape the frame arrived in. */
|
|
70
|
-
function toolName(tool) {
|
|
71
|
-
if (!tool) return '';
|
|
72
|
-
if (typeof tool === 'string') return tool;
|
|
73
|
-
return String(tool.name || tool.tool || tool.id || '');
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Which signal a tool call is worth. Unknown verbs are `tool.run` — see header.
|
|
78
|
-
* A read frame is the ONLY thing that may say "reading".
|
|
79
|
-
*/
|
|
80
|
-
function signalForTool(tool) {
|
|
81
|
-
const name = toolName(tool);
|
|
82
|
-
for (const row of TOOL_SIGNALS) if (row.re.test(name)) return row.signal;
|
|
83
|
-
return 'tool.run';
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Wrap the shared machine with the app's vocabulary.
|
|
88
|
-
*
|
|
89
|
-
* @param {object} [opts]
|
|
90
|
-
* @param {object} [opts.machine] an `events.createMachine()` instance (tests inject one)
|
|
91
|
-
* @param {number} [opts.idleTimeoutMs] silence before `dozing`
|
|
92
|
-
* @param {function} [opts.onChange] called with `{ state, expression, label, signal }`
|
|
93
|
-
*/
|
|
94
|
-
function createAvatarSignals(opts = {}) {
|
|
95
|
-
const machine = opts.machine || pkg.events.createMachine({
|
|
96
|
-
state: opts.state,
|
|
97
|
-
now: opts.now,
|
|
98
|
-
idleTimeoutMs: opts.idleTimeoutMs,
|
|
99
|
-
});
|
|
100
|
-
const listeners = new Set();
|
|
101
|
-
let lastSent = null;
|
|
102
|
-
|
|
103
|
-
function labelFor(expression) {
|
|
104
|
-
const table = pkg.parts && pkg.parts.EXPRESSIONS;
|
|
105
|
-
return (table && table[expression] && table[expression].label) || expression;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function announce(signal) {
|
|
109
|
-
const payload = {
|
|
110
|
-
state: machine.state,
|
|
111
|
-
expression: machine.expression,
|
|
112
|
-
label: labelFor(machine.expression),
|
|
113
|
-
signal: signal == null ? null : String(signal),
|
|
114
|
-
};
|
|
115
|
-
for (const fn of listeners) {
|
|
116
|
-
try {
|
|
117
|
-
fn(payload);
|
|
118
|
-
} catch {
|
|
119
|
-
// A listener that throws must not take the turn with it: app.js calls
|
|
120
|
-
// this from the chat delta path.
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return payload;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function send(signal, meta) {
|
|
127
|
-
lastSent = String(signal);
|
|
128
|
-
machine.send(signal, meta);
|
|
129
|
-
return announce(signal);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const api = {
|
|
133
|
-
machine,
|
|
134
|
-
get state() {
|
|
135
|
-
return machine.state;
|
|
136
|
-
},
|
|
137
|
-
get expression() {
|
|
138
|
-
return machine.expression;
|
|
139
|
-
},
|
|
140
|
-
get label() {
|
|
141
|
-
return labelFor(machine.expression);
|
|
142
|
-
},
|
|
143
|
-
get lastSignal() {
|
|
144
|
-
return lastSent;
|
|
145
|
-
},
|
|
146
|
-
signal: send,
|
|
147
|
-
/** turn.start / turn.stream / turn.end — the chat path, in order. */
|
|
148
|
-
turnStart(meta) {
|
|
149
|
-
return send('turn.start', meta);
|
|
150
|
-
},
|
|
151
|
-
stream() {
|
|
152
|
-
return send('turn.stream');
|
|
153
|
-
},
|
|
154
|
-
turnEnd(meta) {
|
|
155
|
-
return send('turn.end', meta);
|
|
156
|
-
},
|
|
157
|
-
/** A tool frame: the signal is picked from the tool's name. */
|
|
158
|
-
tool(tool) {
|
|
159
|
-
return send(signalForTool(tool), { tool: toolName(tool) });
|
|
160
|
-
},
|
|
161
|
-
approvalCard() {
|
|
162
|
-
return send('approval.card');
|
|
163
|
-
},
|
|
164
|
-
approvalDecided() {
|
|
165
|
-
return send('approval.decided');
|
|
166
|
-
},
|
|
167
|
-
/** Queue progress frames; the machine treats them as activity. */
|
|
168
|
-
queue() {
|
|
169
|
-
return send('queue.progress');
|
|
170
|
-
},
|
|
171
|
-
syncOk() {
|
|
172
|
-
return send('sync.ok');
|
|
173
|
-
},
|
|
174
|
-
syncError(meta) {
|
|
175
|
-
return send('sync.error', meta);
|
|
176
|
-
},
|
|
177
|
-
poke() {
|
|
178
|
-
return send('poke');
|
|
179
|
-
},
|
|
180
|
-
celebrateDone() {
|
|
181
|
-
return send('celebrate.done');
|
|
182
|
-
},
|
|
183
|
-
reset() {
|
|
184
|
-
return send('reset');
|
|
185
|
-
},
|
|
186
|
-
/** The caller owns the clock — see events.js's "NO TIMERS INSIDE". */
|
|
187
|
-
tick(now) {
|
|
188
|
-
const out = machine.tick(now);
|
|
189
|
-
if (out.changed) announce('idle.timeout');
|
|
190
|
-
return out;
|
|
191
|
-
},
|
|
192
|
-
on(fn) {
|
|
193
|
-
listeners.add(fn);
|
|
194
|
-
return () => listeners.delete(fn);
|
|
195
|
-
},
|
|
196
|
-
off(fn) {
|
|
197
|
-
return listeners.delete(fn);
|
|
198
|
-
},
|
|
199
|
-
};
|
|
200
|
-
return api;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const API = Object.freeze({
|
|
204
|
-
createAvatarSignals,
|
|
205
|
-
signalForTool,
|
|
206
|
-
toolName,
|
|
207
|
-
TOOL_SIGNALS,
|
|
208
|
-
STATES: pkg.events.STATES,
|
|
209
|
-
SIGNALS: pkg.events.SIGNALS,
|
|
210
|
-
EXPRESSIONS: pkg.events.EXPRESSIONS,
|
|
211
|
-
expressionFor: pkg.events.expressionFor,
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
if (typeof module !== 'undefined' && module.exports) module.exports = API;
|
|
215
|
-
else if (typeof globalThis !== 'undefined') globalThis.AegisAvatarMachine = API;
|
|
3
|
+
"use strict";const pkg="undefined"!=typeof window&&window.AegisAvatarEvents?{events:window.AegisAvatarEvents,parts:window.AegisAvatarParts||null}:"undefined"!=typeof module&&module.exports?{events:require("../../lib/avatar/events.js"),parts:require("./parts.js")}:{events:null,parts:null};if(!pkg.events)throw new Error("avatar/machine.js: lib/avatar/events.js must be loaded first");const TOOL_SIGNALS=Object.freeze([Object.freeze({signal:"tool.write",re:/^(write|edit|apply|patch|create|mkdir|append|save|delete|remove|move|rename|memorysave|memory_save)/i}),Object.freeze({signal:"tool.read",re:/^(read|open|list|ls|glob|grep|search|find|cat|stat|memorysearch|memory_search|memorylist|recall)/i}),Object.freeze({signal:"tool.run",re:/^(run|shell|bash|exec|spawn|test|build|npm|node|python|fetch|http|curl|websearch|browser)/i})]);function toolName(e){return e?"string"==typeof e?e:String(e.name||e.tool||e.id||""):""}function signalForTool(e){const t=toolName(e);for(const e of TOOL_SIGNALS)if(e.re.test(t))return e.signal;return"tool.run"}function createAvatarSignals(e={}){const t=e.machine||pkg.events.createMachine({state:e.state,now:e.now,idleTimeoutMs:e.idleTimeoutMs}),r=new Set;let n=null;function o(e){const t=pkg.parts&&pkg.parts.EXPRESSIONS;return t&&t[e]&&t[e].label||e}function s(e){const n={state:t.state,expression:t.expression,label:o(t.expression),signal:null==e?null:String(e)};for(const e of r)try{e(n)}catch{}return n}function a(e,r){return n=String(e),t.send(e,r),s(e)}return{machine:t,get state(){return t.state},get expression(){return t.expression},get label(){return o(t.expression)},get lastSignal(){return n},signal:a,turnStart:e=>a("turn.start",e),stream:()=>a("turn.stream"),turnEnd:e=>a("turn.end",e),tool:e=>a(signalForTool(e),{tool:toolName(e)}),approvalCard:()=>a("approval.card"),approvalDecided:()=>a("approval.decided"),queue:()=>a("queue.progress"),syncOk:()=>a("sync.ok"),syncError:e=>a("sync.error",e),poke:()=>a("poke"),celebrateDone:()=>a("celebrate.done"),reset:()=>a("reset"),tick(e){const r=t.tick(e);return r.changed&&s("idle.timeout"),r},on:e=>(r.add(e),()=>r.delete(e)),off:e=>r.delete(e)}}const API=Object.freeze({createAvatarSignals:createAvatarSignals,signalForTool:signalForTool,toolName:toolName,TOOL_SIGNALS:TOOL_SIGNALS,STATES:pkg.events.STATES,SIGNALS:pkg.events.SIGNALS,EXPRESSIONS:pkg.events.EXPRESSIONS,expressionFor:pkg.events.expressionFor});"undefined"!=typeof module&&module.exports?module.exports=API:"undefined"!=typeof globalThis&&(globalThis.AegisAvatarMachine=API);
|