samagotchi 0.130.0 → 0.131.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 +62 -1
- data/docs/architecture.md +1 -1
- data/docs/cli.md +15 -4
- data/docs/configuration.md +4 -1
- data/docs/desktop.md +28 -9
- data/docs/internals/background-tasks.md +1 -0
- data/docs/internals/participants.md +36 -9
- data/docs/internals/prompt-caching.md +5 -2
- data/docs/sessions.md +4 -2
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/cache_confirm.rb +29 -9
- data/lib/samagotchi/desktop/macos.rb +99 -19
- data/lib/samagotchi/desktop_command.rb +7 -2
- data/lib/samagotchi/engine.rb +31 -2
- data/lib/samagotchi/guardrails/rules.rb +2 -2
- data/lib/samagotchi/guardrails/targets.rb +13 -0
- data/lib/samagotchi/level_setting_command.rb +3 -2
- data/lib/samagotchi/session.rb +49 -4
- data/lib/samagotchi/session_commands.rb +4 -4
- data/lib/samagotchi/toolkit_command.rb +2 -2
- data/lib/samagotchi/tools/execute.rb +5 -2
- data/lib/samagotchi/tools/task_runtime.rb +4 -2
- data/lib/samagotchi/update_command.rb +9 -1
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +47 -8
- data/lib/samagotchi/web/participant_review.rb +9 -2
- data/lib/samagotchi/web/participant_view.rb +22 -6
- data/lib/samagotchi/web/proxy_gate.rb +6 -0
- data/lib/samagotchi/web/public/annotate_bar.js +6 -1
- data/lib/samagotchi/web/public/app.js +14 -5
- data/lib/samagotchi/web/public/participant.js +24 -6
- data/lib/samagotchi/web/public/verdict_card.js +48 -4
- data/lib/samagotchi/web/public/verdict_fresh.js +8 -4
- data/lib/samagotchi/web/public/verify_target.js +73 -0
- data/lib/samagotchi/web/token.rb +20 -1
- data/lib/samagotchi/worker.rb +7 -0
- 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: 37f2a54062786637083ade10186ebb751a1cfd7a7996540defa03da018b29533
|
|
4
|
+
data.tar.gz: 6836e60f2a87358698adaf000592ac68af5ce8fe8f3c26799825fa26641d3f1a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b908ad2c276d4dabb1c8bd64ad8435a2f9ab5408663243f8cd0f24e25ca2e4647cb356228a7a37027c1c5f2895936c4df8424af5203d8c121b5403577e2a2456
|
|
7
|
+
data.tar.gz: a3c96161fc7cb55e200367417e1dac93442845e0a868ad02d58ecca15a504af6824c419e8188252c9100daf405892be203e75fd6d7baaca1406c20186a4ff6f4
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,65 @@ 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.131.0] - 2026-10-29
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Cache: confirmations for prompt-cache changes estimate the reread size when known and warn that Anthropic drops
|
|
15
|
+
preserved thinking after a full-prefix change.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Web: a Verify verdict card sits right after the answer it quotes, on your page and a participant's, instead of
|
|
20
|
+
under the turn's prompt, screens above the claim in a long turn. A quote the page can't find yet puts the card at
|
|
21
|
+
the turn's end.
|
|
22
|
+
- Web: Verify on a claim in a model's reply on a participant's page records its verdict and shows the card again;
|
|
23
|
+
the claim was sent without its turn, so the check ran as a plain comment.
|
|
24
|
+
- Web: after a fresh verdict lands on an unclear card, its button reads **Verify fresh again**: each press starts
|
|
25
|
+
another paid check (a participant's counts against their cap).
|
|
26
|
+
- Web: Verify is offered only on a finished turn's final answer, on your page and a participant's: the one place a
|
|
27
|
+
verdict card can come from. Selecting an earlier reply, a step, a message, a line added mid-turn or a turn still
|
|
28
|
+
running no longer offers a Verify that silently ran as a plain comment; the bar keeps its other actions there.
|
|
29
|
+
- Web: on a participant's page, a turn a delegate's report or a context note woke opens as "An automatic turn"
|
|
30
|
+
(still without the report or note). Its answer read as one more reply of the turn before: Verify was offered on
|
|
31
|
+
it and refused, and hidden on that turn's real final answer.
|
|
32
|
+
- Web: Verify on a claim that names a file in the session's folder or your home folder (or holds a key) records its
|
|
33
|
+
verdict: the pages quote such a path relative, or with `~`, and the check compared it against the answer's full
|
|
34
|
+
path, so it ran as a plain comment with no card.
|
|
35
|
+
|
|
36
|
+
Update with `chi update`, then restart `chi web` and the session workers (`chi sessions restart`).
|
|
37
|
+
|
|
38
|
+
## [0.130.1] - 2026-10-29
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- Sessions started from the desktop helper find the tools your terminal finds (mise, Homebrew, `codex`):
|
|
43
|
+
`chi desktop install` writes your shell's PATH into the helper's launch file, `chi update` adds it to a
|
|
44
|
+
launch file that has none (it never changes a baked one; `chi desktop install --force` does), and
|
|
45
|
+
`chi desktop status` shows it. Run `chi update` once from your terminal, then restart the sessions the
|
|
46
|
+
panel started.
|
|
47
|
+
- `chi update` no longer repoints the desktop helper at the Ruby or chi it runs from, nor refreshes the launch file
|
|
48
|
+
from an older chi: only `chi desktop install --force` and `chi desktop upgrade` pick the current shell's Ruby,
|
|
49
|
+
chi and PATH. When the helper's Ruby or chi is gone, `chi update` says so and names that command.
|
|
50
|
+
- An older chi that saves a session a newer chi wrote (waking it from a project whose Ruby has an older chi, say)
|
|
51
|
+
keeps the session's fields it doesn't know instead of dropping them, and the session file names the chi that saved
|
|
52
|
+
it last (`written_by`; a worker woken on a file a newer chi saved logs `newer_writer`). This works from this release
|
|
53
|
+
on: chi 0.130.0 and older still drop them, so update the chi in every Ruby you run it from.
|
|
54
|
+
- `execute` (and a user's `!cmd`) no longer hands a project's commands chi's own Bundler setup: run under
|
|
55
|
+
`bundle exec`, chi passed on `RUBYOPT=-rbundler/setup` and its `BUNDLE_GEMFILE`, so `ruby -e` or `bundle exec`
|
|
56
|
+
in another project loaded chi's Gemfile. It now scrubs them the way `task_create` always has.
|
|
57
|
+
|
|
58
|
+
### Security
|
|
59
|
+
|
|
60
|
+
- On the LAN (`web.host: lan`), the cookie chi web sets for this machine's 127.0.0.1 no longer holds the access
|
|
61
|
+
token: a server on another local port gets every 127.0.0.1 cookie, so a dev server that logs headers caught the
|
|
62
|
+
token. It now holds a value that works only as that cookie on this machine and ends with `chi web --new-token`.
|
|
63
|
+
An open 127.0.0.1 tab lands on the 401 page once: open the `Local:` link. If you ran a participant's pair turns
|
|
64
|
+
with dev servers on 0.130.0, run `chi web --new-token`. The LAN address's cookie (your phone's) still holds the
|
|
65
|
+
token.
|
|
66
|
+
|
|
67
|
+
Update with `chi update`, then restart `chi web` and the session workers (`chi sessions restart`).
|
|
68
|
+
|
|
10
69
|
## [0.130.0] - 2026-10-29
|
|
11
70
|
|
|
12
71
|
### Changed
|
|
@@ -4933,7 +4992,9 @@ and long-lived sessions.
|
|
|
4933
4992
|
- A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
|
|
4934
4993
|
hotkey panel that send selected text or the clipboard to your sessions.
|
|
4935
4994
|
|
|
4936
|
-
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.
|
|
4995
|
+
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.131.0...HEAD
|
|
4996
|
+
[0.131.0]: https://github.com/dm1try/samagotchi/compare/v0.130.1...v0.131.0
|
|
4997
|
+
[0.130.1]: https://github.com/dm1try/samagotchi/compare/v0.130.0...v0.130.1
|
|
4937
4998
|
[0.130.0]: https://github.com/dm1try/samagotchi/compare/v0.129.0...v0.130.0
|
|
4938
4999
|
[0.129.0]: https://github.com/dm1try/samagotchi/compare/v0.128.0...v0.129.0
|
|
4939
5000
|
[0.128.0]: https://github.com/dm1try/samagotchi/compare/v0.127.0...v0.128.0
|
data/docs/architecture.md
CHANGED
|
@@ -153,7 +153,7 @@ agent logic and can be used without any terminal rendering; the UI is a thin lay
|
|
|
153
153
|
| UI | `Samagotchi::TerminalUI` | Interactive REPL (Reline), rendering (ANSI, spinner, status line), REPL commands. Delegates all core work to an `Engine`. |
|
|
154
154
|
| Model loops and adapters | `KernelLoop`, `LLM::ChatLoop`, `Samagotchi::Client`, `LLM::OpenAIChat`, `LLM::AnthropicMessages`, `LLM::HTTP` | The model↔tool loops and the HTTP adapters they talk through (see "Model loops and adapters"). |
|
|
155
155
|
| Bridge (SSE/HTTP) | `Samagotchi::Bridge`, `SessionManager` | The **single live client transport**: an SSE read stream + HTTP POST turn/cancel/answer surface that attaches to a worker's existing `Engine` via `Engine#subscribe`. Every session worker starts it (bound `127.0.0.1`, localhost-only), and it answers only a request that names its secret (`X-Chi-Bridge`, from the worker's 0600 `bridge.json`). |
|
|
156
|
-
| Web (Rack) | `Samagotchi::Web::App`, `SessionManager` | Single-port `127.0.0.1:4567` control plane via `rack`+`webrick` (serve `index.html` + `/api/*`). With `web.host: lan` (or one of this machine's IPv4 addresses) the server also listens on that address, where every request needs the access token, this machine's on loopback too (`App#token_gate`: the `chi_token` cookie or `Authorization: Bearer`; chi web prints a `Local:` link with a one-time code; [CLI](cli.md#chi-web-on-your-phone)). Experimental: behind an auth proxy (`web.public_url`, `Web::ProxyGate`) every request needs the token, this machine's too, and one that came through the proxy also the configured identity header for the owner ([configuration.md](configuration.md#behind-an-auth-proxy)). A request goes through, in order: the Host check (`local_host_header?`: loopback names, the LAN address, the public host), the cross-site check (`cross_site?`: Origin this server's or the public origin, no `Sec-Fetch-Site` cross-site/same-site on a POST or `/api`), the frame check (`framed?`: a frame/embed/object load from another origin, 403 `framed`; every HTML answer carries `X-Frame-Options: DENY` and `frame-ancestors 'none'`), the token gate (`token_gate` → `principal_for`, `proxied_principal` for a proxied request; the `?token=` / `POST /api/token` trade sets the cookie, `__Host-chi_token` through the proxy; with `web.public_url` a direct request takes Bearer only, and with it or LAN mode chi's token-less `/api/info` probe gets `probe_info`), then the route's permission (`Principal#permits?`). It talks to each worker's Bridge (`App#relay`, `bridge_get`; `/stream` proxies it) and reads the session files; no `Engine` of its own. Markdown (`web.markdown`) is rendered here only (`Web::MarkdownRenderer`): the answers it serves, card bodies, and `POST /api/render` for a text the page already shows. `App` keeps the gate, `ROUTES` and the helpers the handlers share; its handlers live in mixins it includes, each a `Web::` module with private methods: `SessionAccessRoutes` (`web/session_access_routes.rb`), `ParticipantAccess` (`web/participant_access.rb`), `ParticipantsAdmin` (`web/participants_admin.rb`), `ParticipantReview` (`web/participant_review.rb`), `MessageDisplay` (`web/message_display.rb`), `SessionShow` (`web/session_show.rb`), `SessionCreate` (`web/session_create.rb`), `SessionControl` (`web/session_control.rb`), `PushRoutes` (`web/push_routes.rb`) and `ProxyGate` (`web/proxy_gate.rb`). Exact paths in `GATE_FREE_PATHS` are served before the token gate (the fragment trade's script, and the Home Screen app's manifest, service worker and icons). `bin/chi web` entrypoint. |
|
|
156
|
+
| Web (Rack) | `Samagotchi::Web::App`, `SessionManager` | Single-port `127.0.0.1:4567` control plane via `rack`+`webrick` (serve `index.html` + `/api/*`). With `web.host: lan` (or one of this machine's IPv4 addresses) the server also listens on that address, where every request needs the access token, this machine's on loopback too (`App#token_gate`: the `chi_token` cookie, on loopback a value derived from the token (`Token.loopback_cookie`), or `Authorization: Bearer`; chi web prints a `Local:` link with a one-time code; [CLI](cli.md#chi-web-on-your-phone)). Experimental: behind an auth proxy (`web.public_url`, `Web::ProxyGate`) every request needs the token, this machine's too, and one that came through the proxy also the configured identity header for the owner ([configuration.md](configuration.md#behind-an-auth-proxy)). A request goes through, in order: the Host check (`local_host_header?`: loopback names, the LAN address, the public host), the cross-site check (`cross_site?`: Origin this server's or the public origin, no `Sec-Fetch-Site` cross-site/same-site on a POST or `/api`), the frame check (`framed?`: a frame/embed/object load from another origin, 403 `framed`; every HTML answer carries `X-Frame-Options: DENY` and `frame-ancestors 'none'`), the token gate (`token_gate` → `principal_for`, `proxied_principal` for a proxied request; the `?token=` / `POST /api/token` trade sets the cookie, `__Host-chi_token` through the proxy; with `web.public_url` a direct request takes Bearer only, and with it or LAN mode chi's token-less `/api/info` probe gets `probe_info`), then the route's permission (`Principal#permits?`). It talks to each worker's Bridge (`App#relay`, `bridge_get`; `/stream` proxies it) and reads the session files; no `Engine` of its own. Markdown (`web.markdown`) is rendered here only (`Web::MarkdownRenderer`): the answers it serves, card bodies, and `POST /api/render` for a text the page already shows. `App` keeps the gate, `ROUTES` and the helpers the handlers share; its handlers live in mixins it includes, each a `Web::` module with private methods: `SessionAccessRoutes` (`web/session_access_routes.rb`), `ParticipantAccess` (`web/participant_access.rb`), `ParticipantsAdmin` (`web/participants_admin.rb`), `ParticipantReview` (`web/participant_review.rb`), `MessageDisplay` (`web/message_display.rb`), `SessionShow` (`web/session_show.rb`), `SessionCreate` (`web/session_create.rb`), `SessionControl` (`web/session_control.rb`), `PushRoutes` (`web/push_routes.rb`) and `ProxyGate` (`web/proxy_gate.rb`). Exact paths in `GATE_FREE_PATHS` are served before the token gate (the fragment trade's script, and the Home Screen app's manifest, service worker and icons). `bin/chi web` entrypoint. |
|
|
157
157
|
| Sessions | `Samagotchi::Session`, `SessionManager` | File-based `~/.local/state/samagotchi/sessions/<uuid>.json` + sidecar `input/`/`output/`; retention (14d/500) + ordering (`updated_at desc`). |
|
|
158
158
|
|
|
159
159
|
- `bin/chi` in REPL mode (see `LaunchMode` above) builds `TerminalUI`. `TerminalUI#run` is the single
|
data/docs/cli.md
CHANGED
|
@@ -290,8 +290,12 @@ done
|
|
|
290
290
|
one that failed before; one you uninstalled stays out. See [Bundle
|
|
291
291
|
profiles](memory.md#bundle-profiles-core-and-dev).
|
|
292
292
|
- **The desktop helper** (macOS) is rebuilt and restarted only when its Swift
|
|
293
|
-
sources changed
|
|
294
|
-
|
|
293
|
+
sources changed; otherwise only its launch file is refreshed (a newer chi or
|
|
294
|
+
other terminal settings). It keeps the Ruby, chi and PATH the helper runs (a
|
|
295
|
+
PATH is filled in only when the file has none): `chi desktop install --force`
|
|
296
|
+
and `chi desktop upgrade` bake this shell's. A helper whose Ruby or chi is gone
|
|
297
|
+
is reported as failed, naming `chi desktop install --force`. See
|
|
298
|
+
[Desktop helper](desktop.md).
|
|
295
299
|
- **Running processes** are reported, never stopped. A live worker keeps its
|
|
296
300
|
chi until it is restarted: `chi sessions restart ID` (one from before
|
|
297
301
|
restarts: `chi sessions stop ID`, and the next prompt starts it on the new
|
|
@@ -1210,7 +1214,9 @@ pills there (a review turn refuses `/` lines).
|
|
|
1210
1214
|
|
|
1211
1215
|
After the presets, both pages have a built-in **Verify**: the quote, then
|
|
1212
1216
|
an ask to check the claim against the code and end on a line `Verdict:
|
|
1213
|
-
confirmed|refuted|unclear — <reason>`.
|
|
1217
|
+
confirmed|refuted|unclear — <reason>`. Verify is offered only on text in a finished turn's final answer, the
|
|
1218
|
+
claim a verdict card can come from; elsewhere (an earlier reply, a step, a message, a line added mid-turn, a turn
|
|
1219
|
+
still running) the bar keeps its other actions. A session whose worker predates Verify (started by an older chi) refuses it (409
|
|
1214
1220
|
`verify_unavailable`); a participant's page disables Verify and Verify fresh with a hint to restart the session. On your page a selection in a turn
|
|
1215
1221
|
also offers **Copy link**: a link to that turn and quote for a participant's
|
|
1216
1222
|
browser (the one their review link was opened in), which opens their page
|
|
@@ -1271,7 +1277,12 @@ access token", paste the token there (the part of the link after
|
|
|
1271
1277
|
paste the token) and the API answers 401. On this Mac open the `Local:`
|
|
1272
1278
|
link (`chi web --open` opens it): its one-time code (10 minutes, once) sets
|
|
1273
1279
|
the cookie for 127.0.0.1 without the token in the address; the LAN link
|
|
1274
|
-
works here too.
|
|
1280
|
+
works here too. The cookie for 127.0.0.1 holds a value derived from the
|
|
1281
|
+
token, not the token: the browser sends it to a server on any other local
|
|
1282
|
+
port too (a dev server that logs headers), and that server can't use it
|
|
1283
|
+
from the network (unless something forwards it to this machine's
|
|
1284
|
+
loopback), as Bearer, or as the token; `chi web --new-token` ends it. The LAN address's cookie (your phone's, or this Mac's when it opens
|
|
1285
|
+
the LAN link) is the token itself. chi's own probe (`GET /api/info` with no token or cookie)
|
|
1275
1286
|
gets a few fields (version, pid, features, the LAN address, the start
|
|
1276
1287
|
folder), nothing of the sessions.
|
|
1277
1288
|
- The token lives in `$XDG_STATE_HOME/samagotchi/web-token` (0600).
|
data/docs/configuration.md
CHANGED
|
@@ -838,7 +838,10 @@ Gemma's thinking switch, a remote chat host's thinking fields ("Thinking"), a ch
|
|
|
838
838
|
reply has thinking ("Reasoning history"), and any change to the declared tools, which start every request
|
|
839
839
|
("Toolkits"). They ask only when what the next turn runs under really changes and the session has a model reply.
|
|
840
840
|
|
|
841
|
-
- The reply says what it costs and changes nothing yet
|
|
841
|
+
- The reply says what it costs and changes nothing yet. When known, it estimates the reread size: the whole
|
|
842
|
+
conversation for full-prefix changes, or from the first changed reply for reasoning history. On `api: anthropic`,
|
|
843
|
+
a full-prefix change also says preserved thinking in replies is dropped from then on.
|
|
844
|
+
- `Nothing changed yet: send /thinking low --yes to apply.` names the line that applies it.
|
|
842
845
|
- `--yes` (or `-y`, anywhere after the command) applies it at once.
|
|
843
846
|
- The terminal (the REPL and an attached TUI) puts the `--yes` line in the next input: Enter applies it.
|
|
844
847
|
- The web's reply bubble has an **Apply (restarts the cache)** button. The session bar's chips are already an explicit
|
data/docs/desktop.md
CHANGED
|
@@ -194,7 +194,8 @@ grant:
|
|
|
194
194
|
|
|
195
195
|
`tmux.agents` picks the programs listed (kitty.agents' default; `"*"` lists every pane), `tmux.binary` the tmux
|
|
196
196
|
to run (default: the `tmux` on your shell's PATH when the launch file is written, e.g. `/opt/homebrew/bin/tmux`;
|
|
197
|
-
the helper itself
|
|
197
|
+
the helper itself keeps macOS's default PATH, and tmux is resolved when the file is written). See
|
|
198
|
+
[Configuration](configuration.md#desktop-helper-agents-in-kitty-and-tmux).
|
|
198
199
|
2. Run `chi update` (or `chi desktop upgrade`), and again after every edit of `tmux:`. With no tmux found they say
|
|
199
200
|
"tmux.enabled but no tmux found on PATH: set tmux.binary", and the panel "tmux not found…".
|
|
200
201
|
|
|
@@ -256,9 +257,16 @@ don't break it. From a checkout it runs **that** checkout's `bin/chi`; installin
|
|
|
256
257
|
warning, because the helper stops working once that worktree is removed. After switching between a checkout and a
|
|
257
258
|
gem install, run `chi desktop upgrade` from the one you now use.
|
|
258
259
|
|
|
260
|
+
The helper runs the Ruby and chi of **the shell you installed from**, and only `chi desktop install --force` and
|
|
261
|
+
`chi desktop upgrade` change that: they pick the Ruby and chi of the shell you run them in. `chi update` never
|
|
262
|
+
repoints it, so running it from a project with another Ruby (or an older chi) leaves the helper where it was. If
|
|
263
|
+
the Ruby or chi the helper runs is gone (a Ruby upgrade, a removed worktree), `chi update` fails the helper's row
|
|
264
|
+
with "chi not found at …: run chi desktop install --force from the shell whose chi it should run".
|
|
265
|
+
|
|
259
266
|
`chi update` keeps it current: it rebuilds and restarts the helper only when its Swift sources changed since the
|
|
260
|
-
build (`launch.json` records their digest)
|
|
261
|
-
rewrites `launch.json
|
|
267
|
+
build (`launch.json` records their digest), keeping the Ruby, chi and PATH it runs. A newer chi that left the
|
|
268
|
+
sources alone only rewrites `launch.json` (its version and settings), which the app reads at each send, so the app
|
|
269
|
+
keeps its older version number and that's fine. An older chi than the one that wrote the file changes nothing.
|
|
262
270
|
|
|
263
271
|
## Commands
|
|
264
272
|
|
|
@@ -267,7 +275,7 @@ rewrites `launch.json`, which the app reads at each send, so the app keeps its o
|
|
|
267
275
|
| `chi desktop install [--force] [--login]` | builds, installs and starts it; `--force` replaces an existing copy |
|
|
268
276
|
| `chi desktop upgrade` | rebuilds it for this chi and restarts it, keeping the login setting (always; `chi update` does it only when needed) |
|
|
269
277
|
| `chi desktop uninstall` | quits it and removes the app, its login item, launch file and settings |
|
|
270
|
-
| `chi desktop status` | version against chi's, how it runs chi, state dirs, Service, hotkey, process, login item, host keys |
|
|
278
|
+
| `chi desktop status` | version against chi's, how it runs chi, state dirs, PATH, Service, hotkey, process, login item, host keys |
|
|
271
279
|
|
|
272
280
|
`chi self` has a `desktop` line: `0.1.x (matches)`, `0.1.w (up to date for chi 0.1.x)` (an older build whose sources
|
|
273
281
|
haven't changed), `0.1.w (chi is 0.1.x: chi update)` (a rebuild is due) or `not installed`. `chi desktop status`
|
|
@@ -278,17 +286,24 @@ says the same.
|
|
|
278
286
|
Apps started by macOS get a bare environment: no shell rc files, so no rbenv/chruby/mise/asdf and none of your
|
|
279
287
|
exports. So `install` writes `~/Library/Application Support/Chi Helper/launch.json` with:
|
|
280
288
|
|
|
281
|
-
- the absolute path of the Ruby running chi and of chi itself (the gem's wrapper, or a checkout's `bin/chi`)
|
|
289
|
+
- the absolute path of the Ruby running chi and of chi itself (the gem's wrapper, or a checkout's `bin/chi`), as of
|
|
290
|
+
the last `chi desktop install` or `upgrade` (`chi update` keeps them);
|
|
282
291
|
- `LANG=en_US.UTF-8`;
|
|
283
292
|
- only these variables, and only when they are set: `XDG_CONFIG_HOME`, `XDG_STATE_HOME`, `GEM_HOME`, `GEM_PATH`,
|
|
284
293
|
`RUBYLIB` (a relative `XDG_*` dir made absolute from the install's current folder: the helper starts in `/`).
|
|
285
294
|
No tokens and no `SAMAGOTCHI_*` settings go in;
|
|
295
|
+
- `PATH`: the shell's PATH when the file is written, with only absolute folders, in order, each once. The helper's
|
|
296
|
+
chi, the sessions it starts and their `execute` commands can find the tools your terminal finds (mise, Homebrew,
|
|
297
|
+
`codex`, `node`);
|
|
286
298
|
- the settings the panel needs from config.yml, as chi read them then: the `kitty:` section (when
|
|
287
299
|
`kitty.listen_on` is set), the `tmux:` section (when `tmux.enabled`: the tmux binary and the socket dir resolved
|
|
288
300
|
then, and the agents) and `broadcast_timeout` (`broadcast.triage_deadline` + 10 s).
|
|
289
301
|
|
|
290
|
-
The file freezes the install shell's values. If you install with a temporary `XDG_STATE_HOME`, the
|
|
291
|
-
using it. `
|
|
302
|
+
The file freezes the install shell's values, including PATH. If you install with a temporary `XDG_STATE_HOME`, the
|
|
303
|
+
helper keeps using it. `chi update` fills in a PATH only when the file has none (as in a launch file written before
|
|
304
|
+
this change); it never changes an existing baked PATH. To change it (for example, after a mise upgrade changes the
|
|
305
|
+
versioned folders on PATH), run `chi desktop install --force` or `chi desktop upgrade` from the shell whose PATH
|
|
306
|
+
you want. `status` prints the baked state dirs and PATH.
|
|
292
307
|
|
|
293
308
|
**Host API keys** don't go in the launch file either: with the variable a host's `api_key_env:` names unset, chi
|
|
294
309
|
reads the key from the login Keychain (service `chi`, account the variable's name; see
|
|
@@ -331,8 +346,12 @@ broadcast` says the note may be partly delivered.
|
|
|
331
346
|
|
|
332
347
|
## Troubleshooting
|
|
333
348
|
|
|
334
|
-
- **"chi not found at
|
|
335
|
-
|
|
349
|
+
- **"chi not found at …"**: the Ruby or checkout in `launch.json` moved (a Ruby upgrade, a removed worktree).
|
|
350
|
+
`chi update` won't repoint it: run `chi desktop install --force` (or `chi desktop upgrade`) from the shell whose
|
|
351
|
+
chi the helper should run.
|
|
352
|
+
- **A session started from the panel can't find a tool (`codex`, `node`):** `chi desktop status` shows the PATH it
|
|
353
|
+
gets. With none, run `chi update`; to change it, run `chi desktop install --force` from your terminal. Then
|
|
354
|
+
restart that session (`chi sessions restart ID`).
|
|
336
355
|
- **No "Send to chi" in the Services menu:** check `chi desktop status` (service). Try
|
|
337
356
|
`/System/Library/CoreServices/pbs -update`, start the app again, or log out and back in. It must be ticked in
|
|
338
357
|
System Settings → Keyboard → Keyboard Shortcuts… → Services → Text.
|
|
@@ -42,6 +42,7 @@ Behavior:
|
|
|
42
42
|
again or stop it (`TaskWait::MESSAGE_WAITING_NOTE`), and `waited: Ns`; the task keeps running and stays watched
|
|
43
43
|
(below). A task that finished meanwhile gives its finished result, as after a Stop. A `chi -p` turn has no probe.
|
|
44
44
|
- `task_create` accepts `env` as a JSON object string for deterministic overrides such as `PATH`; use an absolute interpreter path when that is simpler. Ruby/Bundler isolation variables remain protected.
|
|
45
|
+
- `execute` and `task_create` both spawn with chi's own Ruby/Bundler setup scrubbed (`TaskRuntime.sanitized_spawn_env`): `RUBYOPT` loses its `-r…bundler/setup` flags (others stay) and `RUBYLIB`, `BUNDLE_GEMFILE`, `BUNDLE_BIN_PATH`, `BUNDLER_VERSION` are unset, so a project's `ruby -e` or `bundle exec` uses its own Gemfile when chi runs under `bundle exec`. `task_create` refuses those keys in the model's `env`; `execute`'s `env` comes from chi's callers (`Builtins`, `!cmd`), not the model, and is set over the scrub. An `execute` handed over at its limit keeps the process it spawned, so the task runs with the same env.
|
|
45
46
|
- `execute` and `task_create` export chi's own markers into the command, set over the model's `env` (which can't set them: `Error: env key is reserved`): `SAMAGOTCHI_PARENT_SESSION` (the session's id, `chi` without one; a chi started there answers as a parent agent), `SAMAGOTCHI_SESSION_MODEL` (the resolved ref the session runs on, read at each call so it follows `/model`; unset when unknown) `SAMAGOTCHI_SESSION_THINKING` (the session's own thinking level; unset when it has none) and `SAMAGOTCHI_SESSION_REASONING_HISTORY` (its own reasoning_history level, the same way). `chi self` reads the last three for its model, thinking level and reasoning history. The four are `ChildEnv::SESSION_MARKERS` (`TaskRuntime::MARKER_ENV_KEYS`), which a spawned worker unsets (`ChildEnv.unset`, merged into its spawn env). A session's worker never inherits them from whoever started it (a `chi send` run by another session's `execute`): its other commands (a user's `!cmd`, an MCP server, a context command) have none.
|
|
46
47
|
- They also pass on a set `XDG_STATE_HOME` / `XDG_CONFIG_HOME` made absolute (`Builtins.xdg_absolute`, via `Paths`), so a chi the command runs in another `cwd` (`chi send`) reads this chi's state and config. These aren't reserved: an XDG dir the model sets in `task_create`'s `env` stays its own.
|
|
47
48
|
|
|
@@ -122,7 +122,13 @@ participant** and **one role**, until it expires or is revoked.
|
|
|
122
122
|
the detail (only the requester's own identity). Unproxied, every request needs a token too (no "loopback is the
|
|
123
123
|
owner"): rule 3 doesn't apply.
|
|
124
124
|
1. The owner's token (Bearer, then the cookie; `?token=` on the page's trade): the **owner**. Checked first, so the
|
|
125
|
-
owner's requests never touch the grants store (measured: no change to an owner route's time).
|
|
125
|
+
owner's requests never touch the grants store (measured: no change to an owner route's time). On a LAN chi web
|
|
126
|
+
(no `web.public_url`) a loopback peer's owner cookie is `Token.loopback_cookie` instead (`chl_`, an HMAC of a
|
|
127
|
+
label keyed by the token, `App#loopback_cookie_peer?` / `#loopback_cookie`): it stands for the token only as that
|
|
128
|
+
cookie from a loopback peer, never as Bearer, a trade or from the LAN address, and `chi web --new-token` ends it.
|
|
129
|
+
The token itself as a loopback cookie (set by 0.130.0) is refused, and its 401 clears it (`#refused_token`). The
|
|
130
|
+
token as Bearer on loopback is the owner as before (SSH tunnels, `chi --remote` over `ssh -L`, curl, the e2e
|
|
131
|
+
harness).
|
|
126
132
|
2. A `chp_` token (`Grants.token?`) whose grant is active: a **participant** (`Principal.new(kind: :participant,
|
|
127
133
|
grant:)`), from any peer: loopback and a chi web not on the LAN too. An ended or unknown one is refused
|
|
128
134
|
(below), never the owner.
|
|
@@ -134,7 +140,10 @@ participant** and **one role**, until it expires or is revoked.
|
|
|
134
140
|
(`Server.local_owner_link`), the fragment trade sets the cookie there. `PairCommand` still denies a pair command
|
|
135
141
|
naming chi web on a loopback name (defense in depth), and URL openers and listeners: a page opened in the owner's
|
|
136
142
|
browser at a port the shell listens on would catch the owner's cookie (one on 127.0.0.1 or the LAN address goes
|
|
137
|
-
to every port)
|
|
143
|
+
to every port: on 127.0.0.1 it holds the loopback value of rule 1, usable only as that cookie from a loopback peer with no proxy trace (anything that can reach loopback, such as an `ssh -L` user or code running as the owner, can still replay it); on
|
|
144
|
+
the LAN address it is the token, a known gap, as a cookie there is replayable from any LAN device anyway). A
|
|
145
|
+
server on another local port can also answer `Set-Cookie: chi_token=…` and overwrite the owner's cookie (cookie
|
|
146
|
+
tossing: it logs the owner out, or swaps in a grant's token; visible, nothing leaks; not fixed).
|
|
138
147
|
4. Anything else: nil. `#token_gate` answers a bound grant's token without its identity **401 `token_bound`**, an
|
|
139
148
|
ended grant's token **401 `token_revoked`** or **401
|
|
140
149
|
`token_expired`** (the page says the same words as a sentence: "This review link was revoked by the session's
|
|
@@ -143,7 +152,8 @@ participant** and **one role**, until it expires or is revoked.
|
|
|
143
152
|
|
|
144
153
|
The `?token=` trade (and `POST /api/token`, with a link's one-time `{code}` or the 401 page's pasted `{token}`; with
|
|
145
154
|
`web.public_url` only through the proxy, as a direct request takes Bearer only) sets the `chi_token` cookie (`__Host-chi_token`, Secure, on a proxied request) to the token that was
|
|
146
|
-
presented, the owner's or the participant's
|
|
155
|
+
presented, the owner's or the participant's (the owner's loopback value instead, on a LAN chi web's loopback peer,
|
|
156
|
+
`App#owner_cookie_value`)
|
|
147
157
|
(one at a time per browser: a participant link opened in the owner's browser replaces the owner's cookie there, and
|
|
148
158
|
makes that browser the participant, on loopback too). A grant's token is traded on any peer; the owner's only where
|
|
149
159
|
the gate asks for it (on the LAN, this machine's loopback too). On a chi web the gate doesn't ask (loopback only), the
|
|
@@ -365,7 +375,10 @@ fields: a field added to an event later reaches nobody until it is named there.
|
|
|
365
375
|
so the owner's `!` line and its output, a delegate's report (a wake turn's too), and the notes chi adds (a declined
|
|
366
376
|
Continue) never show. A turn no person sent (its record's `automatic`, `SessionMetrics`: a task's or a plugin's,
|
|
367
377
|
read from analytics.json and the live worker's records; the running turn's by its start; a delegate child's first
|
|
368
|
-
turn, its parent's task) shows as an automatic turn without its prompt, as the stream does
|
|
378
|
+
turn, its parent's task) shows as an automatic turn without its prompt, as the stream does; so does a wake turn's
|
|
379
|
+
start (`.wake_start`: the delegate report or the context note a wake turn opens with is dropped, its `turn_id` and
|
|
380
|
+
`at` kept), so its answer is never read as one more reply of the turn before (Verify, verdict cards and claim
|
|
381
|
+
links group by `[data-turn-id]`). A raw loop's saved
|
|
369
382
|
reply is cut before the display reads it (`.visible_raw`): what came before a bare `</think>` and everything from
|
|
370
383
|
an unclosed thought or tool call on, which the display would only untag. Each
|
|
371
384
|
prompt carries its author and an image count; replies their text and their step's tool rows. Notes, steers and
|
|
@@ -418,6 +431,13 @@ fields: a field added to an event later reaches nobody until it is named there.
|
|
|
418
431
|
After the presets comes the built-in **Verify** (`verify_preset.js`, on the owner's page too): the quote, then
|
|
419
432
|
"Verify this claim against the code. If this session's context doesn't settle it, say so. End with a line
|
|
420
433
|
`Verdict: confirmed|refuted|unclear — <one-line reason>`." It fills the box like any preset and never sends.
|
|
434
|
+
It is offered only where the Engine will take the claim (`verify_target.js`, the bar's `offeredPreset`): a
|
|
435
|
+
selection in the final answer of an ended turn, the last answer bubble (the owner's `.bubble.output` that isn't a
|
|
436
|
+
delegate report's reply; a participant's last `.p-msg.reply > .p-bubble`) between the turn's opening
|
|
437
|
+
(`[data-turn-id]`) and the next one (or, on the owner's page, a context note: a wake turn opens with one; a
|
|
438
|
+
participant's page gets a wake turn's opening as an automatic turn).
|
|
439
|
+
Anywhere else (an earlier reply, a step, a prompt, a line merged mid-turn, the Plan, the running turn) the button
|
|
440
|
+
is hidden and the bar keeps its other actions.
|
|
421
441
|
- **Deep links to a claim** (`claim_link.js`). The owner selects text in a turn's answer, step or own message and
|
|
422
442
|
Annotate's **Copy link** copies `<base>/#s=<id>&t=<turn8>&q=<quote>&p=<prefix>`: `base` the participants list's
|
|
423
443
|
`link_base` (`ParticipantsAdmin#participant_base`: `web.public_url`'s origin, else the LAN address, else the Host),
|
|
@@ -444,13 +464,18 @@ fields: a field added to an event later reaches nobody until it is named there.
|
|
|
444
464
|
with the submitted turn, without changing its prompt text. A Verify turn that fails fast and is rolled back hands
|
|
445
465
|
the claim back with its prompt (`prompt_restored`'s `verify`), so the owner's resend from the composer is a Verify
|
|
446
466
|
turn again. Before the Engine stamps the anchor or records a verdict, it checks that the source turn exists and
|
|
447
|
-
its assistant reply contains the quote. Matching is case-sensitive, collapses whitespace, and strips common
|
|
467
|
+
its final assistant reply (`AnswerTail`: the turn's last assistant message with text) contains the quote. Matching is case-sensitive, collapses whitespace, and strips common
|
|
448
468
|
Markdown presentation (links, headings, list/blockquote prefixes, emphasis and code delimiters) from both sides;
|
|
449
|
-
this matches the rendered text the page selects without treating different casing as the same claim.
|
|
469
|
+
this matches the rendered text the page selects without treating different casing as the same claim. The quote may
|
|
470
|
+
also be in the answer as the pages show it (`Engine#verify_quote_in?`, `ParticipantView::Redact.text`: the session's
|
|
471
|
+
folder relative, home `~`, secret shapes `[redacted]`): a participant's page has only that text, and the owner's
|
|
472
|
+
page quotes it the same way (`claimOpts()`, `normalizeClaim`; `spec/fixtures/redact_cases.json` keeps the two
|
|
473
|
+
sides alike), so a claim naming a path is taken on both. A fresh child
|
|
450
474
|
checks the same anchor against its parent's history. A completed Verify reply whose final non-empty line has
|
|
451
475
|
a valid `Verdict: confirmed|refuted|unclear — <reason>` appends a verdict to the session. Both pages place its card
|
|
452
|
-
|
|
453
|
-
|
|
476
|
+
in the source turn, right after the bubble that holds the quote (the answer's, matched as a claim link reads the
|
|
477
|
+
page; a quote not found yet goes at the turn's end), name its author, show the reason and quote, and link to the
|
|
478
|
+
checking turn; a live completion adds the card without a reload. The participant view receives only the card fields through its allowlist, with the
|
|
454
479
|
quote and reason redacted by the view's normal rules, never the full reply.
|
|
455
480
|
- **Verify fresh** (`VerifyFresh`, `verdict_fresh.js`). An `unclear` card has a **Verify fresh** button (the owner's
|
|
456
481
|
page always; a participant's while `can_comment`, i.e. while their grant could run a turn now; never in the owner's
|
|
@@ -510,7 +535,9 @@ fields: a field added to an event later reaches nobody until it is named there.
|
|
|
510
535
|
`:verdict_recorded`; chi web shows the ones still waiting (`VerifyFresh.verdicts_of`: the summary, the review
|
|
511
536
|
data). The card shows "Verifying fresh…" while the child runs (the summary's `verify_fresh` `{of, child_id}`,
|
|
512
537
|
the review data's `fresh_running`, ids only) and the fresh verdict as a `fresh: ✅ confirmed — …` line on the same
|
|
513
|
-
card. The
|
|
538
|
+
card. The original stays unclear, so the button stays, as **Verify fresh again**: each press is another paid
|
|
539
|
+
child (a participant's against their cap). The participant view passes `fresh` and `of`, never the child's id (a
|
|
540
|
+
participant can't open it). No push
|
|
514
541
|
for a fresh verdict yet (`Attention` pushes a verdict at a turn's end).
|
|
515
542
|
- **The comment box** is the owner's composer (`composer_mount.js`, styled by the shared `composer.css`), mounted by
|
|
516
543
|
`participant.js` with a subset on. It has: the height that grows with the text, the grip (drag to resize,
|
|
@@ -232,8 +232,11 @@ is prefilled once. The native and the chat loop use the same plan.
|
|
|
232
232
|
classifier: `Thinking.cache_cost` (`:full` for Gemma, `:provider` for a remote chat host, `:tail` otherwise) and
|
|
233
233
|
`ReasoningHistory.cache_cost` (`:history` for a change to or from `all` once an earlier reply has thinking).
|
|
234
234
|
`CacheConfirm.needed?` asks only for `:full`, `:provider` and `:history`, when the effective level changes and
|
|
235
|
-
the session has a model reply.
|
|
236
|
-
|
|
235
|
+
the session has a model reply. The confirmation estimates the reread size from the latest known context count:
|
|
236
|
+
the whole conversation for `:full` / `:provider`, or up to the first changed reply for `:history`; it omits the
|
|
237
|
+
estimate when no count is known. On `api: anthropic`, a `:full` / `:provider` change also warns that preserved
|
|
238
|
+
thinking in replies is dropped from then on. A new setting with a cost (a `/model` across templates,
|
|
239
|
+
`/llm-context` turning forget on) adds its classifier and goes through the same step.
|
|
237
240
|
7. **No wall-clock time rendered at wire time.** A history entry's `at` (`MessageTime::KEY`, when it happened) is
|
|
238
241
|
display-only: no wire format reads it, and a guard spec checks that `ChatLoop#wire_messages` and
|
|
239
242
|
`Prompt.format_with_images` give the same bytes with and without it (`spec/message_time_history_spec.rb`). A
|
data/docs/sessions.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Sessions
|
|
2
2
|
|
|
3
|
-
Sessions are plain files — no DB. Each session is `~/.local/state/samagotchi/sessions/<uuid>.json` (XDG-aware via `XDG_STATE_HOME`) plus a sidecar dir `<uuid>/` with `input/`/`notes/`/`output/`/`owner.lock`/`bridge.json` (`Session.session_dir`), and `tool_output/` once a long `execute` result was cut: its whole output, which the cut result names (`ToolOutputFiles`, the newest 50 kept; see [Tool output guardrails](internals/tool-guardrails.md#the-runners-cut-max_tool_output_chars)). Besides the messages and turn state, the JSON keeps `used_memory_names` (what the session read), `preloaded_memory_names` (`--memory`) and `muted_memory_names` (`--mute`), the last two written before the worker starts so a respawn builds the same prompt, `prompt_notes`, the model notes the system prompt carried at its last build (`[{name, scope, chars, digest}]`, see [Memory](memory.md#model-notes)), `parent_id`, the session that delegated this one (see *Delegating*; `null` otherwise), and `continues`, the session this one continues (the previous link of a chain, see *Session chains*; `null` otherwise); files from before those keys read as empty lists, no parent and no previous link. `model_name` is the resolved model ref (an alias applied, `host:id` when it names a host), so a resumed session keeps its model when an alias is retargeted; `model_typed` is the name it was given as when that differs (an alias), for the `models:` lookup after a resume (`null` otherwise).
|
|
3
|
+
Sessions are plain files — no DB. Each session is `~/.local/state/samagotchi/sessions/<uuid>.json` (XDG-aware via `XDG_STATE_HOME`) plus a sidecar dir `<uuid>/` with `input/`/`notes/`/`output/`/`owner.lock`/`bridge.json` (`Session.session_dir`), and `tool_output/` once a long `execute` result was cut: its whole output, which the cut result names (`ToolOutputFiles`, the newest 50 kept; see [Tool output guardrails](internals/tool-guardrails.md#the-runners-cut-max_tool_output_chars)). Besides the messages and turn state, the JSON keeps `written_by` (the chi version that saved it last; see *Which chi a worker runs*), `used_memory_names` (what the session read), `preloaded_memory_names` (`--memory`) and `muted_memory_names` (`--mute`), the last two written before the worker starts so a respawn builds the same prompt, `prompt_notes`, the model notes the system prompt carried at its last build (`[{name, scope, chars, digest}]`, see [Memory](memory.md#model-notes)), `parent_id`, the session that delegated this one (see *Delegating*; `null` otherwise), and `continues`, the session this one continues (the previous link of a chain, see *Session chains*; `null` otherwise); files from before those keys read as empty lists, no parent and no previous link. `model_name` is the resolved model ref (an alias applied, `host:id` when it names a host), so a resumed session keeps its model when an alias is retargeted; `model_typed` is the name it was given as when that differs (an alias), for the `models:` lookup after a resume (`null` otherwise).
|
|
4
4
|
|
|
5
5
|
**Message times:** an entry of `messages` may carry `at`, when it happened: a local-offset ISO time with milliseconds (`2026-10-24T14:32:05.120+02:00`). User prompts, lines merged into a turn and steers, reminders, context and task notes, and the model's replies have one (a note's is its `created_at`); tool results, `!cmd` messages and chi's own turn notes don't, nor does anything saved before chi stamped messages (no backfill). It is for the UIs only (the gap dividers, a bubble's time): it never goes to the model, so it changes nothing in the requests or their prompt cache. The one time the model reads is the reminders': due reminders go into a turn as a system message headed `[SYSTEM: REMINDERS DUE 2026-10-24 14:32 local]` (the turn's local time; `ReminderQueue#inject!`), written into the saved content then, so a later request sends the same text.
|
|
6
6
|
|
|
@@ -29,7 +29,9 @@ A session is deleted if **expired by age OR overflow by count** (unless `keep_st
|
|
|
29
29
|
|
|
30
30
|
**Attached by default:** plain `chi` and `chi --resume ID` run their session in a background worker and attach to it (`session.shared`, default `true`; env `SAMAGOTCHI_SESSION_SHARED`). The worker runs in the session's `working_directory`, so `!commands` and tools see the directory the session was started in. `session.shared: false` keeps the in-process REPL, `--no-shared` does for one run; a session the REPL has open can't be attached ("close it there first"). See [CLI: Sharing a session](cli.md#sharing-a-session).
|
|
31
31
|
|
|
32
|
-
**Which chi a worker runs:** a new or woken worker runs the newest chi installed for this Ruby (`gem 'samagotchi', '>= 0.a'`, prereleases count), whoever starts it: a `chi web` or terminal still on an older chi starts it on the newest one, so a stale process doesn't spread old code. A worker already running keeps its version until it is restarted. Should the newest install fail to load, the worker runs the starter's own version and logs `boot_fallback` (its stdio is `/dev/null`). Under `bundle exec` the worker drops Bundler's env first, so it activates the newest gem too. Two consequences: a wrapper pin (`chi _0.17.0_ web`) pins only that process, not the workers it starts; and a source checkout (`bin/chi` in a worktree, `bundle exec`) starts its workers on its own `lib/`, but a session it made that is later woken by an installed chi (`chi web`, `chi send`) runs the installed code. The names an older starter hands a newer worker (the boot line, its env keys, `input/` files, the session JSON fields and the Bridge routes sessions use on each other) are pinned in `spec/worker_contract_spec.rb`.
|
|
32
|
+
**Which chi a worker runs:** a new or woken worker runs the newest chi installed for this Ruby (`gem 'samagotchi', '>= 0.a'`, prereleases count), whoever starts it: a `chi web` or terminal still on an older chi starts it on the newest one, so a stale process doesn't spread old code. A worker already running keeps its version until it is restarted. Should the newest install fail to load, the worker runs the starter's own version and logs `boot_fallback` (its stdio is `/dev/null`). Under `bundle exec` the worker drops Bundler's env first, so it activates the newest gem too. Two consequences: a wrapper pin (`chi _0.17.0_ web`) pins only that process, not the workers it starts; and a source checkout (`bin/chi` in a worktree, `bundle exec`) starts its workers on its own `lib/`, but a session it made that is later woken by an installed chi (`chi web`, `chi send`) runs the installed code. The names an older starter hands a newer worker (the boot line, its env keys, `input/` files, the session JSON fields and the Bridge routes sessions use on each other) are pinned in `spec/worker_contract_spec.rb`. A chi newer than 0.130.0 keeps the top-level keys it doesn't know (a newer chi's fields) when it saves a session, after its own, and doesn't act on them; `written_by` names the chi that saved the file last, and a worker woken on a file a newer chi saved logs `newer_writer`. chi 0.130.0 and older drop those keys: a session a newer chi made loses its newer fields when an older chi wakes or saves it (for example `chi --attach` from a project whose Ruby has an older chi). Keep the chi in every Ruby you run it from newer than 0.130.0.
|
|
33
|
+
|
|
34
|
+
A new session field (the rule in `Session::FIELDS`' comment): only top-level keys are kept (a new sub-key of `review`, `toolkit` or another value object is still dropped by an older chi), and an older chi keeps a key without honouring it, so a new field must be safe to come back unchanged after an older chi ran turns on the session.
|
|
33
35
|
|
|
34
36
|
**A worker outlives whoever started it:** a spawned worker leaves its spawner's process tree (it is forked again, in a session of its own, and reparented to init), so closing the terminal of a `chi web`, or a tool tearing down a background `chi send --wait` and its children, no longer takes the worker and its turn with it.
|
|
35
37
|
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Samagotchi
|
|
4
|
-
# The confirm step before a
|
|
5
|
-
#
|
|
6
|
-
# without --yes from a person at a web tab or a chi TUI, changes nothing
|
|
4
|
+
# The confirm step before a command that restarts the prompt cache: sent
|
|
5
|
+
# without --yes by a person at a web tab or a chi TUI, it changes nothing
|
|
7
6
|
# and replies with what it costs and the line that applies it. Two steps,
|
|
8
7
|
# no stored state: the reply carries a Confirm (SessionCommands::Result,
|
|
9
8
|
# the worker's command_ran), the TUI prefills its line, the web draws an
|
|
@@ -43,12 +42,33 @@ module Samagotchi
|
|
|
43
42
|
changed && history && COSTLY.include?(cost)
|
|
44
43
|
end
|
|
45
44
|
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
# @
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
# Whether a cache-changing command should ask, and its reply line.
|
|
46
|
+
# Kept independent of any UI so commands and plugins can share it.
|
|
47
|
+
# @param line [String] the full command line without --yes
|
|
48
|
+
# @param cost [Symbol, nil] the change's cache cost
|
|
49
|
+
# @param changed [Boolean] whether the effective setting changes
|
|
50
|
+
# @param engine [Engine] supplies history, used tokens and model host
|
|
51
|
+
# @return [Confirm, nil]
|
|
52
|
+
def ask(line:, cost:, changed:, engine:)
|
|
53
|
+
return nil unless needed?(cost: cost, history: engine.history?, changed: changed)
|
|
54
|
+
|
|
55
|
+
apply_line = "#{line.to_s.strip} --yes"
|
|
56
|
+
notes = []
|
|
57
|
+
tokens = engine.cache_restart_tokens
|
|
58
|
+
if tokens
|
|
59
|
+
size = tokens >= 1000 ? "#{format("%.1f", tokens / 1000.0).delete_suffix(".0")}k" : tokens.to_s
|
|
60
|
+
notes << case cost
|
|
61
|
+
when :full, :provider
|
|
62
|
+
"The next request reads the whole conversation again (about #{size} tokens)."
|
|
63
|
+
when :history
|
|
64
|
+
"The next request reads again from the first changed reply (up to about #{size} tokens)."
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
if %i[full provider].include?(cost) && engine.cache_restart_anthropic?
|
|
68
|
+
notes << "On an `api: anthropic` host, preserved thinking in replies is dropped from then on."
|
|
69
|
+
end
|
|
70
|
+
notes << "Nothing changed yet: send `#{apply_line}` to apply."
|
|
71
|
+
Confirm.new(line: apply_line, text: notes.compact.join("\n"))
|
|
52
72
|
end
|
|
53
73
|
end
|
|
54
74
|
end
|