@aragon-agent/cli 0.6.33 → 0.6.35
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/CHANGELOG.md +2305 -2242
- package/README.md +119 -18
- package/dist/agent/controller.d.ts +29 -0
- package/dist/agent/controller.js +42 -1
- package/dist/agent/kit-apply.d.ts +56 -0
- package/dist/agent/kit-apply.js +209 -0
- package/dist/agent/model-profile-settings.d.ts +28 -0
- package/dist/agent/model-profile-settings.js +142 -29
- package/dist/agent/reducer.d.ts +1 -1
- package/dist/commands/builtins.js +4 -0
- package/dist/commands/kit-command.d.ts +20 -0
- package/dist/commands/kit-command.js +131 -0
- package/dist/commands/perf.d.ts +10 -0
- package/dist/commands/perf.js +1 -1
- package/dist/config/kit-resolution.d.ts +93 -0
- package/dist/config/kit-resolution.js +226 -0
- package/dist/config/model-kits.d.ts +224 -0
- package/dist/config/model-kits.js +302 -0
- package/dist/config/model-profile-store.d.ts +25 -0
- package/dist/config/model-profiles.d.ts +41 -3
- package/dist/config/model-profiles.js +46 -5
- package/dist/config/schema.d.ts +16 -0
- package/dist/input/keymap.js +18 -4
- package/dist/ui/App.js +135 -15
- package/dist/ui/Header.d.ts +9 -1
- package/dist/ui/Header.js +8 -2
- package/dist/ui/PromptInput.js +1 -1
- package/dist/ui/ScrollbackShell.d.ts +38 -13
- package/dist/ui/ScrollbackShell.js +4 -3
- package/dist/ui/StatusBar.d.ts +13 -0
- package/dist/ui/StatusBar.js +10 -3
- package/dist/ui/TeamPanel.d.ts +12 -0
- package/dist/ui/TeamPanel.js +10 -31
- package/dist/ui/Transcript.js +22 -2
- package/dist/ui/entries/TeamCard.d.ts +18 -2
- package/dist/ui/entries/TeamCard.js +47 -13
- package/dist/ui/layout/header-hints.d.ts +11 -1
- package/dist/ui/layout/header-hints.js +35 -3
- package/dist/ui/layout/kit-chip.d.ts +50 -0
- package/dist/ui/layout/kit-chip.js +81 -0
- package/dist/ui/layout/virtual-window.js +9 -0
- package/dist/ui/overlays/HelpOverlay.js +7 -0
- package/dist/ui/overlays/KitEditor.d.ts +23 -0
- package/dist/ui/overlays/KitEditor.js +101 -0
- package/dist/ui/overlays/KitManager.d.ts +23 -0
- package/dist/ui/overlays/KitManager.js +104 -0
- package/dist/ui/overlays/KitPicker.d.ts +49 -0
- package/dist/ui/overlays/KitPicker.js +58 -0
- package/dist/ui/overlays/ModelProfilePicker.js +16 -6
- package/dist/ui/overlays/SettingsScreen.js +139 -16
- package/dist/ui/overlays/kit-picker-state.d.ts +87 -0
- package/dist/ui/overlays/kit-picker-state.js +136 -0
- package/dist/ui/overlays/kit-settings-state.d.ts +129 -0
- package/dist/ui/overlays/kit-settings-state.js +267 -0
- package/dist/ui/overlays/model-profile-state.d.ts +26 -3
- package/dist/ui/overlays/model-profile-state.js +59 -6
- package/dist/ui/scrollback/LiveTail.d.ts +44 -10
- package/dist/ui/scrollback/LiveTail.js +20 -4
- package/dist/ui/scrollback/limits.d.ts +16 -0
- package/dist/ui/scrollback/limits.js +16 -0
- package/dist/ui/scrollback/live-preview.js +10 -3
- package/dist/ui/scrollback/overflow-print.d.ts +108 -0
- package/dist/ui/scrollback/overflow-print.js +120 -0
- package/dist/ui/scrollback/renderer.d.ts +11 -1
- package/dist/ui/scrollback/renderer.js +53 -25
- package/dist/ui/scrollback/tail-anchor.d.ts +68 -0
- package/dist/ui/scrollback/tail-anchor.js +77 -0
- package/dist/ui/scrollback/tail-window.d.ts +21 -8
- package/dist/ui/scrollback/tail-window.js +26 -10
- package/dist/ui/scrollback/use-scrollback-view.d.ts +38 -10
- package/dist/ui/scrollback/use-scrollback-view.js +372 -85
- package/dist/ui/team-status.d.ts +141 -0
- package/dist/ui/team-status.js +224 -0
- package/dist/ui/transcript-text.js +13 -4
- package/dist/ui/use-wheel-routing.js +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -480,6 +480,68 @@ displayed. `aragon --resume` of such a file inherits the full context and shows
|
|
|
480
480
|
empty screen until the next turn - the alternative, inventing entries, would put
|
|
481
481
|
a conversation the user never saw onto their screen.
|
|
482
482
|
|
|
483
|
+
## Agent kits
|
|
484
|
+
|
|
485
|
+
A **kit** is a named preset that pins the three model roles at once: which
|
|
486
|
+
profile runs the main model, which runs the fast tier (or none), and which model
|
|
487
|
+
subagents use. It is a set of *references* to the profiles you already have, so
|
|
488
|
+
editing a profile changes every kit that names it — which is what "preset"
|
|
489
|
+
should mean.
|
|
490
|
+
|
|
491
|
+
Kits exist in three layers, and only one of them writes a file:
|
|
492
|
+
|
|
493
|
+
| Layer | Where | Lives until |
|
|
494
|
+
| --- | --- | --- |
|
|
495
|
+
| **Saved** | `/settings` → `Kit pack` → `New kit` / `e` | you delete it |
|
|
496
|
+
| **Default** | `Kit pack` → `s` (*Set default*), then `Ctrl+S` | you change it |
|
|
497
|
+
| **Session** | `Ctrl+K`, or `/kit use <name>` | this process exits |
|
|
498
|
+
|
|
499
|
+
The first row of the `Ctrl+K` overlay is always `Current setup` — the bindings in
|
|
500
|
+
`config.json` — and choosing it releases a session kit, so the feature has an exit
|
|
501
|
+
as well as an entrance.
|
|
502
|
+
|
|
503
|
+
```
|
|
504
|
+
Agent kits Session only; set a default in /settings
|
|
505
|
+
[In use] Deep work [Default]
|
|
506
|
+
Current setup
|
|
507
|
+
────────────────────────────────────────────────────────
|
|
508
|
+
main anthropic:claude-opus-4-6 default URL
|
|
509
|
+
subagent anthropic:claude-haiku-4-5 profile Haiku
|
|
510
|
+
fast anthropic:claude-haiku-4-5 tier on
|
|
511
|
+
────────────────────────────────────────────────────────
|
|
512
|
+
Enter use for this session | Esc close
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
- **`Ctrl+K` never writes the config file.** That is a promise with a test
|
|
516
|
+
behind it: the session path runs the same prepare chain as a save and skips
|
|
517
|
+
only the commit, so a failed switch leaves both the file and the session
|
|
518
|
+
exactly as they were, and a successful one produces no bytes on disk.
|
|
519
|
+
- **A save decides slots, not the whole kit.** Change the subagent model while a
|
|
520
|
+
session kit is in effect and only the subagent slot leaves the kit; main and
|
|
521
|
+
fast keep the kit's choices. Rebinding main releases main, and so on.
|
|
522
|
+
- **The status bar names what is running** with a `kit <name>` chip, and appends
|
|
523
|
+
`~` when the live connections no longer match the preset (for example after
|
|
524
|
+
`/model`). With no kit in effect the chip is not rendered at all.
|
|
525
|
+
- **`Ctrl+G` reveals `^K switch kit`** in the header row; on terminals narrower
|
|
526
|
+
than 79 columns it gives way to `^L` and `^I`, and `/help` plus `/kit` are the
|
|
527
|
+
fallback. (`Alt+K` is where `Ctrl+K`'s old "kill to end of line" went.)
|
|
528
|
+
- **Setting a default applies its three slots**, because "the default" *is* the
|
|
529
|
+
binding in the config file. A pointer alone would not be in effect at the next
|
|
530
|
+
launch.
|
|
531
|
+
- **Deleting a profile deletes the kits that name it**, in the same
|
|
532
|
+
confirmation, and says so: a preset pointing at a deleted profile can neither
|
|
533
|
+
be applied nor repaired from any screen. Kits that a hand-edited file leaves
|
|
534
|
+
dangling degrade to `Profile missing` and are refused with a stable message.
|
|
535
|
+
|
|
536
|
+
Config keys (all inside the existing `modelProfiles` section, so an older build
|
|
537
|
+
ignores them rather than rejecting the library):
|
|
538
|
+
|
|
539
|
+
| Key | Meaning |
|
|
540
|
+
| --- | --- |
|
|
541
|
+
| `modelProfiles.kits` | The library: id, name, main profile, fast profile (`null` = off), subagent mode + profile |
|
|
542
|
+
| `modelProfiles.defaultKitId` | The default kit, or `null`. A dangling value heals to `null` |
|
|
543
|
+
| `modelProfiles.kitsVersion` | Migration marker, always `1`. **Downgrading drops kits** (the older build's whitelist does not know the keys); profiles and bindings are unaffected, and `New kit` prefills from your current setup so rebuilding is one Save |
|
|
544
|
+
|
|
483
545
|
## Slash commands (interactive)
|
|
484
546
|
|
|
485
547
|
| Command | Action |
|
|
@@ -505,6 +567,7 @@ a conversation the user never saw onto their screen.
|
|
|
505
567
|
| `/context` | What the context gauge is showing: occupancy split into measured and estimated, the window and **where it came from**, how stale the measurement is, compaction's actual state, and what the session-spend readout includes |
|
|
506
568
|
| `/bg [list\|logs <id> [n]\|stop <id\|all>\|status]` | Background jobs (services and tasks): what is running, its log tail, and how to stop it. `list` is the default |
|
|
507
569
|
| `/update [status\|now\|skip\|off]` | Auto-update: what is running, what is available, where it came from, when it looks again. `now` forces a check past the throttle; `skip` declines the offered version until a newer one appears |
|
|
570
|
+
| `/kit [list\|use <name\|current>]` | Agent kits: what is in effect and where it came from, the whole library with each preset's three connections, or apply one for **this session**. `/kit use current` releases it. Never writes the config file |
|
|
508
571
|
| `/theme <auto\|warm\|cool\|light>` | Switch the color theme live (persisted) |
|
|
509
572
|
| `/expand [n]` | Expand / collapse the n-th-from-last (default last) tool card |
|
|
510
573
|
| `/copy` | Copy the last answer to the clipboard |
|
|
@@ -528,6 +591,7 @@ completes, `Up` / `Down` moves the selection, `Esc` closes it.
|
|
|
528
591
|
| `Shift+Tab` | Toggle **plan mode** (`BUILD` <-> `PLAN`). Equivalent: `/plan`. On Windows this needs a console flag Node < 22.17 does not set; aragon sets it at startup — see [Requirements](#requirements) |
|
|
529
592
|
| `Ctrl+P` | The same toggle, over a channel no Windows console can swallow. Use it if `Shift+Tab` does nothing |
|
|
530
593
|
| `Ctrl+I` | **Build the project index** (bundled `project-indexer` skill). Asks for confirmation, then runs as a normal agent turn that writes `.claude-index/` and updates `CLAUDE.md`. On terminals without keyboard enhancement `Ctrl+I` is the Tab byte and completes input instead |
|
|
594
|
+
| `Ctrl+K` | **Agent kits**: pick a model preset — main / subagent / fast profile in one choice — for **this session only**. Nothing is written to the config file. `Current setup` (the first row) releases the preset and returns to the file's bindings. See [Agent kits](#agent-kits) |
|
|
531
595
|
| `Esc` | Close an overlay or popup; otherwise ask for interruption confirmation. While idle, cancel pending auto-continuation |
|
|
532
596
|
| `Esc` ×2 | Interrupt the run when pressed within 1.5 seconds. Other keys or closing a menu cancel the first press |
|
|
533
597
|
| `Esc` again after interruption | **Force-stop** a run that is still stopping, kill foreground shell commands and return to `idle`; background services remain running |
|
|
@@ -536,7 +600,7 @@ completes, `Up` / `Down` moves the selection, `Esc` closes it.
|
|
|
536
600
|
| `Ctrl+L` | **Branch this session into a new terminal**: opens a PowerShell (Windows) or bash (macOS/Linux) window at the current `/cwd` and runs `aragon --resume <id>` there, so the branch gets its own id and the session you branched from is untouched. Works while the agent is running - the child inherits the last settled turn. Same code path as `/branch` |
|
|
537
601
|
| `Ctrl+R` | Redraw the frame (moved here from `Ctrl+L`, which is now the branch key) |
|
|
538
602
|
| `Ctrl+T` | Show / hide thinking (**off by default**) for the entire session |
|
|
539
|
-
| `Ctrl+O` | Expand / collapse the most recent tool, team, compaction or **service** card — including a diff or a service's log tail. In native scroll mode printed rows cannot change, so it **prints an expanded copy** below instead |
|
|
603
|
+
| `Ctrl+O` | Expand / collapse the most recent tool, team, compaction or **service** card — including a diff or a service's log tail. In native scroll mode printed rows cannot change, so it **prints an expanded copy** below instead. When no card is asking for the key it re-prints the **last finished answer** in full — the same escape hatch for a reply taller than the region |
|
|
540
604
|
| Terminal scrolling (native mode, the default) | The wheel, the scrollbar, `Shift+PgUp` / `Shift+PgDn`, text selection and in-terminal search are all your terminal's own — see [Scrolling](#scrolling) |
|
|
541
605
|
| `PgUp` / `PgDn` | Page through an open overlay; in full-screen mode also scroll the transcript a page |
|
|
542
606
|
| `Shift+↑` / `Shift+↓` | Scroll the transcript a line (full-screen mode only) |
|
|
@@ -545,7 +609,7 @@ completes, `Up` / `Down` moves the selection, `Esc` closes it.
|
|
|
545
609
|
| `Home` / `End`, `Ctrl+A` / `Ctrl+E` | Cursor to line start / end |
|
|
546
610
|
| `Alt/Ctrl+←` / `→` | Word-wise cursor jump |
|
|
547
611
|
| `Ctrl+W`, `Alt+Backspace` | Delete the previous word |
|
|
548
|
-
| `Ctrl+U` / `
|
|
612
|
+
| `Ctrl+U` / `Alt+K` | Kill to line start / end (`Alt+K` was `Ctrl+K`, which now opens the kit picker) |
|
|
549
613
|
| `Up` / `Down` | 按视觉行移动;空草稿或正在浏览历史时,才在边界召回历史,避免覆盖新草稿 |
|
|
550
614
|
| Paste | Inserted up to 6 lines / 400 chars; larger collapses to `[Pasted text #1 +15 lines]`, which deletes as one unit and expands in full when you send. Escape sequences are removed as sequences (so a coloured log arrives as its text, not as `[38;2;18;52;86m`), and a TAB is kept in the payload but laid out at the terminal's 8-column tab stops, so the draft and the echoed message line up. `--no-paste` restores the pre-0.6.3 behaviour |
|
|
551
615
|
| `?` | Open help (empty input) |
|
|
@@ -810,24 +874,56 @@ gnome-terminal and friends.
|
|
|
810
874
|
same paragraph twice. Scrolling up to read earlier output while a reply streams is
|
|
811
875
|
safe; how the viewport behaves when new output arrives is your terminal's own
|
|
812
876
|
"scroll on output" setting. A message you have just sent is therefore on the screen
|
|
813
|
-
only
|
|
877
|
+
only while the region can still show it: it reaches the scrollback when the region
|
|
878
|
+
cannot show it any more, when ten newer entries push it out, or when you exit.
|
|
879
|
+
- **A reply taller than the region is complete, not clipped.** The conversation only has
|
|
880
|
+
`rows - 6` rows of the screen (24 of them in a 30-row terminal: one header row, the
|
|
881
|
+
three-row composer slot and the status line are not conversation), so a long answer cannot
|
|
882
|
+
fit. The rows the region hides above its own top edge are printed into your scrollback as
|
|
883
|
+
they become hidden, so **`scrollback + screen` covers the whole answer exactly once and in
|
|
884
|
+
order** - scroll up and the beginning is there, the way it would be for `cat`. Three
|
|
885
|
+
consequences worth knowing:
|
|
886
|
+
- **A finished answer is complete immediately.** The rows are given back the moment the
|
|
887
|
+
answer stops streaming, not after five more turns and not only after you exit. While it
|
|
888
|
+
is still streaming the head keeps being clipped, because a printed row can never be
|
|
889
|
+
rewritten and a streaming line can still re-wrap (a code fence closing, a heading
|
|
890
|
+
gaining its rule); the rest is printed the instant it settles.
|
|
891
|
+
- **Nothing on the screen moves.** The rows leave through the top of the box exactly as
|
|
892
|
+
they did before, so the visible frame is unchanged - the head simply exists one wheel
|
|
893
|
+
turn up instead of nowhere.
|
|
894
|
+
- **`Ctrl+O` gives you a second copy on demand.** With no expandable card in the
|
|
895
|
+
conversation it re-prints the last finished answer in full below, which is also the way
|
|
896
|
+
to re-read one that has a window resize behind it.
|
|
814
897
|
- **Printed rows are final once they leave the screen.** A row that has scrolled into
|
|
815
898
|
your scrollback is never rewritten, and it is wrapped to the width at which it was
|
|
816
899
|
printed, so a later window resize does not re-wrap it. The rows still *on* the screen
|
|
817
900
|
are redrawn in place when the layout has to move them (a reply landing, a draft
|
|
818
|
-
getting taller), at the same width, without
|
|
819
|
-
(thinking) and `/theme` affect only *new* messages,
|
|
820
|
-
expanded copy of a tool card instead of expanding the old one.
|
|
821
|
-
`---- conversation cleared ----` separator; it cannot erase
|
|
901
|
+
getting taller, the conversation outgrowing the region), at the same width, without
|
|
902
|
+
duplicating anything. So `Ctrl+T` (thinking) and `/theme` affect only *new* messages,
|
|
903
|
+
and `Ctrl+O` prints an expanded copy of a tool card instead of expanding the old one.
|
|
904
|
+
`/clear` prints a `---- conversation cleared ----` separator; it cannot erase
|
|
905
|
+
scrollback. Resizing in the middle of a long answer is the one case where rows can be
|
|
906
|
+
printed twice: the rows above the seam keep the width they were measured at and the rest
|
|
907
|
+
are printed again at the new width behind a `[-- reflowed to N columns ... --]` marker,
|
|
908
|
+
because row numbers do not survive a re-flow. Nothing is lost in that case, which is the
|
|
909
|
+
property that matters.
|
|
822
910
|
- **The interface fills the terminal.** The header is the first row of the screen, the
|
|
823
911
|
input box and status line are the last two, and everything between them belongs to
|
|
824
|
-
the conversation
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
912
|
+
the conversation. A reply is written **downward from under the header**, the way a
|
|
913
|
+
terminal prints: while the conversation is shorter than the region the unused rows
|
|
914
|
+
stay *below* it, and once it is taller the newest line takes the row above the input
|
|
915
|
+
box and the block moves up one row per new line. The hand-over between those two
|
|
916
|
+
layouts is a one-off downward move, not a smooth blend while an answer is streaming:
|
|
917
|
+
the height the switch is decided from is then an estimate, so the block changes over a
|
|
918
|
+
few rows before it would really have filled the region (three rows on a 30-row
|
|
919
|
+
terminal), and only then starts scrolling. It happens once per crossing, never
|
|
920
|
+
repeatedly. A settled conversation is measured instead (the same measurement the rows
|
|
921
|
+
above are printed from), so the switch lands exactly where the content stops fitting.
|
|
922
|
+
Three things are still
|
|
923
|
+
true. A terminal that cannot be measured (`/perf` shows `anchor bottom via fill`, or
|
|
924
|
+
`none` when the probe is unavailable) is filled to the bottom once at startup, which
|
|
925
|
+
scrolls your screen up by a page — that one-off flip is the price of pinning without a
|
|
926
|
+
cursor query. Making the window narrower or shorter drops
|
|
831
927
|
the geometry until the next measurement (a couple of hundred milliseconds) instead of
|
|
832
928
|
guessing: new output parks under the last printed line until the composer pins itself
|
|
833
929
|
again, and old content is neither re-wrapped nor redrawn. `Ctrl+G`, `/help` and the
|
|
@@ -840,8 +936,10 @@ gnome-terminal and friends.
|
|
|
840
936
|
elapsed time. The tail underneath them is drawn with the same cards the full-screen
|
|
841
937
|
mode uses, so a running tool or team card looks identical in both modes. The band
|
|
842
938
|
takes **every row between the header and the composer**: the rail spans the full
|
|
843
|
-
height above the input box instead of stopping partway down, the tail column
|
|
844
|
-
|
|
939
|
+
height above the input box instead of stopping partway down, the tail column owns
|
|
940
|
+
every row between the roster and the composer (a tail that fits is drawn from its top
|
|
941
|
+
row down, a taller one keeps its newest row against the input box), and the printed
|
|
942
|
+
conversation above always keeps the full width.
|
|
845
943
|
Below 76 columns there is no room for the rail and the status line's `todo` chip
|
|
846
944
|
takes over; `/perf`'s `band:` line says which of the six reasons applied.
|
|
847
945
|
- **If something looks wrong**, `/perf` has a `scrollback` line (`guardHits`,
|
|
@@ -850,14 +948,17 @@ gnome-terminal and friends.
|
|
|
850
948
|
The `mode native` line carries `top <n>`, the canvas's first screen row. A `band:`
|
|
851
949
|
line reports the panel band - `rail=<rows>x<cols> team=<rows> tail=<rows>
|
|
852
950
|
pool=<rows> cols=<contentCols>/<previewCols> reason=<visible|empty|disabled|`
|
|
853
|
-
`narrow|short|overlay> held=<rail>/<team> window=<start>/<n>` - which is the
|
|
854
|
-
place that says why a panel is not on screen.
|
|
951
|
+
`narrow|short|overlay> held=<rail>/<team> window=<start>/<n> head=<rows>` - which is the
|
|
952
|
+
only place that says why a panel is not on screen.
|
|
855
953
|
`gap` is the blank space above the content and is **always 0** now (the region is the
|
|
856
954
|
whole screen), `canvas` is the canvas height including the live region, `pool` is the
|
|
857
955
|
band's height (the rows between the header and the composer) and `window=<start>/<n>`
|
|
858
956
|
is the tail window: the first entry on screen and how many entries it holds. That
|
|
859
957
|
number is the honest answer to "why is a message I can see not in my scrollback yet"
|
|
860
958
|
- the region shows ten entries, and an entry is printed when it leaves them.
|
|
959
|
+
`head=<rows>` counts the rows the region could not show and handed back to your
|
|
960
|
+
scrollback instead, so a wide `window=` with a rising `head=` is the healthy state of a
|
|
961
|
+
long answer and not a leak.
|
|
861
962
|
`scrolled` counts the rows that have moved into your scrollback.
|
|
862
963
|
The one-step escape hatch is `aragon config set scrollMode app`, or `--scroll-mode
|
|
863
964
|
app` for a single run.
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type ModelSettingsSnapshot } from './model-profile-settings.js';
|
|
2
|
+
import { type ActiveKit } from '../config/kit-resolution.js';
|
|
3
|
+
import type { KitApplyResult } from '../config/model-kits.js';
|
|
2
4
|
import type { ModelSettingsDraft, ModelSettingsPatch, ModelSettingsSaveResult } from '../config/model-profile-store.js';
|
|
3
5
|
import { type CompactionIdentity } from '../compaction/memory-identity.js';
|
|
4
6
|
/**
|
|
@@ -629,6 +631,33 @@ export declare class AgentController {
|
|
|
629
631
|
saveModelSettingsPatch(patch: ModelSettingsPatch): ModelSettingsSaveResult;
|
|
630
632
|
/** Refresh both role connections and credentials while retaining conversation state. */
|
|
631
633
|
reloadModelSettings(): ModelSettingsSaveResult;
|
|
634
|
+
/**
|
|
635
|
+
* The narrow port `agent/kit-apply.ts` runs against (agent-model-kits §6.3).
|
|
636
|
+
*
|
|
637
|
+
* A LITERAL ADAPTER RATHER THAN `this`: the port's member names (`isBusy`,
|
|
638
|
+
* `hasKey`, `preview`) are deliberately NOT the controller's names, because a
|
|
639
|
+
* port that shadows a public method is one signature change away from silently
|
|
640
|
+
* calling itself. Three of the five members are one-liners onto existing
|
|
641
|
+
* methods; nothing here is new state.
|
|
642
|
+
*/
|
|
643
|
+
private kitApplyPort;
|
|
644
|
+
/**
|
|
645
|
+
* Apply a kit for THIS SESSION ONLY (D-7 / I-5): no commit, ever.
|
|
646
|
+
*
|
|
647
|
+
* Failed applies leave the live config and the file untouched (I-10), because
|
|
648
|
+
* the whole operation is one prepare-and-apply with no commit in between.
|
|
649
|
+
*/
|
|
650
|
+
applySessionKit(kitId: string): KitApplyResult;
|
|
651
|
+
/** Release the session kit and return to the config file's bindings (RV-P1-6). */
|
|
652
|
+
clearSessionKit(): KitApplyResult;
|
|
653
|
+
/**
|
|
654
|
+
* Which kit is in effect right now (session > default > disk bindings).
|
|
655
|
+
*
|
|
656
|
+
* DERIVED, never stored (D-5): the header chip, the settings root row and
|
|
657
|
+
* `/kit` all read this, so there is exactly one answer to "which preset am I
|
|
658
|
+
* running" and no copy of it that can drift.
|
|
659
|
+
*/
|
|
660
|
+
getActiveKit(): ActiveKit;
|
|
632
661
|
/** Prebuild all potentially failing model/prompt work before committing the file. */
|
|
633
662
|
prepareModelSettingsSnapshot(config: CliConfig): ModelSettingsSnapshot;
|
|
634
663
|
/** Apply prepared state synchronously, without rebuilding the controller or its history. */
|
package/dist/agent/controller.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { createModelSettingsDraft, saveModelSettings, reloadModelSettings, } from './model-profile-settings.js';
|
|
1
|
+
import { createModelSettingsDraft, previewModelSettings, saveModelSettings, reloadModelSettings, } from './model-profile-settings.js';
|
|
2
|
+
import { applySessionKit as applySessionKitPort, clearSessionKit as clearSessionKitPort } from './kit-apply.js';
|
|
3
|
+
import { resolveActiveKit } from '../config/kit-resolution.js';
|
|
2
4
|
import { randomUUID } from 'node:crypto';
|
|
3
5
|
import { join } from 'node:path';
|
|
4
6
|
import { verifyCompactionIdentity } from '../compaction/memory-identity.js';
|
|
@@ -1680,6 +1682,45 @@ export class AgentController {
|
|
|
1680
1682
|
}
|
|
1681
1683
|
/** Refresh both role connections and credentials while retaining conversation state. */
|
|
1682
1684
|
reloadModelSettings() { return reloadModelSettings(this); }
|
|
1685
|
+
/**
|
|
1686
|
+
* The narrow port `agent/kit-apply.ts` runs against (agent-model-kits §6.3).
|
|
1687
|
+
*
|
|
1688
|
+
* A LITERAL ADAPTER RATHER THAN `this`: the port's member names (`isBusy`,
|
|
1689
|
+
* `hasKey`, `preview`) are deliberately NOT the controller's names, because a
|
|
1690
|
+
* port that shadows a public method is one signature change away from silently
|
|
1691
|
+
* calling itself. Three of the five members are one-liners onto existing
|
|
1692
|
+
* methods; nothing here is new state.
|
|
1693
|
+
*/
|
|
1694
|
+
kitApplyPort() {
|
|
1695
|
+
return {
|
|
1696
|
+
getConfig: () => this.config,
|
|
1697
|
+
isBusy: () => this.isModelSettingsBusy(),
|
|
1698
|
+
hasKey: (providerId, role) => this.hasApiKey(providerId, role),
|
|
1699
|
+
createDraft: () => this.createModelSettingsDraft(),
|
|
1700
|
+
preview: (draft) => previewModelSettings(draft, this),
|
|
1701
|
+
};
|
|
1702
|
+
}
|
|
1703
|
+
/**
|
|
1704
|
+
* Apply a kit for THIS SESSION ONLY (D-7 / I-5): no commit, ever.
|
|
1705
|
+
*
|
|
1706
|
+
* Failed applies leave the live config and the file untouched (I-10), because
|
|
1707
|
+
* the whole operation is one prepare-and-apply with no commit in between.
|
|
1708
|
+
*/
|
|
1709
|
+
applySessionKit(kitId) {
|
|
1710
|
+
return applySessionKitPort({ kitId, port: this.kitApplyPort() });
|
|
1711
|
+
}
|
|
1712
|
+
/** Release the session kit and return to the config file's bindings (RV-P1-6). */
|
|
1713
|
+
clearSessionKit() {
|
|
1714
|
+
return clearSessionKitPort(this.kitApplyPort());
|
|
1715
|
+
}
|
|
1716
|
+
/**
|
|
1717
|
+
* Which kit is in effect right now (session > default > disk bindings).
|
|
1718
|
+
*
|
|
1719
|
+
* DERIVED, never stored (D-5): the header chip, the settings root row and
|
|
1720
|
+
* `/kit` all read this, so there is exactly one answer to "which preset am I
|
|
1721
|
+
* running" and no copy of it that can drift.
|
|
1722
|
+
*/
|
|
1723
|
+
getActiveKit() { return resolveActiveKit(this.config); }
|
|
1683
1724
|
/** Prebuild all potentially failing model/prompt work before committing the file. */
|
|
1684
1725
|
prepareModelSettingsSnapshot(config) {
|
|
1685
1726
|
return { config, model: { providerId: config.provider, modelId: config.model,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The session exit: apply a kit WITHOUT writing the config file (§4.2 exit B).
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY - `src/agent/**` is inside `glyphs.test.ts::inScope`.
|
|
5
|
+
*
|
|
6
|
+
* WHY A NARROW PORT AND NOT THE CONTROLLER: the same reason
|
|
7
|
+
* `ModelSettingsController` exists next door - a five-method port can be stubbed
|
|
8
|
+
* by one object literal in a test, so every refusal code is reachable without a
|
|
9
|
+
* controller, a provider, or a disk. The controller supplies the real one.
|
|
10
|
+
*
|
|
11
|
+
* EVERY PATH EITHER APPLIES OR CHANGES NOTHING (I-10). There is no partial state
|
|
12
|
+
* to clean up, because the whole thing is one `preview` call: the reused prepare
|
|
13
|
+
* chain builds the snapshot before it touches the live config, exactly as a save
|
|
14
|
+
* does - only the commit is missing.
|
|
15
|
+
*/
|
|
16
|
+
import type { CliConfig } from '../config/schema.js';
|
|
17
|
+
import type { ModelSettingsDraft, ModelSettingsSaveResult } from '../config/model-profile-store.js';
|
|
18
|
+
import type { HasApiKey } from '../team/model.js';
|
|
19
|
+
import { type KitApplyResult, type KitNote } from '../config/model-kits.js';
|
|
20
|
+
export interface KitApplyPort {
|
|
21
|
+
getConfig(): CliConfig;
|
|
22
|
+
/** `controller.isModelSettingsBusy` - a run, a team, a review, a compaction. */
|
|
23
|
+
isBusy(): boolean;
|
|
24
|
+
/** `controller.hasApiKey`: two-parameter form, so a role-scoped key is seen. */
|
|
25
|
+
hasKey: HasApiKey;
|
|
26
|
+
/** `controller.createModelSettingsDraft`: already seeded with `sessionKit`. */
|
|
27
|
+
createDraft(): ModelSettingsDraft;
|
|
28
|
+
/** `previewModelSettings`: validate + prepare + apply, never commit. */
|
|
29
|
+
preview(draft: ModelSettingsDraft): ModelSettingsSaveResult;
|
|
30
|
+
}
|
|
31
|
+
/** One note, as the sentence the toast shows. Exhaustive by construction. */
|
|
32
|
+
export declare function kitNoteMessage(note: KitNote): string;
|
|
33
|
+
/** Every note for a result, in the order the estimator emitted it. */
|
|
34
|
+
export declare function kitNoteMessages(notes: readonly KitNote[]): string[];
|
|
35
|
+
/** A non-empty sentence for EVERY code, including the two kit-only ones. */
|
|
36
|
+
export declare function kitApplyMessage(result: KitApplyResult): string;
|
|
37
|
+
/**
|
|
38
|
+
* Apply a kit for THIS SESSION ONLY.
|
|
39
|
+
*
|
|
40
|
+
* `kitId` is a resolved id: the `/kit use` command and the picker may accept a
|
|
41
|
+
* name or an `#id` prefix, but resolving those is the command layer's job, so
|
|
42
|
+
* an ambiguous name is reported once, at the surface that accepted it.
|
|
43
|
+
*/
|
|
44
|
+
export declare function applySessionKit(input: {
|
|
45
|
+
kitId: string;
|
|
46
|
+
port: KitApplyPort;
|
|
47
|
+
}): KitApplyResult;
|
|
48
|
+
/**
|
|
49
|
+
* Release the session kit: back to the bindings in the config file (RV-P1-6).
|
|
50
|
+
*
|
|
51
|
+
* WITHOUT THIS the feature is one-way: `ctrl+K` could enter a session kit and
|
|
52
|
+
* nothing could leave it except restarting the process. It shares the session
|
|
53
|
+
* exit's prepare chain, so it inherits every refusal code rather than defining
|
|
54
|
+
* a second set of failure semantics.
|
|
55
|
+
*/
|
|
56
|
+
export declare function clearSessionKit(port: KitApplyPort): KitApplyResult;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The session exit: apply a kit WITHOUT writing the config file (§4.2 exit B).
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY - `src/agent/**` is inside `glyphs.test.ts::inScope`.
|
|
5
|
+
*
|
|
6
|
+
* WHY A NARROW PORT AND NOT THE CONTROLLER: the same reason
|
|
7
|
+
* `ModelSettingsController` exists next door - a five-method port can be stubbed
|
|
8
|
+
* by one object literal in a test, so every refusal code is reachable without a
|
|
9
|
+
* controller, a provider, or a disk. The controller supplies the real one.
|
|
10
|
+
*
|
|
11
|
+
* EVERY PATH EITHER APPLIES OR CHANGES NOTHING (I-10). There is no partial state
|
|
12
|
+
* to clean up, because the whole thing is one `preview` call: the reused prepare
|
|
13
|
+
* chain builds the snapshot before it touches the live config, exactly as a save
|
|
14
|
+
* does - only the commit is missing.
|
|
15
|
+
*/
|
|
16
|
+
import { emptyModelProfiles } from '../config/model-profiles.js';
|
|
17
|
+
import { findKit, kitCandidate, kitSlotConnections } from '../config/model-kits.js';
|
|
18
|
+
import { ALL_SLOTS } from '../config/kit-resolution.js';
|
|
19
|
+
import { modelSettingsResultMessage } from './model-profile-settings.js';
|
|
20
|
+
/**
|
|
21
|
+
* Kit-specific wording, layered over `modelSettingsResultMessage` (RV-P1-3).
|
|
22
|
+
*
|
|
23
|
+
* ONLY the codes whose save-screen text would send the user to the wrong screen
|
|
24
|
+
* are overridden. Everything else keeps the wording the settings screen already
|
|
25
|
+
* uses, so a `read_failed` reads the same wherever it appears.
|
|
26
|
+
*/
|
|
27
|
+
const KIT_CODE_MESSAGES = {
|
|
28
|
+
busy: 'Wait for the current run to finish before switching kit.',
|
|
29
|
+
conflict: 'Settings changed elsewhere. Reload before switching kit.',
|
|
30
|
+
write_failed: 'Could not write config. Your settings are unchanged.',
|
|
31
|
+
apply_failed: 'Could not apply the kit to this session. Nothing changed.',
|
|
32
|
+
kit_missing: 'That kit is not in your library. Create it in /settings (Settings > Kit pack).',
|
|
33
|
+
kit_references_missing_profile: 'That kit points at a profile that no longer exists.'
|
|
34
|
+
+ ' Fix it in /settings (Settings > Kit pack).',
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* The `KitNote` sentences, verbatim (agent-model-kits §4.2).
|
|
38
|
+
*
|
|
39
|
+
* ONE TABLE, THREE CALLERS: the picker, `/kit use`, and any future surface must
|
|
40
|
+
* say the same thing about the same consequence. `fast_review_enabled` is the
|
|
41
|
+
* one that matters most - it is a recurring charge the user did not ask for by
|
|
42
|
+
* name, only by applying a kit that switches the tier on.
|
|
43
|
+
*/
|
|
44
|
+
const KIT_NOTE_MESSAGES = {
|
|
45
|
+
fast_tier_enabled: 'Fast tier enabled by this kit.',
|
|
46
|
+
fast_review_enabled: 'Periodic review is on for this tier (/fast review off to stop it).',
|
|
47
|
+
fast_tier_off: 'Fast tier turned off by this kit.',
|
|
48
|
+
restart_required: 'Fast tier needs a restart to take effect.',
|
|
49
|
+
main_key_missing: 'Main profile has no API key; set one in /settings.',
|
|
50
|
+
subagent_key_missing: 'Subagent profile has no API key; children fall back to main.',
|
|
51
|
+
subagent_fast_unavailable: 'Subagent uses the fast tier, which this kit leaves off; children fall back to main.',
|
|
52
|
+
};
|
|
53
|
+
/** One note, as the sentence the toast shows. Exhaustive by construction. */
|
|
54
|
+
export function kitNoteMessage(note) { return KIT_NOTE_MESSAGES[note]; }
|
|
55
|
+
/** Every note for a result, in the order the estimator emitted it. */
|
|
56
|
+
export function kitNoteMessages(notes) {
|
|
57
|
+
return notes.map(kitNoteMessage);
|
|
58
|
+
}
|
|
59
|
+
/** A non-empty sentence for EVERY code, including the two kit-only ones. */
|
|
60
|
+
export function kitApplyMessage(result) {
|
|
61
|
+
if (result.ok) {
|
|
62
|
+
return result.released
|
|
63
|
+
? 'Kit released; using the config file\'s settings.'
|
|
64
|
+
: 'Kit applied for this session.';
|
|
65
|
+
}
|
|
66
|
+
const code = result.code ?? 'invalid';
|
|
67
|
+
const specific = KIT_CODE_MESSAGES[code];
|
|
68
|
+
if (specific)
|
|
69
|
+
return specific;
|
|
70
|
+
// NO `apply_failed` BRANCH ABOVE: it is in the table, because a stopped apply
|
|
71
|
+
// must say "nothing changed" rather than repeat the save screen's wording.
|
|
72
|
+
// Every remaining code falls through to the shared table, which is what makes
|
|
73
|
+
// "there is a code with no sentence" unrepresentable instead of merely unlikely.
|
|
74
|
+
return modelSettingsResultMessage({ ok: false, persisted: false, status: 'rejected',
|
|
75
|
+
code: code });
|
|
76
|
+
}
|
|
77
|
+
/** Slots whose referenced profile is gone (I-8 / AC-39). */
|
|
78
|
+
function missingSlots(kit, draft) {
|
|
79
|
+
const connections = kitSlotConnections(draft.profiles, kit);
|
|
80
|
+
const missing = [];
|
|
81
|
+
if (!connections.main)
|
|
82
|
+
missing.push('main');
|
|
83
|
+
if (kit.fastProfileId !== null && !connections.fast)
|
|
84
|
+
missing.push('fast');
|
|
85
|
+
if (kit.subagent.model === 'profile' && !connections.subagent.name)
|
|
86
|
+
missing.push('subagent');
|
|
87
|
+
return missing;
|
|
88
|
+
}
|
|
89
|
+
function missingIssue(slot, kit) {
|
|
90
|
+
const field = slot === 'subagent' ? 'subagent.modelProfileId' : `${slot}ProfileId`;
|
|
91
|
+
return { path: `modelProfiles.kits[${kit.id}].${field}`,
|
|
92
|
+
code: 'missing_reference',
|
|
93
|
+
message: `Kit "${kit.name}" references a profile that no longer exists (${slot}).` };
|
|
94
|
+
}
|
|
95
|
+
function keyMissing(port, draft, id, role) {
|
|
96
|
+
const entry = id ? draft.profiles?.entries.find((candidate) => candidate.id === id) : undefined;
|
|
97
|
+
if (!entry)
|
|
98
|
+
return false;
|
|
99
|
+
return !entry.apiKey && !port.hasKey(entry.provider, role);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Everything the user is entitled to know about the apply (§4.2 `KitNote`).
|
|
103
|
+
*
|
|
104
|
+
* Computed from the config AS IT WAS BEFORE, because half of these are about
|
|
105
|
+
* transitions ("the tier just came ON") and the other half are still true after.
|
|
106
|
+
*/
|
|
107
|
+
function kitNotes(input) {
|
|
108
|
+
const { before, draft, kit, port } = input;
|
|
109
|
+
const notes = [];
|
|
110
|
+
const wasEnabled = before.fast.enabled;
|
|
111
|
+
if (kit.fastProfileId !== null && !wasEnabled)
|
|
112
|
+
notes.push('fast_tier_enabled');
|
|
113
|
+
// The honest disclosure of a real cost (F-23): a fast tier with review on
|
|
114
|
+
// spends money on its own, every N turns, without being asked.
|
|
115
|
+
if (kit.fastProfileId !== null && !wasEnabled && before.fast.review) {
|
|
116
|
+
notes.push('fast_review_enabled');
|
|
117
|
+
}
|
|
118
|
+
if (kit.fastProfileId === null && wasEnabled)
|
|
119
|
+
notes.push('fast_tier_off');
|
|
120
|
+
if (input.restartRequired)
|
|
121
|
+
notes.push('restart_required');
|
|
122
|
+
if (keyMissing(port, draft, kit.mainProfileId, 'main'))
|
|
123
|
+
notes.push('main_key_missing');
|
|
124
|
+
if (kit.subagent.model === 'profile'
|
|
125
|
+
&& keyMissing(port, draft, kit.subagent.modelProfileId, 'main')) {
|
|
126
|
+
notes.push('subagent_key_missing');
|
|
127
|
+
}
|
|
128
|
+
if (kit.subagent.model === 'fast' && kit.fastProfileId === null) {
|
|
129
|
+
notes.push('subagent_fast_unavailable');
|
|
130
|
+
}
|
|
131
|
+
return notes;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Apply a kit for THIS SESSION ONLY.
|
|
135
|
+
*
|
|
136
|
+
* `kitId` is a resolved id: the `/kit use` command and the picker may accept a
|
|
137
|
+
* name or an `#id` prefix, but resolving those is the command layer's job, so
|
|
138
|
+
* an ambiguous name is reported once, at the surface that accepted it.
|
|
139
|
+
*/
|
|
140
|
+
export function applySessionKit(input) {
|
|
141
|
+
const { kitId, port } = input;
|
|
142
|
+
if (port.isBusy())
|
|
143
|
+
return { ok: false, code: 'busy', notes: [] };
|
|
144
|
+
const before = port.getConfig();
|
|
145
|
+
const draft = port.createDraft();
|
|
146
|
+
const library = draft.profiles ?? emptyModelProfiles();
|
|
147
|
+
const kit = findKit(library, kitId);
|
|
148
|
+
if (!kit)
|
|
149
|
+
return { ok: false, code: 'kit_missing', notes: [] };
|
|
150
|
+
const missing = missingSlots(kit, draft);
|
|
151
|
+
if (missing.length) {
|
|
152
|
+
return { ok: false, code: 'kit_references_missing_profile', notes: [],
|
|
153
|
+
issues: missing.map((slot) => missingIssue(slot, kit)) };
|
|
154
|
+
}
|
|
155
|
+
const patch = kitCandidate({ library, kit });
|
|
156
|
+
draft.profiles = patch.modelProfiles;
|
|
157
|
+
draft.patch = { ...draft.patch, team: patch.team, fast: patch.fast };
|
|
158
|
+
// I-13: a kit that turns the tier OFF must NOT project the fast role, or
|
|
159
|
+
// `projectModelProfiles`'s `continue` would copy the DISK fast endpoint into
|
|
160
|
+
// the live config and the kit would read as "restore, then switch off".
|
|
161
|
+
draft.activateRoles = kit.fastProfileId === null ? ['main'] : ['main', 'fast'];
|
|
162
|
+
draft.sessionKit = { id: kit.id, held: [...ALL_SLOTS] };
|
|
163
|
+
// THE MARKER, not a heuristic: this draft IS the kit, so `releasedSlots` must
|
|
164
|
+
// not read the apply's own writes as user decisions (§4.2 exit B).
|
|
165
|
+
draft.sessionKitFresh = true;
|
|
166
|
+
const result = port.preview(draft);
|
|
167
|
+
if (!result.ok) {
|
|
168
|
+
return { ok: false, code: result.code ?? 'invalid', notes: [],
|
|
169
|
+
...(result.issues ? { issues: result.issues } : {}) };
|
|
170
|
+
}
|
|
171
|
+
const notes = kitNotes({ before, draft, kit, port,
|
|
172
|
+
...(result.restartRequired ? { restartRequired: true } : {}) });
|
|
173
|
+
return { ok: true, notes };
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Release the session kit: back to the bindings in the config file (RV-P1-6).
|
|
177
|
+
*
|
|
178
|
+
* WITHOUT THIS the feature is one-way: `ctrl+K` could enter a session kit and
|
|
179
|
+
* nothing could leave it except restarting the process. It shares the session
|
|
180
|
+
* exit's prepare chain, so it inherits every refusal code rather than defining
|
|
181
|
+
* a second set of failure semantics.
|
|
182
|
+
*/
|
|
183
|
+
export function clearSessionKit(port) {
|
|
184
|
+
if (port.isBusy())
|
|
185
|
+
return { ok: false, code: 'busy', notes: [] };
|
|
186
|
+
const before = port.getConfig();
|
|
187
|
+
const draft = port.createDraft();
|
|
188
|
+
const file = draft.baseline;
|
|
189
|
+
draft.sessionKit = null;
|
|
190
|
+
draft.patch = {
|
|
191
|
+
...draft.patch,
|
|
192
|
+
team: { model: file.team.model, modelProfileId: file.team.modelProfileId },
|
|
193
|
+
// The disk fast section, KEY BY KEY: `enabled` restores the tier state and
|
|
194
|
+
// the other three restore the endpoints the file names, so no trace of the
|
|
195
|
+
// kit's fast choice survives in the live config.
|
|
196
|
+
fast: { enabled: file.fast.enabled, provider: file.fast.provider,
|
|
197
|
+
model: file.fast.model, baseUrl: file.fast.baseUrl },
|
|
198
|
+
};
|
|
199
|
+
draft.activateRoles = ['main', 'fast'];
|
|
200
|
+
const result = port.preview(draft);
|
|
201
|
+
if (!result.ok)
|
|
202
|
+
return { ok: false, code: result.code ?? 'invalid', notes: [] };
|
|
203
|
+
const notes = [];
|
|
204
|
+
if (result.restartRequired)
|
|
205
|
+
notes.push('restart_required');
|
|
206
|
+
if (before.fast.enabled && !file.fast.enabled)
|
|
207
|
+
notes.push('fast_tier_off');
|
|
208
|
+
return { ok: true, released: true, notes };
|
|
209
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ModelRef } from '@aragon-agent/core';
|
|
2
2
|
import { type CliConfig } from '../config/schema.js';
|
|
3
3
|
import { type ModelSettingsDraft, type ModelSettingsSaveResult } from '../config/model-profile-store.js';
|
|
4
|
+
import { type KitSlot } from '../config/model-kits.js';
|
|
4
5
|
/** Fully prepared live state; applying it performs no input parsing or disk access. */
|
|
5
6
|
export interface ModelSettingsSnapshot {
|
|
6
7
|
config: CliConfig;
|
|
@@ -22,10 +23,37 @@ export interface ModelSettingsController {
|
|
|
22
23
|
export declare function createModelSettingsDraft(live: CliConfig, liveRevision: number): ModelSettingsDraft;
|
|
23
24
|
/** Map stable failure codes to safe UI text; never include parser or SDK error messages. */
|
|
24
25
|
export declare function modelSettingsResultMessage(result: ModelSettingsSaveResult): string;
|
|
26
|
+
/**
|
|
27
|
+
* The slots THIS transaction explicitly decided, and that therefore leave the
|
|
28
|
+
* session kit (§4.3 / D-6).
|
|
29
|
+
*
|
|
30
|
+
* Released BY SLOT, never wholesale: a user who only retargeted the subagent
|
|
31
|
+
* must not lose the main and fast choices the kit made for this session, and a
|
|
32
|
+
* user who retargeted main must not have it silently ignored. Per-slot is the
|
|
33
|
+
* only rule shaped like the data (three independent slots).
|
|
34
|
+
*/
|
|
35
|
+
export declare function releasedSlots(draft: ModelSettingsDraft): KitSlot[];
|
|
25
36
|
/** Validate and prepare everything before the one synchronous, atomic disk commit. */
|
|
26
37
|
export declare function saveModelSettings(input: {
|
|
27
38
|
draft: ModelSettingsDraft;
|
|
28
39
|
controller: ModelSettingsController;
|
|
29
40
|
}): ModelSettingsSaveResult;
|
|
41
|
+
/**
|
|
42
|
+
* The SAME prepare chain, without the commit: a session kit (§4.2 exit B).
|
|
43
|
+
*
|
|
44
|
+
* NO `diskRevision` GATE: nothing is written, so a save that landed elsewhere is
|
|
45
|
+
* not this operation's business. It still refuses while work is in flight
|
|
46
|
+
* (`busy`) and still refuses a draft whose disk could not be read, so "nothing
|
|
47
|
+
* changed" is guaranteed for every other failure too (I-10).
|
|
48
|
+
*/
|
|
49
|
+
export declare function previewModelSettings(draft: ModelSettingsDraft, controller: ModelSettingsController): ModelSettingsSaveResult;
|
|
50
|
+
/**
|
|
51
|
+
* Apply a prepared snapshot.
|
|
52
|
+
*
|
|
53
|
+
* `persisted` HAS NO DEFAULT (RV-P1-4): the one attribute that distinguishes
|
|
54
|
+
* "the file now says this" from "only this process says this" is not something
|
|
55
|
+
* a call site should be able to forget.
|
|
56
|
+
*/
|
|
57
|
+
export declare function applyPreparedSnapshot(controller: ModelSettingsController, snapshot: ModelSettingsSnapshot, persisted: boolean): ModelSettingsSaveResult;
|
|
30
58
|
/** Reload the complete model snapshot; invalid disk content never replaces a live session. */
|
|
31
59
|
export declare function reloadModelSettings(controller: ModelSettingsController): ModelSettingsSaveResult;
|