samagotchi 0.119.0 → 0.121.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 +100 -1
- data/bin/chi +6 -1
- data/docs/architecture.md +35 -5
- data/docs/cli.md +33 -8
- data/docs/configuration.md +74 -20
- data/docs/internals/participants.md +111 -12
- data/docs/internals/prompt-caching.md +7 -6
- data/docs/internals/remote-sessions.md +6 -1
- data/docs/internals/web-push.md +4 -2
- data/docs/plugins.md +24 -8
- data/docs/sessions.md +4 -3
- data/docs/testing.md +13 -2
- data/lib/samagotchi/answer_command.rb +2 -0
- data/lib/samagotchi/bridge.rb +56 -13
- data/lib/samagotchi/bridge_client/event_stream.rb +34 -3
- data/lib/samagotchi/bridge_client.rb +58 -19
- data/lib/samagotchi/bridge_secret.rb +35 -0
- data/lib/samagotchi/bundles/claude-peer/manifest.yml +2 -2
- data/lib/samagotchi/bundles/claude-peer/scripts/pair +1 -1
- data/lib/samagotchi/bundles/dev/manifest.yml +2 -2
- data/lib/samagotchi/bundles/mcp/manifest.yml +3 -3
- data/lib/samagotchi/bundles/mcp/plugin.rb +5 -1
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/config.rb +3 -0
- data/lib/samagotchi/engine.rb +1 -0
- data/lib/samagotchi/kernel_loop.rb +5 -0
- data/lib/samagotchi/participant_messages.rb +5 -0
- data/lib/samagotchi/participants.rb +16 -6
- data/lib/samagotchi/plugin/api.rb +6 -3
- data/lib/samagotchi/review_command.rb +3 -1
- data/lib/samagotchi/run_options.rb +3 -1
- data/lib/samagotchi/session_access/contract.rb +4 -1
- data/lib/samagotchi/session_access/local.rb +15 -4
- data/lib/samagotchi/session_access/remote.rb +43 -7
- data/lib/samagotchi/session_access.rb +11 -1
- data/lib/samagotchi/session_manager.rb +1 -1
- data/lib/samagotchi/sessions_command.rb +1 -2
- data/lib/samagotchi/share_command.rb +29 -8
- data/lib/samagotchi/terminal_ui/attached_loop.rb +3 -0
- data/lib/samagotchi/terminal_ui/local_link.rb +18 -3
- data/lib/samagotchi/terminal_ui/question_slot.rb +19 -8
- data/lib/samagotchi/tool_declarations.rb +1 -1
- data/lib/samagotchi/tool_runner.rb +37 -1
- data/lib/samagotchi/toolkit.rb +50 -26
- data/lib/samagotchi/toolkit_binding.rb +32 -0
- data/lib/samagotchi/toolkit_command.rb +23 -0
- data/lib/samagotchi/tools/deferred_tools.rb +10 -2
- data/lib/samagotchi/tools/delegate_relay.rb +4 -0
- data/lib/samagotchi/tools/registry.rb +20 -6
- data/lib/samagotchi/tools/tool_index.rb +103 -20
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +117 -43
- data/lib/samagotchi/web/grants.rb +127 -16
- data/lib/samagotchi/web/link_codes.rb +24 -15
- data/lib/samagotchi/web/participant_access.rb +69 -13
- data/lib/samagotchi/web/participant_exposure.rb +44 -0
- data/lib/samagotchi/web/participant_review.rb +71 -9
- data/lib/samagotchi/web/participants_admin.rb +12 -6
- data/lib/samagotchi/web/proxy_gate.rb +18 -3
- data/lib/samagotchi/web/public/annotate_bar.css +18 -0
- data/lib/samagotchi/web/public/annotate_bar.js +10 -3
- data/lib/samagotchi/web/public/app.js +66 -155
- data/lib/samagotchi/web/public/composer.css +73 -0
- data/lib/samagotchi/web/public/composer_grip.js +21 -15
- data/lib/samagotchi/web/public/composer_mount.js +196 -0
- data/lib/samagotchi/web/public/forms.css +12 -0
- data/lib/samagotchi/web/public/index.html +14 -88
- data/lib/samagotchi/web/public/level_chip.js +2 -2
- data/lib/samagotchi/web/public/llm_context_chip.js +4 -4
- data/lib/samagotchi/web/public/notifications.js +58 -8
- data/lib/samagotchi/web/public/participant.html +46 -10
- data/lib/samagotchi/web/public/participant.js +101 -18
- data/lib/samagotchi/web/public/participant_annotate.js +75 -0
- data/lib/samagotchi/web/public/participant_model.js +53 -1
- data/lib/samagotchi/web/public/participants_panel.js +37 -13
- data/lib/samagotchi/web/public/question_card.js +15 -1
- data/lib/samagotchi/web/public/question_cards.js +11 -2
- data/lib/samagotchi/web/public/review_history.js +60 -0
- data/lib/samagotchi/web/public/scope.js +12 -8
- data/lib/samagotchi/web/public/stage_frame.js +9 -4
- data/lib/samagotchi/web/public/stage_view.js +4 -4
- data/lib/samagotchi/web/public/toolkit_chip.js +3 -0
- data/lib/samagotchi/web/push/notifier.rb +17 -3
- data/lib/samagotchi/web/running.rb +7 -4
- data/lib/samagotchi/web/server.rb +34 -11
- data/lib/samagotchi/web/session_create.rb +7 -7
- data/lib/samagotchi/web/start_dir.rb +26 -0
- data/lib/samagotchi/web/tunnel.rb +10 -1
- data/lib/samagotchi/worker_sidecar.rb +22 -11
- metadata +10 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 78051f4f253f400d0b791fe2694f0dc3d07a19e7d1268ef30557ff3a780d8d64
|
|
4
|
+
data.tar.gz: c0bf5fbba8c530df5bf56a3947addddc0a9952a39f2077ddd5f59129683ba8f6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c438bac307263d1a56cd9a97b56a2c3ab7175d9be0944c4e327fb92a57c9fdd9ffb82bdac8ec7048ac780a4a9e5f3545a3186dfcb3fec7842e687e2d47099307
|
|
7
|
+
data.tar.gz: 2738c2cc9f4f81f34c3c88f02568066dced57578a88ac6d1e301ca62508a3e343179a97a7d6ef9d6778f463a00648d2aa0f0b1aded820e8bce7442b2601a0d8a
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,103 @@ 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.121.0] - 2026-10-26
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Toolkit kits pick the MCP servers their sessions reach: `groups: [chrome]` in `toolkit.kits` (names or globs; no
|
|
15
|
+
key = the base's; the built-ins reach every server, `review` none). `/toolkit` switches them from the next turn
|
|
16
|
+
(asking first: the cached prompt changes). A call into a server the kit doesn't reach is refused without starting
|
|
17
|
+
it, naming a kit that reaches it; its deferred stub says why, `/mcp` marks such servers, and the web's toolkit chip
|
|
18
|
+
lists a restricted kit's servers. Kits without `groups:` send the same request as before.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- `ask_user_question`'s description asks the model, when it recommends one option, to put it first and end its
|
|
23
|
+
label with " (Recommended)" (eval 0005: Flash Next and DeepSeek v4.1-flash mark it 5/5 on a decision question,
|
|
24
|
+
0/5 before; an answer comes back with the suffix, so `chi answer --option` takes that exact label or the number).
|
|
25
|
+
- The recommended option shows as such: chi web's question card marks its row in the accent colour with a small
|
|
26
|
+
"recommended" tag (the label shown, and the answered line, without the suffix; nothing is pre-checked), and the
|
|
27
|
+
terminal's question paints its row bold. Approval and step-limit cards are unchanged.
|
|
28
|
+
- chi web's select boxes have one shape in every dialog: the Invite panel's person/agent and Expires now match the
|
|
29
|
+
text fields beside them (flat box, same height and corners, one thin caret), as the Thinking and LLM context
|
|
30
|
+
dialogs' selects already did, in place of the browser's own pill.
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- claude-peer (0.4.4): `pair --inbox-socket PATH` says the remote socket is forwarded to this Claude's socket, not
|
|
35
|
+
from it (`ssh -R`).
|
|
36
|
+
- An ended participant link forgets its comment history even when its first review read is refused.
|
|
37
|
+
- The participant review status stays idle when a live snapshot has no current turn and its status is stale.
|
|
38
|
+
- The participant usage row no longer says "0 queued" when no worker is running.
|
|
39
|
+
- The participant usage row counts link time down and shows its end while the page stays open.
|
|
40
|
+
|
|
41
|
+
Update with `chi update` (the mcp bundle 0.7.2, claude-peer 0.4.4, the dev profile 0.7.3).
|
|
42
|
+
|
|
43
|
+
## [0.120.0] - 2026-10-26
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
|
|
47
|
+
- `chi web --start-dir DIR` and `web.start_dir`: the folder chi web starts in, in place of the one you run it in
|
|
48
|
+
(`~` and a relative path work; one that isn't a folder stops it, exit 1). Its page opens on that folder's project,
|
|
49
|
+
and a new session from the all view starts there. A chi web already running in another folder keeps it and the
|
|
50
|
+
second one says so: restart it to change it.
|
|
51
|
+
- Annotate on the participant page: selecting text in the Plan (the review's files) or the thread offers your
|
|
52
|
+
`web.annotate_presets` and quotes the selection into the comment box (`From <path>:` / `From <name>'s comment:`),
|
|
53
|
+
as on your own page.
|
|
54
|
+
- The participant page's comment box is the owner's composer: it grows with the text, its grip resizes or folds it
|
|
55
|
+
(Annotate and typing open it again), it stays one row on a phone, and ↑/↓ recall the participant's own earlier
|
|
56
|
+
comments from that browser (kept per review link, never your prompt history). No `/` list, delivery choices or
|
|
57
|
+
images there yet; every comment is still a queued review turn. Your participant view has no comment box.
|
|
58
|
+
- The participant page shows the participant their own usage in one line under the comment box: what their
|
|
59
|
+
comments have cost you of their cap (a running turn's too), their queued comments of their queue limit, when their
|
|
60
|
+
link ends, and the session's model. Never another participant's spend, yours or the session's. It sits on a
|
|
61
|
+
per-link exposure level (`own`, the only one so far), so a later tier can show more.
|
|
62
|
+
- Review links bound to one account: `chi share ID --name NAME --identity EMAIL` (and the Invite dialog's email field,
|
|
63
|
+
behind an auth proxy that names the signed-in user: `web.public_url` with `web.proxy.identity_header`, or
|
|
64
|
+
`chi web --tunnel tailscale`) makes a link that opens only through that proxy, signed in as EMAIL. A forwarded
|
|
65
|
+
link opens for nobody else, and on the LAN or this machine for nobody. `--identity` without such a proxy is refused
|
|
66
|
+
(the link could never open), and `chi share` warns when a link could be bound and isn't. `chi share ls` shows
|
|
67
|
+
`alex@corp · bound`. Through `chi --remote`, `--identity` needs that chi web to be this version or newer (an older
|
|
68
|
+
one would ignore it and make a link anyone could open).
|
|
69
|
+
- chi web trusts proxy headers (`X-Forwarded-*`, the identity header, the public Host) only from this machine
|
|
70
|
+
(a loopback peer): the proxy must run where chi web runs. With `web.host: lan` and `web.public_url` together, a LAN
|
|
71
|
+
device sending those headers is a LAN request, never one through the proxy. An identity header that isn't ASCII is
|
|
72
|
+
refused.
|
|
73
|
+
- A bound link opened by another account, or not through the proxy, is refused with its own words (401
|
|
74
|
+
`token_bound`: "this review link is for another account …", on the page too), and the refusal goes into the
|
|
75
|
+
session's participants audit naming who tried. Its one-time code stays usable: the person it was sent to can still
|
|
76
|
+
open it. Behind a proxy the audit's new-peer lines are per signed-in identity (every peer there is 127.0.0.1).
|
|
77
|
+
|
|
78
|
+
### Changed
|
|
79
|
+
|
|
80
|
+
- chi web's bare page (`/`: the LAN link and its QR code, the Home Screen app) opens on the project chi web starts in,
|
|
81
|
+
as its printed link already did; outside a repo it shows every session as before. Every project's sessions are now
|
|
82
|
+
`/?scope=all` (the header's `all`), and `chi web --scope=all` (or a `chi web` run outside a repo) prints that link.
|
|
83
|
+
- A pushed notification opens its session in that session's own project (the all view for a session outside a repo),
|
|
84
|
+
not the project the app shows. The Home Screen app's notifications and badge still cover every project: on a
|
|
85
|
+
project's page it tidies them from every session's events, where before a project's page (`?dir=`) left them as
|
|
86
|
+
they were.
|
|
87
|
+
- The participants' grants store (`web-grants/grants.json`) carries a version: 2 once a grant is bound to an identity.
|
|
88
|
+
chi keeps a grant's fields it doesn't know when it rewrites the store, and never writes over a store a newer chi
|
|
89
|
+
wrote (its links still work; invite, cap and revoke say to update chi). A chi from before this one doesn't check.
|
|
90
|
+
|
|
91
|
+
### Fixed
|
|
92
|
+
|
|
93
|
+
- chi web's stage: on a phone its height follows the whole bottom dock (an info bar that wraps late after a reload
|
|
94
|
+
no longer leaves it too tall), and the keyboard opening during a smooth scroll no longer leaves a question card
|
|
95
|
+
out of sight.
|
|
96
|
+
|
|
97
|
+
### Security
|
|
98
|
+
|
|
99
|
+
- A session's worker answers only chi's own clients on its machine: its Bridge makes a random secret, written to the
|
|
100
|
+
session's `bridge.json` (readable by you only), and refuses every request that doesn't name it (403
|
|
101
|
+
`bridge_secret`). A proxy or tunnel that forwards every local port, or another account on the machine, reaches the
|
|
102
|
+
port but no longer a session. **After updating chi, restart chi web, re-attach terminals** (`chi --attach ID`) **and
|
|
103
|
+
restart older session workers** (`chi sessions restart ID...`): a chi web, attached terminal or session worker from
|
|
104
|
+
before the update sends no secret, and a worker the new chi starts (a delegate child too) refuses it with a message
|
|
105
|
+
saying so. Workers started before the update ask for no secret and answer both until they restart.
|
|
106
|
+
|
|
10
107
|
## [0.119.0] - 2026-10-26
|
|
11
108
|
|
|
12
109
|
### Added
|
|
@@ -4454,7 +4551,9 @@ and long-lived sessions.
|
|
|
4454
4551
|
- A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
|
|
4455
4552
|
hotkey panel that send selected text or the clipboard to your sessions.
|
|
4456
4553
|
|
|
4457
|
-
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.
|
|
4554
|
+
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.121.0...HEAD
|
|
4555
|
+
[0.121.0]: https://github.com/dm1try/samagotchi/compare/v0.120.0...v0.121.0
|
|
4556
|
+
[0.120.0]: https://github.com/dm1try/samagotchi/compare/v0.119.0...v0.120.0
|
|
4458
4557
|
[0.119.0]: https://github.com/dm1try/samagotchi/compare/v0.118.0...v0.119.0
|
|
4459
4558
|
[0.118.0]: https://github.com/dm1try/samagotchi/compare/v0.117.0...v0.118.0
|
|
4460
4559
|
[0.117.0]: https://github.com/dm1try/samagotchi/compare/v0.116.0...v0.117.0
|
data/bin/chi
CHANGED
|
@@ -134,6 +134,10 @@ if options[:tunnel] && !web
|
|
|
134
134
|
warn "Error: --tunnel goes with chi web (chi web --tunnel tailscale)"
|
|
135
135
|
exit 2
|
|
136
136
|
end
|
|
137
|
+
if options[:start_dir] && !web
|
|
138
|
+
warn "Error: --start-dir goes with chi web (chi web --start-dir DIR)"
|
|
139
|
+
exit 2
|
|
140
|
+
end
|
|
137
141
|
if options[:sets] && web
|
|
138
142
|
warn "Error: --set is a new session's (chi --set, chi send --new --set), not chi web's"
|
|
139
143
|
exit 2
|
|
@@ -225,7 +229,8 @@ if options[:web]
|
|
|
225
229
|
markdown: markdown, view: view, new_token: options[:new_token], tunnel: options[:tunnel],
|
|
226
230
|
working_set: Samagotchi::Config.get("web.working_set"),
|
|
227
231
|
annotate_presets: Samagotchi::Config.get("web.annotate_presets"),
|
|
228
|
-
editor: Samagotchi::Config.get("web.editor")
|
|
232
|
+
editor: Samagotchi::Config.get("web.editor"),
|
|
233
|
+
start_dir: options[:start_dir] || Samagotchi::Config.get("web.start_dir"))
|
|
229
234
|
end
|
|
230
235
|
|
|
231
236
|
# --resume and --attach take a unique id prefix too, like git.
|
data/docs/architecture.md
CHANGED
|
@@ -152,7 +152,7 @@ agent logic and can be used without any terminal rendering; the UI is a thin lay
|
|
|
152
152
|
| Core | `Samagotchi::Engine` | System prompt, memory injection, tool declarations, session lifecycle, the model↔tool loop (`run_turn`). No terminal coupling. |
|
|
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
|
-
| 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`,
|
|
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
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 but this machine's needs the access token (`App#token_gate`: the `chi_token` cookie or `Authorization: Bearer`; [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 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
|
|
|
@@ -390,8 +390,27 @@ process that already owns the `Engine`); every worker starts it, and it exposes:
|
|
|
390
390
|
worker only through `chi web`. A request carrying any trace a proxy adds (`ProxyTrace`: `X-Forwarded-*`,
|
|
391
391
|
`Forwarded`, `X-Real-IP`, `X-Auth-Request-*`, `Cf-*`…, underscore twins too) is `403 proxied`
|
|
392
392
|
(`Bridge#proxied_request?`): an auth proxy that forwards every local port and adds such a header doesn't reach a
|
|
393
|
-
worker.
|
|
394
|
-
|
|
393
|
+
worker.
|
|
394
|
+
- **The secret.** Every request, on every route (the stream, the reads such as `state` and `stats`, the writes, and
|
|
395
|
+
an unknown path), names the Bridge's secret in `X-Chi-Bridge`; one without it, or with another one, is
|
|
396
|
+
`403 bridge_secret` with one message (`BridgeSecret::REFUSED`: restart chi web, re-attach terminals and restart
|
|
397
|
+
older session workers), after the browser and proxy checks and before any route (`Bridge#secret_ok?`,
|
|
398
|
+
`OpenSSL.secure_compare`). No route is left open: what the reads show is the session itself. Only a request that
|
|
399
|
+
got past it counts as client activity, so a refused one (an older chi web's polls) never holds the idle exit. Each Bridge makes its own (`SecureRandom.hex(32)`, 64 hex
|
|
400
|
+
characters) when it starts and writes it to `bridge.json`, which is 0600 from the moment it exists
|
|
401
|
+
(`AtomicFile` creates the temporary file with that mode, then renames it), so only this machine's user can read
|
|
402
|
+
it: a forward that adds no proxy header, or another local account, reaches the port but not a session. Clients get
|
|
403
|
+
it only from the sidecar: `BridgeClient.discover`/`.wait_for`/`.for_sidecar` take the port and the secret from the
|
|
404
|
+
same read (chi web makes its client from the sidecar for every request, the attached terminal again on a
|
|
405
|
+
`worker_changed`), so a respawned worker's new one is used and nothing passes a port by hand. A worker from before
|
|
406
|
+
the secret writes none and asks for none; a chi web or attached terminal from before it sends none, so a worker
|
|
407
|
+
the newer chi started refuses it until it is restarted. A client tells that refusal from a route's own 403
|
|
408
|
+
(`BridgeClient::Response#secret_refused?`) and shows the one message: chi web's relays answer 502 `bridge_secret`,
|
|
409
|
+
`chi answer` and a parent's approval relay say it instead of "refused", and an event stream ends with reason
|
|
410
|
+
`bridge_secret` once rediscovery finds no newer worker (the attached terminal's closing line), never retrying in
|
|
411
|
+
silence; chi web's stream proxy passes no refusal on as frames. The secret is never logged (a refusal logs
|
|
412
|
+
`secret_refused` with the path, at most once a minute), never in an event, a reply, `/api/info` or a session
|
|
413
|
+
file, and `WorkerSidecar`, `BridgeClient` and `Bridge` leave it out of `#inspect`.
|
|
395
414
|
|
|
396
415
|
The metrics in `/state`, `/snapshot`, `/stats` and the SSE `snapshot`/`reset` frames
|
|
397
416
|
(`SessionMetrics#snapshot`) carry the session's totals and only the recent timing records: the
|
|
@@ -472,7 +491,7 @@ Every route goes through `Bridge#dispatch`: an id other than the bridge's own se
|
|
|
472
491
|
`handler_failed`) rather than dropping the connection.
|
|
473
492
|
|
|
474
493
|
The per-session port is OS-assigned (bound to `0`) and published to a `bridge.json` sidecar
|
|
475
|
-
for client discovery. `chi web`'s `GET /api/sessions/:id/stream` proxies this bridge
|
|
494
|
+
for client discovery, with the Bridge's secret (see above). `chi web`'s `GET /api/sessions/:id/stream` proxies this bridge
|
|
476
495
|
(503 `not_live` when the worker is not running; full history of any session is served by
|
|
477
496
|
`GET /api/sessions/:id/output`), as a byte pipe for the owner: `App#proxy_stream_body` gives `ProxyStreamBody` the
|
|
478
497
|
request's frame filter (`App#stream_filter`: nil for the owner; for a participant, and the owner's participant view
|
|
@@ -493,7 +512,18 @@ in RubyGems' specification dirs, or a checkout's `version.rb` on disk; looked at
|
|
|
493
512
|
probe, every 10 s), and a change is a `chi` frame `{version, installed}`. A newer one than the
|
|
494
513
|
server runs is said in chi web's terminal and on the page: restart chi web (its workers already
|
|
495
514
|
run the newest chi, [Sessions](sessions.md)). `GET /api/info` carries `installed` too, and the
|
|
496
|
-
feature `newest_workers
|
|
515
|
+
feature `newest_workers`; next to `cwd` (the folder chi web runs in) it names `start_dir`, the folder it
|
|
516
|
+
starts in (`web.start_dir`, `--start-dir`; `Web::StartDir`), which a second `chi web` given another one
|
|
517
|
+
mentions.
|
|
518
|
+
|
|
519
|
+
The page's scope is in its URL, and the server picks the default: `?dir=<folder>` is that folder's
|
|
520
|
+
project (the list, the events stream and a new chat follow it), `?scope=all` every session, and a bare
|
|
521
|
+
`/` the start folder's project when that folder is in a repo (`App#index_data_attributes` writes
|
|
522
|
+
`data-scope-dir`, which the page reads when the URL has no `?dir`), else every session. The page never
|
|
523
|
+
redirects, so the `?token=` trade's 303, a link code's `#code=` and the Home Screen app's `start_url` `/`
|
|
524
|
+
all land on the start project; a push opens its session's own project (`/?dir=<root>#/s/<id>`, or
|
|
525
|
+
`/?scope=all#/s/<id>` outside a repo). `?dir=` beats `?scope=all`. A session created
|
|
526
|
+
without a folder starts in the start folder.
|
|
497
527
|
Files stay the source of truth and workers don't know the hub. Its watcher is a 1 s tick that
|
|
498
528
|
stats the sessions dir (every session writer goes tmp + rename, which bumps the dir's mtime) and
|
|
499
529
|
each `<id>/` folder (recap.json, bridge.json, the stopped and archived markers), re-parsing only the files whose mtime or size
|
data/docs/cli.md
CHANGED
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
- `chi scratch [options]` — a one-time session in the plain in-process REPL, in this folder, that leaves nothing behind (see [Scratch sessions](#scratch-sessions))
|
|
12
12
|
- `chi --attach <session-id>` — attach the terminal to a session's worker (e.g. one started from the Web UI), waking one if it has exited
|
|
13
13
|
- A session id can be shortened to any unique prefix (like git): `chi --attach 2ea8`. `--resume`, `--attach`, `sessions stop`, `sessions archive` and `sessions delete` take one; an ambiguous prefix lists the sessions it matches.
|
|
14
|
-
- `chi web [--port 4567] [--open] [--scope=all]` — start the Web UI (single localhost port session control plane) on this git project's sessions (`--scope=all`, or a folder in no repo: every session); if a chi web already runs on the port, print (with `--open`, open) its page for this folder and exit. Something else on the port (an older chi web too) exits 1 with "port N is in use"
|
|
14
|
+
- `chi web [--port 4567] [--open] [--scope=all] [--start-dir DIR]` — start the Web UI (single localhost port session control plane) on this git project's sessions (`--scope=all`, or a folder in no repo: `/?scope=all`, every session); if a chi web already runs on the port, print (with `--open`, open) its page for this folder and exit. Something else on the port (an older chi web too) exits 1 with "port N is in use"
|
|
15
15
|
- `chi web --web-host lan` — the Web UI on your home network too, for your phone: a link with an access token and its QR code (see [chi web on your phone](#chi-web-on-your-phone)); `chi web --new-token` replaces the token
|
|
16
|
+
- `chi web --start-dir DIR` (config `web.start_dir`) — the folder chi web starts in, in place of the one you run it in (`~` and a relative path work; chi web never changes its own folder): its page opens on that folder's project (the printed link, a bare `/`, the LAN link and its QR code, the Home Screen app), and a new session from the all view starts there; `/?scope=all` (the header's `all`) is every project. One that isn't a folder exits 1; `--start-dir` without `web` exits 2. A chi web already running in another folder keeps it (the second one says so: restart it to change it)
|
|
16
17
|
- `chi web --tunnel tailscale` — the Web UI over https on your tailnet, for your phone anywhere: Tailscale Serve (never Funnel) on this machine's `<machine>.<tailnet>.ts.net` name for this run, the owner your Tailscale login, turned off on exit (see [Tailscale Serve](configuration.md#tailscale-serve-your-tailnet)); `--tunnel` without `web` exits 2
|
|
17
18
|
- `chi web push ls [--format json]`, `chi web push rm ID`, `chi web push test [ID]` — the phones chi web pushes to (see [Phone notifications](configuration.md#phone-notifications-web-push)): list them (short id, label, origin, added, last delivery, done turns, last error; `stale` for one made on another public URL than chi web's now: never sent to), remove one, or send a test notification to ID or to every device not stale. chi web need not run; nothing printed holds a device's push address or keys. A usage error exits 2; a store others can read, a corrupt one, an unknown device or a failed test push exit 1. This machine only: `chi --remote X web push` exits 2
|
|
18
19
|
- `chi web --web-markdown` — opt in to sanitized Markdown rendering for completed assistant messages, the stage's asking text and a resolved question card's reply
|
|
@@ -26,7 +27,7 @@
|
|
|
26
27
|
- `chi answer ID --question QID (--option N|LABEL)... [--text T] [--timeout S] [--format json]` (or `--dismiss`) — answer the question a session waits on (the one `chi send --wait` exited 3 with), then wait and print what comes next as `chi send --wait` does: the reply (0), the next question (3), still running after `--timeout` (4). `--option` is 1-based or the label, repeated on a multi-select question; `--text` is free text, or a Deny's reason; `--dismiss` leaves it unanswered and the model finishes its reply. A question no longer open (the web answered first) isn't answered again: it waits for that turn's reply. An option the question doesn't offer exits 2; a worker that is gone exits 1 (`send the task again: chi send --wait -m "…" ID`). What a parent may pick follows `guardrails.parent_answers`, or the session's own `parent_answers` when stricter (`soft`, the default: a deny, a plain answer, Continue and "Allow once"; `standard`: no allow; `hard`: no Continue either), never a wider scope or an allow on chi's own config; a refused pick exits 1 with what to answer instead (a convention, not a security boundary; see [Guardrails](guardrails.md#answers-from-a-parent-agent))
|
|
27
28
|
- `chi remote add NAME URL [--token-file F | --token-stdin]`, `chi remote ls`, `chi remote rm NAME` — the other machines whose chi web the session commands reach with `--remote NAME` (see [Remote sessions](#remote-sessions))
|
|
28
29
|
- `chi review FILE… [--dir DIR] [--model M]`, `chi review --from ID [FILE…]` — start a review session to invite participants to: for files of a project, or as a review of a working session that starts from its recap, not its transcript (see [Review sessions](#review-sessions))
|
|
29
|
-
- `chi share ID --name NAME [--agent] [--expires 3d] [--cap 2]`, `chi share ls [ID]`, `chi share cap ID P DOLLARS`, `chi share revoke ID [P] | --all` — invite a participant to review a session through chi web on the LAN (a link of their own, 7 days, a $2 spending cap), list them, change one's cap, revoke them (see [Participants](#participants))
|
|
30
|
+
- `chi share ID --name NAME [--identity EMAIL] [--agent] [--expires 3d] [--cap 2]`, `chi share ls [ID]`, `chi share cap ID P DOLLARS`, `chi share revoke ID [P] | --all` — invite a participant to review a session through chi web on the LAN (a link of their own, 7 days, a $2 spending cap), list them, change one's cap, revoke them (see [Participants](#participants))
|
|
30
31
|
- `chi --remote NAME send|answer|note|context|sessions|share|review …` (or `CHI_REMOTE=NAME`) — those commands on that machine's sessions, through its chi web (see [Remote sessions](#remote-sessions))
|
|
31
32
|
- `chi --remote NAME --attach ID` (`--resume ID`, `--dir DIR [-p TEXT]`) — the attached TUI on a session of that machine (see [Remote sessions](#remote-sessions))
|
|
32
33
|
- `chi memory ls [--scope project|system] [--format text|json]` — the memories of both scopes (this folder's project first), one row each: scope, name, size, date, `from <bundle>`, the index description and its model overlays; `chi memory rm NAME [--scope project|system]` removes one as `memory_write` with `remove: true` does: its file and model overlays go to the bundle trash (the line names the dir; `chi bundle trash` lists and empties it) and its index line is dropped. NAME in both scopes without `--scope` exits 2 naming both; a missing NAME or a bundle's memory (`chi bundle uninstall` removes it) exits 1 (see [Memory: Listing and removing from the shell](memory.md#listing-and-removing-from-the-shell))
|
|
@@ -238,7 +239,12 @@ done
|
|
|
238
239
|
hands over to the new chi, which does the rest and prints the table. Old
|
|
239
240
|
versions stay installed: running workers and an old `chi web` still load
|
|
240
241
|
files from them, so a `gem cleanup` while they run breaks them (a
|
|
241
|
-
`LoadError`, missing web assets); restart them first.
|
|
242
|
+
`LoadError`, missing web assets); restart them first. A chi web, an
|
|
243
|
+
attached terminal or a session worker started before the update may not
|
|
244
|
+
reach a worker the new chi starts (403 `bridge_secret`: its Bridge asks for
|
|
245
|
+
a secret they don't send; an older worker's delegate child is one): restart
|
|
246
|
+
chi web, re-attach, and restart the older workers (`chi sessions restart
|
|
247
|
+
ID...`, as the `workers` row says). Offline, the row says
|
|
242
248
|
"couldn't check" and the rest still runs; a failed install fails the row
|
|
243
249
|
and the rest runs on the current version. Under Bundler (`bundle exec`)
|
|
244
250
|
the row says `bundle update samagotchi` instead.
|
|
@@ -1164,6 +1170,12 @@ On a touch screen (a phone, a tablet) the bar sits just above the composer
|
|
|
1164
1170
|
instead of next to the selection, where the system's own Copy menu and the
|
|
1165
1171
|
selection handles would cover it; several pills wrap to a second row.
|
|
1166
1172
|
|
|
1173
|
+
A review's participants get the same on their page (a `chi share` / `chi
|
|
1174
|
+
review` link): selecting text in the Plan or the thread quotes it into
|
|
1175
|
+
their comment box, labelled `From docs/plan.md:` or `From Sam's comment:`,
|
|
1176
|
+
with the same presets. Not in the owner's participant view, and no plugin
|
|
1177
|
+
pills there (a review turn refuses `/` lines).
|
|
1178
|
+
|
|
1167
1179
|
The list is `web.annotate_presets`, `|`-separated (at most five; a preset
|
|
1168
1180
|
can't contain `|`; a YAML list works too):
|
|
1169
1181
|
|
|
@@ -1200,7 +1212,10 @@ Plain http: the link and your traffic can be read by anyone on this Wi-Fi.
|
|
|
1200
1212
|
|
|
1201
1213
|
Scan the QR code with the phone's camera. The page trades the token in the
|
|
1202
1214
|
link for a cookie (kept 400 days) and drops it from the address, so a
|
|
1203
|
-
bookmark or a home-screen icon keeps working across restarts.
|
|
1215
|
+
bookmark or a home-screen icon keeps working across restarts. It opens on
|
|
1216
|
+
the project chi web starts in (`--start-dir DIR` or `web.start_dir` picks
|
|
1217
|
+
another; outside a repo it shows every session); the header's `all` shows
|
|
1218
|
+
every project's. A home-screen
|
|
1204
1219
|
web app on iOS has cookies of its own: if it opens on "needs chi web's
|
|
1205
1220
|
access token", paste the token there (the part of the link after
|
|
1206
1221
|
`token=`), or open the link once in it.
|
|
@@ -1350,8 +1365,9 @@ chi --remote work sessions list
|
|
|
1350
1365
|
```
|
|
1351
1366
|
|
|
1352
1367
|
- Pick a local port other than 14567 when this machine runs its own chi web.
|
|
1353
|
-
- Forward only chi web's port, never a worker's: a session's Bridge
|
|
1354
|
-
|
|
1368
|
+
- Forward only chi web's port, never a worker's: a session's Bridge answers
|
|
1369
|
+
only chi's own clients on its machine (they read its secret from the
|
|
1370
|
+
session's folder), so a forwarded worker port is of no use.
|
|
1355
1371
|
- Without `web.public_url` there, a request that arrives through the tunnel
|
|
1356
1372
|
looks local and needs no token, so any process here reaching the forwarded
|
|
1357
1373
|
port acts as you there. With it set, every request needs the token, which
|
|
@@ -1403,8 +1419,9 @@ chi share revoke --all # every link to every session
|
|
|
1403
1419
|
- **The participant page.** Their link opens a page of its own (never yours): the session's title and status, who
|
|
1404
1420
|
else takes part, and the conversation as a participant sees it: every prompt and reply with who wrote it, each
|
|
1405
1421
|
step's tools as one line (`read docs/plan.md`: the tool and a project file's path, never arguments or output), and
|
|
1406
|
-
the reply streaming in while a turn runs. A "Comment or ask" box sends their comment
|
|
1407
|
-
|
|
1422
|
+
the reply streaming in while a turn runs. A "Comment or ask" box sends their comment (your composer's box, with its
|
|
1423
|
+
grip to resize or fold it and ↑/↓ over their own earlier comments from that browser; no `/` list, no images yet);
|
|
1424
|
+
their queued comments show with **Take back**; "waiting for the owner" shows while you answer a question or an approval of the turn; a refused
|
|
1408
1425
|
comment (a `!` or `/` line, too many waiting, their cap reached) says why. On a phone it is the thread first with
|
|
1409
1426
|
the box pinned, tool lines folded. They never see tool output or arguments, thinking, approvals or questions,
|
|
1410
1427
|
memories, context notes, plugin and delegate input, cards, command output, the working set, cost, timing or host
|
|
@@ -1433,6 +1450,14 @@ chi share revoke --all # every link to every session
|
|
|
1433
1450
|
`https://<public url>/#code=…`; with `chi web --tunnel tailscale` too (its `ts.net` name, which web.json records
|
|
1434
1451
|
while that chi web answers on its port: a chi web on another port takes web.json over, and the link falls back to
|
|
1435
1452
|
127.0.0.1). A tunnel link opens only on a device on your tailnet; the grant still decides.
|
|
1453
|
+
- **A link for one account.** Behind an auth proxy that names the signed-in user (`web.public_url` with
|
|
1454
|
+
`web.proxy.identity_header`, or `chi web --tunnel tailscale`), `--identity EMAIL` binds the link to that account:
|
|
1455
|
+
it opens only through the proxy, signed in as EMAIL. A forwarded link opened by anyone else says "This review link
|
|
1456
|
+
is for another account …", and its one-time code still works for EMAIL. `chi share` says `· bound to EMAIL`, `chi
|
|
1457
|
+
share ls` `alex@corp · bound`, and the panel's Invite has an email field there. Without such a proxy `--identity`
|
|
1458
|
+
is refused (the link could never open); with one, an unbound invite gets a warning line. Through `chi --remote`
|
|
1459
|
+
it needs that chi web to have bound links (this version or newer), else it is refused before anything is issued.
|
|
1460
|
+
See [configuration.md](configuration.md#inviting-colleagues).
|
|
1436
1461
|
- The owner's own token and the `chi web --new-token` rotation are separate: rotating it ends no participant's link.
|
|
1437
1462
|
- **In the attached terminal.** A participant's message reads `Alex ▸ review> is step 3 right?`, the name in that
|
|
1438
1463
|
participant's own colour (queued, running, and the last exchange when you join); yours stay as they were. A line
|
data/docs/configuration.md
CHANGED
|
@@ -1400,6 +1400,8 @@ toolkit:
|
|
|
1400
1400
|
kits: # your own, on a base
|
|
1401
1401
|
audit: { base: lean, add: [web_fetch, delegate], remove: [memory_write] }
|
|
1402
1402
|
terse: { base: lean, prompt: "- Be concise in your responses" }
|
|
1403
|
+
web: { base: lean, groups: [chrome] }
|
|
1404
|
+
coding: { base: lean, groups: [] }
|
|
1403
1405
|
```
|
|
1404
1406
|
|
|
1405
1407
|
| toolkit | declares |
|
|
@@ -1412,8 +1414,21 @@ toolkit:
|
|
|
1412
1414
|
| `review` | `read`, `memory_read` (not `forget_outputs`, even under the `forget` layer): what a [participant's](internals/participants.md#turns) turn may call, whatever the session declares (the review boundary, [guardrails](guardrails.md#a-participants-review-boundary)). Reserved: `toolkit.kits.review` is refused ("review is reserved for participants' turns"), and a bundle can't declare it. A session may run on it too, as any kit |
|
|
1413
1415
|
|
|
1414
1416
|
- **Plugin tools** are declared in every toolkit unless its `remove:` names them, and so is `forget_outputs` under
|
|
1415
|
-
the `forget` layer. A `kits:` entry takes `base:` (default `full`), `add:`, `remove:` and `
|
|
1416
|
-
key, a typo or a `tools:` list, is ignored with a warning); it can't reuse a built-in's name
|
|
1417
|
+
the `forget` layer. A `kits:` entry takes `base:` (default `full`), `add:`, `remove:`, `prompt:` and `groups:`
|
|
1418
|
+
(any other key, a typo or a `tools:` list, is ignored with a warning); it can't reuse a built-in's name
|
|
1419
|
+
(`review`'s least of all: it is the participants' boundary). A bundle declares its own with `chi.toolkit`
|
|
1420
|
+
([plugins](plugins.md#chitoolkitname-base-add--remove--prompt-nil-groups-nil)).
|
|
1421
|
+
- **Tool-index groups.** `groups:` selects source groups by name or glob (`chrome`, `db*`); for the MCP bundle
|
|
1422
|
+
each group is a server. Selectors are sorted and deduplicated and match the groups declared when plugins load.
|
|
1423
|
+
An omitted key inherits the base's selectors; `full` and `lean` (and `child`, which is on `lean`) reach all
|
|
1424
|
+
groups, `review` (the participants' kit) reaches none, `groups: ["*"]` matches every group, and `groups: []`
|
|
1425
|
+
reaches none. A kit whose base doesn't resolve (an unknown name, a cycle) falls back to `full`, which reaches
|
|
1426
|
+
every group.
|
|
1427
|
+
A selector that matches no group in that load-time snapshot warns once. `find_tools` and `call_tool` are
|
|
1428
|
+
declared only when the toolkit reaches a source group or has a reachable built-in. Calls naming an unreached
|
|
1429
|
+
group are refused before guardrails and do not start its server. The first-run init wait is unchanged: it can
|
|
1430
|
+
still block the first turn even when its server's group is unreached; `start: eager` and stale-cache refreshes
|
|
1431
|
+
also still start. `/mcp` marks servers outside the current toolkit.
|
|
1417
1432
|
- **A kit's prompt text.** `prompt:` (text, several lines allowed) goes into the system prompt of the sessions on
|
|
1418
1433
|
that kit, verbatim, with no header: after AGENTS.md and the appended files, before the memory indexes. A kit
|
|
1419
1434
|
without one inherits its base's (`audit: {base: terse, add: [web_fetch]}` stays concise); `prompt: ""` clears
|
|
@@ -1422,12 +1437,13 @@ toolkit:
|
|
|
1422
1437
|
fits the kit's work (the coordinator bundle's `coding`: be concise): one that isn't text or is over 2000
|
|
1423
1438
|
characters is left out with a warning, and long text belongs in `append_system_prompt:`. `/toolkit` shows it
|
|
1424
1439
|
(`prompt: …`), `/stats` its kit and size (`kit prompt:`), the web's tools chip in its tooltip.
|
|
1425
|
-
- **Saved at creation,
|
|
1440
|
+
- **Saved at creation, definitions resolve at worker start.** A session stores its toolkit as named
|
|
1426
1441
|
(`"toolkit": {"name": "lean", "add": [], "remove": []}`) when it is created: `toolkit.default`, `toolkit.child`
|
|
1427
1442
|
for a delegate child (or the kit its `delegate` call names), or `chi --toolkit NAME` / `--tool` / `--no-tool` ([cli](cli.md)). Changing `toolkit.default`
|
|
1428
1443
|
doesn't move existing sessions, not even when their worker is woken again; a session from before toolkits is on
|
|
1429
|
-
`full`. A continue and a fork keep the toolkit. `/toolkit` shows and changes it. The
|
|
1430
|
-
|
|
1444
|
+
`full`. A continue and a fork keep the toolkit name. `/toolkit` shows and changes it. The kit's tools, prompt
|
|
1445
|
+
text and group selectors are part of its definition, not the session file: edits reach a saved name at its next
|
|
1446
|
+
worker start.
|
|
1431
1447
|
- **Left out: reachable or removed.** A built-in missing from a kit's base list (`lean` has no `delegate`; a
|
|
1432
1448
|
`kits:` entry on `base: lean` or `child` inherits that) is left out softly: it is **reachable**. `find_tools` searches it as group `chi` (`chi/delegate`, with its inputSchema) beside
|
|
1433
1449
|
the MCP servers' tools, and `call_tool(tool: "chi/delegate", args: {…})` runs it as `delegate` itself, through the
|
|
@@ -1435,10 +1451,10 @@ toolkit:
|
|
|
1435
1451
|
tool in a toolkit's `remove:`, a `--no-tool` or a `/toolkit remove` is **removed**: hard, also after an add, and
|
|
1436
1452
|
call_tool can't reach it (`child` removes `memory_write`, `delegate` and `delegate_result`, so a child on it
|
|
1437
1453
|
starts no children). To keep a built-in away from the model, name it under `remove:`. `find_tools` and `call_tool`
|
|
1438
|
-
are declared when a toolkit has a reachable built-in
|
|
1454
|
+
are declared when a toolkit has a reachable built-in or reaches a source group; `full` with no reachable source
|
|
1439
1455
|
declares neither, and removing them there (`--no-tool call_tool`) leaves nothing out: no prompt line, no chip.
|
|
1440
1456
|
- **On `api: anthropic`, reachable tools are callable by name.** Every tool `call_tool` reaches (the reachable
|
|
1441
|
-
built-ins,
|
|
1457
|
+
built-ins, tools from reached MCP groups as `mcp_<server>_<tool>`) also goes in the request's tools deferred
|
|
1442
1458
|
(`defer_loading`: Messages keeps them out of the model's view until something loads them, at a flat ~100 tokens).
|
|
1443
1459
|
A direct call of one (`list_sessions`, `mcp_fs_read`) runs, under the same guardrails, approvals and rows as
|
|
1444
1460
|
through `call_tool` (an MCP tool's call is `call_tool`'s). A `find_tools` hit **loads** its matches (up to 5):
|
|
@@ -1455,10 +1471,13 @@ toolkit:
|
|
|
1455
1471
|
the route: `Error: delegate isn't declared in this session (toolkit lean); call it with call_tool(tool:
|
|
1456
1472
|
"chi/delegate", args: {…}) (find_tools gives its inputSchema).` A removed tool's: `Error: memory_write isn't
|
|
1457
1473
|
loaded in this session (toolkit child) and can't be loaded from here; only the user can (/toolkit add
|
|
1458
|
-
memory_write).`
|
|
1474
|
+
memory_write).` An MCP tool in an unreached group gets a toolkit-specific refusal before guardrails; a deferred
|
|
1475
|
+
stub for it points to the toolkit instead of suggesting a search. `/toolkit` lists group selectors and the
|
|
1476
|
+
change; the web's toolkit chip says its source-group scope in the tooltip.
|
|
1459
1477
|
- **Applied at a turn's start.** A change (`/toolkit`, a plugin's `ctx.use_toolkit`) is saved at once and the next
|
|
1460
|
-
turn declares it, with its kit's prompt text; once the session has a model reply, `/toolkit`
|
|
1461
|
-
("Changes that restart the prompt cache"), also
|
|
1478
|
+
turn declares it, with its kit's prompt text and group filter; once the session has a model reply, `/toolkit`
|
|
1479
|
+
asks first ("Changes that restart the prompt cache"), also when only the group selectors change and the
|
|
1480
|
+
declared tools stay the same.
|
|
1462
1481
|
- **A name that doesn't resolve** (a typo, a bundle that was uninstalled) declares every tool for that process,
|
|
1463
1482
|
with one `toolkit>` warning at the first turn; the saved name stays, so the bundle's return brings it back.
|
|
1464
1483
|
- **Versions.** A toolkit's definition (its tools and its prompt text) can change with chi or a bundle, which
|
|
@@ -1608,7 +1627,11 @@ web:
|
|
|
1608
1627
|
|
|
1609
1628
|
Experimental. `chi web` binds 127.0.0.1 only; to use it from elsewhere over https, put it behind a proxy that does TLS
|
|
1610
1629
|
and, usually, a sign-in (oauth2-proxy, nginx `auth_request`, Caddy `forward_auth`, Cloudflare Access, Tailscale
|
|
1611
|
-
Serve; a work pod forwarding `https://<port>.<pod>.<domain>` through oauth2-proxy) and tell chi web its public URL
|
|
1630
|
+
Serve; a work pod forwarding `https://<port>.<pod>.<domain>` through oauth2-proxy) and tell chi web its public URL.
|
|
1631
|
+
The proxy must run **on the same machine** as chi web: only a request from a loopback peer (127.0.0.0/8, ::1) counts
|
|
1632
|
+
as one through the proxy, with its identity header. A proxy on another host, reaching chi web on its LAN address
|
|
1633
|
+
(`web.host: lan`), isn't supported: its requests are LAN requests (the LAN token; a link bound to an account refused),
|
|
1634
|
+
since any LAN device could send the same headers.
|
|
1612
1635
|
|
|
1613
1636
|
```yaml
|
|
1614
1637
|
web:
|
|
@@ -1649,23 +1672,21 @@ What changes once `web.public_url` is set:
|
|
|
1649
1672
|
to the proxy, but it outlives the sign-in's redirects: the sign-in page sits at that URL (its scripts can read it)
|
|
1650
1673
|
and it stays in the browser's history, which is why it holds a code that works once. The page trades the code for
|
|
1651
1674
|
the cookie (`POST /api/token`) and drops it from the URL; a page that already has a session asks first, since the
|
|
1652
|
-
trade replaces it.
|
|
1653
|
-
a link can spend it (`chi web` again, or a new invite, makes another)
|
|
1675
|
+
trade replaces it. An owner's code (or an unbound participant's) presented with the wrong identity is used up and
|
|
1676
|
+
gives nothing, so whoever already holds a link can spend it (`chi web` again, or a new invite, makes another); a
|
|
1677
|
+
link bound to one account ([Inviting colleagues](#inviting-colleagues)) is refused to anyone else and keeps its code. The 401 page's paste box posts the token in
|
|
1654
1678
|
the request's body, never in the URL.
|
|
1655
1679
|
- When the proxy's sign-in or chi's token ends, the page says so: it reloads once (the proxy's sign-in brings you
|
|
1656
1680
|
back), and within a minute of that shows "Signed out … Reload" instead of a list that quietly stopped updating. Its
|
|
1657
1681
|
API calls ask for JSON, so oauth2-proxy answers an ended sign-in with 401 instead of a redirect.
|
|
1658
|
-
- A session's Bridge (its worker's own port
|
|
1659
|
-
|
|
1660
|
-
|
|
1682
|
+
- A session's Bridge (its worker's own port) answers only a request that names its secret, which only chi's own
|
|
1683
|
+
clients on this machine read (from the worker's 0600 `bridge.json`): a proxy or tunnel that forwards every local
|
|
1684
|
+
port reaches the port but gets 403. It also refuses a request that carries a proxy header such as
|
|
1685
|
+
`X-Forwarded-For` or `Forwarded` (403 `proxied`).
|
|
1661
1686
|
- `chi --remote` to a chi web behind a sign-in proxy isn't supported yet.
|
|
1662
1687
|
SSH to that machine works instead: forward its port and add it as a remote
|
|
1663
1688
|
([Through an SSH tunnel](cli.md#through-an-ssh-tunnel)).
|
|
1664
1689
|
|
|
1665
|
-
> **Warning.** Until Bridges get a secret of their own (planned), don't run chi web behind a proxy or tunnel that can
|
|
1666
|
-
> reach any local port, such as a pod forwarding every `https://<port>.<pod>…`, unless it adds `X-Forwarded-For` or
|
|
1667
|
-
> `Forwarded` to every request; and never one that also lets other people through its sign-in.
|
|
1668
|
-
|
|
1669
1690
|
Which header to trust: only one the proxy **sets** on every signed-in request **and replaces** when a client sends
|
|
1670
1691
|
it. Check before setting `identity_header`: run an echo on a free port behind the same proxy (`python3 -c` with
|
|
1671
1692
|
`http.server`, or any header-echo server), open it signed in, and send your own copies (`curl -H 'X-Forwarded-Email:
|
|
@@ -1685,6 +1706,33 @@ one.
|
|
|
1685
1706
|
|
|
1686
1707
|
Keep the state dir (`web-token`, the grants) out of any folder others can read, such as a team's shared folder.
|
|
1687
1708
|
|
|
1709
|
+
### Inviting colleagues
|
|
1710
|
+
|
|
1711
|
+
Behind a proxy whose sign-in lets colleagues in (a work pod behind Okta and oauth2-proxy, a tailnet you share), give
|
|
1712
|
+
each reviewer a link bound to their account:
|
|
1713
|
+
|
|
1714
|
+
```sh
|
|
1715
|
+
chi share 3f9a1c2e --name Alex --identity alex@corp.example
|
|
1716
|
+
# p_7c01d2aa Alex (human, reviewer) · session 3f9a1c2e · expires … · cap $2.00 · bound to alex@corp.example
|
|
1717
|
+
# https://4567.pod.example.com/#code=chc_…&s=3f9a1c2e-…
|
|
1718
|
+
```
|
|
1719
|
+
|
|
1720
|
+
- It needs `web.proxy.identity_header` (or `chi web --tunnel tailscale`): the link opens only through the proxy,
|
|
1721
|
+
signed in as that email (any case). Opened by someone else (forwarded, pasted in the wrong chat), on the LAN or on
|
|
1722
|
+
this machine, it says "This review link is for another account …", and its one-time code stays: Alex can still open
|
|
1723
|
+
it. The refusal is in the session's participants audit with the identity that tried. Without an identity header
|
|
1724
|
+
`--identity` is refused: the link could never open.
|
|
1725
|
+
- Run only chi 0.109 or newer on the same state dir: an older chi rewriting the grants store drops the binding and
|
|
1726
|
+
makes every bound link open for anyone the proxy lets in.
|
|
1727
|
+
- An unbound link there opens for anyone the proxy lets in who has it; `chi share` warns when you make one. The web's
|
|
1728
|
+
Invite has an email field behind such a proxy (the name defaults to the email's first part).
|
|
1729
|
+
- The identity is what the trusted header carries: the work email through Okta, the Tailscale login (`alex@github`)
|
|
1730
|
+
through Tailscale Serve. Bind to the one your proxy sends. A tagged Tailscale device has no user login, so a bound
|
|
1731
|
+
link refuses it.
|
|
1732
|
+
- The header is the proxy's word only for requests from this machine (the proxy is local; a LAN device's copy is
|
|
1733
|
+
ignored): any program on this machine that reaches chi web's port directly can send it too. On a machine or pod only you use, that is you already; on a host
|
|
1734
|
+
other users share, a bound link's identity isn't a boundary against them.
|
|
1735
|
+
|
|
1688
1736
|
### Tailscale Serve (your tailnet)
|
|
1689
1737
|
|
|
1690
1738
|
chi web on your phone away from the home Wi-Fi: [Tailscale](https://tailscale.com) Serve gives this Mac an https
|
|
@@ -1798,6 +1846,9 @@ What it needs:
|
|
|
1798
1846
|
|
|
1799
1847
|
Turning it on, on the phone: open the `Proxied:` link in Safari → Share → **Add to Home Screen** → open the **Chi**
|
|
1800
1848
|
app → tap the **bell** at the top → turn on **This device** in the sheet → Allow. A test notification arrives at once.
|
|
1849
|
+
The app opens on the project chi web starts in (`web.start_dir`; the header's `all` shows every project), but its
|
|
1850
|
+
notifications are every project's: opening it closes the ones whose question or card is no longer open and sets its
|
|
1851
|
+
badge for every project, whichever project the page shows.
|
|
1801
1852
|
In a browser tab the bell stays the tab's own notifications (while the tab is in the background), as before.
|
|
1802
1853
|
|
|
1803
1854
|
**The devices** (the bell's sheet in the app; a bottom sheet on the phone): this device first, with its switch (off
|
|
@@ -1809,6 +1860,8 @@ are never sent to; `chi web push rm ID` removes them. From the Mac: `chi web pus
|
|
|
1809
1860
|
shows a recent delivery (Apple keeps accepting pushes for it): if one stops getting notifications, switch it off and on
|
|
1810
1861
|
there; the new subscription replaces the dead one.
|
|
1811
1862
|
|
|
1863
|
+
- **What a tap opens**: the session, in its own project's view (the all view for a session in no repo), whichever
|
|
1864
|
+
project the app was showing; an open app window moves there.
|
|
1812
1865
|
- **What is sent**: the session's name and the reason ("needs approval", "needs an answer", "hit its step limit",
|
|
1813
1866
|
"needs you", "turn failed", "done in N s"), never the question or the command, as the tab's notification on the
|
|
1814
1867
|
Mac. The session's name shows on the lock screen and the Watch.
|
|
@@ -1970,6 +2023,7 @@ column says so, a `--kebab-name` flag. The maps (`hosts:`, `models:`,
|
|
|
1970
2023
|
| `web.working_set` | `true` | yes | The stage view's working set: the files, memories and URLs a turn brought into the model's context, in a rail in the stage, a footprint line under each finished turn and a `set N` chip in place of the mem chip; `false` brings the mem chip back. `?working_set=1` or `?working_set=0` on the page URL overrides it for that page load; the turn view ignores it. See [CLI: the working set](cli.md#the-working-set). |
|
|
1971
2024
|
| `web.annotate_presets` | `Agreed\|Could you please elaborate?` | yes | Quick replies next to Annotate in `chi web`, `\|`-separated (a YAML list works too); `""` in the file or on the CLI leaves only Annotate (an empty env value means the default). See [CLI](cli.md#web-annotate-presets). |
|
|
1972
2025
|
| `web.editor` | `vscode` | yes | What a file ref in `chi web` (a file tool row's title, a diff line) opens, for a viewer on this machine: a preset (`vscode`, `vscode-insiders`, `cursor`, `zed`) or a URL template with `{path}` and an optional `{line}`; `none` turns refs off. See [Opening files from chi web](#opening-files-from-chi-web). |
|
|
2026
|
+
| `web.start_dir` | the folder `chi web` runs in | | The folder `chi web` starts in (`chi web --start-dir DIR` for one run): its page (the printed link, a bare `/`, the LAN link and QR code, the Home Screen app) opens on that folder's project, outside a repo on every session, and a new session from the all view starts there; `/?scope=all` is every project. `~` and a relative path work; one that isn't a folder stops `chi web` from starting. `chi web` never changes its own folder. See [CLI](cli.md). |
|
|
1973
2027
|
| `web.participants.expires` | `7d` | | How long a participant's link lasts unless `chi share --expires` says otherwise: `Nh` or `Nd`, 1h to 30d. See [participants](internals/participants.md). |
|
|
1974
2028
|
| `web.participants.cap` | `2.0` | | What a participant's turns, and the side calls they cause, may cost you in dollars unless `chi share --cap` says otherwise ($0 to $1000); at the cap their next comment is refused (429 `cap_reached`) and you get a notice. `chi share cap ID P DOLLARS` changes one grant's. |
|
|
1975
2029
|
| `web.participants.queue` | `3` | | How many of a participant's turns may wait behind their running one (0 to 20); one more is refused (429 `too_many_turns`). |
|