samagotchi 0.137.0 → 0.139.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 +49 -1
- data/docs/architecture.md +33 -7
- data/docs/cli.md +8 -4
- data/docs/configuration.md +57 -4
- data/docs/context.md +7 -9
- data/docs/internals/background-tasks.md +2 -2
- data/docs/internals/engine.md +2 -1
- data/docs/internals/participants.md +2 -1
- data/docs/internals/tool-guardrails.md +1 -1
- data/docs/internals/web-push.md +20 -0
- data/docs/plugins.md +2 -2
- data/docs/sessions.md +9 -8
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/config.rb +4 -0
- data/lib/samagotchi/engine.rb +4 -2
- data/lib/samagotchi/engine_peers.rb +2 -1
- data/lib/samagotchi/guardrails/mise_paths.rb +36 -0
- data/lib/samagotchi/guardrails/review_paths.rb +1 -30
- data/lib/samagotchi/reply_wait/poll.rb +9 -2
- data/lib/samagotchi/reply_wait.rb +9 -3
- data/lib/samagotchi/task_finish_note.rb +8 -5
- data/lib/samagotchi/task_watch.rb +11 -6
- data/lib/samagotchi/tool_activity.rb +3 -3
- data/lib/samagotchi/tool_declarations.rb +2 -2
- data/lib/samagotchi/tools/builtins.rb +2 -1
- data/lib/samagotchi/tools/delegate_wait.rb +23 -2
- data/lib/samagotchi/tools/execute.rb +51 -9
- data/lib/samagotchi/tools/task_runtime.rb +1 -1
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +30 -158
- data/lib/samagotchi/web/auto_update.rb +282 -0
- data/lib/samagotchi/web/public/app.js +20 -21
- 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/public/task_stop.js +3 -3
- data/lib/samagotchi/web/public/update_notice.js +10 -4
- data/lib/samagotchi/web/public/update_sheet.js +3 -2
- data/lib/samagotchi/web/push/notifier.rb +66 -5
- data/lib/samagotchi/web/server.rb +45 -1
- data/lib/samagotchi/web/session_access_routes.rb +1 -1
- data/lib/samagotchi/web/session_hub.rb +20 -3
- data/lib/samagotchi/web/session_summary.rb +1 -1
- data/lib/samagotchi/web/update_check.rb +56 -10
- data/lib/samagotchi/web/update_ops.rb +240 -0
- data/lib/samagotchi/worker_task_wakes.rb +1 -1
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4acefa9a1809c64422fa70d4cc0603318d37ed5405ba73729b6f1c6e98c79c54
|
|
4
|
+
data.tar.gz: 5f4f9b8181e0490272402c570f6bd1c70f72e6432fc380a383b6ff5d83a3e6fb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0aaf6e6d8ee1cdfd0b3001beb44f884614cf45373a1cf8c0f566d7419407b01818ae3155bcfe368f485f657985fd5177aa897174567df9c0a7efcb8ba44b0504
|
|
7
|
+
data.tar.gz: d53d479d947de0f63b78dfd5c67fecc41cf6cface32d6ffad6cb64a743fb9e0ba08dc5115746e6513d0bb7f11fac21b3c350215dbc9ba8544385b7606de19acf
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,52 @@ 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.139.0] - 2026-10-30
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Unattended updates (opt-in): `update.auto: install` makes chi web install a new release by itself once no turn runs
|
|
15
|
+
and no question waits anywhere (one try per release). With `update.restart: idle` (the default) it then restarts
|
|
16
|
+
workers whose sessions have been idle for 10 minutes, then chi web itself after checking the new chi starts; the
|
|
17
|
+
phone gets "chi updated to X", or a push saying what failed. See docs/configuration.md (Unattended updates).
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- A message you send while a turn waits on a delegate (`delegate_result`, `delegate` with wait) ends that wait; the
|
|
22
|
+
child keeps running and its reply still comes.
|
|
23
|
+
- A message you send while `execute` runs a command hands the command over as a background task after 3 seconds,
|
|
24
|
+
instead of at its 120 s limit (never in pair turns or `!cmd`).
|
|
25
|
+
|
|
26
|
+
Update with `chi update`.
|
|
27
|
+
|
|
28
|
+
## [0.138.0] - 2026-10-30
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- Web: All sessions' header is the top bar: the sessions pill shows pressed in the all view and takes you back (Esc
|
|
33
|
+
too); the search is a pill, "archived" a toggle chip, the count reads "4 · 5 delegates", the storage path a footer
|
|
34
|
+
under the list.
|
|
35
|
+
- Web: session cards show one mark per state and no status dots: a waiting card a warn outline and "question", an
|
|
36
|
+
error a red badge, "live" only when not waiting; titles one line; an untitled session reads "Untitled" over its
|
|
37
|
+
first message.
|
|
38
|
+
- Web: Stop turn is a button in the running turn's status row (the composer's and the bar's Stop buttons are gone);
|
|
39
|
+
after a reload that knows only that a turn runs, the stage shows that row alone.
|
|
40
|
+
- Web: the session bar reads in three groups (setup, relations, telemetry) with hairlines between, on one line on
|
|
41
|
+
a desktop; the parent chip reads "↑ id", the clock "7m 52s"; continue is a plain button unless archived.
|
|
42
|
+
- Web: the start composer keeps model, ⚙ and 🧰 left and 🖼 and Start right; the keyboard hint shows under the
|
|
43
|
+
placeholder only while the box is focused and empty.
|
|
44
|
+
- Web: the "+ URL" chip is gone (`chi context` still attaches URLs).
|
|
45
|
+
- Web: on a phone a tap no longer leaves a hover tint on a card, a bar button, a question option or a fold.
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- Web: on a phone the sessions search takes a whole line, so its placeholder reads; the start page keeps the
|
|
50
|
+
project chip's words.
|
|
51
|
+
- Web: a session with no turn yet shows no clock, not "0ms".
|
|
52
|
+
- Web: the cards' shadow is no longer cut into a band at the sessions list's edges (light theme).
|
|
53
|
+
|
|
54
|
+
Update with `chi update`, then restart `chi web`.
|
|
55
|
+
|
|
10
56
|
## [0.137.0] - 2026-10-30
|
|
11
57
|
|
|
12
58
|
### Changed
|
|
@@ -5257,7 +5303,9 @@ and long-lived sessions.
|
|
|
5257
5303
|
- A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
|
|
5258
5304
|
hotkey panel that send selected text or the clipboard to your sessions.
|
|
5259
5305
|
|
|
5260
|
-
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.
|
|
5306
|
+
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.139.0...HEAD
|
|
5307
|
+
[0.139.0]: https://github.com/dm1try/samagotchi/compare/v0.138.0...v0.139.0
|
|
5308
|
+
[0.138.0]: https://github.com/dm1try/samagotchi/compare/v0.137.0...v0.138.0
|
|
5261
5309
|
[0.137.0]: https://github.com/dm1try/samagotchi/compare/v0.136.1...v0.137.0
|
|
5262
5310
|
[0.136.1]: https://github.com/dm1try/samagotchi/compare/v0.136.0...v0.136.1
|
|
5263
5311
|
[0.136.0]: https://github.com/dm1try/samagotchi/compare/v0.135.0...v0.136.0
|
data/docs/architecture.md
CHANGED
|
@@ -152,7 +152,7 @@ home's Ruby is gone, when no `samagotchi-*.gemspec` is under its gem path, and f
|
|
|
152
152
|
|---|---|---|
|
|
153
153
|
| `SessionManager.worker_command`: every worker (attach, `chi send`, chi web, delegate and Verify-fresh children, `resume_session`, `:restart`) | the home's ruby, `Gem.paths`, `>= 0.a` with the fallback pin (`ChiHome.fallback_version`: the spawner's version when the home has it, else the home's newest; a home older than the spawner is warned once); a chi that still can't load writes one `boot_failed` line to `SAMAGOTCHI_LOG_FILE` and exits 1 | `RbConfig.ruby -e` with `>= 0.a` (a checkout: `-I lib`) |
|
|
154
154
|
| `Web::Server.reexec_command` (chi web's restart) | `CHI_BOOT -- web …`; the `:no_wrapper` refusal never applies; a home whose newest chi is older than this one is passed over (logged `restart_home_older`) | `[RbConfig.ruby, wrapper, …]` (`$PROGRAM_NAME` in a checkout) |
|
|
155
|
-
| `Web::
|
|
155
|
+
| `Web::UpdateOps#update_argv` (the **Update** button's child) | `CHI_BOOT -- update --to V --no-desktop --json` | `[RbConfig.ruby, wrapper, update, …]` |
|
|
156
156
|
| `UpdateCommand` | on another Ruby than the home (`Record#running_here?`): exec `CHI_BOOT -- update …` once (`SAMAGOTCHI_UPDATE_IN_HOME=1`: an older home chi ignores it); the install (`GemUpdate`) runs `ChiHome.gem_bin` with `ChiHome.install_env` (the home's GEM_HOME/GEM_PATH for that child, `mise`'s dir on PATH); the hand-over execs `CHI_BOOT -- update --no-gem --gem-from …` | this Ruby's `gem`, the wrapper |
|
|
157
157
|
| `BootstrapCommand` (commonmarker) | `ChiHome.gem_bin` + `install_env`; installed? = in the home's specifications | this Ruby's `gem` |
|
|
158
158
|
| `Desktop::MacOS#launch_config` | argv `CHI_BOOT + ["--"]`; `launch_ok?` / `launch_problem` check only absolute argv entries | `[ruby, wrapper or bin/chi]` |
|
|
@@ -606,23 +606,30 @@ reload in a toast, since an open tab keeps its old JS across a `chi web` upgrade
|
|
|
606
606
|
`installed`, the newest chi on this machine (`InstalledVersions`: the highest `samagotchi-<v>.gemspec`
|
|
607
607
|
in RubyGems' specification dirs, or a checkout's `version.rb` on disk; looked at again on each full
|
|
608
608
|
probe, every 10 s). A `chi` frame and the `GET /api/events` snapshot carry the full state
|
|
609
|
-
`{version, installed, available, update_blocked}
|
|
610
|
-
carry the same fields. `SessionHub#update_available` publishes the checker result under the hub's
|
|
609
|
+
`{version, installed, available, update_blocked, auto}` (`auto`: `update.auto`, `off` or `install`, which the page's
|
|
610
|
+
"available" wording follows); `/api/info` and the session-command wake response carry the same fields. `SessionHub#update_available` publishes the checker result under the hub's
|
|
611
611
|
Monitor, but fetching never holds it.
|
|
612
612
|
|
|
613
|
-
`Web::UpdateCheck` runs its own thread beside the hub. When `update.notify: on` (the default)
|
|
613
|
+
`Web::UpdateCheck` runs its own thread beside the hub. When `update.notify: on` (the default) or
|
|
614
|
+
`update.auto: install` (then with `notify: off` the hub's `available` stays nil: nothing is shown or pushed, only
|
|
615
|
+
AutoUpdate reads `UpdateCheck#available`), its
|
|
614
616
|
first RubyGems check is about 30 seconds after chi web starts, then it checks at `update.check_hours`
|
|
615
617
|
intervals. A stable release is available only after `update.min_age_hours` and when newer than both
|
|
616
618
|
the running and newest installed chi. Its atomic, mode-0600
|
|
617
619
|
`$XDG_STATE_HOME/samagotchi/update_check.json` keeps the check cooldown and versions already
|
|
618
|
-
announced across restarts. `update.notify: off`
|
|
619
|
-
request. `update_blocked` names why the local gem cannot be updated in place (checkout, Bundler, or
|
|
620
|
+
announced across restarts, and AutoUpdate's record (`auto`, `auto_failed`; `save_auto`). `update.notify: off`
|
|
621
|
+
with `update.auto: off` starts no checker thread and makes no network request. `update_blocked` names why the local gem cannot be updated in place (checkout, Bundler, or
|
|
620
622
|
an unwritable gem directory). The page tells the owner to run `chi update`; a newer `installed`
|
|
621
623
|
version than the server itself still means restart chi web (its workers already run the newest chi,
|
|
622
624
|
[Sessions](sessions.md)). `GET /api/info` also names `newest_workers`; next to `cwd` (the folder chi
|
|
623
625
|
web runs in) it names `start_dir`, the folder it starts in (`web.start_dir`, `--start-dir`;
|
|
624
626
|
`Web::StartDir`), which a second `chi web` given another one mentions.
|
|
625
627
|
|
|
628
|
+
The three update operations live in `Web::UpdateOps` (`web/update_ops.rb`), which `App`'s routes ask for
|
|
629
|
+
(`handle_update`, `handle_restart_workers` and `handle_restart_web` only validate, ask and map `:started` /
|
|
630
|
+
`:busy` / a refusal to 202 / 409 / 503) and `Web::AutoUpdate` asks for too; the one-at-a-time rule is its mutex.
|
|
631
|
+
`Server.start` hands `app.update_ops` to AutoUpdate and sets its restart lambda through `App#restart_web=`.
|
|
632
|
+
|
|
626
633
|
The owner's update routes publish progress and results as `chi_update` events with an
|
|
627
634
|
`operation` (`install` or `workers`), `state`, and `rows`; install events also name the target
|
|
628
635
|
`version` and may include a progress line. The hub retains the latest event for a reconnecting
|
|
@@ -632,7 +639,7 @@ it holds with `update.notify: off`; a child that prints no rows fails with its o
|
|
|
632
639
|
done install refreshes the hub's installed version (`SessionHub#refresh_installed`) before its `done`.
|
|
633
640
|
Worker restarts target every worker older than the version installed now (refreshed the same way)
|
|
634
641
|
and publish per-session results. Only one of an install, a worker restart pass and a web restart runs
|
|
635
|
-
at a time (409 `update_running`). `POST /api/update/restart_workers`
|
|
642
|
+
at a time (409 `update_running`). `POST /api/update/restart_workers` starts the worker pass
|
|
636
643
|
by itself. `POST /api/update/restart_web` (409 `restart_unavailable` without a
|
|
637
644
|
chi wrapper to exec) schedules WEBrick shutdown only after its response is sent; after the hub and
|
|
638
645
|
listener stop, `Server` execs the RubyGems chi wrapper with every flag chi web was started with, as
|
|
@@ -642,6 +649,24 @@ listener stop, `Server` execs the RubyGems chi wrapper with every flag chi web w
|
|
|
642
649
|
themselves are detached), and adopts an exact Tailscale Serve handler. A failed exec, or a restarted
|
|
643
650
|
chi web that doesn't start for any reason, turns off only that exact adopted handler.
|
|
644
651
|
|
|
652
|
+
`Web::AutoUpdate` (`web/auto_update.rb`; its own thread, started and stopped by `Server.start` beside the checker)
|
|
653
|
+
is `update.auto: install`. Every 5 minutes (the first step 60 s after start) it looks at `UpdateCheck#available`
|
|
654
|
+
and installs it through `UpdateOps#install(wait: true, auto: true)` when the install isn't blocked, no operation
|
|
655
|
+
runs, no hub summary has `status: running` or a `pending_question`, and the version isn't in `auto_failed` or
|
|
656
|
+
already in the `auto` record (one automatic try per version). Its frames carry `auto: true` (the page: "Automatic
|
|
657
|
+
chi update in progress", "(automatic)" in the sheet's title). Its record is `update_check.json`'s `auto`:
|
|
658
|
+
`{version, from, at, kept, phase: installed|workers|web|done|failed, workers: {restarted, left}, detail, pushed}`.
|
|
659
|
+
With `update.restart: never` the run ends at the install (`chi_notice` `installed`). With `idle` it goes on
|
|
660
|
+
(`phase` `installed` → `workers` → `web` → `done`): `UpdateOps#restart_workers(select:)` over the stale workers whose
|
|
661
|
+
summary is idle with no question and `updated_at` at least 10 minutes old (`QUIET`; the others are `skipped` rows;
|
|
662
|
+
the worker's own atomic refusal still applies), the left ones retried each step for 24 h (`RETRY_FOR`); then, with no
|
|
663
|
+
turn running, `Server.restart_preflight` (the restart's command with `--version`, 20 s, killed on a timeout; it must
|
|
664
|
+
print a chi at least the release) and, if it passes, the record's `phase: web` is written **before**
|
|
665
|
+
`UpdateOps#restart_web`. A failed preflight, or a refused restart, publishes `restart_failed` and ends the run
|
|
666
|
+
(`failed`). The restarted chi web's AutoUpdate (first step after 5 s) finds `phase: web`, checks it runs the
|
|
667
|
+
release, counts the workers once more and publishes `updated`, then `done`. Results reach the phone as `chi_notice`
|
|
668
|
+
hub events (`SessionHub#publish_notice`), see [internals/web-push.md](internals/web-push.md).
|
|
669
|
+
|
|
645
670
|
The page's scope is in its URL, and the server picks the default: `?dir=<folder>` is that folder's
|
|
646
671
|
project (the list, the events stream and a new chat follow it), `?scope=all` every session, and a bare
|
|
647
672
|
`/` the start folder's project when that folder is in a repo (`App#index_data_attributes` writes
|
|
@@ -862,6 +887,7 @@ A map of the parts the overview above doesn't name (paths under `lib/samagotchi/
|
|
|
862
887
|
| Review sessions | `review_start.rb` (`ReviewStart`), `session_review.rb` (`SessionReview`, the session's `review` field), `review_command.rb` (`ReviewCommand`, `chi review`), `review_apply.rb` (`ReviewApply`) | A session made to be shared: idle, its subjects project files checked by `Guardrails::ReviewPaths`, a fork of a working session from its recap (`--from`); Apply quotes a participant's review turn into the owner's composer ([sessions.md](sessions.md#review-sessions), [internals/participants.md](internals/participants.md)). |
|
|
863
888
|
| chi web's routes | `web/app.rb` (`Web::App`: the gate, `ROUTES`, the shared helpers), `web/message_display.rb` (`Web::MessageDisplay`: saved messages and cards as display items, `POST /api/render`), `web/session_show.rb` (`Web::SessionShow`: `GET /api/sessions/:id` with its `cards`, `tail` and `timing` re-reads, `GET …/output`, the timing payload), `web/session_create.rb` (`Web::SessionCreate`: `POST /api/sessions`, a continue, a review fork), `web/session_control.rb` (`Web::SessionControl`: answer, command, turn and images, cancel, stop, restart, archive, delete), `web/push_routes.rb` (`Web::PushRoutes`: `/api/push`, its subscriptions, prefs, test and presence) | The handlers `App`'s `ROUTES` name, in the file that holds their kind; each mixin is included in `App` and reads its helpers and ivars, so `app.send(:handle_show, …)` and the specs that stub `App` methods work as before. |
|
|
864
889
|
| Web Push | `web/push.rb` (`Web::Push`: `Runtime`, `preflight!`, the `web.push.*` settings), `web/push/crypto.rb` (`Push::Crypto`: RFC 8291 aes128gcm, RFC 8292 VAPID), `web/push/store.rb` (`Push::Store`, `Store::Sub`: `web-push/vapid.pem`, `subscriptions.json`), `web/push/client.rb` (`Push::Client`, `Client::Result`), `web/push/attention.rb` (`Push::Attention`: notify.js's rules in Ruby, shared cases in `spec/fixtures/attention_cases.json`), `web/push/presence.rb` (`Push::Presence`: the pages' "in front" reports), `web/push/notifier.rb` (`Push::Notifier`, a hub subscriber with a sender thread), `web/push_routes.rb` (`Web::PushRoutes`), `web/private_files.rb` (`Web::PrivateFiles`, shared with the grants), `public/sw.js`, `public/push.js`, `public/manifest.webmanifest` | chi web pushes what the tab notifies for (approvals, questions, step-limit questions, cards, failed turns; done turns to a device that asks) to the owner's Home Screen app over an https public URL: the Notifier turns the hub's new attentions into show pushes after a hold, unless a chi web page is in front, at most 30 an hour, with one retry and a restart catch-up (`sent.json`) (each carries the keys still open, and the badge; no close push: every push shows a notification); the bell (`notifications.js`) subscribes the device, and the service worker and the page close the answered ones. See [internals/web-push.md](internals/web-push.md). |
|
|
890
|
+
| chi web's updates | `web/update_check.rb` (`Web::UpdateCheck`), `web/update_ops.rb` (`Web::UpdateOps`), `web/auto_update.rb` (`Web::AutoUpdate`) | The RubyGems checker and its `update_check.json`; the install, worker-restart and chi-web-restart operations (one at a time) the owner's routes and AutoUpdate share; `update.auto: install`'s thread (install when no turn runs, then idle workers and chi web). See [configuration](configuration.md#unattended-updates-updateauto). |
|
|
865
891
|
| Behind an auth proxy | `web/public_url.rb` (`Web::PublicUrl`: `web.public_url`, `web.proxy.identity_header`, `web.proxy.owner`), `web/proxy_gate.rb` (`Web::ProxyGate`, mixed into App; `RawHeaderNames`, prepended to WEBrick's request by `Web::Server`), `web/link_codes.rb` (`Web::LinkCodes`: the links' one-time codes), `proxy_trace.rb` (`ProxyTrace`), `web/public/token_fragment.js`, `web/public/auth_lost.js`, `web/tunnel.rb` + `web/tunnel/tailscale.rb` (`Web::Tunnel`, `Web::Tunnel::Tailscale`: `chi web --tunnel tailscale`, Tailscale Serve set for one run and the PublicUrl it makes) | Experimental: chi web behind an auth proxy. Every request needs the token; a proxied one's owner also needs the identity header (one clean value under one name); the `__Host-` cookie, an absolute `Location` from the public URL, https links with a one-time code in the fragment; the page's lost-sign-in banner. Bridges refuse proxy traces ([configuration.md](configuration.md#behind-an-auth-proxy)). |
|
|
866
892
|
| Participants | `participants.rb` (`Participants`, the `participants.json` sidecar), `share_command.rb` (`ShareCommand`, `chi share`), `session_participants.rb`, `participants_watch.rb`, `web/grants.rb` (`Web::Grants`), `web/principal.rb` (`Web::Principal`), `web/participant_access.rb` (`Web::ParticipantAccess`: the participant's gate and routes), `web/participants_admin.rb` (`Web::ParticipantsAdmin`: the owner's invite, list, revoke, cap and Apply routes), `web/participant_review.rb` + `web/participant_view.rb` (`Web::ParticipantReview`, `Web::ParticipantView`), `web/participant_stream_filter.rb`, `guardrails/review_boundary.rb` + `guardrails/review_paths.rb` | The people or agents an owner invites to review one session through chi web: their grants and gate, what they read, their read-only review turns ([internals/participants.md](internals/participants.md), [guardrails.md](guardrails.md)). |
|
|
867
893
|
| Analytics | `analytics_file.rb` (`AnalyticsFile`), `session_metrics/totals.rb` (`SessionMetrics::Totals`), `session_metrics/model_segment.rb` (`SessionMetrics::ModelSegment`) | A session's `analytics.json` (`SessionMetrics#persist`); the sums over its turn records (`Totals`: tokens, cost, speeds, kept as running counters) and their split per model after a `/model` switch (`ModelSegment`: a run of turns on one model, the `model_segments` rows); `session_metrics/cost.rb` (`SessionMetrics::Cost`): the spend `ctx.cost` reads; `session_metrics/usage.rb` (`SessionMetrics::Usage`): the work `ctx.usage` reads. `session_metrics/side_totals.rb` (`SessionMetrics::SideTotals`) and `side_usage.rb` (`SideUsage`): the side requests' sums, apart from the turns'. |
|
data/docs/cli.md
CHANGED
|
@@ -256,6 +256,10 @@ done
|
|
|
256
256
|
`update.min_age_hours` tune the interval and release age (see
|
|
257
257
|
[configuration](configuration.md#all-settings)). If the gem can't be updated
|
|
258
258
|
in place, the notice gives the blocker and its command instead.
|
|
259
|
+
With `update.auto: install` chi web installs the release by itself once no
|
|
260
|
+
turn runs anywhere, then restarts idle workers and itself (`update.restart`),
|
|
261
|
+
and the notice reads "installs by itself" with no **Update** button; see
|
|
262
|
+
[Unattended updates](configuration.md#unattended-updates-updateauto).
|
|
259
263
|
|
|
260
264
|
- **The chi home.** With a [chi home](configuration.md#the-chi-home), a `chi
|
|
261
265
|
update` started on another Ruby first says `updating the chi home (<ruby>)`
|
|
@@ -462,7 +466,7 @@ of UIs at once: the Web UI and attached terminals (`chi`, `--resume`,
|
|
|
462
466
|
`--attach`). They all see the same turns as they happen, and any of them can send
|
|
463
467
|
a prompt, also while a turn runs (it merges into that turn as steering: the model
|
|
464
468
|
is told it is a steer and who sent it). Steering waits for the model's next step
|
|
465
|
-
and cuts nothing; a `task_wait` the model is blocked in ends early for it (the task keeps running). `/cut TEXT` in a terminal or the web composer (or `chi send --cut`) asks for a cut:
|
|
469
|
+
and cuts nothing; a `task_wait` or `delegate_result` the model is blocked in ends early for it (the task or child keeps running), and a running `execute` command moves to a background task once it has run 3 s. `/cut TEXT` in a terminal or the web composer (or `chi send --cut`) asks for a cut:
|
|
466
470
|
once the model has streamed only thinking for `steer.cut_after` seconds (per host/model: `steer_cut_after`; default
|
|
467
471
|
20), now or while your message waits, that generation is cut and the step starts again with your message, marked
|
|
468
472
|
`↪ cut in for your message` (see
|
|
@@ -585,7 +589,7 @@ list --archived` shows them too, marked `[archived]` (`archived: true` in
|
|
|
585
589
|
`--format json`); `chi sessions unarchive ID...` brings them back, and so does
|
|
586
590
|
a message you send to one (the web, an attached terminal, `chi send`), but not
|
|
587
591
|
a delegate's follow-up or a reminder. The web archives from the info bar
|
|
588
|
-
(`archive`, before `stop`);
|
|
592
|
+
(`archive`, before `stop`); the `archived` toggle by the all-sessions search
|
|
589
593
|
finds archived sessions. `/archive` in a terminal leaves the session and
|
|
590
594
|
archives it (an empty session is discarded instead; `chi scratch` refuses
|
|
591
595
|
it). See
|
|
@@ -1563,8 +1567,8 @@ chi share revoke --all # every link to every session
|
|
|
1563
1567
|
far as it fits the plan. It's quoted: …` and the reply quoted line by line. Read it, edit it, send it: the
|
|
1564
1568
|
participant's text reaches the model as a quote under your line, never as your instruction. Don't send "do what
|
|
1565
1569
|
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,
|
|
1570
|
+
- **Your side of it.** In the session bar, the participants chip (`participants · invite` while nobody has a link,
|
|
1571
|
+
then `2 participants`, with a green dot while one is on their page) opens the panel: each participant's state, last visit,
|
|
1568
1572
|
spend against their cap, turns and expiry, with **Cap** and **Revoke** (revoking ends their page and stream and
|
|
1569
1573
|
drops their queued comments). **Invite** takes a name, person or agent, the expiry and a cap, and shows the link
|
|
1570
1574
|
once, with Copy, Share (a phone's share sheet) and its QR code. **Participant view ↗** opens their page for this
|
data/docs/configuration.md
CHANGED
|
@@ -179,7 +179,7 @@ or `chi web` restart.
|
|
|
179
179
|
| `history.file` | Next history access | The TUI and web composer resolve its path when they read or append history. |
|
|
180
180
|
| `broadcast.*` | Next `chi broadcast` | Each broadcast invocation reads its targeting and triage settings. |
|
|
181
181
|
| `update.gem`, `update.bundles`, `update.desktop` | Next `chi update` | The command reads which components to update when it runs. |
|
|
182
|
-
| `update.notify`, `update.check_hours`, `update.min_age_hours` | `chi web` start | The update checker starts with chi web; restart it to apply changes. |
|
|
182
|
+
| `update.notify`, `update.check_hours`, `update.min_age_hours`, `update.auto`, `update.restart` | `chi web` start | The update checker starts with chi web; restart it to apply changes. |
|
|
183
183
|
| `kitty.*`, `tmux.*` | `chi desktop upgrade` or desktop update | These settings are copied into the desktop helper's launch file. |
|
|
184
184
|
| `model.profile`, `reasoning_history` | Not from config.yml | These are CLI/environment settings; `models:` and `hosts:` provide their model- and host-specific forms. |
|
|
185
185
|
|
|
@@ -1143,8 +1143,13 @@ thinking is lost. A generation that has streamed visible text or a tool call is
|
|
|
1143
1143
|
(`ctx.steer`, `ctx.sessions.send`) never cuts.
|
|
1144
1144
|
|
|
1145
1145
|
A message that would go in at the next step (any but a queued one, one with images or a participant's) also ends a
|
|
1146
|
-
`task_wait` the model is blocked in, within half a second: the task
|
|
1147
|
-
reads it at once and decides whether to wait again.
|
|
1146
|
+
`task_wait` or a `delegate_result` (and `delegate`'s own wait) the model is blocked in, within half a second: the task
|
|
1147
|
+
or the child keeps running, the model is told a message waits, reads it at once and decides whether to wait again. It
|
|
1148
|
+
also moves a running `execute` command to a background task early, once the command has run 3 seconds (a command that
|
|
1149
|
+
ends sooner just returns its result): the result says `Still running at the 4s mark: a message from the user is
|
|
1150
|
+
waiting …` and names the `task_id`, and `task_wait` waits for it as for any hand-over. With `execute.on_timeout:
|
|
1151
|
+
kill`, in a participant's pair turn and for the `!cmd` bang an `execute` is never handed over early (they kill at the
|
|
1152
|
+
limit). A plugin's steer and a delegate child's report don't end any of them.
|
|
1148
1153
|
|
|
1149
1154
|
The wait resolves per model, in this order: env `SAMAGOTCHI_STEER_CUT_AFTER`, then
|
|
1150
1155
|
`models.<key>.steer_cut_after`, `hosts.<name>.steer_cut_after`, `steer.cut_after` in config.yml, then 20. `0` means
|
|
@@ -2033,6 +2038,9 @@ there; the new subscription replaces the dead one.
|
|
|
2033
2038
|
approval, which its parent may relay instead); one push each. A delegated child's failed or finished turn is its
|
|
2034
2039
|
parent's to report. **Done turns** (10 s or longer, or one nobody started: a delegate's report, a context change, a
|
|
2035
2040
|
finished task) are **off** by default, per device: the **Long turns done** switch in the devices sheet turns them on.
|
|
2041
|
+
- **Updates**: a new chi release ("chi X is available", once per version) and, with
|
|
2042
|
+
[`update.auto: install`](#unattended-updates-updateauto), how the automatic update went ("chi updated to X", "chi X
|
|
2043
|
+
didn't install", …): kept 24 h for an offline phone, `normal` urgency, never the file names.
|
|
2036
2044
|
- **Quiet while you look**: nothing is pushed while any chi web page (a tab on the Mac, or the app) is in front and
|
|
2037
2045
|
was touched in the last 2 minutes; what came meanwhile is never pushed later (the page has it). A page left in
|
|
2038
2046
|
front untouched stops quieting the phone after about 2 minutes.
|
|
@@ -2052,6 +2060,49 @@ there; the new subscription replaces the dead one.
|
|
|
2052
2060
|
(it shows the "needs chi web's access token" page; paste the token there). Pushes keep coming meanwhile.
|
|
2053
2061
|
- chi web not running: no pushes (Serve answers 502; a tap couldn't open the page anyway).
|
|
2054
2062
|
|
|
2063
|
+
## Unattended updates (`update.auto`)
|
|
2064
|
+
|
|
2065
|
+
By default chi web only tells you a release is out (`update.notify`, [CLI: Updating](cli.md#updating)). With
|
|
2066
|
+
`update.auto: install` in `config.yml` it also installs the release its checker found, aged by `update.min_age_hours`
|
|
2067
|
+
(24 h by default, so a release is never installed the day it ships), and goes on to `update.restart`:
|
|
2068
|
+
|
|
2069
|
+
```yaml
|
|
2070
|
+
update:
|
|
2071
|
+
auto: install # off (default): a notice only
|
|
2072
|
+
restart: idle # idle (default): restart workers and chi web onto it; never: install only
|
|
2073
|
+
```
|
|
2074
|
+
|
|
2075
|
+
Both are read when chi web starts (restart it to apply a change). What it does, in order:
|
|
2076
|
+
|
|
2077
|
+
1. **Waits for a quiet moment.** The install starts when no turn runs and no question waits for an answer in any
|
|
2078
|
+
session (checked every 5 minutes), no other update operation runs (the **Update** button, a restart) and the
|
|
2079
|
+
install isn't blocked (a source checkout, Bundler, a RubyGems directory chi can't write: those stay a notice, with
|
|
2080
|
+
the command to run). `chi update` also merges the shipped bundles that live sessions read, hence "no turn anywhere".
|
|
2081
|
+
2. **Installs** exactly the release the notice named, as the **Update** button does (the system bundle and shipped
|
|
2082
|
+
bundles are merged three ways; files you edited are kept and counted in the push). The page's sticky toast says
|
|
2083
|
+
"Automatic chi update in progress" and its sheet shows the rows. A failed install is tried **once per version**:
|
|
2084
|
+
it pushes the failed row's reason and stays quiet until a newer release (or you, with the button) tries again.
|
|
2085
|
+
3. **Restarts** (`update.restart: idle`). Stale workers first, only those whose session is idle and untouched for
|
|
2086
|
+
10 minutes (so nobody who is about to type loses their worker), and each worker still refuses by itself when
|
|
2087
|
+
something holds it ([sessions](sessions.md)); the ones left are tried again for a day, then wait for their own
|
|
2088
|
+
idle exit or the session's **Restart**. Then chi web itself, once no turn runs anywhere: it first runs the
|
|
2089
|
+
restart's own command with `--version` (20 s at most) and restarts only if the newest chi answers with the
|
|
2090
|
+
release's version; otherwise chi web stays up and the phone hears "chi X is installed; chi web didn't restart".
|
|
2091
|
+
The restart is the **Restart chi web** one: same port, same token, same Tailscale Serve handler, the open page's
|
|
2092
|
+
streams reconnect in a few seconds and a Reload toast follows. It doesn't wait for a page in front: the page isn't
|
|
2093
|
+
reloaded by itself, so a draft in the composer stays. A **Send** or **Allow** pressed in those few seconds fails
|
|
2094
|
+
with its error and leaves the text in the composer: press it again.
|
|
2095
|
+
4. **Says so.** The restarted chi web pushes "chi updated to X" ("chi web and 3 sessions restarted; 1 restarts when
|
|
2096
|
+
idle. Kept your edits in 2 files."). With `update.restart: never` the push is "chi X is installed" and chi web and
|
|
2097
|
+
the workers move over when you restart them (the page's toast offers it). The push needs the phone set up
|
|
2098
|
+
([Phone notifications](#phone-notifications-web-push)); without it the page's toasts and
|
|
2099
|
+
`$XDG_STATE_HOME/samagotchi/update_check.json` (`auto`) are the record.
|
|
2100
|
+
|
|
2101
|
+
There is no watchdog: chi web restarts into the newest chi it just proved can start, and nothing brings an old one
|
|
2102
|
+
back by itself. To roll back, install the older gem with `gem install samagotchi -v X` and restart chi web
|
|
2103
|
+
(`chi update --to` refuses a version that isn't newer than the running chi). The desktop helper isn't rebuilt (that
|
|
2104
|
+
stays a manual `chi update`).
|
|
2105
|
+
|
|
2055
2106
|
## All settings
|
|
2056
2107
|
|
|
2057
2108
|
Every setting below takes the three forms described in
|
|
@@ -2165,6 +2216,8 @@ column says so, a `--kebab-name` flag. The maps (`hosts:`, `models:`,
|
|
|
2165
2216
|
| `update.bundles` | `true` | | `false`: `chi update` leaves the shipped bundles to `chi bundle upgrade` (`--no-bundles`). |
|
|
2166
2217
|
| `update.desktop` | `true` | | `false`: `chi update` leaves the desktop helper alone (`--no-desktop`). |
|
|
2167
2218
|
| `update.notify` | `on` | | `on`: chi web checks RubyGems once a day and shows aged stable releases newer than both this chi and the newest installed chi; `off`: no thread or network check. |
|
|
2219
|
+
| `update.auto` | `off` | | `install`: chi web installs the release its checker found (aged by `update.min_age_hours`) once no turn runs and no question waits in any session, as the **Update** button would (shipped bundles merged, your edits kept and reported), then pushes how it went; `update.restart` says what follows. An install that fails is tried once per version (a newer release, or the **Update** button, tries again). The check runs even with `update.notify: off` (which then only silences the page's and the phone's "available"). An install that can't run here (a checkout, Bundler, an unwritable gem dir) stays a notice. `off`: notice only. See [Unattended updates](#unattended-updates-updateauto). |
|
|
2220
|
+
| `update.restart` | `idle` | | With `update.auto: install`: `idle` restarts the stale workers whose session is idle and untouched for 10 minutes, then chi web itself, once no turn runs anywhere and the newest chi has answered `chi --version`; it pushes "chi updated to X". `never`: the install only (the page's toast offers the restarts, workers also exit on their own when idle). |
|
|
2168
2221
|
| `update.check_hours` | `24` | | Hours between RubyGems checks (at least 1). The first check starts about 30 seconds after chi web starts. |
|
|
2169
2222
|
| `update.min_age_hours` | `24` | | A release must be this old before chi web offers it; `0` disables the age delay. |
|
|
2170
2223
|
| `kitty.listen_on` | none | | The desktop helper's kitty socket, copied from `kitty.conf`; unset = no kitty targets. See "Desktop helper: agents in kitty". |
|
|
@@ -2182,7 +2235,7 @@ column says so, a `--kebab-name` flag. The maps (`hosts:`, `models:`,
|
|
|
2182
2235
|
| `execute.telemetry_threshold_pct` | `80` | yes | |
|
|
2183
2236
|
| `execute.cut_head_chars` | `1000` | yes | An `execute` result over `max_tool_output_chars` keeps at most this many characters of its start (and never more than a quarter of the cap); its end gets the rest. |
|
|
2184
2237
|
| `execute.timeout_sec` | `120` | yes | Seconds one `execute` command may run before `execute.on_timeout` applies. |
|
|
2185
|
-
| `execute.on_timeout` | `task` | yes | What happens to a command still running at that limit: `task` keeps it running as a background task (the result names its `task_id`; `task_wait` waits for it, `task_stop` stops it), `kill` stops it (`Error: command timed out after …s`). The `!cmd` bang always kills. See [Background tasks](internals/background-tasks.md). |
|
|
2238
|
+
| `execute.on_timeout` | `task` | yes | What happens to a command still running at that limit: `task` keeps it running as a background task (the result names its `task_id`; `task_wait` waits for it, `task_stop` stops it; also early, after 3 s, when a message from the user waits for the turn), `kill` stops it (and never hands over early) (`Error: command timed out after …s`). The `!cmd` bang always kills. See [Background tasks](internals/background-tasks.md). |
|
|
2186
2239
|
| `execute.description` | `true` | | `execute` offers the model an optional `description` (a few words on what the command does), shown as the tool row's title in `chi web` and in place of the command in the TUI's tool line. `false`: the parameter isn't declared, and rows show the command's first step. |
|
|
2187
2240
|
| `web.port` | `4567` | `--port` | `chi web`'s port. See [CLI](cli.md). |
|
|
2188
2241
|
| `web.host` | `127.0.0.1` | yes | `127.0.0.1`, `::1` or `localhost`; `lan` (this machine's private IPv4 address) or one of its IPv4 addresses also opens `chi web` to the network, with an access token, asked of this machine's 127.0.0.1 too (its `Local:` link carries a one-time code; `chi web --new-token` replaces the token). Anything else binds `127.0.0.1` with a warning. See [CLI: chi web on your phone](cli.md#chi-web-on-your-phone). |
|
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
|
|
@@ -25,7 +25,7 @@ Behavior:
|
|
|
25
25
|
- `execute` runs its command with stdout and stderr in two logs under `$XDG_STATE_HOME/samagotchi/execute/<id>/` (always an absolute path: a relative `XDG_STATE_HOME` is taken from the directory chi started in; outside the workspace: `git status` and `rm -rf tmp` never see them), stdin `/dev/null` and the same `exit_code` wrapper as a task. Its own copies of the logs hold a shared `flock` across the spawn: while a fresh exclusive `flock` fails, some process the command started still has them open. When the shell exits, a background child still holding them (`server &`) gets 1.5 s, then its group is stopped; one holding none (`nohup server >log 2>&1 &`) is left alone. A holder that left the group (setsid) is out of the stop's reach: it runs on, the result ends with the "left running" hint, and its dir stays for it to write to (the sweep removes it once no one holds the logs). Otherwise a finished command's dir is removed. Known gap on Linux: a command that writes through `/dev/stdout` or `/dev/stderr` (`echo b >/dev/stdout`) reopens the log file itself with `O_TRUNC`, so the output before it is lost (macOS dups the descriptor; the fix would be a relay pipe). The check needs the state dir on a local file system: over NFS, `flock` is emulated with `fcntl` locks, which children don't inherit.
|
|
26
26
|
- The exit code: the command runs after an EXIT trap that writes the shell's status to the `exit_code` file (`TaskRuntime.wrapped_shell_command`, shared with `task_create`), so `exit N`, a `set -e` failure and a trailing `\` or `|` in the command all record what the shell exited with. The trap runs wherever the shell is when it exits (a `task_create` `cwd`, or a `cd` in the command), so `task_create`'s `exit_code_path` is absolute; its `output_path` stays relative (`tmp/tasks/<id>/output.log`, opened by chi in the worker's cwd before the spawn). A command that sets its own EXIT trap replaces it, and a shell killed by a signal skips it (zsh's sh mode, on TERM, INT and KILL): `execute` still reports the shell's status, and a `task_create` task or a handed-over execute gets its code from chi's waiter while the chi that started it runs (`TaskRuntime.record_exit_status`): the exit status, or `128 + the signal` (TERM → 143), with no new `stop_reason`. A record a refresh already finished without a code in between (`failed`, `process_ended_without_exit_code` or the not-chi's-process reason) is corrected, its `finished_at` kept; a stopped task (its stop flag on disk) is never touched. `process_ended_without_exit_code` is left only when the shell wrote nothing and the chi that started it was gone too.
|
|
27
27
|
- A stop of an `execute` (the user's Stop, the limit in kill mode) is done once the shell is reaped and nothing holds the logs: a child that ignores TERM after its shell died gets the KILL.
|
|
28
|
-
- A command still running at `execute.timeout_sec` is handed over as a task (`execute.on_timeout: task`, the default; `kill` stops it instead, and the `!cmd` bang always kills): the result starts `Still running at the 120s limit: moved to a background task, not killed.`, then `task_id: …` and the tails of both logs so far. A timeout during the 1.5 s background grace is a kill (the shell is gone, nothing to hand over). If the record can't be written, the command is killed. `TaskRuntime.adopt` writes a record for the process execute already started, with `handed_over_by: "execute"`. Its logs stay where execute wrote them, so the record's paths are absolute: `output_path` (stdout), `stderr_path` (stderr, a log of its own) and `exit_code_path` (a `task_create` record's `exit_code_path` is absolute too; its `output_path` is relative). `started_at` (and `created_at`) is the spawn time, not the hand-over's; `pid_started` is read at the hand-over, while execute's detach waiter is still running. From there it is an ordinary task: `task_wait`, `task_get`, `task_list` and `task_stop` treat it as any other. Its dir gets a `task` back-pointer file holding the record's absolute path. Once per process, the first `execute` sweeps `…/execute/`: a dir whose logs no process holds is removed when it has no back-pointer and is a day old (chi died mid-command), when its record is gone, or when it is 14 days old. The records live in each project's `tmp/tasks`, which the state dir can't list: hence the back-pointer.
|
|
28
|
+
- A command still running at `execute.timeout_sec` is handed over as a task (`execute.on_timeout: task`, the default; `kill` stops it instead, and the `!cmd` bang always kills): the result starts `Still running at the 120s limit: moved to a background task, not killed.`, then `task_id: …` and the tails of both logs so far. A command is also handed over early when a person's message waits for the turn's next step (the `message_waiting:` probe, the same `Engine#input_waiting?` as `task_wait`'s): after `Execute::EARLY_HAND_OVER_FLOOR_SEC` (3 s: a short command ends first and its result is the better answer), in the next 0.2 s slice. The first line then reads `Still running at the 4s mark: a message from the user is waiting, so the command moved to a background task, not killed.` (the seconds it had run), and the line after `task_id:` starts `Read the message first.`; the rest is the same hand-over, and the task stays watched. Never in `kill` mode (`execute.on_timeout: kill`, a participant's pair turn, the `!cmd` bang): there the probe is not even read and the command runs to its limit and is killed. A command that can't be adopted at that moment (a record that can't be written) isn't killed early: it runs on to its limit. Everything that matches the first line builds on `Execute::HANDED_OVER` (`Still running at the`) and `Execute::HANDED_OVER_AFTER` (`120s limit:` or `4s mark:`): `TaskRuntime.handed_over_pattern`, `ToolActivity::HANDED_OVER_START`, `TaskWatch` (`limit_sec` for the limit's form, `early_sec` for the mark's, which the finish note words as `handed over by execute after 4s: a message from the user was waiting`) and the web's `task_stop.js`; the slow-commands bundle reads only the limit's form, which is the only one that is a timeout. A timeout during the 1.5 s background grace is a kill (the shell is gone, nothing to hand over). If the record can't be written, the command is killed. `TaskRuntime.adopt` writes a record for the process execute already started, with `handed_over_by: "execute"`. Its logs stay where execute wrote them, so the record's paths are absolute: `output_path` (stdout), `stderr_path` (stderr, a log of its own) and `exit_code_path` (a `task_create` record's `exit_code_path` is absolute too; its `output_path` is relative). `started_at` (and `created_at`) is the spawn time, not the hand-over's; `pid_started` is read at the hand-over, while execute's detach waiter is still running. From there it is an ordinary task: `task_wait`, `task_get`, `task_list` and `task_stop` treat it as any other. Its dir gets a `task` back-pointer file holding the record's absolute path. Once per process, the first `execute` sweeps `…/execute/`: a dir whose logs no process holds is removed when it has no back-pointer and is a day old (chi died mid-command), when its record is gone, or when it is 14 days old. The records live in each project's `tmp/tasks`, which the state dir can't list: hence the back-pointer.
|
|
29
29
|
- `task_wait` and `task_get` show `stderr_path` when the record has one; `task_wait`'s output tail is followed by a `stderr_tail:` of at most 10 lines when that log isn't empty. A `task_create` task has one log (stdout and stderr together) and no `stderr_path`. A `task_wait` result longer than `max_tool_output_chars` keeps its header and the tail's end, with a gap line naming `output_path`, where the whole log already is (and `stderr_path`, when the record has one) (`ToolOutputCut`'s `:head_tail_ref`; nothing is copied; see [Tool output guardrails](tool-guardrails.md#the-runners-cut-max_tool_output_chars)).
|
|
30
30
|
- Which tasks are a session's own (a cancelled turn's note, a worker's restart hold, the finish wake below, the web's stop-task button): the ids its `task_create` results and its handed-over executes name (`TaskRuntime.created_ids_in`, `created_id`). An execute's id is read only after its first line (`Execute::HANDED_OVER`, through `TaskRuntime.handed_over_id`; the web has a JS twin in `task_stop.js`), not from a `task_id:` line inside a run, which the command can print. (A command that prints the `---` separator chi joins a turn's results with, then a hand-over line, still fakes one: a known gap.) Either result may start with the `ran as: …` line of a gate-corrected call.
|
|
31
31
|
- `task_get` returns metadata and `output_path`; use `read` for output contents.
|
|
@@ -40,7 +40,7 @@ Behavior:
|
|
|
40
40
|
(`--no-shared`) a line in its steering queue. The probe only peeks: the drain still merges the message at the
|
|
41
41
|
next boundary. The result says `note: a message from the user is waiting; the task is STILL RUNNING.`, how to wait
|
|
42
42
|
again or stop it (`TaskWait::MESSAGE_WAITING_NOTE`), and `waited: Ns`; the task keeps running and stays watched
|
|
43
|
-
(below). A task that finished meanwhile gives its finished result, as after a Stop. A `chi -p` turn has no probe.
|
|
43
|
+
(below). A task that finished meanwhile gives its finished result, as after a Stop. A `chi -p` turn has no probe. The same probe ends a blocking `delegate_result` (and `delegate`'s wait) early; see [Sessions: Delegating](../sessions.md).
|
|
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
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.
|
|
46
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.
|
data/docs/internals/engine.md
CHANGED
|
@@ -23,7 +23,8 @@ Engine#run_turn(session, prompt, on_event:, max_iterations:, cancel_controller:,
|
|
|
23
23
|
│ # turn.resume_from = resume_from (its run's start; nil: it starts cold))
|
|
24
24
|
│ # @session = session, session.status = RUNNING, used memories absorbed,
|
|
25
25
|
│ # TurnState#begin!(controller:, sink:, input_waiting:) (a new CancellationController when none
|
|
26
|
-
│ # is given; input_waiting: the caller's peek, Engine#input_waiting?, which task_wait
|
|
26
|
+
│ # is given; input_waiting: the caller's peek, Engine#input_waiting?, which task_wait,
|
|
27
|
+
│ # delegate_result's wait and execute's early hand-over poll),
|
|
27
28
|
│ # a recap in flight invalidated, GuardrailWiring#begin_turn(origin)
|
|
28
29
|
│ # Returns a Turn {session, prompt (nil on a continue), continue, on_event, controller,
|
|
29
30
|
│ # origin, started_at, id (the caller's id:, else a new uuid), resumed, resume_from,
|
|
@@ -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.
|
|
@@ -48,7 +48,7 @@ Configuration (config.yml `execute.*` keys and CLI flags too, as for `read`):
|
|
|
48
48
|
- `SAMAGOTCHI_EXECUTE_PREVIEW_BYTES` (default `12288`): total preview budget split across head and tail.
|
|
49
49
|
- `SAMAGOTCHI_EXECUTE_TELEMETRY_THRESHOLD_PCT` (default `80`): include estimated output token impact only when threshold is crossed.
|
|
50
50
|
- `SAMAGOTCHI_EXECUTE_TIMEOUT_SEC` (`execute.timeout_sec`, default `120`): what a command still running then gets is `execute.on_timeout`.
|
|
51
|
-
- `SAMAGOTCHI_EXECUTE_ON_TIMEOUT` (`execute.on_timeout`, default `task`): `task` hands the command over as a background task, not killed; the result starts `Still running at the 120s limit: moved to a background task, not killed.` (`Execute::HANDED_OVER`), then its `task_id:` and the tails of stdout and stderr so far, and `task_wait` waits for it. `kill` stops it and answers `Error: command timed out after …s` (`Execute::TIMEOUT_PREFIX`) with the hint to use `task_create`. The `!cmd` bang always kills. See [Background tasks](background-tasks.md).
|
|
51
|
+
- `SAMAGOTCHI_EXECUTE_ON_TIMEOUT` (`execute.on_timeout`, default `task`): `task` hands the command over as a background task, not killed; the result starts `Still running at the 120s limit: moved to a background task, not killed.` (`Execute::HANDED_OVER`), then its `task_id:` and the tails of stdout and stderr so far, and `task_wait` waits for it. `kill` stops it and answers `Error: command timed out after …s` (`Execute::TIMEOUT_PREFIX`) with the hint to use `task_create`. The `!cmd` bang always kills. With `task`, a message from the user waiting for the turn hands a command over early, after 3 s, with the first line `Still running at the 4s mark: a message from the user is waiting, …` (same `Execute::HANDED_OVER` start; never under `kill`). See [Background tasks](background-tasks.md).
|
|
52
52
|
|
|
53
53
|
The env a command runs in (the session's project env, `ProjectEnv.tools_env`, then the Ruby/Bundler scrub and chi's markers) is the same for `execute`, `task_create`, `!cmd` and context sources: see [Background tasks](background-tasks.md).
|
|
54
54
|
|
data/docs/internals/web-push.md
CHANGED
|
@@ -169,6 +169,26 @@ each `session` / `session_gone` event into a `Show` (with its text) or a `Close`
|
|
|
169
169
|
Queue. A `chi` event marked `update_notice` becomes a `chi:update:<version>` show for the bare page; the checker
|
|
170
170
|
sets that marker only for a newly selected version not already in its persistent `notified` list. It uses the same
|
|
171
171
|
Presence skip and hourly CAP as session shows, and the 24-hour TTL lets an offline phone receive it later.
|
|
172
|
+
With `update.auto: install` (and an install that is not blocked) that marker is ignored: the page says the release
|
|
173
|
+
installs by itself, and the phone hears the result instead.
|
|
174
|
+
|
|
175
|
+
**The automatic update's shows.** `Web::AutoUpdate` says how a run went with `SessionHub#publish_notice`, a
|
|
176
|
+
`chi_notice` hub event (`{kind, version, …notes}`): not a `chi_update` frame, which is the page's (it would repeat
|
|
177
|
+
in a reconnect's snapshot and toast), and not kept. The Notifier turns it into a show with reason `update` (24 h TTL,
|
|
178
|
+
`Urgency: normal`, counted in CAP, skipped with the key marked handled while a page is in front, shown for the bare
|
|
179
|
+
page `/`), once per key in its process (`@updates_seen`; AutoUpdate's own record in `update_check.json` keeps a run
|
|
180
|
+
from saying it twice across restarts). The key's `chi:` prefix keeps sw.js from treating it as a waiting key:
|
|
181
|
+
|
|
182
|
+
| `kind` | key | title / body |
|
|
183
|
+
|---|---|---|
|
|
184
|
+
| `installed` (`update.restart: never`) | `chi:installed:<X>` | "chi X is installed" / "chi web and its sessions switch over when you restart them." + kept edits |
|
|
185
|
+
| `failed` | `chi:update_failed:<X>` | "chi X didn't install" (or "installed with a problem" when the gem row was fine) / the first failed row's status, "Open chi web for details." |
|
|
186
|
+
| `updated` | `chi:updated:<X>` | "chi updated to X" / "chi web and 3 sessions restarted; 1 restarts when idle." + kept edits |
|
|
187
|
+
| `restart_failed` | `chi:restart_failed:<X>` | "chi X is installed; chi web didn't restart" / why (a failed `--version` preflight, no wrapper), "Restart chi web in its terminal." |
|
|
188
|
+
|
|
189
|
+
`updated` is sent by the **restarted** chi web (its AutoUpdate finds the run at `phase: web`): the exec ends the
|
|
190
|
+
old process, and `Notifier#stop` does not flush the shows still in their hold, so a push queued just before it would
|
|
191
|
+
be lost. Sending from the new process also proves it came up.
|
|
172
192
|
|
|
173
193
|
**Seeding**: `Notifier#start(hub)` subscribes with `snapshot: true` and takes the summaries as the seed, so what is
|
|
174
194
|
open already never pushes. The hub's projection is empty before its first scan, so `Server.start` runs `hub.scan`
|
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
|