aegis-desktop 0.8.5 → 0.8.6

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 (47) hide show
  1. package/README.md +15 -25
  2. package/docs/demo.gif +0 -0
  3. package/docs/marketing-assets/01-model-class-picker.png +0 -0
  4. package/docs/marketing-assets/02-answer-complete.png +0 -0
  5. package/docs/marketing-assets/03-tool-approval-diff.png +0 -0
  6. package/docs/screenshot.png +0 -0
  7. package/lib/avatar/companion.js +2 -2
  8. package/lib/avatar/cosmetics.js +2 -2
  9. package/lib/avatar/events.js +2 -2
  10. package/lib/avatar/holder-turn.js +6 -7
  11. package/lib/avatar/holders.js +6 -7
  12. package/lib/avatar/identity.js +7 -7
  13. package/lib/avatar/level.js +1 -1
  14. package/lib/avatar/mirror.js +6 -7
  15. package/lib/avatar/packs/README.md +1 -1
  16. package/lib/avatar/persona.js +5 -5
  17. package/lib/avatar/profile.js +13 -14
  18. package/lib/avatar/register.js +2 -2
  19. package/lib/avatar/store.js +6 -6
  20. package/lib/avatar/transfer.js +1 -2
  21. package/lib/avatar/turn.js +4 -4
  22. package/lib/local/agents.js +1 -1
  23. package/lib/local/autonomous.js +1 -1
  24. package/lib/local/engine.js +9 -9
  25. package/lib/local/git-scope.js +1 -1
  26. package/lib/local/prompt.js +3 -3
  27. package/lib/local/queue.js +2 -2
  28. package/lib/local/shell.js +1 -1
  29. package/lib/local/tools.js +2 -2
  30. package/lib/local/turn-guard.js +1 -1
  31. package/lib/local/worktree-lock.js +1 -1
  32. package/lib/settings.js +3 -3
  33. package/main.js +4 -4
  34. package/package.json +4 -8
  35. package/renderer/app.js +4 -4
  36. package/renderer/avatar/assemble.js +1 -1
  37. package/renderer/avatar/avatar.css +2 -2
  38. package/renderer/avatar/avatar.js +5 -5
  39. package/renderer/avatar/cards.js +1 -2
  40. package/renderer/avatar/hud.js +1 -2
  41. package/renderer/avatar/machine.js +1 -3
  42. package/renderer/avatar/pane.js +3 -3
  43. package/renderer/avatar/parts.js +4 -4
  44. package/renderer/budget.js +2 -2
  45. package/renderer/index.html +1 -1
  46. package/renderer/transcript-view.js +1 -1
  47. package/renderer/usage.js +6 -6
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # AEGIS Desktop
2
2
 
