samagotchi 0.137.0 → 0.138.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -1
- data/docs/cli.md +3 -3
- data/docs/context.md +7 -9
- data/docs/internals/participants.md +2 -1
- data/docs/plugins.md +2 -2
- data/docs/sessions.md +7 -7
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/guardrails/mise_paths.rb +36 -0
- data/lib/samagotchi/guardrails/review_paths.rb +1 -30
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/public/app.js +17 -18
- data/lib/samagotchi/web/public/chains.js +6 -5
- data/lib/samagotchi/web/public/composer.css +15 -11
- data/lib/samagotchi/web/public/composer_grip.js +11 -13
- data/lib/samagotchi/web/public/composer_mount.js +2 -2
- data/lib/samagotchi/web/public/context_chips.js +18 -97
- data/lib/samagotchi/web/public/data.js +2 -0
- data/lib/samagotchi/web/public/families_view.js +15 -32
- data/lib/samagotchi/web/public/index.html +121 -97
- data/lib/samagotchi/web/public/info_bar.js +18 -7
- data/lib/samagotchi/web/public/info_bar_model.js +3 -1
- data/lib/samagotchi/web/public/lists_view.js +68 -42
- data/lib/samagotchi/web/public/participants_panel.js +3 -3
- data/lib/samagotchi/web/public/stage_view.js +51 -2
- data/lib/samagotchi/web/session_summary.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 57a409114b8194ae2d17416055b7f52c468e6a2ab5a650d8f36a4b6a0e0b0b3f
|
|
4
|
+
data.tar.gz: 78f20cbc62ab65e54c300bad75ec2478388810c1328679c3c48d3842f80cc0a3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e285adda4382592d1cca2811e05eebdc5999d5808fc0f23a0ff7f73a87db6b4cbb504cbc43647feab56db006b66c7439d075398ef2a8eae6339bae3d42824887
|
|
7
|
+
data.tar.gz: c796ce3b9121c74bf838959f4f1ea97b9213930bd449922f42f617afab92c8eb38f685b7098f9c67c4d20ab1622e51b7fbed99de24a71964159fa892ecec827f
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,34 @@ and commands may change between minor versions. How releases are made:
|
|
|
7
7
|
[docs/releasing.md](docs/releasing.md).
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.138.0] - 2026-10-30
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Web: All sessions' header is the top bar: the sessions pill shows pressed in the all view and takes you back (Esc
|
|
15
|
+
too); the search is a pill, "archived" a toggle chip, the count reads "4 · 5 delegates", the storage path a footer
|
|
16
|
+
under the list.
|
|
17
|
+
- Web: session cards show one mark per state and no status dots: a waiting card a warn outline and "question", an
|
|
18
|
+
error a red badge, "live" only when not waiting; titles one line; an untitled session reads "Untitled" over its
|
|
19
|
+
first message.
|
|
20
|
+
- Web: Stop turn is a button in the running turn's status row (the composer's and the bar's Stop buttons are gone);
|
|
21
|
+
after a reload that knows only that a turn runs, the stage shows that row alone.
|
|
22
|
+
- Web: the session bar reads in three groups (setup, relations, telemetry) with hairlines between, on one line on
|
|
23
|
+
a desktop; the parent chip reads "↑ id", the clock "7m 52s"; continue is a plain button unless archived.
|
|
24
|
+
- Web: the start composer keeps model, ⚙ and 🧰 left and 🖼 and Start right; the keyboard hint shows under the
|
|
25
|
+
placeholder only while the box is focused and empty.
|
|
26
|
+
- Web: the "+ URL" chip is gone (`chi context` still attaches URLs).
|
|
27
|
+
- Web: on a phone a tap no longer leaves a hover tint on a card, a bar button, a question option or a fold.
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- Web: on a phone the sessions search takes a whole line, so its placeholder reads; the start page keeps the
|
|
32
|
+
project chip's words.
|
|
33
|
+
- Web: a session with no turn yet shows no clock, not "0ms".
|
|
34
|
+
- Web: the cards' shadow is no longer cut into a band at the sessions list's edges (light theme).
|
|
35
|
+
|
|
36
|
+
Update with `chi update`, then restart `chi web`.
|
|
37
|
+
|
|
10
38
|
## [0.137.0] - 2026-10-30
|
|
11
39
|
|
|
12
40
|
### Changed
|
|
@@ -5257,7 +5285,8 @@ and long-lived sessions.
|
|
|
5257
5285
|
- A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
|
|
5258
5286
|
hotkey panel that send selected text or the clipboard to your sessions.
|
|
5259
5287
|
|
|
5260
|
-
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.
|
|
5288
|
+
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.138.0...HEAD
|
|
5289
|
+
[0.138.0]: https://github.com/dm1try/samagotchi/compare/v0.137.0...v0.138.0
|
|
5261
5290
|
[0.137.0]: https://github.com/dm1try/samagotchi/compare/v0.136.1...v0.137.0
|
|
5262
5291
|
[0.136.1]: https://github.com/dm1try/samagotchi/compare/v0.136.0...v0.136.1
|
|
5263
5292
|
[0.136.0]: https://github.com/dm1try/samagotchi/compare/v0.135.0...v0.136.0
|
data/docs/cli.md
CHANGED
|
@@ -585,7 +585,7 @@ list --archived` shows them too, marked `[archived]` (`archived: true` in
|
|
|
585
585
|
`--format json`); `chi sessions unarchive ID...` brings them back, and so does
|
|
586
586
|
a message you send to one (the web, an attached terminal, `chi send`), but not
|
|
587
587
|
a delegate's follow-up or a reminder. The web archives from the info bar
|
|
588
|
-
(`archive`, before `stop`);
|
|
588
|
+
(`archive`, before `stop`); the `archived` toggle by the all-sessions search
|
|
589
589
|
finds archived sessions. `/archive` in a terminal leaves the session and
|
|
590
590
|
archives it (an empty session is discarded instead; `chi scratch` refuses
|
|
591
591
|
it). See
|
|
@@ -1563,8 +1563,8 @@ chi share revoke --all # every link to every session
|
|
|
1563
1563
|
far as it fits the plan. It's quoted: …` and the reply quoted line by line. Read it, edit it, send it: the
|
|
1564
1564
|
participant's text reaches the model as a quote under your line, never as your instruction. Don't send "do what
|
|
1565
1565
|
Alex said" instead: that hands their words your tools.
|
|
1566
|
-
- **Your side of it.** In the session bar, the participants chip (`invite` while nobody has a link,
|
|
1567
|
-
participants`, with a green dot while one is on their page) opens the panel: each participant's state, last visit,
|
|
1566
|
+
- **Your side of it.** In the session bar, the participants chip (`participants · invite` while nobody has a link,
|
|
1567
|
+
then `2 participants`, with a green dot while one is on their page) opens the panel: each participant's state, last visit,
|
|
1568
1568
|
spend against their cap, turns and expiry, with **Cap** and **Revoke** (revoking ends their page and stream and
|
|
1569
1569
|
drops their queued comments). **Invite** takes a name, person or agent, the expiry and a cap, and shows the link
|
|
1570
1570
|
once, with Copy, Share (a phone's share sheet) and its QR code. **Participant view ↗** opens their page for this
|
data/docs/context.md
CHANGED
|
@@ -230,8 +230,7 @@ outside: information about your work, never instructions.
|
|
|
230
230
|
The web's session bar shows a chip per source (name · age; a dot when the
|
|
231
231
|
agent hasn't read a change, red after a failed refresh). A chip opens the
|
|
232
232
|
summary, the hint's link, the text and detach (or mute here, for a project
|
|
233
|
-
source). The
|
|
234
|
-
attaches a URL; the web never adds a command.
|
|
233
|
+
source). The web attaches nothing itself: `chi context add <URL>` does.
|
|
235
234
|
|
|
236
235
|
## Waking
|
|
237
236
|
|
|
@@ -325,9 +324,8 @@ It needs [`gh`](https://cli.github.com), logged in (`gh auth login`).
|
|
|
325
324
|
open PR #42"). Not in a `chi scratch` session or a delegate child, and
|
|
326
325
|
quietly nothing without `gh`, a login, a repository or an open PR. Once
|
|
327
326
|
you remove it (`chi context rm`, the chip's detach) it stays removed:
|
|
328
|
-
the next start doesn't bring it back; `chi context add <PR URL>`
|
|
329
|
-
|
|
330
|
-
- `chi context add <PR URL>` and the web's "+ URL" attach any PR.
|
|
327
|
+
the next start doesn't bring it back; `chi context add <PR URL>` does.
|
|
328
|
+
- `chi context add <PR URL>` attaches any PR.
|
|
331
329
|
- `auto_attach` (below) can make it ask first, or attach nothing.
|
|
332
330
|
- The text: title, state, branch, description, reviews, comments (oldest
|
|
333
331
|
first) and checks, every 5 minutes.
|
|
@@ -358,10 +356,10 @@ bundles:
|
|
|
358
356
|
turns the card into a one-line notice. Not here on a PR that is attached
|
|
359
357
|
already changes nothing and says so: `chi context rm pr-42` removes it. A worker restart (idle exit,
|
|
360
358
|
resume) doesn't ask again; once the card has scrolled out of the last 20,
|
|
361
|
-
`/pr-attach 42` or
|
|
359
|
+
`/pr-attach 42` or `chi context add <PR URL>` still attaches it. `/pr-attach` and
|
|
362
360
|
`/pr-decline` act only on a PR offered in that session.
|
|
363
|
-
- `off`: nothing is attached or offered.
|
|
364
|
-
|
|
361
|
+
- `off`: nothing is attached or offered. `chi context add <PR URL>` and line
|
|
362
|
+
links work as before.
|
|
365
363
|
|
|
366
364
|
`true` and `false` (YAML reads a bare `on`/`off` as those) mean `attach` and
|
|
367
365
|
`off`; an unknown value is `attach`, with a warning in the log. The setting
|
|
@@ -392,7 +390,7 @@ links are display only (the model's text stays as it was), and terminals
|
|
|
392
390
|
show the answer as it is.
|
|
393
391
|
|
|
394
392
|
- **Which PR**: the session's attached PRs (the branch's, `chi context add
|
|
395
|
-
<PR URL
|
|
393
|
+
<PR URL>`) and the PR URLs in your messages, newest first (a
|
|
396
394
|
delegate child's task counts: a parent that names the PR there gets links
|
|
397
395
|
in the child's answers). PR URLs in tool output or answers don't count.
|
|
398
396
|
- **Which file**: a file the PR changes, by its path, a renamed file's old
|
|
@@ -549,7 +549,8 @@ fields: a field added to an event later reaches nobody until it is named there.
|
|
|
549
549
|
data's `viewer.id`), at most 50, forgotten when the link ends (a 401 with chi's words), never the owner's `GET
|
|
550
550
|
/api/history` (the owner may open a link in the same browser). It lacks: the `/` list and plugin pills (a review turn
|
|
551
551
|
refuses `/` lines), the delivery line and its steer/queue choices (every comment is `POST …/turn`, a queued review
|
|
552
|
-
turn; Alt+Enter is Enter), the model picker, Stop turn
|
|
552
|
+
turn; Alt+Enter is Enter), the model picker, Stop turn (the owner page's sits in its stage's status row; this
|
|
553
|
+
page has no stage), and images: the chips are mounted but `imagesOff` refuses a
|
|
553
554
|
paste, drop or pick with one line (the image button is hidden) until participant images land. The grip's floor and
|
|
554
555
|
the fold are the owner page's keys (`chi_composer_floor`, `chi_composer_collapsed`), per browser. The owner's
|
|
555
556
|
participant view mounts none: its dock stays hidden.
|
data/docs/plugins.md
CHANGED
|
@@ -1181,7 +1181,7 @@ a quiet `chi.init` task.
|
|
|
1181
1181
|
### Context providers in the manifest
|
|
1182
1182
|
|
|
1183
1183
|
A bundle can turn URLs into sources for every process, without loading
|
|
1184
|
-
plugins (`chi context add URL`,
|
|
1184
|
+
plugins (`chi context add URL`, `ctx.context.attach(url:)`):
|
|
1185
1185
|
|
|
1186
1186
|
```yaml
|
|
1187
1187
|
# manifest.yml
|
|
@@ -2129,7 +2129,7 @@ chi bundle install github-pr # in the dev profile; needs gh, logged in
|
|
|
2129
2129
|
Attaches the branch's open GitHub pull request to a session as `pr-<n>` when
|
|
2130
2130
|
its worker starts (not in a scratch session or a delegate child; quietly
|
|
2131
2131
|
nothing without `gh`, a repository or an open PR), and resolves PR URLs for
|
|
2132
|
-
`chi context add
|
|
2132
|
+
`chi context add`. Its script prints the PR as text
|
|
2133
2133
|
and a summary of what changed in counts, authors and states only; it wakes
|
|
2134
2134
|
the session for a review requesting changes, checks turning red, or the PR
|
|
2135
2135
|
merged or closed. In the web, a `path:line` in a worker's answer links to
|
data/docs/sessions.md
CHANGED
|
@@ -43,7 +43,7 @@ A new session field (the rule in `Session::FIELDS`' comment): only top-level key
|
|
|
43
43
|
|
|
44
44
|
**Deleting a session:** `chi sessions delete ID...`, `/exit --delete` in a terminal and the Web UI's delete all go through `SessionManager.delete_session`: it resolves a unique prefix, removes `<id>.json` and the whole `<id>/` dir, and returns what it removed. A session a plain REPL has open (`owner.lock` kind `tui`) is always refused. One a worker runs is refused unless the caller asks to stop it: then it stops the worker as `chi sessions stop` does and deletes once `owner.lock` is free (`--force` on the CLI, 10 s; the web page always, 2 s; `/exit --delete` after the worker agreed to exit, 10 s). A worker that outlives the wait leaves the session in place. The web's route is `DELETE /api/sessions/:id?force=true` (the page sends `force`; without it a live worker's session is 409 `worker_running`, as the CLI without `--force`; 200 `{status: "deleted", session_id, stopped}`; 409 `owned_by_tui` or `still_stopping`; 404).
|
|
45
45
|
|
|
46
|
-
<a id="archiving-a-session"></a>**Archiving a session:** `chi sessions archive ID...`, `/archive` in a terminal and the Web UI's `archive` go through `SessionManager.archive_session`: it writes `<id>/archived` (`{"archived_at": …}`; not a session.json field, which every save rewrites) for the session and its delegated children. `Session.list` leaves archived sessions out unless `include_archived:`, and everything built on it follows: `chi sessions list` (`--archived` shows them, `[archived]`), the summaries (`--live`, json/tsv, the desktop panel, the agent's `list_sessions`) and the retention prune, which neither deletes nor counts them against `max_count`. `children_of` (max_children, `delegate_result`) still sees them, and `--resume`/`--attach ID`, `chi send`/`chi note ID` reach them as before. Refused: a turn running in the session or in one of its children (naming the child; 409 `busy` on the web), a prompt queued in `input/` (it would run on the next resume) or a live worker's step-limit question still open, in it or a child (409 `busy` too), a plain REPL owning it (`owned_by_tui`), a `chi scratch` session (`scratch`). A live idle worker is stopped first, and the marker is written even if it is still shutting down. Input a human typed brings a session back (`{"unarchived_at": …}`): a prompt from the web, an attached or plain terminal or `chi send` (origin `web:*`, `tui:*`, `cli:send`, none), steering merged into a running turn, a `/continue` answer or a question answered (the step-limit question too); a delegate's follow-up, a plugin's turn, a reminder, the idle recap, a note and just opening it don't. A child's input brings only the child back. The retention sweep ages an unarchived session from when it was unarchived, so unarchiving an old session doesn't let the next sweep take it. `/archive` leaves the session first, as `/exit` does, then archives it: in an attached terminal the worker is asked to exit (one that stays up for another UI is stopped by the archive; a running turn, a queued prompt or an open step-limit question refuses it), and an empty session is discarded instead; `chi scratch` refuses it. Typed while a turn runs, `/archive` waits for the turn's end (an attached terminal for the commands queued behind it too) and then archives. `chi sessions unarchive ID...` and the web's `unarchive` bring back the whole family. Web routes: `POST /api/sessions/:id/archive` (200 `{status, session_id, archived, stopped, discarded}`, ids) and `POST /api/sessions/:id/unarchive` (200 `{status, session_id, unarchived}`). The session hub and `GET /api/sessions` keep archived sessions, `archived: true` in their summaries: the page leaves them out of the strip, its count and the list at render, and
|
|
46
|
+
<a id="archiving-a-session"></a>**Archiving a session:** `chi sessions archive ID...`, `/archive` in a terminal and the Web UI's `archive` go through `SessionManager.archive_session`: it writes `<id>/archived` (`{"archived_at": …}`; not a session.json field, which every save rewrites) for the session and its delegated children. `Session.list` leaves archived sessions out unless `include_archived:`, and everything built on it follows: `chi sessions list` (`--archived` shows them, `[archived]`), the summaries (`--live`, json/tsv, the desktop panel, the agent's `list_sessions`) and the retention prune, which neither deletes nor counts them against `max_count`. `children_of` (max_children, `delegate_result`) still sees them, and `--resume`/`--attach ID`, `chi send`/`chi note ID` reach them as before. Refused: a turn running in the session or in one of its children (naming the child; 409 `busy` on the web), a prompt queued in `input/` (it would run on the next resume) or a live worker's step-limit question still open, in it or a child (409 `busy` too), a plain REPL owning it (`owned_by_tui`), a `chi scratch` session (`scratch`). A live idle worker is stopped first, and the marker is written even if it is still shutting down. Input a human typed brings a session back (`{"unarchived_at": …}`): a prompt from the web, an attached or plain terminal or `chi send` (origin `web:*`, `tui:*`, `cli:send`, none), steering merged into a running turn, a `/continue` answer or a question answered (the step-limit question too); a delegate's follow-up, a plugin's turn, a reminder, the idle recap, a note and just opening it don't. A child's input brings only the child back. The retention sweep ages an unarchived session from when it was unarchived, so unarchiving an old session doesn't let the next sweep take it. `/archive` leaves the session first, as `/exit` does, then archives it: in an attached terminal the worker is asked to exit (one that stays up for another UI is stopped by the archive; a running turn, a queued prompt or an open step-limit question refuses it), and an empty session is discarded instead; `chi scratch` refuses it. Typed while a turn runs, `/archive` waits for the turn's end (an attached terminal for the commands queued behind it too) and then archives. `chi sessions unarchive ID...` and the web's `unarchive` bring back the whole family. Web routes: `POST /api/sessions/:id/archive` (200 `{status, session_id, archived, stopped, discarded}`, ids) and `POST /api/sessions/:id/unarchive` (200 `{status, session_id, unarchived}`). The session hub and `GET /api/sessions` keep archived sessions, `archived: true` in their summaries: the page leaves them out of the strip, its count and the list at render, and the `archived` toggle beside the all-sessions search shows them, dimmed with an `archived` badge. The info bar marks a session whose live worker runs an older chi than the newest installed (`worker_version` in its summary): `chi 0.18.1 · Restart`, which asks to confirm and calls `POST /api/sessions/:id/restart` (a refusal's reason shows in a toast); a worker from before restarts shows `chi sessions stop ID` instead. The info bar reads `continue → · archive · stop · delete` (`unarchive` on an archived one; no `continue →` on a link continued already, see *Session chains*; `continue →` is a plain button like `archive`, in the accent on an archived session, where it is the thing to do); archiving the open session stays on it, with an Undo toast.
|
|
47
47
|
|
|
48
48
|
**Lazy sweep:** automatic prune runs only while `chi web` runs, at most once per `session.sweep_interval_hours` (default 24): the web's session hub gives it a chance on its periodic full scan, and `GET /api/sessions` does too. No cron, and a terminal-only chi never sweeps. Manual prune is always available.
|
|
49
49
|
|
|
@@ -87,7 +87,7 @@ chi sessions clean --dry-run --days 7 # test sessions older than 7 da
|
|
|
87
87
|
|
|
88
88
|
`--dry-run` is the safe preview. `--keep N` keeps the N newest sessions that stay: one that goes anyway (a leftover scratch session, one left empty, one older than `--days`) takes no place in the count. Web has no prune endpoint; use the CLI.
|
|
89
89
|
|
|
90
|
-
`chi sessions list` shows each session's saved recap, its first sentence without the "The user was…" opening (as on the web cards), in place of its last prompt, cut to 60 characters; a session with no recap shows its last prompt. A session whose last turn a hook stopped carries `[looped]` when the hook was loop-guard (the thinking watch's cut, or its stop) and `[stopped by <hook>]` for any other hook, after `[archived]` (`--format json` has it as `stopped_by`; from `Session#last_turn`, so it stays until a later turn ends differently). The web's session cards wear the same marker as a badge: `looped` or `stopped by <hook>`
|
|
90
|
+
`chi sessions list` shows each session's saved recap, its first sentence without the "The user was…" opening (as on the web cards), in place of its last prompt, cut to 60 characters; a session with no recap shows its last prompt. A session whose last turn a hook stopped carries `[looped]` when the hook was loop-guard (the thinking watch's cut, or its stop) and `[stopped by <hook>]` for any other hook, after `[archived]` (`--format json` has it as `stopped_by`; from `Session#last_turn`, so it stays until a later turn ends differently). The web's session cards wear the same marker as a badge: `looped` or `stopped by <hook>` in its meta row (a session that also waits on you shows only its waiting badge: the stop is behind the need), and the all-view's search matches its words too. A delegated session's row ends with `↳ <parent's short id>` (plain and `--live`; `--format json` has `parent_id`), and a chain's link with `↪ <the previous link's short id>` (`--format json`: `continues`); rows keep their `updated_at` order, so a parent may be on another page than its children. After the status, `ctx 12%` says how full the context was after the session's last counted turn (from its `analytics.json`; blank when no turn had a server count or the window is unknown; `ctx_pct` in `--format json`). The fill is counted against the session's LLM context budget when one is set and smaller than the window (its own `--llm-context-budget`, else the model's/host's/config's; see [configuration.md](configuration.md#llm-context-a-sessions-own-strategy)), as the live meter counts it; the list, the web card and the session page otherwise agree. The web's session cards show it quietly too (`12%`), and a session's ctx meter shows it on load, before its next turn streams.
|
|
91
91
|
|
|
92
92
|
**Projects:** a session belongs to the git project it was started in: the repository, whichever worktree or subfolder of it (the same project root memories use). `chi sessions list` (with `--live` and `--format` too) and `chi web` show the current folder's project; `--scope=all` shows every session, and so does a folder in no repo (`~`). `--cwd PATH` is a folder filter instead of the project. The project is stored with the session (`project_root` in its JSON, `project` in `--format json`), so a session keeps it after its worktree is deleted; sessions older than that are placed by their folder, and one whose folder is gone shows in `--scope=all` only. Retention, `--resume`/`--attach ID`, `chi send`/`chi note ID` and `chi note --all` (every live session) are not scoped. The agent's `list_sessions` lists its own project's sessions; `cwd: "/"` lists every one.
|
|
93
93
|
|
|
@@ -98,11 +98,11 @@ chi sessions clean --dry-run --days 7 # test sessions older than 7 da
|
|
|
98
98
|
- The web header's project chip opens a picker. The start project is the default row (including its start folder); other rows show their root, session and waiting counts. **All projects** opens every session, and choosing a project drops the open-session hash while keeping `?view=`.
|
|
99
99
|
|
|
100
100
|
- `Session.list` / `SessionManager.list_sessions` / `GET /api/sessions?sort=&order=&limit=&offset=` default to `updated_at desc` (newest activity first). Also supports `created_at`, `asc`. `X-Total-Count` header when paginated.
|
|
101
|
-
- Web UI (`chi web`): the page's scope is in its URL. Started in a git repo, `chi web` opens `/?dir=<that folder>`: that project's sessions, and new sessions start in that folder; the header chip says `<project> · all`, and `all` opens the same place as `?scope=all` (every session; a new session there starts in chi web's start folder, shown on the start page, and cards name their folder; `← <project>` goes back to the project view it came from, or to the start folder's project). A bare `/` (the LAN link, the Home Screen app) is the start folder's project too: the folder chi web runs in, or `web.start_dir` / `chi web --start-dir DIR`; outside a repo it shows every session. A pushed notification opens its session in that session's own project (`/?dir=<its project>#/s/<id>`; `?scope=all` outside a repo). One server serves every project: a second `chi web` (from another repo) finds it through `GET /api/info` and prints (with `--open`, opens) its page for its own folder instead of starting another. The 3 latest sessions sit above the chat, and the grip on the chat's top edge — the same thin handle as the composer's, its twin — puts them away and brings them back: a drag up (or a click, or Enter/Space on it) hides them, a drag down (or a click) shows them again, and the choice is remembered. A hidden strip leaves its status in the top bar (`N sessions · ● 1 live · ● 1 waiting`, from the cards), and a click on that pill opens All sessions, without showing the strip first. In a window under 900 px wide, where the strip scrolls sideways, the grip stays under it, outside the scroller; a window under 700 px tall hides the strip by itself, so a question or approval card in the stage keeps room, and the grip shows it until the window is tall again. A parent and its delegates are one card there: its `▸ 2 delegates · 1 live · 1 waiting` chip (the waiting part in the attention colour, the card's border too) opens their rows in a popover under it (a row per delegate at any depth, indented; a click opens that one, Esc or a click outside closes it, and the strip's order holds while it is open). A fork is a card of its own, and so is a delegate whose parent isn't listed (archived, another project), with its `↳ <parent>` chip; the pill counts cards, and every live or waiting session. "All sessions" (or `/`) opens every session at `#/sessions`, with a search over preview, id and status (Esc or Back returns). There a parent's card holds its delegates the same way, the chip opening their rows inside the card (each with an archive button on hover), open by itself while one of them waits on you; the count says `14 (+7 delegates)`, a search that matches a delegate shows its parent's card with the list open and the matching rows marked, and select mode picks cards (archiving a parent takes its delegates). The open session is in the URL (`#/s/<id>`), so a reload or a copied link opens it again; the chi logo top left goes back to the empty start for a new session. The message box grows with its text; drag its top edge to keep it taller (double-click resets). A file in a tool row (a read, edit or write's title) and its diff lines are refs: hover one and click to open the current file in your editor at that line, on this machine only (`web.editor`; the browser's "always allow" for the page's port and VS Code's "allow opening local files" are asked once; see [Opening files from chi web](configuration.md#opening-files-from-chi-web)). So are an edit or write approval card's paths (at the first line the dry run changes) and its diff lines, before you approve, and the numbered lines of a read's output: `lines 40–60` under it opens the text as read, numbered, when the output is the file as-is (not the head/tail preview of a big file, an error or an image; no numbers after a cut). A `path:line` (or `path:line-line`) in an answer or a step's text is one too once the turn has ended, and after a reload, when it names a file a row or card on the page touched: the path under the session's folder, else the one touched file it ends with (a bare `app.js:12` when only one touched file is named so; two such files make it none); a path without a line counts only when it is a touched file exactly. Code blocks, links, bare URLs and inline code with more than the ref in it are left alone, and a file the session didn't touch is never linked; a link whose text is such a ref alone (github-pr's link to the PR diff) becomes a ref too, and its plain click still goes to the PR. A plugin's refs on an answer (an `after_turn` hook's, see [Refs on the answer](hooks.md#refs-on-the-answer-display-only)) are paired with its links and text whatever the rows touched: they can link a file the session didn't touch, or an issue or a commit, and add their own actions to the ref's menu. A plugin tool's row carries the refs its `refs:` gives (see [Refs on a tool's row](plugins.md#refs-on-a-tools-row)): its title opens the first, and its `▾` menu adds the others as `<ref> · <action>` (the URL on hover); their URL actions work on a phone too. A ref with more than one target shows a small `▾` on hover: it opens a menu of them, in order **PR diff** (a link's), **Open in editor**, **Show in turn** and **Copy path**, and a plain click runs the first (a link's own, else the editor, else show in turn). **Show in turn** scrolls to the tool row that touched the file (looked for in the ref's own step, then its own turn, then the rows before it, then the nearest after it; there, the last one showing the ref's line, else the last one), opens the steps, the stage's steps and the row's diff (or the read showing the line) and flashes the row and the line; an answer's or a step's ref and an approval card's path have it, not a row's own title or lines. It needs no editor, so a viewer without one (a phone on the LAN) gets an answer's refs with **Show in turn** as their click; a row's title and diff lines stay plain text there. On a touch screen a tap opens the menu. A ref with a target is a tab stop: Enter runs its first target, ↓ opens the menu (↑/↓ move, Enter picks, Esc closes back to the ref). Diff and read lines have their click only. The info bar's id copies the full session id on click ("Session id copied"), the id every CLI command takes (`chi --attach ID`, `chi send ID`). A session whose prompt carried model notes has a `notes: deepseek` chip there (the names without `model_notes_`; the tooltip lists each with its scope and size; `session.prompt_notes` in `GET /api/sessions/:id`, the live worker's, else the session file's; see [Memory](memory.md#model-notes)). Its ⚙ chip, after the model, holds the session's thinking level, reasoning history and LLM context: `⚙` alone while the session follows the model for all three, else `⚙` and what the session set, in the accent colour (`⚙ think low · ctx stale,forget`; a phone shows ⚙ with a dot; the tooltip gives each value and its source). Its panel is the start page's ⚙ panel for this session: a Thinking row (`/thinking`), on a chat host a Reasoning row (the reasoning history, `/reasoning-history`), and a Context row with its Apply and budget fields (`/llm-context`); a tap runs that row's command at once in the worker (one reply in the conversation, as a typed one) and changes that field alone, the Context row's `default` resets the strategy, apply rule and budget (`/llm-context reset`), and a tap while a turn runs is queued for after it (the panel's note says so). `GET /api/sessions/:id`'s `session.thinking`, `session.reasoning_history` (null on a native host) and `session.llm_context` carry the values (the live worker's, else resolved from the session file), and a `command_ran` that changed them carries the new ones. After ⚙, the 🧰 chip names the session's toolkit: `🧰` alone on the configured default (`toolkit.default`, `session.toolkit_default`), `🧰 lean` on another (see [Toolkits](configuration.md#toolkits); `/toolkit` changes it). A `/thinking` or `/reasoning-history` change typed in the composer that restarts the prompt cache asks first: the reply bubble has an **Apply (restarts the cache)** button that sends its `--yes` line (the ⚙ panel sends that line itself; see [Changes that restart the prompt cache](configuration.md#changes-that-restart-the-prompt-cache)). It shows the context used (`ctx 42%`) and the last generation's decode speed (`87 tok/s`, `~64 tok/s` when estimated; see `/stats` in [cli.md](cli.md#session-commands)); hovering `ctx` there or on a session card lists the session's tokens in (summed over every request, with the cached share), out (with reasoning) and the cost when the provider reports one, for this session only (one estimated from a `hosts.<name>.models` price shows with `~`: `cost: ~$0.54 ($0.42 reported, ~$0.12 estimated)`); after a `/model` switch the info bar's tooltip adds a `per model:` row per run of turns on one model above those totals (`splash:qwen3-35b · 6 turns · 96.0k in / 9.0k out · ~$0.02`, a switch's prefill under its row: `└ switch prefill 58.0k uncached prompt ≈$0.01`; the timing's `model_segments`, replaced by each `generation_completed`'s; the session cards stay as they were), and right after the switch its ctx % is the new model's window's; and the memory indexes its prompt holds (`memory index: ~3.4k tokens in this session's prompt (system 2.0k, project 1.4k)`; see [Memory](memory.md#the-indexs-size)). The start page's model picker (bottom left of the composer, the chosen name `▾`) chooses the model a new session starts on, and a fresh chain link's (see [Session chains](#session-chains); there a pick shows as a `/model` command bubble in the link); it opens a search list over the conversation (↓ or a click; downward when the start page leaves no room above): an empty search shows the last 5 picks (Recent) and then each host's model ids A–Z under the host, the default host first (an id the host declares under `hosts.<name>.models` first, noted `config`, its tooltip `served by config (hosts.<name>.models)`; a host that failed to list its models keeps those ids, greyed and noted `host down`, still pickable; a remembered or default model stays chosen even then, greyed on the button); typed words must each match the host or the id (a substring, else for 3+ characters the letters in order, so `deepseek4.1 fla` finds `openrouter · deepseek/deepseek-v4.1-flash`), ⏎ picks, Esc/Tab close, a pick puts the focus in the message box: `GET /api/models` lists the hosts' models as chi spells them (`{default, models: [{name, host, id, configured?, unavailable?, sampling?, llm_context?}], warning?}`; `configured: true` marks an id declared under `hosts.<name>.models`, listed first under its host, `unavailable: true` one whose host failed to list its models; `sampling` is the model's configured request parameters as `/model` words them, the row's tooltip in the list; bare for the default host, `host:model` for the others, from the same cached lists as `/models`, a bounded wait, a host that is down noted in `warning`; `toolkits`: `{default, suggest, kits: [{name, description?}]}`, the start page's 🧰 list: the configured `toolkit.default`, `suggest` `on` only when a new session's first message may get a suggested kit (`toolkit.suggest.mode: on`, a System 1 host, two described kits; else `audit` or `off`), and the built-in and config kits a new session can start on, without `review`, `child`, `undecided`, a kit `groups:` refuses or one that doesn't resolve (a base that is a bundle kit), the default always listed; left out when it can't be read), and `POST /api/sessions` takes `model` (blank means the default). Beside it, ⚙ opens the new session's rarely used settings (thinking and LLM context, following each `GET /api/models` row's `thinking` and `llm_context`, and the review files; see [configuration.md](configuration.md#llm-context-a-sessions-own-strategy)), and 🧰 its toolkit: **Auto** (with `toolkit.suggest.mode: on`: the first message may get a suggested kit, else the default) or a kit from the payload's `toolkits`, sent as `POST /api/sessions`'s `toolkit` (see [configuration.md](configuration.md#toolkits)); a refused kit (`400 toolkit_refused`) is said in the list, opened again. What is set shows on the row as an accent chip (`think high`, `tools: lean`; a dot on ⚙ or 🧰 on a phone); each is for the next new session only, never remembered. The start page's first message goes as later ones do: `POST /api/sessions` with `idle: true` and `preview` (the message, which names the session until its turn is saved), then `POST /api/sessions/:id/turn`, so a failed first turn puts its prompt back in the composer too; a first line that is one of the new session's commands (`/model …`) goes to `POST /api/sessions/:id/command` instead, as later ones do. The browser remembers the last model picked; a running session's model is in the info bar and changes only with `/model`. In the composer a line is a command when it is `!…` or starts with one of the session's command names (its `commands` list; `/stats`, `/exit` and the other terminal words get the page's own reply); an unknown `/word` (`/usr/bin/env is missing`) goes to the model as a prompt, as in a terminal; a one-word `/word` no command answers (`/modle`) is not sent: the page shows `Unknown command /modle. Did you mean /model? /help lists the commands.` (the "Did you mean" part only when a name is close). A `POST /turn` (or a session's first `prompt`) that is one of the worker's commands runs as that command too (`202 {status, command_id, session_id}`), so `chi send`, `chi -p` and a page whose command list lags all get the command. ↑/↓ in the composer walk the prompt history the TUI's ↑ walks ([Persistent Prompt History](cli.md#persistent-prompt-history)): ↑ with the caret on the first line, ↓ from the last line while browsing, ↓ past the newest brings the draft back; `GET /api/history` returns `{entries: [...]}`, oldest first (the page fetches it on focus and after each send). The server records a sent line in the routes once the worker took it (`POST /turn` accepted, a `!command` through `POST /command` or as a session's first line); `"history": false` in the body keeps it out (an image-only send).
|
|
101
|
+
- Web UI (`chi web`): the page's scope is in its URL. Started in a git repo, `chi web` opens `/?dir=<that folder>`: that project's sessions, and new sessions start in that folder; the header chip says `<project> · all`, and `all` opens the same place as `?scope=all` (every session; a new session there starts in chi web's start folder, shown on the start page, and cards name their folder; `← <project>` goes back to the project view it came from, or to the start folder's project). A bare `/` (the LAN link, the Home Screen app) is the start folder's project too: the folder chi web runs in, or `web.start_dir` / `chi web --start-dir DIR`; outside a repo it shows every session. A pushed notification opens its session in that session's own project (`/?dir=<its project>#/s/<id>`; `?scope=all` outside a repo). One server serves every project: a second `chi web` (from another repo) finds it through `GET /api/info` and prints (with `--open`, opens) its page for its own folder instead of starting another. The 3 latest sessions sit above the chat, and the grip on the chat's top edge — the same thin handle as the composer's, its twin — puts them away and brings them back: a drag up (or a click, or Enter/Space on it) hides them, a drag down (or a click) shows them again, and the choice is remembered. A hidden strip leaves its status in the top bar (`N sessions · ● 1 live · ● 1 waiting`, from the cards), and a click on that pill opens All sessions, without showing the strip first; there the pill shows whatever the strip's state, pressed, and a click on it goes back to the chat (as Esc does). In a window under 900 px wide, where the strip scrolls sideways, the grip stays under it, outside the scroller; a window under 700 px tall hides the strip by itself, so a question or approval card in the stage keeps room, and the grip shows it until the window is tall again. A parent and its delegates are one card there: its `▸ 2 delegates · 1 live · 1 waiting` chip (the waiting part in the attention colour, the card's border too; just `▸ 2 delegates` while the rows are open: they carry the marks then) opens their rows in a popover under it (a row per delegate at any depth, indented; a click opens that one, Esc or a click outside closes it, and the strip's order holds while it is open). A fork is a card of its own, and so is a delegate whose parent isn't listed (archived, another project), with its `↳ <parent>` chip; the pill counts cards, and every live or waiting session. "All sessions" (or `/`) opens every session at `#/sessions`; its header is the top bar's right end: a search over preview, id and status, the count, an `archived` toggle and Select (on a phone a row of its own under the bar). Esc or the pressed pill returns; where the sessions are stored is a faint line under the list. A card is its title on one line (an untitled session reads `Untitled` over its first message), a line under it (its recap, else its first message) and its meta row, with one mark per state and no status dot: a waiting card has its warn outline and its `question` (or `approval`…) chip, an errored one an `error` badge, a session a worker runs the `live` badge (not on a waiting card: waiting means a worker), and idle, completed and stopped ones are unmarked; a family's and a chain's rows the same. There a parent's card holds its delegates the same way, the chip opening their rows inside the card (each with an archive button on hover), open by itself while one of them waits on you; the count says `14 · 7 delegates` (`3 / 14` while a search narrows it), a search that matches a delegate shows its parent's card with the list open and the matching rows marked, and select mode picks cards (archiving a parent takes its delegates). The open session is in the URL (`#/s/<id>`), so a reload or a copied link opens it again; the chi logo top left goes back to the empty start for a new session. The message box grows with its text; drag its top edge to keep it taller (double-click resets). A file in a tool row (a read, edit or write's title) and its diff lines are refs: hover one and click to open the current file in your editor at that line, on this machine only (`web.editor`; the browser's "always allow" for the page's port and VS Code's "allow opening local files" are asked once; see [Opening files from chi web](configuration.md#opening-files-from-chi-web)). So are an edit or write approval card's paths (at the first line the dry run changes) and its diff lines, before you approve, and the numbered lines of a read's output: `lines 40–60` under it opens the text as read, numbered, when the output is the file as-is (not the head/tail preview of a big file, an error or an image; no numbers after a cut). A `path:line` (or `path:line-line`) in an answer or a step's text is one too once the turn has ended, and after a reload, when it names a file a row or card on the page touched: the path under the session's folder, else the one touched file it ends with (a bare `app.js:12` when only one touched file is named so; two such files make it none); a path without a line counts only when it is a touched file exactly. Code blocks, links, bare URLs and inline code with more than the ref in it are left alone, and a file the session didn't touch is never linked; a link whose text is such a ref alone (github-pr's link to the PR diff) becomes a ref too, and its plain click still goes to the PR. A plugin's refs on an answer (an `after_turn` hook's, see [Refs on the answer](hooks.md#refs-on-the-answer-display-only)) are paired with its links and text whatever the rows touched: they can link a file the session didn't touch, or an issue or a commit, and add their own actions to the ref's menu. A plugin tool's row carries the refs its `refs:` gives (see [Refs on a tool's row](plugins.md#refs-on-a-tools-row)): its title opens the first, and its `▾` menu adds the others as `<ref> · <action>` (the URL on hover); their URL actions work on a phone too. A ref with more than one target shows a small `▾` on hover: it opens a menu of them, in order **PR diff** (a link's), **Open in editor**, **Show in turn** and **Copy path**, and a plain click runs the first (a link's own, else the editor, else show in turn). **Show in turn** scrolls to the tool row that touched the file (looked for in the ref's own step, then its own turn, then the rows before it, then the nearest after it; there, the last one showing the ref's line, else the last one), opens the steps, the stage's steps and the row's diff (or the read showing the line) and flashes the row and the line; an answer's or a step's ref and an approval card's path have it, not a row's own title or lines. It needs no editor, so a viewer without one (a phone on the LAN) gets an answer's refs with **Show in turn** as their click; a row's title and diff lines stay plain text there. On a touch screen a tap opens the menu. A ref with a target is a tab stop: Enter runs its first target, ↓ opens the menu (↑/↓ move, Enter picks, Esc closes back to the ref). Diff and read lines have their click only. The info bar's id copies the full session id on click ("Session id copied"), the id every CLI command takes (`chi --attach ID`, `chi send ID`). A session whose prompt carried model notes has a `notes: deepseek` chip there (the names without `model_notes_`; the tooltip lists each with its scope and size; `session.prompt_notes` in `GET /api/sessions/:id`, the live worker's, else the session file's; see [Memory](memory.md#model-notes)). Its ⚙ chip, after the model, holds the session's thinking level, reasoning history and LLM context: `⚙` alone while the session follows the model for all three, else `⚙` and what the session set, in the accent colour (`⚙ think low · ctx stale,forget`; a phone shows ⚙ with a dot; the tooltip gives each value and its source). Its panel is the start page's ⚙ panel for this session: a Thinking row (`/thinking`), on a chat host a Reasoning row (the reasoning history, `/reasoning-history`), and a Context row with its Apply and budget fields (`/llm-context`); a tap runs that row's command at once in the worker (one reply in the conversation, as a typed one) and changes that field alone, the Context row's `default` resets the strategy, apply rule and budget (`/llm-context reset`), and a tap while a turn runs is queued for after it (the panel's note says so). `GET /api/sessions/:id`'s `session.thinking`, `session.reasoning_history` (null on a native host) and `session.llm_context` carry the values (the live worker's, else resolved from the session file), and a `command_ran` that changed them carries the new ones. After ⚙, the 🧰 chip names the session's toolkit: `🧰` alone on the configured default (`toolkit.default`, `session.toolkit_default`), `🧰 lean` on another (see [Toolkits](configuration.md#toolkits); `/toolkit` changes it). A `/thinking` or `/reasoning-history` change typed in the composer that restarts the prompt cache asks first: the reply bubble has an **Apply (restarts the cache)** button that sends its `--yes` line (the ⚙ panel sends that line itself; see [Changes that restart the prompt cache](configuration.md#changes-that-restart-the-prompt-cache)). It shows the context used (`ctx 42%`) and the last generation's decode speed (`87 tok/s`, `~64 tok/s` when estimated; see `/stats` in [cli.md](cli.md#session-commands)); hovering `ctx` there or on a session card lists the session's tokens in (summed over every request, with the cached share), out (with reasoning) and the cost when the provider reports one, for this session only (one estimated from a `hosts.<name>.models` price shows with `~`: `cost: ~$0.54 ($0.42 reported, ~$0.12 estimated)`); after a `/model` switch the info bar's tooltip adds a `per model:` row per run of turns on one model above those totals (`splash:qwen3-35b · 6 turns · 96.0k in / 9.0k out · ~$0.02`, a switch's prefill under its row: `└ switch prefill 58.0k uncached prompt ≈$0.01`; the timing's `model_segments`, replaced by each `generation_completed`'s; the session cards stay as they were), and right after the switch its ctx % is the new model's window's; and the memory indexes its prompt holds (`memory index: ~3.4k tokens in this session's prompt (system 2.0k, project 1.4k)`; see [Memory](memory.md#the-indexs-size)). The start page's model picker (bottom left of the composer, the chosen name `▾`) chooses the model a new session starts on, and a fresh chain link's (see [Session chains](#session-chains); there a pick shows as a `/model` command bubble in the link); it opens a search list over the conversation (↓ or a click; downward when the start page leaves no room above): an empty search shows the last 5 picks (Recent) and then each host's model ids A–Z under the host, the default host first (an id the host declares under `hosts.<name>.models` first, noted `config`, its tooltip `served by config (hosts.<name>.models)`; a host that failed to list its models keeps those ids, greyed and noted `host down`, still pickable; a remembered or default model stays chosen even then, greyed on the button); typed words must each match the host or the id (a substring, else for 3+ characters the letters in order, so `deepseek4.1 fla` finds `openrouter · deepseek/deepseek-v4.1-flash`), ⏎ picks, Esc/Tab close, a pick puts the focus in the message box: `GET /api/models` lists the hosts' models as chi spells them (`{default, models: [{name, host, id, configured?, unavailable?, sampling?, llm_context?}], warning?}`; `configured: true` marks an id declared under `hosts.<name>.models`, listed first under its host, `unavailable: true` one whose host failed to list its models; `sampling` is the model's configured request parameters as `/model` words them, the row's tooltip in the list; bare for the default host, `host:model` for the others, from the same cached lists as `/models`, a bounded wait, a host that is down noted in `warning`; `toolkits`: `{default, suggest, kits: [{name, description?}]}`, the start page's 🧰 list: the configured `toolkit.default`, `suggest` `on` only when a new session's first message may get a suggested kit (`toolkit.suggest.mode: on`, a System 1 host, two described kits; else `audit` or `off`), and the built-in and config kits a new session can start on, without `review`, `child`, `undecided`, a kit `groups:` refuses or one that doesn't resolve (a base that is a bundle kit), the default always listed; left out when it can't be read), and `POST /api/sessions` takes `model` (blank means the default). Beside it, ⚙ opens the new session's rarely used settings (thinking and LLM context, following each `GET /api/models` row's `thinking` and `llm_context`, and the review files; see [configuration.md](configuration.md#llm-context-a-sessions-own-strategy)), and 🧰 its toolkit: **Auto** (with `toolkit.suggest.mode: on`: the first message may get a suggested kit, else the default) or a kit from the payload's `toolkits`, sent as `POST /api/sessions`'s `toolkit` (see [configuration.md](configuration.md#toolkits)); a refused kit (`400 toolkit_refused`) is said in the list, opened again. What is set shows on the row as an accent chip (`think high`, `tools: lean`; a dot on ⚙ or 🧰 on a phone); each is for the next new session only, never remembered. The start page's first message goes as later ones do: `POST /api/sessions` with `idle: true` and `preview` (the message, which names the session until its turn is saved), then `POST /api/sessions/:id/turn`, so a failed first turn puts its prompt back in the composer too; a first line that is one of the new session's commands (`/model …`) goes to `POST /api/sessions/:id/command` instead, as later ones do. The browser remembers the last model picked; a running session's model is in the info bar and changes only with `/model`. In the composer a line is a command when it is `!…` or starts with one of the session's command names (its `commands` list; `/stats`, `/exit` and the other terminal words get the page's own reply); an unknown `/word` (`/usr/bin/env is missing`) goes to the model as a prompt, as in a terminal; a one-word `/word` no command answers (`/modle`) is not sent: the page shows `Unknown command /modle. Did you mean /model? /help lists the commands.` (the "Did you mean" part only when a name is close). A `POST /turn` (or a session's first `prompt`) that is one of the worker's commands runs as that command too (`202 {status, command_id, session_id}`), so `chi send`, `chi -p` and a page whose command list lags all get the command. ↑/↓ in the composer walk the prompt history the TUI's ↑ walks ([Persistent Prompt History](cli.md#persistent-prompt-history)): ↑ with the caret on the first line, ↓ from the last line while browsing, ↓ past the newest brings the draft back; `GET /api/history` returns `{entries: [...]}`, oldest first (the page fetches it on focus and after each send). The server records a sent line in the routes once the worker took it (`POST /turn` accepted, a `!command` through `POST /command` or as a session's first line); `"history": false` in the body keeps it out (an image-only send).
|
|
102
102
|
- Web notifications: when a session needs you while the tab is hidden or behind another window (a question or a guardrail approval, a plugin card with buttons during a turn such as check-in's, a failed turn, a turn done after 10 s or more; a delegated session only for its questions, a canceled turn never), the page title counts it, `(N) Chi`, until you come back to the tab. The bell at the right of the top bar also turns on OS notifications ("needs an answer", "needs approval", "needs you", "turn failed", "done in 42 s", under the session's first message; a click opens the session): its first click asks the browser for the permission, and the choice is kept per browser. Any session of the page's scope counts, not only the open one. What was already so when the page loaded never counts, and several chi tabs show one notification per event. A chi tab in front tells the others (a `BroadcastChannel`), so while you look at one, the ones behind neither notify nor count what it shows; coming back to a tab clears those events in the other tabs' titles too. The hub's `session` frames carry what this needs: `pending_question` (`{id, kind}`, kind `question` or `approval`), `pending_card` (`{id, bundle}`: the running turn's card with actions, from `pending_card.json` that the worker keeps in the session's folder while one is open; a card without actions, such as loop-guard's, never counts) and `last_turn` (`{outcome, ended_at, seconds, origin}`, saved in the session's JSON as the engine ends each turn).
|
|
103
|
-
- The web frontend is a zero-build ES-module stack in `lib/samagotchi/web/public/`: `data.js` (retrieval, typed SSE `openStream` for a session and `openEvents` for the session list), `sessions_list.js` (the list as a pure reducer over `GET /api/events`: a snapshot replaces it, an upsert keeps a known card in place, a removal drops it; `families()` groups delegates under their parent), `families_view.js` (a parent's family chip, its rows and the strip's popover; the chip and rows as lit-html templates), `keep_focus.js` (lit reuses a list's nodes by position: a redraw keeps the focus on the same item by its `data-id` / `data-name`), `chains.js` (session chains: the links from `continues`, the cards folded into the latest link's, its `#N` chip and rows as lit-html templates, the info bar's ← → links, a refused continue, and the fresh-link rule), `notify.js` (which of those events needs the user, for the notifications), `app.js` (presentation/state; a session with no live stream gets one when its `session` event says `bridge_up`, after one re-read: `event_seq` starts over in each worker, so a dropped stream is never resumed with its old cursor), `bubbles.js` (the history's bubbles between the turns' rows: output chunks, end lines, every client's prompt bubbles by `enqueued_id` with their queued/steered badges, the idle recap, guardrail warnings, hooks' notices, plugins' steers, command and note bubbles; lit-html templates, put in the history with the sticky follow by `createBubbles`), `cards_place.js` (where plugin cards and a snapshot's notices go in the history: a card added or updated in place by its id, a running turn's into its step, a snapshot's each above the first turn after it, a finished turn's notice in its step's rows and its question resolved where it was asked; a card's action buttons), `format.js` (pure formatters such as `previewOf`), `plain_line.js` (markdown out of the plain one-line places: the stage headline, a closed step's title, an answered question card's kept-reply line), `prompt_history.js` (the composer's ↑/↓ over the shared prompt history), `commands.js` (session commands: which composer lines are commands, the page's own replies, a `command_ran`'s bubble, the page's command bubbles kept across a re-read), `composer.js` (where a composer line goes, `routeComposerLine`: /cut and /queue, the start page's own replies, a session command or `!cmd`, a typo hint, else a turn; and a session command sent from the composer or a chip), `delivery_line.js` (the composer's row above the text field in a session, always there so the composer keeps its height: the key hint while idle, and the delivery line while a turn runs: where a message typed now goes in, from the step's phase, clock and size, whether "cut in now" can land, and the queue link for a worker with the `delivery` feature, as a pure `deliveryLine`, its lit-html template and `createDeliveryControl`, which ticks it and keeps a choice armed from a link; see docs/cli.md, "Typing during a turn"), `restore.js` (a failed turn's prompt handed back into this tab's composer, before or after its `/turn` ack), `lists_view.js` (the session lists: the strip and its pill, all sessions with its search, count and select mode, their cards, the family and chain popovers; the cards, tile and pill as lit-html templates, drawn as new nodes each time, and the lists' orders as pure functions), `stream_handlers.js` (the open session's stream event handlers, by event type, over the page's views and hooks; a snapshot replays through them too), `stage_view.js` (the stage view: the running turn pinned above the composer, then handed off into the history; its pure rules and texts in `stage_model.js`, its live slots (headline, now row, trail, ticks) in `stage_slots.js`, its cap and held height in `stage_frame.js`, the in-use watch that times the hand-off in `stage_watch.js`), `info_bar.js` (the session bar's info line as a lit-html template over a plain model: id, status, model, the ⚙ and 🧰 chips, chain links,
|
|
103
|
+
- The web frontend is a zero-build ES-module stack in `lib/samagotchi/web/public/`: `data.js` (retrieval, typed SSE `openStream` for a session and `openEvents` for the session list), `sessions_list.js` (the list as a pure reducer over `GET /api/events`: a snapshot replaces it, an upsert keeps a known card in place, a removal drops it; `families()` groups delegates under their parent), `families_view.js` (a parent's family chip, its rows and the strip's popover; the chip and rows as lit-html templates), `keep_focus.js` (lit reuses a list's nodes by position: a redraw keeps the focus on the same item by its `data-id` / `data-name`), `chains.js` (session chains: the links from `continues`, the cards folded into the latest link's, its `#N` chip and rows as lit-html templates, the info bar's ← → links, a refused continue, and the fresh-link rule), `notify.js` (which of those events needs the user, for the notifications), `app.js` (presentation/state; a session with no live stream gets one when its `session` event says `bridge_up`, after one re-read: `event_seq` starts over in each worker, so a dropped stream is never resumed with its old cursor), `bubbles.js` (the history's bubbles between the turns' rows: output chunks, end lines, every client's prompt bubbles by `enqueued_id` with their queued/steered badges, the idle recap, guardrail warnings, hooks' notices, plugins' steers, command and note bubbles; lit-html templates, put in the history with the sticky follow by `createBubbles`), `cards_place.js` (where plugin cards and a snapshot's notices go in the history: a card added or updated in place by its id, a running turn's into its step, a snapshot's each above the first turn after it, a finished turn's notice in its step's rows and its question resolved where it was asked; a card's action buttons), `format.js` (pure formatters such as `previewOf`), `plain_line.js` (markdown out of the plain one-line places: the stage headline, a closed step's title, an answered question card's kept-reply line), `prompt_history.js` (the composer's ↑/↓ over the shared prompt history), `commands.js` (session commands: which composer lines are commands, the page's own replies, a `command_ran`'s bubble, the page's command bubbles kept across a re-read), `composer.js` (where a composer line goes, `routeComposerLine`: /cut and /queue, the start page's own replies, a session command or `!cmd`, a typo hint, else a turn; and a session command sent from the composer or a chip), `delivery_line.js` (the composer's row above the text field in a session, always there so the composer keeps its height: the key hint while idle, and the delivery line while a turn runs: where a message typed now goes in, from the step's phase, clock and size, whether "cut in now" can land, and the queue link for a worker with the `delivery` feature, as a pure `deliveryLine`, its lit-html template and `createDeliveryControl`, which ticks it and keeps a choice armed from a link; see docs/cli.md, "Typing during a turn"), `restore.js` (a failed turn's prompt handed back into this tab's composer, before or after its `/turn` ack), `lists_view.js` (the session lists: the strip and its pill, all sessions with its search, count and select mode, their cards, the family and chain popovers; the cards, tile and pill as lit-html templates, drawn as new nodes each time, and the lists' orders as pure functions), `stream_handlers.js` (the open session's stream event handlers, by event type, over the page's views and hooks; a snapshot replays through them too), `stage_view.js` (the stage view: the running turn pinned above the composer, with Stop turn in its status row while it runs, then handed off into the history; its pure rules and texts in `stage_model.js`, its live slots (headline, now row, trail, ticks) in `stage_slots.js`, its cap and held height in `stage_frame.js`, the in-use watch that times the hand-off in `stage_watch.js`), `info_bar.js` (the session bar's info line as a lit-html template over a plain model, three groups with hairlines between them, on one line above a phone's width (the folder gives way first): the setup (id, status, model, the ⚙ and 🧰 chips, notes, the memory or working-set chip), the relations (chain links, the parent as `↑ <id>`, delegates, participants; only when there is one) and the telemetry (folder, ctx, speed, the session's time, worker badge)), `settings_chip.js` (the session bar's ⚙ chip and its panel over `settings_panel.js`: a tap runs that row's command, one field), `toolkit_chip.js` (the session bar's 🧰 chip: the kit against `toolkit.default`), `info_bar_model.js` (that model and the bar's buttons' states, built from the page's session state, its list and its in-flight continues and restarts as pure functions; `app.js`'s `updateInfoBar` applies them every second while a turn runs), `session_state.js` (what the page knows about the open session: its empty state, and `sessionStateFrom`, the fields a `GET /sessions/:id` payload sets), `refs.js` (refs: a kind and a target on the element, a plugin's ref data (label, http(s) URL actions) bound in a WeakMap by `bindRef`, the targets per kind, show in turn, one delegated set of click, key and hover listeners), `ref_menu.js` (a ref's `▾` and its menu), `ref_scan.js` (the `path:line` refs in an answer's or a step's text that name a file the session's rows touched), `answer_refs.js` (the refs a plugin attached to an answer, its `display_refs`: bound to the answer's bubble, paired with its links and text, bound with `bindRef`), `markdown_place.js` (where rendered markdown and its refs go: a text as markdown HTML, its `path:line` refs, a final answer's rendering), `images.js` (image chips in the composer: paste, drop and the image button, the upload before a turn, the thumbnails and the lightbox), `composer_grip.js` (the composer's grip and fold: the drag, tap and keys, the fold per visit), `composer_mount.js` (the composer mounted on a page's markup: the message box and its height, the image chips from a paste, a drop or the image button, the grip and fold, ↑/↓ over the prompt history and the keys; the page's own parts come in as hooks: what Enter and Send do, keys taken first, Esc, where the history comes from, the info bar's drop while folded, typing anywhere to open it; its styles, the card it sits in (`.composer-card`, its footer and its line above the text field) included, are the shared `composer.css`, linked after `tokens.css` by `index.html` and `participant.html`), `strip_grip.js` (the latest-sessions strip's grip, the composer grip's twin: hides and shows the strip), `review_history.js` (a participant's ↑/↓ list on the participant page: their own comments, in localStorage per grant, never the owner's `/api/history`; `participant.js` mounts `composer_mount.js` with it, no `/` list, no delivery choices, images off), `storage.js` (the page's per-browser choices in localStorage, guarded: a choice that can't be read or written is not remembered), `unqueue.js` (the × on a message queued for after the turn: takes it back, its text into an empty composer or a toast), `toasts.js` (the page's toast stack: a timer each, sticky ones until acted on, the same text reusing its item), `toast_place.js` (where the toast sits: above the composer card, the stage and an open model panel), `dock_reserve.js` (what floats over the history: the history's bottom reserve (`--dock-h`) at the composer card's and the showing stage's height, a reader at the end kept there, and the toast's place measured for `toast_place.js`), `notice_links.js` (a hook notice line's http(s) URLs as links), `start_page.js` (the start page's model picker, its ⚙ panel's and 🧰 list's choices for the next new session only and their override chips; a fresh chain link's picker and its `/model`), `start_settings.js` (the start page's ⚙ panel: thinking and LLM context as rows of segments, applied at once, the Apply and budget disclosure, the review files' view; the override chips' words and ⚙'s dot), `settings_panel.js` (the panel's rows as lit-html templates, shared: the segments, the disclosure, a hidden row's sent-anyway line, the review line; the caller passes its rows and applies the picks), `start_toolkit.js` (the start page's 🧰 list: Auto with suggest on, the kits, the default marked; a pick closes it; its override chip and dot), `session_actions.js` (Continue, Archive and its Undo, Delete and Stop, and the start page when the open session is gone), `send_turn.js` (the composer's send: a new session first on the start page, the route, the echo, the image upload, the `/turn` ack, a fast failure's restore and each error's place). Unit-tested via `npm test` (`spec/web/public/*.test.js`).
|
|
104
104
|
- Selecting a session in the web UI is read-only: `GET /api/sessions/:id` never spawns a worker (it reads a live worker's snapshot when one runs, else the session file). A prompt (`POST /turn`) or a command (`POST /command`) wakes the worker, and `/stream` briefly waits for a freshly-spawned bridge before answering. A caught-up SSE reconnect holds the stream open; `reset` markers are only sent for reconnects behind the ring window, or with a cursor from another worker (event ids are `<event_seq>-<epoch>`, one epoch per worker).
|
|
105
|
-
- The web session page's message box folds away for room to read. Drag the grip on its top edge (the pill that shows on hover; always on a phone) down, past the box by 32 px: the pill turns accent while releasing would hide it, and on release the box folds and the info bar stays as the page's bottom edge. The grip stays as a handle on the info bar's top edge, drawn always: pull it up (8 px opens it, and the box follows the pointer in the same gesture), click or tap it (opens it and focuses the box), or just type on the page (the key lands in the box) to bring it back; Enter or Space on the focused grip folds or opens too. A draft (text or images, which folding never clears) colours the handle with the accent, a session waiting on you with the attention colour; its tooltip says so. Stop turn
|
|
105
|
+
- The web session page's message box folds away for room to read. Drag the grip on its top edge (the pill that shows on hover; always on a phone) down, past the box by 32 px: the pill turns accent while releasing would hide it, and on release the box folds and the info bar stays as the page's bottom edge. The grip stays as a handle on the info bar's top edge, drawn always: pull it up (8 px opens it, and the box follows the pointer in the same gesture), click or tap it (opens it and focuses the box), or just type on the page (the key lands in the box) to bring it back; Enter or Space on the focused grip folds or opens too. A draft (text or images, which folding never clears) colours the handle with the accent, a session waiting on you with the attention colour; its tooltip says so. Stop turn isn't the message box's: it sits in the running turn's stage, at the right edge of its status row beside the fold caret (collapsed stage or not), while the turn runs, and goes when the turn ends A turn the page knows runs but has nothing of to draw (a reload whose session payload carries no current turn) shows that status row alone, `running` and Stop turn. The fold is remembered per browser (`chi_composer_collapsed`), an archived session opens folded for the visit, and opening it there, like the page opening it itself (a restored line, a quote, continue →'s opener), is for that visit only. The fold keeps the height the grip set (`chi_composer_floor`): the box opens again at the height it had, and only a double-click on the grip forgets it. That height never takes more than half of the window the box is drawn in: a floor saved in a bigger window is held to that half in a smaller one (a rotated phone, a resized window, another device on the same origin), and the saved floor itself stays, so the height comes back when the room does. A click on the expanded grip hides the box as well: it folds 250 ms after the press, so a second click inside that window is read as a double-click and resets the height instead (a drag, Enter or Space act at once, and a drag after a click takes the wait back). The start page's grip only resizes.
|
|
106
106
|
|
|
107
107
|
**Test-session hygiene:**
|
|
108
108
|
|
|
@@ -265,7 +265,7 @@ A session can **continue** an earlier one: "the next run of the same routine" (a
|
|
|
265
265
|
- **If the continue fails part-way:** a new link that fails to start gives its moved delegates back before it is deleted (each gets a note saying so; its worker is stopped first, if one started, and a worker that won't stop is reported, the link left), with any report one of them rang it with meanwhile; the previous link, back in the lists, is woken for reports waiting. A continue that dies in the middle leaves a `move.json` in the new link's folder: the new link's next worker finishes that move (only the delegates it names, and without a second note to one already told) as it starts. Until the new link's own worker holds it, a ring from a moved delegate doesn't start another (a `starting` marker in its folder, which that worker removes; a stale one, over 60 s old or from a process that is gone, is ignored). An open delegate below an archived delegate refuses the continue (`under archived …`): unarchive that one, or archive this one.
|
|
266
266
|
- **Refused too:** a link that is continued already (naming the link that continues it; continue that one, or `last:<id>`), a previous link whose folder is gone, and one whose model's host can't run it when no default model is configured to fall back to. A refusal starts nothing and archives nothing; a new link that fails to start puts back what was archived. Every refusal that can be known up front (the archive's ones included) comes before the recap wait. One continue of a session runs at a time (a lock in its folder; other chains don't wait), so a double click finds the first one's link and is refused, while a `last:<id>` continue that waited follows the chain to its new end.
|
|
267
267
|
- **Web:** `POST /api/sessions` takes `continues: "<id, prefix or last:id>"`, with `idle: true` (a new link waiting for its first message, previewed by the previous link's, or `preview`) or a `prompt`, and `model` (or `model_name`) for the new link's model, but not `dir`, `llm_context`, `thinking`, `settings`, `toolkit`, `tools`, `no_tools` or `parent_answers` (400 `invalid_continues`: the previous link's are taken). 201 is the new session's summary, `continues` set, with `moved`: the ids of the delegates that moved to it (`[]` for none), and `model_warning`: the default-model fallback's warning, then any about the model id, joined by `; ` (`null` for none), and `opener`: the previous link's first typed message, in full, when that link continues one itself (`null` otherwise: a chain's first link's first message is its task); 404 `not_found`; 400 `ambiguous_id` for a prefix of several sessions, `invalid_model` for a given model whose host config.yml doesn't have (or a fallback with no default model); 409 `continued` with `next_id` (the link that continues it already, which the page opens instead), `open_children` with `ids` (open delegates that can't move), `folder_gone`, `busy`, `scratch` or `owned_by_tui`. Every session summary carries `continues`.
|
|
268
|
-
- **The page:** the info bar's `continue →` continues the open session (`continues: <id>, idle: true`; it reads `continuing…` through the recap wait) and opens the new link with an opener in the composer, not sent (a draft written in this session stays instead; one carried over from another session or the start page gives way to the opener, and the toast's **Restore draft** brings it back): the previous link's own first typed message when that link continues one (the 201's `opener`; a `!shell` line isn't taken), else `Continue where we left off.`. So whatever you sent on the last link comes back on the next one, and editing it once changes it for every later link; the previous link is archived, a toast says so (`Continued 3fa2c1d0 in 4b7e9a01; 3fa2c1d0 is archived; 1 open delegate moved`), and a moved delegate's card folds under the new link. When the previous link's model's host is gone the link starts on the default model and the toast carries the server's `model_warning` too, and then stays until closed. A fresh link (one with no turn yet, however it was made: `continue →`, `chi send --new --continues`, a reload) shows the model picker in its composer with the link's model, until its first turn starts (a failed or canceled first turn ends it too); a pick runs `/model <name>` in the link (a command bubble; the info bar follows) and changes neither the start page's remembered model nor its Recent list, and a Send right after it waits for the `/model` to be taken. A link a chi REPL owns, or one continued already, shows none. A link continued already has no `continue →`: its `→` link goes to the next link (a stale page's continue answered 409 `continued` opens `next_id`). A continue refused for open delegates that can't move shows a toast that names each one (its preview and what the server says to do: `older chi worker 0.46.1; restart it`, `open in a chi REPL; close it there`), each a link to its page; any other refusal shows the server's words. The info bar of a link carries `← 2026-10-08` (the previous link's day) and, on an earlier link, `2026-10-10 →`, each a link with its id, its full start (`2026-10-10 14:32`) and its preview in the tooltip; a neighbour that started on the open link's own day shows its time instead (`← 09:12`, `14:32 →`). A link made by `continue →` inherits the previous link's preview (and its title, if it has one) when it is created, so its card keeps its chain's title whatever opener it is sent, and its top bar and browser tab show its card's title. A link with a recap of its own reads its auto title, else its recap's first sentence, instead (a link without one reads its chain's name, and a first link's recap names its chain). A link whose title is the opener and nothing else (a `chi send --new --continues` link whose first message is `Continue where we left off.`) shows its chain's first link's title instead, on its card, in the popover and in its info bar's title line, display only (its messages and data keep the opener; its own title when the first link is gone). The lists fold a chain into the card of its latest listed link, whatever the earlier links' archived state (typing in an archived link brings it back, folded there still), their delegates with them: the card's `↩ #N` chip (`#N of M` when later links aren't listed; its tooltip `Session chain: link N of M`) opens a popover of the other links, newest first (`#N`, its date, with its time (`2026-10-17 14:32`) when another link of the chain started the same day, its own title when it differs from the card's (a link inherits its chain's, so mostly it doesn't), else its recap's first sentence, else its preview, `archived`), a click opens one; the chip shows a live dot or `waiting` for a link that has a worker or waits on the user, and a waiting link puts its chain's card first. All sessions folds nothing while a search is typed or
|
|
268
|
+
- **The page:** the info bar's `continue →` continues the open session (`continues: <id>, idle: true`; it reads `continuing…` through the recap wait) and opens the new link with an opener in the composer, not sent (a draft written in this session stays instead; one carried over from another session or the start page gives way to the opener, and the toast's **Restore draft** brings it back): the previous link's own first typed message when that link continues one (the 201's `opener`; a `!shell` line isn't taken), else `Continue where we left off.`. So whatever you sent on the last link comes back on the next one, and editing it once changes it for every later link; the previous link is archived, a toast says so (`Continued 3fa2c1d0 in 4b7e9a01; 3fa2c1d0 is archived; 1 open delegate moved`), and a moved delegate's card folds under the new link. When the previous link's model's host is gone the link starts on the default model and the toast carries the server's `model_warning` too, and then stays until closed. A fresh link (one with no turn yet, however it was made: `continue →`, `chi send --new --continues`, a reload) shows the model picker in its composer with the link's model, until its first turn starts (a failed or canceled first turn ends it too); a pick runs `/model <name>` in the link (a command bubble; the info bar follows) and changes neither the start page's remembered model nor its Recent list, and a Send right after it waits for the `/model` to be taken. A link a chi REPL owns, or one continued already, shows none. A link continued already has no `continue →`: its `→` link goes to the next link (a stale page's continue answered 409 `continued` opens `next_id`). A continue refused for open delegates that can't move shows a toast that names each one (its preview and what the server says to do: `older chi worker 0.46.1; restart it`, `open in a chi REPL; close it there`), each a link to its page; any other refusal shows the server's words. The info bar of a link carries `← 2026-10-08` (the previous link's day) and, on an earlier link, `2026-10-10 →`, each a link with its id, its full start (`2026-10-10 14:32`) and its preview in the tooltip; a neighbour that started on the open link's own day shows its time instead (`← 09:12`, `14:32 →`). A link made by `continue →` inherits the previous link's preview (and its title, if it has one) when it is created, so its card keeps its chain's title whatever opener it is sent, and its top bar and browser tab show its card's title. A link with a recap of its own reads its auto title, else its recap's first sentence, instead (a link without one reads its chain's name, and a first link's recap names its chain). A link whose title is the opener and nothing else (a `chi send --new --continues` link whose first message is `Continue where we left off.`) shows its chain's first link's title instead, on its card, in the popover and in its info bar's title line, display only (its messages and data keep the opener; its own title when the first link is gone). The lists fold a chain into the card of its latest listed link, whatever the earlier links' archived state (typing in an archived link brings it back, folded there still), their delegates with them: the card's `↩ #N` chip (`#N of M` when later links aren't listed; its tooltip `Session chain: link N of M`) opens a popover of the other links, newest first (`#N`, its date, with its time (`2026-10-17 14:32`) when another link of the chain started the same day, its own title when it differs from the card's (a link inherits its chain's, so mostly it doesn't), else its recap's first sentence, else its preview, `archived`), a click opens one; the chip shows a live dot or `waiting` for a link that has a worker or waits on the user, and a waiting link puts its chain's card first. All sessions folds nothing while a search is typed or the `archived` toggle is on: every link is a card of its own then, its chip `#N of M`, with its own delegates folded in, so a search finds an earlier link and its delegates.
|
|
269
269
|
- **CLI:** `chi send --new --continues (ID|PREFIX|last:ID) [--model M] [-m TEXT]` (see *Starting a session*).
|
|
270
270
|
- **Which link:** an id, a unique prefix, or `last:<id or prefix>`, the latest link of the chain that session is in. It walks the links that still exist: a deleted link in the middle (`chi sessions delete`, the retention sweep of an unarchived one) ends the walk before it, so `last:` of an earlier link then names that earlier link, and continuing it starts a second line beside the links after the gap; name a link after the gap instead. A trigger names the chain this way: `chi send` to an archived link would bring it back (a human's input), not continue the chain.
|
|
271
271
|
|
|
@@ -423,7 +423,7 @@ mise.toml pins Ruby 3.4 gets that `ruby`, `gem` and `bundle` in its tools while
|
|
|
423
423
|
|
|
424
424
|
## Delegating
|
|
425
425
|
|
|
426
|
-
The `delegate` tool hands a task to a **child session**: an ordinary chi session in a worker of its own, started in the parent's `working_directory` with the task, verbatim, as its first user message, on the parent's model unless the call names one (`model:` takes a name or an alias, resolved as `--model` is; an unknown host comes back as the tool's `Error:` line; an id the host's saved model list doesn't have starts the child with a `Warning: host 'h' doesn't list model 'x' (did you mean: …?)` line in the result). Children run in parallel and only their **final reply** comes back to the parent: the child's newest `output/<timestamp>.txt` file, which the worker writes at the end of each turn that produced visible text, cut head-and-tail above 64 KB (`DelegateWait.cut`); a reply longer than `max_tool_output_chars` keeps its start and its end (the conclusion), with a gap line naming that `output/` file, where the whole reply is (`ToolOutputCut`'s `:head_tail_ref`; see [Tool output guardrails](internals/tool-guardrails.md#the-runners-cut-max_tool_output_chars)). Nothing of the child's trace enters the parent's context. Nothing is hidden: a child shows in `chi sessions list` (`↳ <parent>`), the web (folded into its parent's card on the strip and in all sessions, whose `▸ N delegates` chip lists the delegates at any depth, see Web UI above; a card of its own with a `↳` chip when its parent isn't listed;
|
|
426
|
+
The `delegate` tool hands a task to a **child session**: an ordinary chi session in a worker of its own, started in the parent's `working_directory` with the task, verbatim, as its first user message, on the parent's model unless the call names one (`model:` takes a name or an alias, resolved as `--model` is; an unknown host comes back as the tool's `Error:` line; an id the host's saved model list doesn't have starts the child with a `Warning: host 'h' doesn't list model 'x' (did you mean: …?)` line in the result). Children run in parallel and only their **final reply** comes back to the parent: the child's newest `output/<timestamp>.txt` file, which the worker writes at the end of each turn that produced visible text, cut head-and-tail above 64 KB (`DelegateWait.cut`); a reply longer than `max_tool_output_chars` keeps its start and its end (the conclusion), with a gap line naming that `output/` file, where the whole reply is (`ToolOutputCut`'s `:head_tail_ref`; see [Tool output guardrails](internals/tool-guardrails.md#the-runners-cut-max_tool_output_chars)). Nothing of the child's trace enters the parent's context. Nothing is hidden: a child shows in `chi sessions list` (`↳ <parent>`), the web (folded into its parent's card on the strip and in all sessions, whose `▸ N delegates` chip lists the delegates at any depth, see Web UI above; a card of its own with a `↳` chip when its parent isn't listed; `↑ <parent id>` in its info bar, its tooltip `delegated by session …`; the parent's info bar carries a `⑂ N` chip counting its delegates, with how many run, wait, are done, failed, stopped or idle in the tooltip and the waiting ones named in the attention colour; a click opens all sessions), the terminal's status row (a parent's `⑂ 2 running · 1 waiting`, see [Status Line](cli.md#status-line)) and the `list_sessions` tool (`child`; the parent shows as `parent` in the child's own list); the user can `chi --attach <child id>` and steer it mid-run, and `chi send` and `send_note` reach it like any session.
|
|
427
427
|
|
|
428
428
|
- `delegate(task, model:, session:, cwd:, wait:, timeout:)`: starts a child and, by default, waits for its reply. `wait: false` is the usual way for work that takes a while, several children side by side, or to keep talking with the user: it returns at once, the parent ends its turn or works on, and the reply comes back by itself as a *delegate report* (below), in a turn of its own if the parent is idle. The tool descriptions tell the model so, and to wait (`wait: true`, `delegate_result`) only when its turn can't go on without the reply. `timeout`, default 600 s, leaves a slow child running; 0 checks once without waiting. With `session:` (a child's id or prefix) the task goes to that child as a follow-up instead, delivered like `chi send` (client id `delegate:<parent short id>`, shown as `delegate>` in the child's UIs); only this session's children take one. The wait (`wait: true`, a later `delegate_result`) and the delegate report are for the reply of the child's turn the follow-up went to: its running turn, which it merges into, or the turn of its own it runs when the child is idle (`turn` in `delegates.json`, set when the child's worker names `turn_ids`), not a newer reply of a turn queued behind it. A follow-up merged as a steer into a turn the child started at that moment (a wake, a reminder, another message) gets that turn's reply or question. A child on its first turn, or with an older worker, gives its next reply, as before.
|
|
429
429
|
- `cwd:` starts the child in another folder of the parent's repository: a linked worktree (`git worktree add ../<repo>-<task> -b <branch>`, which the model runs with `execute` first, under guardrails; chi runs no git here) or a subfolder; a relative path is taken from the parent's folder. The folder must exist, have the parent's project root (the same common git dir, `MemoryPaths.project_root`) and be in a work tree: a git dir (`.git`, `.git/objects`, a bare layout's `.bare`) or a bare layout's container is refused (`git rev-parse --is-inside-work-tree`, read-only, decides). Outside git only a subfolder of the parent's folder is allowed. Otherwise the tool returns `Error: cwd must be a folder of this session's repository …` or `… is not in a work tree …`. A follow-up (`session:`) refuses `cwd:`: the child keeps its folder. The child's guardrails measure from its own worktree, and project memories, `list_sessions` and the web's project are the same as the parent's.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "path_resolve"
|
|
4
|
+
|
|
5
|
+
module Samagotchi
|
|
6
|
+
module Guardrails
|
|
7
|
+
# mise's file names, in one place for the boundaries that judge them:
|
|
8
|
+
# its local files hold secrets (ReviewPaths' SECRET_FILE / SECRET_PATH,
|
|
9
|
+
# PairBoundary's SECRET_TEXT) and its config runs at the owner's next
|
|
10
|
+
# worker start (PairCommand's mise config writes, ProjectEnv).
|
|
11
|
+
module MisePaths
|
|
12
|
+
# A local file's name: mise.local.toml, .mise.dev.local.toml (a
|
|
13
|
+
# Regexp source, to be embedded under the embedding pattern's flags).
|
|
14
|
+
LOCAL_FILE = '\.?mise(?:\.[\w-]+)?\.local\.toml'
|
|
15
|
+
# A local file in mise's config dirs: .config/mise/config.local.toml,
|
|
16
|
+
# config.<env>.local.toml; mise/ and .mise/ too (a Regexp source).
|
|
17
|
+
LOCAL_DIR_FILE = '(?:\.config/mise|\.?mise)/config(?:\.[\w-]+)?\.local\.toml'
|
|
18
|
+
|
|
19
|
+
# mise's config files, by a path's end: mise.toml, .mise.toml,
|
|
20
|
+
# mise.<env>.toml, .config/mise.toml, anything under .config/mise/,
|
|
21
|
+
# mise/ or .mise/ config*.toml, and the .tool-versions it reads too.
|
|
22
|
+
CONFIG_PATH = %r{(?:\A|/)(?:\.?mise(?:\.[^/]+)?\.toml|\.config/mise\.toml|\.config/mise/.+|
|
|
23
|
+
\.?mise/config[^/]*\.toml|\.tool-versions)\z}x
|
|
24
|
+
# The same files named in a command's or script's text.
|
|
25
|
+
CONFIG_TEXT = %r{(?<![\w-])(?:\.?mise(?:\.[\w.-]+)?\.toml|\.config/mise|\.?mise/config[\w.-]*\.toml|
|
|
26
|
+
\.tool-versions(?![\w.-]))}x
|
|
27
|
+
|
|
28
|
+
# Whether +path+ is one of mise's config files (CONFIG_PATH), as
|
|
29
|
+
# given or as it resolves.
|
|
30
|
+
def self.config?(path)
|
|
31
|
+
path.to_s.match?(CONFIG_PATH) ||
|
|
32
|
+
(path.to_s.start_with?("/") && PathResolve.reals(path).any? { |real| real.match?(CONFIG_PATH) })
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -1,42 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "chi_dirs"
|
|
4
|
+
require_relative "mise_paths"
|
|
4
5
|
require_relative "path_resolve"
|
|
5
6
|
require_relative "shell_paths"
|
|
6
7
|
require_relative "../paths"
|
|
7
8
|
|
|
8
9
|
module Samagotchi
|
|
9
10
|
module Guardrails
|
|
10
|
-
# mise's file names, in one place for the boundaries that judge them:
|
|
11
|
-
# its local files hold secrets (ReviewPaths' SECRET_FILE / SECRET_PATH,
|
|
12
|
-
# PairBoundary's SECRET_TEXT) and its config runs at the owner's next
|
|
13
|
-
# worker start (PairCommand's mise config writes, ProjectEnv). Kept in
|
|
14
|
-
# this file: the testing kit loads it already (its file budget).
|
|
15
|
-
module MisePaths
|
|
16
|
-
# A local file's name: mise.local.toml, .mise.dev.local.toml (a
|
|
17
|
-
# Regexp source, to be embedded under the embedding pattern's flags).
|
|
18
|
-
LOCAL_FILE = '\.?mise(?:\.[\w-]+)?\.local\.toml'
|
|
19
|
-
# A local file in mise's config dirs: .config/mise/config.local.toml,
|
|
20
|
-
# config.<env>.local.toml; mise/ and .mise/ too (a Regexp source).
|
|
21
|
-
LOCAL_DIR_FILE = '(?:\.config/mise|\.?mise)/config(?:\.[\w-]+)?\.local\.toml'
|
|
22
|
-
|
|
23
|
-
# mise's config files, by a path's end: mise.toml, .mise.toml,
|
|
24
|
-
# mise.<env>.toml, .config/mise.toml, anything under .config/mise/,
|
|
25
|
-
# mise/ or .mise/ config*.toml, and the .tool-versions it reads too.
|
|
26
|
-
CONFIG_PATH = %r{(?:\A|/)(?:\.?mise(?:\.[^/]+)?\.toml|\.config/mise\.toml|\.config/mise/.+|
|
|
27
|
-
\.?mise/config[^/]*\.toml|\.tool-versions)\z}x
|
|
28
|
-
# The same files named in a command's or script's text.
|
|
29
|
-
CONFIG_TEXT = %r{(?<![\w-])(?:\.?mise(?:\.[\w.-]+)?\.toml|\.config/mise|\.?mise/config[\w.-]*\.toml|
|
|
30
|
-
\.tool-versions(?![\w.-]))}x
|
|
31
|
-
|
|
32
|
-
# Whether +path+ is one of mise's config files (CONFIG_PATH), as
|
|
33
|
-
# given or as it resolves.
|
|
34
|
-
def self.config?(path)
|
|
35
|
-
path.to_s.match?(CONFIG_PATH) ||
|
|
36
|
-
(path.to_s.start_with?("/") && PathResolve.reals(path).any? { |real| real.match?(CONFIG_PATH) })
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
11
|
# What a participant may read (the shared-sessions plan's §2): a file
|
|
41
12
|
# under the session's project root that isn't on the read denylist
|
|
42
13
|
# (chi's state and config dirs, home secret dirs, project secret files),
|