3
- ![AEGIS Desktop running: the three-class model picker, an agent turn, a gated tool-approval diff, and the unattended work queue](https://raw.githubusercontent.com/aegisinfo/aegiscode-plugin/main/desktop/docs/demo.gif)
3
+ ![AEGIS Desktop running: the three-class model picker, an agent turn, a gated tool-approval diff, and the unattended work queue](docs/demo.gif)
4
4
 
5
5
  A standalone Electron chat app over the [AEGIS](https://aegiscloud.org) API,
6
6
  with an **agentic tool loop**: the model can read, write, and edit files,
@@ -9,9 +9,8 @@ delegate whole sub-tasks to subagents. It does **not** require Claude Code.
9
9
 
10
10
  ## Install
11
11
 
12
- Download a build from the
13
- [releases page](https://github.com/aegisinfo/aegiscode-plugin/releases) — the
14
- repo the release workflow publishes binaries to — or install from npm:
12
+ Prebuilt binaries (AppImage / MSI+NSIS / dmg) ship with each release, and the
13
+ package is on npm:
15
14
 
16
15
  ```bash
17
16
  npm install -g aegis-desktop # requires Node 18+
@@ -114,16 +113,16 @@ stub and refuse to publish an asset that fails their assertions.
114
113
 
115
114
  **The model-class picker** — all three routes, switchable mid-conversation:
116
115
 
117
- ![The model-class picker listing Aegis Cloud, Bring your own key, and Local](https://raw.githubusercontent.com/aegisinfo/aegiscode-plugin/main/docs/marketing-assets/01-model-class-picker.png)
116
+ ![The model-class picker listing Aegis Cloud, Bring your own key, and Local](docs/marketing-assets/01-model-class-picker.png)
118
117
 
119
118
  **A completed answer** in the transcript:
120
119
 
121
- ![A completed answer in the transcript](https://raw.githubusercontent.com/aegisinfo/aegiscode-plugin/main/docs/marketing-assets/02-answer-complete.png)
120
+ ![A completed answer in the transcript](docs/marketing-assets/02-answer-complete.png)
122
121
 
123
122
  **The tool-call approval card** with a proposed edit — what blocks `exec`,
124
123
  `writeFile` and `editFile` until you allow, allow for the session, or deny:
125
124
 
126
- ![The tool-call approval card with a proposed edit](https://raw.githubusercontent.com/aegisinfo/aegiscode-plugin/main/docs/marketing-assets/03-tool-approval-diff.png)
125
+ ![The tool-call approval card with a proposed edit](docs/marketing-assets/03-tool-approval-diff.png)
127
126
 
128
127
  ## Tools available to the model
129
128
 
@@ -156,7 +155,7 @@ aegiscode autonomous add "fix the flaky retry test" --cwd ~/repo --commit
156
155
  aegiscode autonomous list
157
156
  aegiscode autonomous run # drain one task, then stop
158
157
  aegiscode autonomous proceed --max 3 # drain up to three
159
- aegiscode autonomous reconcile --auto # queue the next unfinished PLAN.md phase, then drain it
158
+ aegiscode autonomous reconcile --auto # queue the next unfinished phase of your plan file, then drain it
160
159
  aegiscode autonomous retry <id> # put a finished task back
161
160
  aegiscode autonomous clear --all # empty the queue
162
161
  ```
@@ -278,17 +277,12 @@ The app registers an `aegis://` protocol handler:
278
277
 
279
278
  ## Run from source
280
279
 
281
- ```bash
282
- git clone https://github.com/aegiscloud/aegiscode-desktop.git
283
- cd aegiscode-desktop
284
- npm install
285
- npm start
286
- ```
287
-
288
- Or, from the monorepo checkout, run this directory directly:
280
+ A source checkout is available to contributors with repository access only — the
281
+ desktop host is not a public repository. From the checkout, run this directory
282
+ directly:
289
283
 
290
284
  ```bash
291
- cd aegiscode-plugin/desktop
285
+ cd desktop
292
286
  npm install
293
287
  npm start
294
288
  ```
@@ -328,11 +322,7 @@ transport over the same AEGIS backend — see the repo root for that fuller
328
322
  architecture picture, and [cli/README.md](../cli/README.md) for the terminal
329
323
  host over the same engine.
330
324
 
331
- **Two repos, one product.** This directory is the source of truth. The
332
- standalone repo at
333
- [aegiscloud/aegiscode-desktop](https://github.com/aegiscloud/aegiscode-desktop)
334
- is a `git subtree split` of it — same code, published separately so it can be
335
- cloned and built on its own. Edits land here and are synced out; nothing is
336
- authored there. The npm package
337
- [`aegis-desktop`](https://www.npmjs.com/package/aegis-desktop) is built from
338
- that standalone repo.
325
+ **One source of truth.** This directory is it. The npm package
326
+ [`aegis-desktop`](https://www.npmjs.com/package/aegis-desktop) is built from a
327
+ `git subtree split` of it — same code, published without the rest of the
328
+ monorepo. Edits land here and are synced out; nothing is authored elsewhere.
package/docs/demo.gif ADDED
Binary file
Binary file
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * companion.js — the four companion behaviours (PLAN Phase 23, spec §4).
4
+ * companion.js — the four companion behaviours.
5
5
  *
6
6
  * The behaviours are the morning brief, the idea log, the session reflection
7
7
  * and in-session recall. What they have in common is not their content, it is
@@ -42,7 +42,7 @@
42
42
  * checkable: nothing here may be a shell, a network call or anything that sends
43
43
  * data off the device, and `carveOuts()` is the assertion that says so.
44
44
  *
45
- * Pure: no `fs`, no Electron, no globals. See `docs/avatar-plan.md` §2 and §4.
45
+ * Pure: no `fs`, no Electron, no globals.
46
46
  */
47
47
 
48
48
  const level = require('./level.js');
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * cosmetics.js — the cosmetics pack format and its validator (Phase 24).
4
+ * cosmetics.js — the cosmetics pack format and its validator.
5
5
  *
6
6
  * WHY A FORMAT AT ALL. Spec §3 says the avatar is *data, not art*: a persona is
7
7
  * one JSON record and a look is a layered SVG assembled from a part manifest, so
@@ -196,7 +196,7 @@ function validatePack(raw) {
196
196
  for (const where of forbidden) {
197
197
  errors.push(
198
198
  `cosmetics packs may not carry \`${where}\` — a pack sells LOOKS only, never XP, ` +
199
- 'levels, recall breadth, approval friction or personalization (docs/avatar-plan.md §3)',
199
+ 'levels, recall breadth, approval friction or personalization',
200
200
  );
201
201
  }
202
202
 
@@ -17,7 +17,7 @@
17
17
  * that throws on an unrecognised signal turns every future feature into a
18
18
  * crash in the avatar, so the default transition is "stay put".
19
19
  *
20
- * The states are the ones `docs/avatar-plan.md` §4 lists, wired to signals that
20
+ * The states are wired to signals that
21
21
  * already exist: turn start/stream/end, `tool.*` from the tool host, the
22
22
  * approval card, queue progress, and sync status. No new event bus.
23
23
  *
@@ -175,7 +175,7 @@ const TABLE = Object.freeze({
175
175
 
176
176
  /**
177
177
  * Expression id per state. The renderer maps these to the persona's expression
178
- * pack; keeping the mapping here means Phase 22 needs no state knowledge.
178
+ * pack; keeping the mapping here means the renderer needs no state knowledge.
179
179
  */
180
180
  const EXPRESSIONS = Object.freeze({
181
181
  idle: 'neutral',
@@ -1,13 +1,12 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * holder-turn.js — the seam where a holder reaches turn assembly (PLAN Phase 28;
5
- * docs/avatar-identity-plan.md §3, §4, §7).
4
+ * holder-turn.js — the seam where a holder reaches turn assembly.
6
5
  *
7
6
  * `register.js` turns a persona into a bounded tone fragment, `profile.js` folds
8
7
  * a holder's memory into evidence-gated facets, and `holders.js` owns the
9
8
  * directory and the isolation filter. This file is the only place they meet, and
10
- * it is where the one claim Phase 28 is really about gets decided:
9
+ * it is where the one claim this file is really about gets decided:
11
10
  *
12
11
  * **Holder A's assembled prompt cannot contain holder B's text.**
13
12
  *
@@ -22,10 +21,10 @@
22
21
  * - *The recall block.* The same filter runs on the recall rows, so an entry
23
22
  * that arrives after the wrong cache still cannot be quoted into A's turn.
24
23
  *
25
- * The Phase 28 test drives both through `assemble()` — the function main.js
24
+ * The test drives both through `assemble()` — the function main.js
26
25
  * actually calls — and then through `engine.js` with this file's prompt builder
27
26
  * injected, grepping the system prompt a scripted transport received. That is
28
- * the Phase 18 lesson (never assert on a harness leg that was re-pointed) turned
27
+ * the lesson (never assert on a harness leg that was re-pointed) turned
29
28
  * on privacy: the interesting failure is a filter applied after the wrong cache,
30
29
  * and only the real path can show it.
31
30
  *
@@ -157,7 +156,7 @@ function profileFragment(fold, opts = {}) {
157
156
  * rows. Exported because main.js renders it into the memory pane as well, and
158
157
  * one implementation is one thing to get right.
159
158
  *
160
- * `opts.header` exists for the one caller that has no holder: Phase 20's turn
159
+ * `opts.header` exists for the one caller that has no holder: the account-level turn
161
160
  * assembly recalls the account's memory before any holder exists, and captioning
162
161
  * that block "this holder's memory" would be a lie told in the prompt. The
163
162
  * default stays the holder header, so every holder-scoped caller is unchanged.
@@ -253,7 +252,7 @@ function assemble(input = {}) {
253
252
  entryIds: scoped.map((row) => String(row && row.id)).filter(Boolean),
254
253
  recallIds: recall.entryIds,
255
254
  // Rows the recall block could not fit. Facet drops have their own field, so
256
- // neither the usage accounting nor the Phase 28 assertions have to guess
255
+ // neither the usage accounting nor the assertions have to guess
257
256
  // which kind of "dropped" this is.
258
257
  dropped: recall.dropped,
259
258
  neutralizations: profile.neutralizations + recall.neutralizations + registerFragment.neutralizations,
@@ -1,11 +1,10 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * holders.js — who holds the memory (PLAN Phase 28; docs/avatar-identity-plan.md
5
- * §3, §4, §5).
4
+ * holders.js — who holds the memory.
6
5
  *
7
- * `identity.js` (Phase 25) decides *which* holder a set of signals points at and
8
- * refuses to merge two people on a fingerprint match. `profile.js` (Phase 25)
6
+ * `identity.js` decides *which* holder a set of signals points at and
7
+ * refuses to merge two people on a fingerprint match. `profile.js`
9
8
  * folds one holder's entries into bounded, evidence-gated facets. Neither of
10
9
  * them owns a directory. This file is that owner — the layout, the registry, the
11
10
  * switch, the conflict decision and the "why does it know this" surface — and it
@@ -32,7 +31,7 @@
32
31
  * is the client-side filter §3 calls "the assertion that the fix happened":
33
32
  * the server-side scope is the fix, and this is what proves it ran. It is
34
33
  * used by the fold *and* by the recall block, so a leak needs to defeat one
35
- * function rather than two call sites — and the Phase 28 test drives two
34
+ * function rather than two call sites — and the test drives two
36
35
  * holders through the real turn assembly and greps the prompt.
37
36
  *
38
37
  * 4. **Every claim is traceable and deletable.** `provenance()` renders a
@@ -63,7 +62,7 @@ const PROFILE_FILE = 'profile.json';
63
62
  const FORGOTTEN_FILE = 'forgotten.json';
64
63
 
65
64
  /**
66
- * The one thing a switch may leave behind. A list, not a sentence: the Phase 28
65
+ * The one thing a switch may leave behind. A list, not a sentence: the
67
66
  * test iterates it, so adding a survivor to the code without adding it here
68
67
  * makes the test fail rather than the promise quietly rot.
69
68
  */
@@ -445,7 +444,7 @@ function applyConflict(registry, resolution, choice = {}) {
445
444
  * particular and therefore to the holder asking, which is what makes a legacy
446
445
  * unstamped ledger readable instead of invisible.
447
446
  *
448
- * `clientFilter:false` exists only so the Phase 28 isolation test can prove the
447
+ * `clientFilter:false` exists only so the isolation test can prove the
449
448
  * filter is load-bearing by removing it and watching the leg go red. It is not
450
449
  * reachable from any IPC handler (holder-ipc.js never passes it).
451
450
  */
@@ -3,7 +3,7 @@
3
3
  /**
4
4
  * identity.js — whose memory it is.
5
5
  *
6
- * `docs/avatar-plan.md` made memory the value; value is meaningless
6
+ * Memory is the value; value is meaningless
7
7
  * unattributed. This module answers exactly one question — *which holder is at
8
8
  * the keyboard* — from signals the caller already has, and it answers with a
9
9
  * **proposal plus provenance**, never with a merge.
@@ -38,7 +38,7 @@
38
38
  * 4. **Resolution is pure and never writes.** `holders.json` is validated,
39
39
  * repaired and copied; the input registry is never mutated, and the fields
40
40
  * `proposal`/`conflict`/`needsMint` exist precisely so main writes the file
41
- * with the user's confirmation (Phase 26) instead of this module guessing.
41
+ * with the user's confirmation instead of this module guessing.
42
42
  *
43
43
  * Pure: no fs, no Electron, no crypto import — the caller supplies `hash` so
44
44
  * this file is testable in plain Node exactly like `lib/sync/*`.
@@ -149,10 +149,10 @@ function looksLikeRawKey(value) {
149
149
  *
150
150
  * Deterministic on purpose — the same machine seed produces the same id on a
151
151
  * reinstall, which is what keeps "the ledger survives a reinstall"
152
- * (`avatar-plan.md` §1.5) true for an install that never had a cloud key.
152
+ * true for an install that never had a cloud key.
153
153
  *
154
154
  * @returns {string|null} `null` when there is nothing trustworthy to mint from;
155
- * the caller then falls back to random bytes (main, Phase 26).
155
+ * the caller then falls back to random bytes.
156
156
  */
157
157
  function mintHolderId(hash, seed) {
158
158
  if (typeof hash !== 'function') return null;
@@ -424,7 +424,7 @@ function setActive(current, id) {
424
424
  return validate(base);
425
425
  }
426
426
 
427
- /** Phase 29 «forget»: drop one row, and re-point `active` if it was that row. */
427
+ /** «forget»: drop one row, and re-point `active` if it was that row. */
428
428
  function removeHolder(current, id) {
429
429
  const base = validate(current).registry;
430
430
  const wanted = holderIdShape(id);
@@ -475,7 +475,7 @@ const CONFIDENCE = Object.freeze({
475
475
  * `conflict.a` is what you would be switching away from (the registry's
476
476
  * active holder, or the losing signal's holder) and `conflict.b` is the
477
477
  * proposal. Returned so main can show a "who is at the keyboard?" card
478
- * (Phase 28) instead of switching silently.
478
+ * instead of switching silently.
479
479
  */
480
480
  function resolve(signals, registry) {
481
481
  const raw = signals && typeof signals === 'object' ? signals : {};
@@ -519,7 +519,7 @@ function resolve(signals, registry) {
519
519
  // A fingerprint that matches no registry row proves nothing about who this
520
520
  // is, so it can never *select* a holder. It can only be proposed for
521
521
  // binding onto the holder that WAS resolved, which main writes only after
522
- // the user confirms (Phase 26) — this module never writes anything. Built
522
+ // the user confirms — this module never writes anything. Built
523
523
  // here rather than in the last rung so every rung reports it identically:
524
524
  // an explicit persona import with a freshly rotated key is the same
525
525
  // question as a fresh install with one.
@@ -31,7 +31,7 @@
31
31
  * they too cannot widen write/shell/network approval.
32
32
  *
33
33
  * Pure: no fs, no Electron, no process access except the `env` the caller
34
- * passes. See `docs/avatar-plan.md` §2.
34
+ * passes.
35
35
  */
36
36
 
37
37
  const { TIERS, tierFor, tierIndex } = require('./tiers.js');
@@ -1,11 +1,10 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * mirror.js — the per-holder cloud mirror (PLAN Phase 29;
5
- * docs/avatar-identity-plan.md §6, extending docs/avatar-plan.md §1.5).
4
+ * mirror.js — the per-holder cloud mirror.
6
5
  *
7
- * Phase 20 put the ledger on disk, Phase 25 made a profile foldable from it and
8
- * Phase 28 gave every row a holder. All three are local. This file is the half
6
+ * Earlier work put the ledger on disk, made a profile foldable from it and
7
+ * gave every row a holder. All three are local. This file is the half
9
8
  * that leaves the machine, and it exists to make four claims true — each of
10
9
  * which is a test in `desktop/test/avatar-mirror.test.mjs`, not a paragraph:
11
10
  *
@@ -22,8 +21,8 @@
22
21
  * `reconcile()` unions two sides by a content-addressed row id, deduplicates
23
22
  * a row both machines already have, and sorts by `(t, id)`. Two machines
24
23
  * that appended in opposite orders converge on the *same bytes*, and
25
- * `derive()`'s order independence (docs/avatar-plan.md §1) makes that the
26
- * same level. The Phase 29 ordering test feeds both interleavings and
24
+ * `derive()`'s order independence makes that the
25
+ * same level. The ordering test feeds both interleavings and
27
26
  * asserts one level; the idempotence leg asserts feeding the union back in
28
27
  * cannot double a level, which is what would actually hurt.
29
28
  *
@@ -337,7 +336,7 @@ function readMirror(memoryRows, holderId) {
337
336
  * trusted as a conclusion — `payload.profile`/`payload.facets` are dropped
338
337
  * explicitly and recorded in `refused`. The fold itself is `profile.fold()`
339
338
  * with `holder` supplied, so a facet can only cite an entry id that survived
340
- * the holder filter (Phase 25's pure-seam isolation) *and* exists in this
339
+ * the holder filter *and* exists in this
341
340
  * input (the `citable` set inside the fold).
342
341
  */
343
342
  function refold(input = {}) {
@@ -23,7 +23,7 @@ paid.
23
23
  ## The rule this directory exists to enforce
24
24
 
25
25
  **Cosmetics only.** Never XP, levels, recall breadth, approval friction or
26
- personalization (`docs/avatar-plan.md` §3, invariant 8). `validatePack` refuses a
26
+ personalization. `validatePack` refuses a
27
27
  pack carrying `xp`, `level`, `recallEntries`, `approvals`, `toolGrants`,
28
28
  `modelFloor`, `proactivity`, `price`, `entitlement`, … at any depth, and refuses
29
29
  a `tier` that contradicts the unlock table — so a "paid pack that also widens a
@@ -7,7 +7,7 @@
7
7
  * that is a settings pane with hand-written branches for every option, which
8
8
  * makes a new outfit a code change and a commissioned art pack a fork. The way
9
9
  * this module does it: a persona is one JSON record, validated and coerced
10
- * here, and Phase 22's renderer assembles layered SVG from a part manifest. A
10
+ * here, and the renderer assembles layered SVG from a part manifest. A
11
11
  * theme is then a file. Zero code changes.
12
12
  *
13
13
  * Validation is REPAIR-ORIENTED, not reject-oriented, with exactly two hard
@@ -23,20 +23,20 @@
23
23
  * it is the user's own file.
24
24
  *
25
25
  * The one place that IS adversarial is the `register` block and the identity
26
- * strings, because they end up in the system prompt (Phase 21). Those are
26
+ * strings, because they end up in the system prompt. Those are
27
27
  * sanitized here at the storage boundary — control characters and newlines are
28
28
  * stripped so a name cannot break out of its line — and the *prompt* fragment
29
29
  * is additionally bounded and neutralized in `register.js`. Two layers, because
30
30
  * a name is user-authored text going into a prompt.
31
31
  *
32
- * Pure: no fs, no Electron. Phase 21's main process reads/writes the file.
32
+ * Pure: no fs, no Electron. The main process reads/writes the file.
33
33
  */
34
34
 
35
35
  /** Bump when the record shape changes; `migrate` walks old records forward. */
36
36
  const SCHEMA_VERSION = 1;
37
37
 
38
38
  /**
39
- * The part manifest. Ids only — geometry lives in `renderer/avatar/` (Phase 22),
39
+ * The part manifest. Ids only — geometry lives in `renderer/avatar/`,
40
40
  * which reads these same ids. Validation is against THIS list so a typo in a
41
41
  * hand-edited persona is caught at load rather than rendering an empty head.
42
42
  */
@@ -82,7 +82,7 @@ function has(list, value) {
82
82
 
83
83
  /**
84
84
  * Collapse whitespace and strip control characters. A name is a single line of
85
- * text in a UI, and (Phase 21) a single line in a prompt — so newlines, tabs and
85
+ * text in a UI, and a single line in a prompt — so newlines, tabs and
86
86
  * the unicode line separators are removed rather than escaped.
87
87
  */
88
88
  function cleanText(value, max) {
@@ -1,8 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * profile.js — what *this holder's* memory is allowed to personalise (Phase 25,
5
- * identity plan §4).
4
+ * profile.js — what *this holder's* memory is allowed to personalise.
6
5
  *
7
6
  * `identity.js` answers "whose memory is it"; this module answers the question
8
7
  * that follows immediately: given that holder's own rows, what may the avatar
@@ -36,21 +35,21 @@
36
35
  * Absence of evidence produces no facet: never "probably prefers terse
37
36
  * answers".
38
37
  * 4. COLD START IS NEUTRAL. No evidence ⇒ `coldStart: true`, `facets: []`,
39
- * `budget.used: 0`. Phase 27 renders that as an empty fragment, i.e. an
38
+ * `budget.used: 0`. The renderer shows that as an empty fragment, i.e. an
40
39
  * avatar that behaves exactly like an unconfigured install. It does not
41
- * guess a personality, the same honesty rule as `avatar-plan.md` §1.4.
40
+ * guess a personality.
42
41
  * 5. DETERMINISTIC. No clock (`now` is an argument), no randomness, no map
43
42
  * iteration order leaking into output: facets are ordered by
44
43
  * `(confidence desc, lastSeen desc, id asc)` and, for two facets of the
45
44
  * same class on the same day, `key asc` — one extra term so the order is
46
45
  * total and a permutation of the input cannot change the output bytes.
47
- * 6. BOUNDED. The assembled facets are capped at `BUDGET_TOKENS` (900, per
48
- * §Phase 27), dropped whole, lowest priority first — never sliced
46
+ * 6. BOUNDED. The assembled facets are capped at `BUDGET_TOKENS` (900),
47
+ * dropped whole, lowest priority first — never sliced
49
48
  * mid-sentence, which is the failure mode a naive `slice()` produces.
50
49
  *
51
50
  * Pure: no fs, no Electron, no clock, no crypto. It reuses `persona.cleanText`
52
51
  * (the storage boundary's sanitizer) and `register.estimateTokens` (the same
53
- * 4-chars-per-token ceiling Phase 21 uses) so the two prompt fragments Phase 27
52
+ * 4-chars-per-token ceiling used elsewhere) so the two prompt fragments the renderer
54
53
  * concatenates are measured with one ruler.
55
54
  */
56
55
 
@@ -73,7 +72,7 @@ const FACET_IDS = Object.freeze([
73
72
  'toneCalibration',
74
73
  ]);
75
74
 
76
- /** §Phase 27: the profile fragment's ceiling. */
75
+ /** The profile fragment's ceiling. */
77
76
  const BUDGET_TOKENS = 900;
78
77
 
79
78
  /** Longest a single facet `value` may be (it is UI text and prompt text). */
@@ -360,7 +359,7 @@ function orderFacets(facets) {
360
359
  /**
361
360
  * Take facets in priority order while they fit. Whole facets only: a fragment
362
361
  * that ends mid-sentence reads as a truncated thought, which is worse than a
363
- * shorter one, and §Phase 27 asserts exactly this.
362
+ * shorter one, and the test asserts exactly this.
364
363
  */
365
364
  function applyBudget(ordered, limit = BUDGET_TOKENS) {
366
365
  const kept = [];
@@ -384,12 +383,12 @@ function applyBudget(ordered, limit = BUDGET_TOKENS) {
384
383
  /**
385
384
  * Does this row belong to the holder being folded?
386
385
  *
387
- * Unstamped rows belong to whoever is asking: they predate Phase 26's stamping
386
+ * Unstamped rows belong to whoever is asking: they predate the stamping
388
387
  * and the alternative — dropping them — would silently empty the profile of
389
388
  * every existing install. A row stamped for *someone else* is never folded, and
390
389
  * that is the pure-seam half of §3's "isolation is the load-bearing property":
391
390
  * `sources` built downstream can only cite ids that survived this filter. The
392
- * real-seam half (Phase 28) asserts it again through actual turn assembly,
391
+ * real-seam half asserts it again through actual turn assembly,
393
392
  * because a filter applied after the wrong cache is exactly the bug a
394
393
  * pure-function test misses.
395
394
  */
@@ -844,9 +843,9 @@ if (Object.keys(FOLDERS).length !== FACET_IDS.length || FACET_IDS.some((id) => t
844
843
  * @param {object} input
845
844
  * @param {string} [input.holder] the holder these rows belong to. Supplying
846
845
  * it is how a caller states *whose* memory this is: rows stamped for another
847
- * holder are dropped here and can therefore never be cited by a facet. Phase
848
- * 26 always passes it (the registry's `active`); omitting it means "the caller
849
- * already scoped this input", which is why Phase 28 asserts the same property
846
+ * holder are dropped here and can therefore never be cited by a facet. The
847
+ * caller always passes it (the registry's `active`); omitting it means "the caller
848
+ * already scoped this input", which is why the test asserts the same property
850
849
  * again at the real turn-assembly seam.
851
850
  * @param {Array} [input.entries] memory entries for this holder, aegis1's
852
851
  * row shape: `{ id, content, role, tags: [], session, createdAt, … }`.
@@ -2,7 +2,7 @@
2
2
 
3
3
  /**
4
4
  * register.js — the persona's register turned into ONE bounded system-prompt
5
- * fragment (Phase 21; shared by Phase 27's profile fragment).
5
+ * fragment.
6
6
  *
7
7
  * `persona.js` is the storage boundary: it sanitizes identity/register text on
8
8
  * the way to disk. This is the SECOND layer, and the one that matters at the
@@ -38,7 +38,7 @@
38
38
 
39
39
  const personaModule = require('./persona.js');
40
40
 
41
- /** ~300 tokens, per docs/avatar-plan.md §3. */
41
+ /** ~300 tokens. */
42
42
  const MAX_FRAGMENT_TOKENS = 300;
43
43
 
44
44
  /**
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * store.js — the main process's half of level authority (Phase 20).
4
+ * store.js — the main process's half of level authority.
5
5
  *
6
- * Phase 19 shipped the avatar core as five pure modules with no IO at all:
6
+ * The avatar core shipped as five pure modules with no IO at all:
7
7
  * `xp.js` knows what an event is worth and how a ledger replays into a level,
8
8
  * `level.js` knows what a level buys, but neither can read a file or a clock.
9
9
  * This file is the missing half and it deliberately owns *only* the two things
@@ -51,11 +51,11 @@ const personaModule = require('./persona.js');
51
51
  const LEDGER_DIR = 'avatar';
52
52
  const LEDGER_FILE = 'ledger.jsonl';
53
53
  /**
54
- * `<userData>/avatar/persona.json` — the persona DOCUMENT (Phase 21).
54
+ * `<userData>/avatar/persona.json` — the persona DOCUMENT.
55
55
  *
56
56
  * It lives here, next to the ledger and not in settings.json, because a persona
57
57
  * is a document rather than a preference: it is imported, exported, hand-edited
58
- * and eventually synced per holder (Phase 26), and every one of those paths
58
+ * and eventually synced per holder, and every one of those paths
59
59
  * wants validation on the way in. The `__avatar` settings namespace holds only
60
60
  * the three switches (lib/settings.js), so a renderer can never write a persona
61
61
  * through the settings surface.
@@ -206,7 +206,7 @@ function createAvatarStore({ dir, now = () => Date.now(), env = process.env, log
206
206
  *
207
207
  * `award()` above is the internal path: its callers are hooks that already
208
208
  * know which kind they are recording. This is the externally reachable one
209
- * (Phase 21's `aegis:avatarAward`), so it is the one that must not be a
209
+ * (`aegis:avatarAward`), so it is the one that must not be a
210
210
  * generic "write a line into the ledger" bridge: an unknown kind is refused
211
211
  * with a reason rather than written as a zero-XP line, which keeps the ledger
212
212
  * a record of priced events only. `xp` is never accepted from the caller —
@@ -226,7 +226,7 @@ function createAvatarStore({ dir, now = () => Date.now(), env = process.env, log
226
226
  return { ok: true, kind: name, xp: xp.xpFor(name), entry };
227
227
  }
228
228
 
229
- // --- persona document (Phase 21) ----------------------------------------
229
+ // --- persona document ----------------------------------------
230
230
  // The persona is read and written ONLY here, by main. Both directions run
231
231
  // through persona.js validation, so a hand-edited or imported file is
232
232
  // repaired with warnings rather than trusted, and the identity/register text
@@ -1,8 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * transfer.js — persona export, persona import and per-holder forget
5
- * (PLAN Phase 29; docs/avatar-identity-plan.md §5 and §7.3).
4
+ * transfer.js — persona export, persona import and per-holder forget.
6
5
  *
7
6
  * A persona is taste. Memory is not. This file exists to keep those two apart
8
7
  * across the one boundary where they would otherwise merge — moving a persona
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * turn.js — the ONE seam where a level reaches turn assembly (Phase 20) and
4
+ * turn.js — the ONE seam where a level reaches turn assembly and
5
5
  * where the persona's register fragment is attached to the engine's system
6
- * prompt (Phase 21).
6
+ * prompt.
7
7
  *
8
8
  * `level.js` says what a level buys and `register.js` says what the persona's
9
9
  * tone is; neither of them knows what a turn is. This file is the only place
@@ -23,7 +23,7 @@
23
23
  * level that silently starts spending per turn is a billing surprise.
24
24
  * - No approval, grant or tool field is produced here. Turn assembly reads a
25
25
  * recall policy; it is structurally incapable of reading a widened
26
- * permission out of it, which is what makes the Phase 20 carve-out test a
26
+ * permission out of it, which is what makes the carve-out test a
27
27
  * statement about the code rather than a promise about the developers.
28
28
  * - `applyModelFloor(model, policy)` turns the L20 floor into the model this
29
29
  * turn actually travels on. It is a ROUTING constraint and nothing more: it
@@ -101,7 +101,7 @@ function recallPolicy(caps, opts = {}) {
101
101
  * Ids that reason on their own, as this build can tell without a round trip:
102
102
  *
103
103
  * - DeepSeek's reasoning family — the same anchored set `engine.js` sizes its
104
- * budget from (DEEPSEEK_REASONING_MODEL_RE, mirrored from aegiscodex-dev),
104
+ * budget from (DEEPSEEK_REASONING_MODEL_RE, mirrored from the upstream engine),
105
105
  * because a model whose hidden chain-of-thought bills against its own output
106
106
  * budget is by definition a model that reasons.
107
107
  * - the pooled brain tier (`nexus-brain` and the `-smart`/`-neo` spellings the
@@ -2,7 +2,7 @@
2
2
 
3
3
  /**
4
4
  * agents.js — subagent prompt presets for the desktop `task` tool, ported
5
- * from aegiscodex-dev's src/agents.js (same roles, same synthesis presets).
5
+ * from the upstream engine reference (same roles, same synthesis presets).
6
6
  * A preset is a system prompt: `task` runs it through the normal engine.chat
7
7
  * loop as a nested turn (its own tool rounds, same model class), not a
8
8
  * separate runtime, so the presets are pure prompt composition.
@@ -311,7 +311,7 @@ function resolveEffort({ effort, env, fanout } = {}) {
311
311
 
312
312
  /**
313
313
  * The operating directive injected as the autonomous turn's prompt preamble.
314
- * Ported verbatim in spirit from aegiscodex-dev/src/autonomous.js so both
314
+ * Ported verbatim in spirit from the upstream engine reference so both
315
315
  * clients behave the same; duplicated rather than vendored because the plugin
316
316
  * hosts no ESM build of that module.
317
317
  */
@@ -8,7 +8,7 @@
8
8
  * chosen class is the user's explicit selection.
9
9
  *
10
10
  * Since the tool-calling port it ALSO owns the agent loop (the client half of
11
- * aegiscodex-dev's src/backend.js runProvider): every turn carries a real
11
+ * the upstream engine's src/backend.js runProvider): every turn carries a real
12
12
  * system prompt (prompt.js) and the builtin tool schemas (tools.js), and when
13
13
  * a provider answers with tool calls the loop executes them in-process and
14
14
  * feeds the results back for as many rounds as the model keeps calling tools
@@ -27,7 +27,7 @@
27
27
  * executor never has to be exposed to the renderer.
28
28
  *
29
29
  * Two turn-scoped resources ride along with the loop, mirroring
30
- * aegiscodex-dev's runProvider exactly:
30
+ * the upstream engine's runProvider exactly:
31
31
  * - a lazily-started ShellSession (shell.js) that the `exec` tool shares,
32
32
  * so cd/export state persists across calls within one turn instead of
33
33
  * each call spawning a fresh process;
@@ -159,7 +159,7 @@ function providerKeyFromEnv(provider) {
159
159
  }
160
160
 
161
161
  /**
162
- * Mirrors aegiscodex-dev's src/backend.js DEEPSEEK_REASONING_MODEL_RE +
162
+ * Mirrors the upstream engine's src/backend.js DEEPSEEK_REASONING_MODEL_RE +
163
163
  * EFFORT_TOKEN_BUDGET verbatim. DeepSeek's reasoning models (deepseek-flash,
164
164
  * i.e. "Flash 4.1", deepseek-v4-pro, the deprecated deepseek-reasoner, and the
165
165
  * legacy v4-flash/v4.1-flash aliases some configs still carry) spend part of
@@ -170,7 +170,7 @@ function providerKeyFromEnv(provider) {
170
170
  * tool calls, just a turn that "completes" with nothing to show for it (the
171
171
  * empty-response bug). A user pointing the Custom OpenAI-compatible class
172
172
  * straight at DeepSeek's API hits exactly this, so the request is sized by the
173
- * same effort budget aegiscodex-dev uses for its own direct DeepSeek calls.
173
+ * same effort budget the upstream engine uses for its own direct DeepSeek calls.
174
174
  *
175
175
  * desktop/renderer/budget.js carries the renderer's copy of these two
176
176
  * constants plus budgetFor() below; test/budget.test.mjs requires both and
@@ -437,7 +437,7 @@ function createLocalEngine({
437
437
  settings,
438
438
  tools,
439
439
  promptBuilder,
440
- // Phase 20 — the level's recall breadth, injected by the host that owns the
440
+ // The level's recall breadth, injected by the host that owns the
441
441
  // avatar ledger (desktop/main.js builds one from lib/avatar/turn.js). It is a
442
442
  // SEAM rather than a call into lib/avatar/* because this engine is shared with
443
443
  // the CLI (cli/src/deps.js loads this file): the engine must not grow a
@@ -920,7 +920,7 @@ function createLocalEngine({
920
920
  // that used to claim a figure for the pooled class is gone — see
921
921
  // desktop/renderer/budget.js). Omitting the field is
922
922
  // what tells aegis1 "no cap stated — let effort decide", the same
923
- // contract aegiscodex-dev sends.
923
+ // contract the upstream engine sends.
924
924
  //
925
925
  // A cap the caller DID state is a different thing, and dropping it was
926
926
  // a bug: aegis1 reads a body max_tokens as a ceiling over its ladder,
@@ -960,7 +960,7 @@ function createLocalEngine({
960
960
  //
961
961
  // Writing is still available, explicitly: the aegis_memory_save tool
962
962
  // (mcp/tools.js; the CLI exposes it as /memory). Recall on every turn,
963
- // store only what the user asks for — which is what aegiscodex-dev's
963
+ // store only what the user asks for — which is what the upstream engine's
964
964
  // own cross-session memory does, and what this comment claimed to
965
965
  // match while sending both halves.
966
966
  //
@@ -1324,7 +1324,7 @@ function createLocalEngine({
1324
1324
  let truncationRetried = false;
1325
1325
  let synthesisDone = false;
1326
1326
 
1327
- // Round cap — the bound aegiscodex-dev has had all along and this engine
1327
+ // Round cap — a bound the upstream engine has had all along and this engine
1328
1328
  // did not. Removing the old fixed cap (12) was right in spirit and wrong
1329
1329
  // in effect: it left the turn with NO horizon, and on 2026-09-15 the
1330
1330
  // question "can you check the plan" ran ~70 rounds, grew the context
@@ -1333,7 +1333,7 @@ function createLocalEngine({
1333
1333
  // re-sends the whole conversation, so an unbounded loop gets more
1334
1334
  // expensive the longer it runs.
1335
1335
  //
1336
- // The numbers match aegiscodex-dev's (src/autonomous.js) so both clients
1336
+ // The numbers match the upstream engine's (src/autonomous.js) so both clients
1337
1337
  // behave the same: 24 rounds for a chat turn, 40 for an autonomous one.
1338
1338
  // Env-overridable for a deliberately long job.
1339
1339
  // A stated horizon (env) still wins outright — an explicit number is the
@@ -4,7 +4,7 @@
4
4
  * Minimal git worktree introspection: where the repo root is, and what is
5
5
  * dirty right now with a content hash per path.
6
6
  *
7
- * Ported from aegiscodex-dev/src/git-scope.js (ESM → CommonJS). `turn-guard.js`
7
+ * Ported from the upstream engine reference (ESM → CommonJS). `turn-guard.js`
8
8
  * uses `gitRoot` and `gitStatusSnapshot`; the queue now hosts the commit path
9
9
  * too, so `foreignChanges` and `scopedCommit` are carried over as well — they
10
10
  * were deliberately absent while nothing here queued work, because a copy of
@@ -2,9 +2,9 @@
2
2
 
3
3
  /**
4
4
  * prompt.js — the desktop client's system prompt (client half of
5
- * aegiscodex-dev's tool calling).
5
+ * the upstream engine's tool calling).
6
6
  *
7
- * aegiscodex-dev sends a real persona on every provider turn
7
+ * The upstream engine sends a real persona on every provider turn
8
8
  * (src/backend.js MAIN_CHAT_PROMPT plus a docs/operating-context.md block).
9
9
  * The desktop renderer used to send nothing at all, so every model answered a
10
10
  * bare user string with no identity, no work rules and no idea which machine
@@ -23,7 +23,7 @@
23
23
  * repo roots the host already knows (main.js), so the model stops asking.
24
24
  */
25
25
 
26
- /** Identity + work rules. Ported from aegiscodex-dev src/backend.js. */
26
+ /** Identity + work rules. Ported from the upstream engine reference. */
27
27
  const MAIN_CHAT_PROMPT =
28
28
  `You are Aegiscodex, a terminal coding assistant that works in the user's repository. ` +
29
29
  `Help with software engineering tasks: read and reason about code, write and edit files, ` +
@@ -443,8 +443,8 @@ function parsePlanStatus(text) {
443
443
  if (line.trim() === '' || /^\s+\S/.test(line)) continue;
444
444
  inStatus = false; // anything else (e.g. '---') ends the block
445
445
  }
446
- // Every PLAN.md in these repos writes `## Phase 7 ✅ — title` (✅ right after
447
- // the number). A trailing `## Phase 7 — title ✅` is accepted too, because
446
+ // Every PLAN.md in these repos writes `## Phase N ✅ — title` (✅ right after
447
+ // the number). A trailing `## Phase N — title ✅` is accepted too, because
448
448
  // reconcile reads plans other sessions and agents wrote, and a heading that
449
449
  // says it shipped means it shipped wherever the ✅ sits.
450
450
  const hm = line.match(/^##\s*Phase\s+(\d+)\b/);
@@ -2,7 +2,7 @@
2
2
 
3
3
  /**
4
4
  * shell.js — a persistent shell session for the `exec` tool, ported from
5
- * aegiscodex-dev's src/shell.js (client half of the same design).
5
+ * the upstream engine reference (client half of the same design).
6
6
  *
7
7
  * The desktop `exec` tool used to spawn ONE process per call: `cd /foo` in
8
8
  * one turn had no effect on the next call, so a model that wanted to work
@@ -2,9 +2,9 @@
2
2
 
3
3
  /**
4
4
  * tools.js — the local tool layer for the desktop agent loop (client half of
5
- * aegiscodex-dev's tool calling).
5
+ * the upstream engine's tool calling).
6
6
  *
7
- * Two jobs, mirroring aegiscodex-dev/src/tools.js:
7
+ * Two jobs, mirroring the upstream engine reference:
8
8
  * 1. Tool schemas in the wire format each API family expects — Anthropic's
9
9
  * `{name, description, input_schema}` vs OpenAI-compatible
10
10
  * `{type:'function', function:{name, description, parameters}}`.
@@ -3,7 +3,7 @@
3
3
  /**
4
4
  * Turn-start foreign-write detection.
5
5
  *
6
- * Ported from aegiscodex-dev/src/turn-guard.js (ESM → CommonJS).
6
+ * Ported from the upstream engine reference (ESM → CommonJS).
7
7
  *
8
8
  * Bug this exists for: the commit-time scope check already knew how to tell
9
9
  * "my changes" from "somebody else's in-flight work" — but only *after* a
@@ -3,7 +3,7 @@
3
3
  /**
4
4
  * Cross-process lock on a git working tree.
5
5
  *
6
- * Ported from aegiscodex-dev/src/worktree-lock.js (ESM → CommonJS).
6
+ * Ported from the upstream engine reference (ESM → CommonJS).
7
7
  *
8
8
  * Bug this exists for: nothing keyed on *the working tree*. A session-scoped
9
9
  * lock only stops two processes resuming the SAME transcript, so three
package/lib/settings.js CHANGED
@@ -74,13 +74,13 @@ const CONFIRM_MODE_NAMESPACE = '__confirmMode';
74
74
  * what turns it off. Nothing secret lives here, so no encryption. */
75
75
  const MEMORY_PERSIST_NAMESPACE = '__memoryPersist';
76
76
 
77
- /** Reserved namespace for the avatar/companion preferences (Phase 21):
77
+ /** Reserved namespace for the avatar/companion preferences:
78
78
  * `{ enabled, showLevel, motion }` — app-level, not a provider, so it stays out
79
79
  * of the provider CRUD surface for the same reason the AEGIS key does. The
80
80
  * persona itself is deliberately NOT stored here: it is a document, not a
81
81
  * preference, and it lives at `<userData>/avatar/persona.json`, read and
82
82
  * written by main through lib/avatar/store.js so a hand-edited or imported
83
- * persona is validated on the way in (see docs/avatar-plan.md §3).
83
+ * persona is validated on the way in.
84
84
  * Defaults are the shipped behaviour: the avatar is ON, the level readout is
85
85
  * shown, and motion follows the OS `prefers-reduced-motion` setting. */
86
86
  const AVATAR_NAMESPACE = '__avatar';
@@ -374,7 +374,7 @@ function createSettingsStore({ dir, safeStorage } = {}) {
374
374
  return memoryPersistState();
375
375
  }
376
376
 
377
- // --- Avatar: reserved namespace, plain preferences (Phase 21) ------------
377
+ // --- Avatar: reserved namespace, plain preferences ------------
378
378
  // The companion's on/off switch, its level readout, and its motion mode.
379
379
  // Nothing secret lives here, so no encryption — same reasoning as the quick
380
380
  // launcher and memory-persist namespaces above.
package/main.js CHANGED
@@ -85,7 +85,7 @@ const { createSettingsStore, isReservedNamespace } = require('./lib/settings.js'
85
85
  const sessionStore = require('./lib/sync/sessions.js');
86
86
  const memoryQueue = require('./lib/sync/memory-queue.js');
87
87
  const persistGate = require('./lib/sync/persist-gate.js');
88
- // Avatar level authority (Phase 20). The ledger, the replay and the award
88
+ // Avatar level authority. The ledger, the replay and the award
89
89
  // hooks live here in main — a renderer-held counter is a suggestion, not a
90
90
  // fact, the same reasoning persist-gate.js follows. The pure halves
91
91
  // (xp.js/level.js/events.js/persona.js) are unchanged and stay IO-free; store.js
@@ -104,7 +104,7 @@ try {
104
104
  } catch {
105
105
  foreignMemory = require('./vendor/foreign-memory.js');
106
106
  }
107
- // Builtin tool executor for the agent loop (client half of aegiscodex-dev's
107
+ // Builtin tool executor for the agent loop (client half of the upstream engine's
108
108
  // tool calling). MAIN-process only: it is reachable from the renderer solely
109
109
  // through the whitelisted `tools:` IPC surface registered below — see the
110
110
  // sandbox note on registerToolsIpc().
@@ -356,7 +356,7 @@ async function billingResult(run) {
356
356
  * would not survive the IPC trip — and the entry is left un-stored so the
357
357
  * renderer can keep it in the box and point at the subscribe page.
358
358
  *
359
- * Phase 20 — level authority. This is the memory path's front door, so it is
359
+ * Level authority. This is the memory path's front door, so it is
360
360
  * the first of the award hooks. Two things happen before the write leaves:
361
361
  *
362
362
  * 1. The entry is *normalised* (store.js `normalizeMemoryEntry`). aegis1
@@ -450,7 +450,7 @@ function quotaFromPayload(data) {
450
450
  * 1000 entries x 2 kB over IPC is pure waste, and the bodies are already
451
451
  * either in the cloud or in the queue by the time this resolves.
452
452
  *
453
- * Phase 20: each batch that the cloud accepted pays `memory.imported` per
453
+ * Each batch that the cloud accepted pays `memory.imported` per
454
454
  * entry, keyed by the import's own source (foreign-memory.js collapses one
455
455
  * source's entries into ONE `import:<source>` session — aegis1 meters distinct
456
456
  * session values — so that value is the right per-source key). The replay caps
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "aegis-desktop",
3
3
  "productName": "AEGIS Desktop",
4
- "version": "0.8.5",
5
- "description": "Thin Electron host for AEGIS — a local chat UI over the shared client/aegis.js transport. Ships transport + UI only; engine logic stays server-side.",
4
+ "version": "0.8.6",
5
+ "description": "Thin Electron host for AEGIS \u2014 a local chat UI over the shared client/aegis.js transport. Ships transport + UI only; engine logic stays server-side.",
6
6
  "author": {
7
7
  "name": "AEGIS Code",
8
8
  "email": "nborneklint@gmail.com"
@@ -10,11 +10,6 @@
10
10
  "license": "MIT",
11
11
  "main": "main.js",
12
12
  "homepage": "https://aegiscloud.org",
13
- "repository": {
14
- "type": "git",
15
- "url": "git+https://github.com/aegisinfo/aegiscode-plugin.git",
16
- "directory": "desktop"
17
- },
18
13
  "bin": {
19
14
  "aegis": "bin/aegis.js"
20
15
  },
@@ -25,7 +20,8 @@
25
20
  "lib",
26
21
  "vendor",
27
22
  "bin",
28
- "build"
23
+ "build",
24
+ "docs"
29
25
  ],
30
26
  "scripts": {
31
27
  "start": "electron .",
package/renderer/app.js CHANGED
@@ -1330,7 +1330,7 @@ function handleQuickLauncherPush(payload) {
1330
1330
  // timestamp, createdAt (epoch ms), source, role, tags[], content, session,
1331
1331
  // importance, summary(bool), topics[], entities[], sentiment, tokenCount,
1332
1332
  // embedding[]. There is no `tier` — L0–L3 is the local CLI engine's concept
1333
- // (aegiscodex-dev/src/memory.js) and does not exist on the cloud rows, so
1333
+ // (the upstream engine's memory module) and does not exist on the cloud rows, so
1334
1334
  // source/role are the real provenance axes here.
1335
1335
  //
1336
1336
  // `embedding` is dropped on ingest: it is a raw float vector, sometimes
@@ -1771,7 +1771,7 @@ async function importMemory() {
1771
1771
  els.memoryImportBtn.disabled = true;
1772
1772
  els.memoryHint.textContent = 'scanning for other AI tool memory…';
1773
1773
  try {
1774
- // Phase 1 — dry run. The scan is read-only against the foreign stores
1774
+ // Dry run. The scan is read-only against the foreign stores
1775
1775
  // (client/foreign-memory.js never writes to them) and the user already
1776
1776
  // asked for the import by clicking, so this runs unattended: no confirm
1777
1777
  // prompt. The preview text left in the hint is the audit trail of what
@@ -1787,7 +1787,7 @@ async function importMemory() {
1787
1787
  .map((s) => `${s.label}: ${s.count}`)
1788
1788
  .join(' · ');
1789
1789
 
1790
- // Phase 2 — the confirmed write, immediately.
1790
+ // The confirmed write, immediately.
1791
1791
  els.memoryHint.textContent = `importing ${preview.totals.entries} entries — ${detail}`;
1792
1792
  const result = await aegis.memoryImport({ confirm: true, limit: 1000 });
1793
1793
  if (result && result.upgrade) {
@@ -3984,7 +3984,7 @@ async function init() {
3984
3984
  transcript.attachScrollVeto();
3985
3985
  // Read-only diagnostic surface for the headless smoke run
3986
3986
  // (test/electron-smoke.mjs). Everything in this file lives inside the IIFE,
3987
- // so an injected script cannot otherwise see the scroll veto — the Phase 9
3987
+ // so an injected script cannot otherwise see the scroll veto — the
3988
3988
  // harness read `transcript.isScrolledUp()` directly and silently got `null`,
3989
3989
  // which made its veto assertion unfalsifiable. Exposes state only: no
3990
3990
  // setters, nothing that can drive the UI. Frozen so a stray write in a test
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * assemble.js — persona JSON in, layered SVG out (PLAN Phase 22).
4
+ * assemble.js — persona JSON in, layered SVG out.
5
5
  *
6
6
  * A pure string builder. It takes a validated persona record plus one
7
7
  * expression id and returns the markup, the layer list it walked, and — the
@@ -1,4 +1,4 @@
1
- /* avatar.css — the avatar's presentation layer (PLAN Phase 22).
1
+ /* avatar.css — the avatar's presentation layer.
2
2
  *
3
3
  * Motion is CSS only, and it is the ONE thing in this phase that can be turned
4
4
  * off wholesale. Two switches do that, deliberately overlapping:
@@ -35,7 +35,7 @@
35
35
  width: 132px;
36
36
  height: auto;
37
37
  /* The face is decoration: it must never intercept a pointer, a scroll or a
38
- drag — the two behaviours Phase 9 protects start as input events. */
38
+ drag — the two protected behaviours start as input events. */
39
39
  pointer-events: none;
40
40
  user-select: none;
41
41
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  /**
4
4
  * avatar.js — the mount: stage, HUD, pane, and the rules that keep the avatar
5
- * out of everything else's way (PLAN Phase 22).
5
+ * out of everything else's way.
6
6
  *
7
7
  * The three modules beside this one are pure or nearly so (`parts.js` is data,
8
8
  * `assemble.js` builds a string, `hud.js` builds a view model, `machine.js`
@@ -12,7 +12,7 @@
12
12
  *
13
13
  * 1. **Never blocks input.** No key listener is registered anywhere in
14
14
  * `renderer/avatar/`, so the avatar cannot consume a keystroke — Escape
15
- * still reaches Phase 9's interrupt handler and typing still reaches the
15
+ * still reaches the interrupt handler and typing still reaches the
16
16
  * composer, whatever the avatar is doing. Painting is coalesced to at most
17
17
  * one write per animation frame (`schedule()`), so a stream that emits
18
18
  * fifty deltas in a frame still costs one repaint, and every entry point is
@@ -33,8 +33,8 @@
33
33
  * function the storage layer uses, so a persona cannot opt out of the
34
34
  * accessibility preference.
35
35
  *
36
- * Source resolution is deliberately forward-compatible: `window.avatar` (the
37
- * Phase 21 bridge) is used when it exists, and `preview()` exists so the
36
+ * Source resolution is deliberately forward-compatible: `window.avatar` is
37
+ * used when it exists, and `preview()` exists so the
38
38
  * harnesses (and the customization pane, which previews edits before anything
39
39
  * is written) can drive the avatar without it. Both are one code path — the
40
40
  * preview state is an override layer over the real one, and it is labelled as
@@ -77,7 +77,7 @@ function safe(fn, fallback) {
77
77
  }
78
78
 
79
79
  /**
80
- * The data source. `window.avatar` is the preload bridge Phase 21 will expose
80
+ * The data source. `window.avatar` is the preload bridge the host will expose
81
81
  * (`state()` returning `{ persona, projection, capabilities, warnings }`); when
82
82
  * it is missing the avatar renders the honest unknown state rather than
83
83
  * inventing numbers.
@@ -2,8 +2,7 @@
2
2
 
3
3
  /**
4
4
  * cards.js — the companion card, rendered in the shape of the tool approval
5
- * card (PLAN Phase 23, spec §4: "same shape as the approval card the user
6
- * already trusts").
5
+ * card.
7
6
  *
8
7
  * Two things make that more than a styling choice:
9
8
  *
@@ -1,8 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * hud.js — the level HUD: XP bar, next unlock, and the honest held state
5
- * (PLAN Phase 22, spec §1.4).
4
+ * hud.js — the level HUD: XP bar, next unlock, and the honest held state.
6
5
  *
7
6
  * The HUD is a *renderer*: it draws a projection main handed it and derives
8
7
  * nothing. That is not a stylistic preference — §1.5 of the plan says a level is
@@ -1,9 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * machine.js — the avatar's face, driven by the engine's real signals
5
- * (PLAN Phase 22: "expression state machine bound to `lib/avatar/events.js`
6
- * signals").
4
+ * machine.js — the avatar's face, driven by the engine's real signals.
7
5
  *
8
6
  * The state machine itself is NOT re-implemented here. `lib/avatar/events.js`
9
7
  * is a pure module with no timers and no DOM, and index.html loads that same
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * pane.js — the customization pane (PLAN Phase 22, spec §3).
4
+ * pane.js — the customization pane.
5
5
  *
6
6
  * The whole pane is BUILT FROM THE MANIFEST. Every select is a list of
7
7
  * `persona.PARTS` ids, every label comes from `parts.LABELS` (and falls back to
@@ -26,8 +26,8 @@
26
26
  * 3. **Nothing here touches input routing.** No key handlers, no focus trap, no
27
27
  * `preventDefault`, no modal. The pane is a plain block of form controls in
28
28
  * the sidebar; opening or closing it cannot consume a keystroke meant for
29
- * the composer, and the Escape-interrupt path stays exactly where Phase 9
30
- * put it.
29
+ * the composer, and the Escape-interrupt path stays exactly where it
30
+ * was put.
31
31
  */
32
32
 
33
33
  'use strict';
@@ -1,10 +1,10 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * parts.js — the layered SVG part manifest (PLAN Phase 22).
4
+ * parts.js — the layered SVG part manifest.
5
5
  *
6
6
  * `lib/avatar/persona.js` says it plainly: "Ids only — geometry lives in
7
- * `renderer/avatar/` (Phase 22), which reads these same ids." This is that
7
+ * `renderer/avatar/`, which reads these same ids." This is that
8
8
  * geometry, and the sentence above is enforced rather than trusted: `IDS` below
9
9
  * IS `persona.PARTS`, read from the shared module — this file declares no id
10
10
  * list of its own. Adding `hair: 'mohawk-9'` to the persona schema whose
@@ -15,8 +15,8 @@
15
15
  * Everything here is DATA. There is no branch on a part id anywhere in the
16
16
  * renderer: the assembler walks `LAYERS` in order and emits whatever shapes the
17
17
  * manifest holds, so a commissioned art pack or a seasonal outfit is a file,
18
- * not a fork. That is the whole point of the phase (docs/avatar-plan.md §3:
19
- * "the avatar is data, not art").
18
+ * not a fork. That is the whole point of the phase: the avatar is data,
19
+ * not art.
20
20
  *
21
21
  * Two deliberate limits, stated here rather than discovered later:
22
22
  *
@@ -74,7 +74,7 @@ const DEEPSEEK_REASONING_MODEL_RE = /^deepseek-(v4(\.\d+)?-(flash|pro)|flash|pro
74
74
 
75
75
  /**
76
76
  * Effort rung -> tokens. Mirrors EFFORT_TOKEN_BUDGET in
77
- * desktop/lib/local/engine.js, which mirrors aegiscodex-dev's src/backend.js.
77
+ * desktop/lib/local/engine.js, which mirrors the upstream engine's src/backend.js.
78
78
  *
79
79
  * These are the numbers the models whose output length cannot be predicted are
80
80
  * sized by, which is why the rung replaced the dropdown: a rung is a
@@ -96,7 +96,7 @@ const EFFORT_TOKEN_BUDGET = { low: 8192, medium: 16384, high: 32768 };
96
96
  */
97
97
 
98
98
  /** An unknown or "auto" rung falls to the top: the same default the engine
99
- * and aegiscodex-dev apply, so "auto" cannot silently mean "smallest". */
99
+ * and the upstream engine apply, so "auto" cannot silently mean "smallest". */
100
100
  function effortRung(effort) {
101
101
  return effort === 'low' || effort === 'medium' ? effort : 'high';
102
102
  }
@@ -129,7 +129,7 @@
129
129
 
130
130
  So the budget is derived from this rung instead. The engine maps
131
131
  low/medium/high to 8192/16384/32768 (EFFORT_TOKEN_BUDGET in
132
- desktop/lib/local/engine.js, mirroring aegiscodex-dev's
132
+ desktop/lib/local/engine.js, mirroring the upstream engine's
133
133
  src/backend.js), "auto" leaves the choice to the server, and no
134
134
  max_tokens is stated by this app at all. The row is always on
135
135
  screen: with the dropdown gone there is nothing to swap it for. -->
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * The DOM-touching half of the transcript policy (plan Phase 8), extracted
4
+ * The DOM-touching half of the transcript policy, extracted
5
5
  * from app.js so the two symptoms this work exists to prevent can be asserted
6
6
  * *behaviourally* — not just as pure math in stream-policy.js:
7
7
  *
package/renderer/usage.js CHANGED
@@ -68,11 +68,11 @@ function usageTokens(usage) {
68
68
 
69
69
  /**
70
70
  * Rough token count for text the wire never measured — a direct port of the
71
- * CLI's own estimator (`aegiscodex-dev/src/tokens.js estimateTokens`), and the
71
+ * CLI's own estimator (the upstream engine's estimateTokens), and the
72
72
  * reason its session total is MONOTONIC.
73
73
  *
74
74
  * This is the whole difference being fixed. The CLI's `appendHistory`
75
- * (aegiscodex-dev/src/history.js) writes a `tokens` object for EVERY finished
75
+ * (the upstream engine's history module) writes a `tokens` object for EVERY finished
76
76
  * exchange: `{input, output, cacheRead, cacheWrite, real: true}` when the wire
77
77
  * reported usage, and `{input: estimateTokens(prompt), output:
78
78
  * estimateTokens(reply), real: false}` when it did not. `real` is a FLAG, not a
@@ -477,7 +477,7 @@ function usageBuckets(usage) {
477
477
  * Cache hit rate for a usage record: the share of prompt tokens the provider
478
478
  * actually served from its cache rather than billing at miss price.
479
479
  *
480
- * Ported from `aegiscodex-dev/src/tokens.js`. It reads through `usageBuckets`
480
+ * Ported from the upstream engine reference. It reads through `usageBuckets`
481
481
  * rather than off the raw object, which is the one deliberate difference: the
482
482
  * engine's copy is handed an already-normalised record, while this module is
483
483
  * also handed WIRE usage (a provider's raw response body) and ledger rows. A
@@ -588,7 +588,7 @@ function turnAccounting(usage, model, opts = {}) {
588
588
  const tokens = usageTokens(usage);
589
589
  const u = usage && typeof usage === 'object' ? usage : {};
590
590
  // The settled charge can arrive either on the response or folded into the
591
- // usage object — aegiscodex-dev/src/main.js does the latter
591
+ // usage object — the upstream engine does the latter
592
592
  // (`{ ...result.usage, costUsd: result.costUsd }`), so both are accepted.
593
593
  const settled = typeof opts.costUsd === 'number'
594
594
  ? opts.costUsd
@@ -703,7 +703,7 @@ function rollTurn(roll, usage, opts = {}) {
703
703
  if (turn.tokens == null) {
704
704
  // No reported usage. The CLI does not let such a turn vanish from the
705
705
  // total: `appendHistory` estimates it from the text and marks it
706
- // `real: false` (aegiscodex-dev/src/history.js), and
706
+ // `real: false` (the upstream engine's history module), and
707
707
  // `aggregateSessionUsage` then adds it like any other row. Folding the same
708
708
  // estimate here is what makes the live roll and the rebuilt roll the same
709
709
  // number, and what stops the total standing still on exactly the turns the
@@ -772,7 +772,7 @@ function rollMessages(messages) {
772
772
  * The ledger row one finished dispatch must carry into the shared session
773
773
  * store, so the rolling total can be REBUILT when the thread is reopened —
774
774
  * the desktop's counterpart of the CLI's `appendHistory`
775
- * (aegiscodex-dev/src/history.js:36).
775
+ * (the upstream engine's history module).
776
776
  *
777
777
  * One authority for the row, on purpose. The CLI writes a `tokens` object for
778
778
  * EVERY exchange and skips none: