samagotchi 0.152.0 → 0.153.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 +18 -1
- data/docs/bundle-authoring.md +1 -1
- data/docs/bundles.md +84 -0
- data/docs/cli.md +3 -3
- data/docs/configuration.md +19 -14
- data/docs/guardrails.md +7 -5
- data/docs/hooks.md +4 -3
- data/docs/internals/anthropic.md +3 -3
- data/docs/internals/attached-context.md +3 -0
- data/docs/internals/engine.md +9 -5
- data/docs/internals/participants.md +1 -1
- data/docs/internals/prompt-caching.md +7 -3
- data/docs/internals/web-frontend.md +20 -17
- data/docs/plugins.md +29 -15
- data/docs/releasing.md +1 -1
- data/docs/sessions.md +1 -1
- data/docs/testing.md +2 -2
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/bundles/web-search/manifest.yml +13 -0
- data/lib/samagotchi/bundles/web-search/plugin.rb +174 -0
- data/lib/samagotchi/engine.rb +10 -5
- data/lib/samagotchi/guardrails/review_boundary.rb +4 -3
- data/lib/samagotchi/llm_context_stale.rb +2 -2
- data/lib/samagotchi/message_parts.rb +3 -2
- data/lib/samagotchi/toolkit.rb +8 -5
- data/lib/samagotchi/toolkit_binding.rb +30 -5
- data/lib/samagotchi/toolkit_report_command.rb +9 -5
- data/lib/samagotchi/toolkit_suggest.rb +12 -6
- data/lib/samagotchi/tools/builtin_calls.rb +14 -10
- data/lib/samagotchi/tools/deferred_tools.rb +2 -2
- data/lib/samagotchi/tools/registry.rb +44 -20
- data/lib/samagotchi/tools/tool_index.rb +27 -21
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/public/app.js +61 -357
- data/lib/samagotchi/web/public/composer_mode.js +110 -0
- data/lib/samagotchi/web/public/owner_claim.js +81 -0
- data/lib/samagotchi/web/public/owner_verify.js +112 -0
- data/lib/samagotchi/web/public/session_fields.js +3 -3
- data/lib/samagotchi/web/public/strip_grip.js +3 -3
- data/lib/samagotchi/web/public/strip_hidden.js +65 -0
- data/lib/samagotchi/web/public/version_notices.js +132 -0
- data/lib/samagotchi/worker.rb +6 -0
- metadata +8 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e9dd8da2f6f95806fccc0df5dad4844525947650b92589461adcb70b8bcdf2d7
|
|
4
|
+
data.tar.gz: a1457568c9d119b661d025be9e39989a33593666d668b328c3cd8528e451aa55
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6ed8fbbd7439b3ff95cb6eb251edf5635812cff0579928469a575bbb4bdc190e14ff6959a2f49eb627f127f711c1782c62c99379a2130433e057d0a55dd2e359
|
|
7
|
+
data.tar.gz: 82b22635aad92e8edb672e61ac0eed1c461c1c81330bfde64ac09cd8c969183e37f9420495cbb6b0fbe0624f9a552a17af9cb70a2707b0f483084d25fb10d936
|
data/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,22 @@ and commands may change between minor versions. How releases are made:
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.153.0] - 2026-11-06
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- A `web-search` bundle (`chi bundle install web-search`, in no profile): a `web_search` tool that searches the web through your own SearXNG and returns titles, URLs and snippets for `web_fetch` to read. It is declared on `full` and kits on `base: full`; on kits built on `lean` or `child` it is reachable (`find_tools` finds `chi/web_search`, `call_tool` runs it); `add: [web_search]` in a kit declares it. [docs/bundles.md](docs/bundles.md#the-web-search-bundle)
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- A bundle's tools (`chi.tool`) follow a toolkit's tool list as built-ins do: `full`, the kits on `base: full` (the `kits:` default) and a kit's `add:` declare them, any other kit leaves them reachable through `find_tools` / `call_tool` (deferred on `api: anthropic`), and `remove:` removes them. [docs/configuration.md](docs/configuration.md#toolkits)
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- An attached context change that arrives while the worker's leaving pass is already in flight is saved as a note before the worker stops, instead of being dropped with its poller.
|
|
24
|
+
|
|
25
|
+
Update with `chi update` (no installed bundle's version moved; `chi bundle install web-search` adds the new one).
|
|
26
|
+
|
|
11
27
|
## [0.152.0] - 2026-11-06
|
|
12
28
|
|
|
13
29
|
### Added
|
|
@@ -5717,7 +5733,8 @@ and long-lived sessions.
|
|
|
5717
5733
|
- A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
|
|
5718
5734
|
hotkey panel that send selected text or the clipboard to your sessions.
|
|
5719
5735
|
|
|
5720
|
-
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.
|
|
5736
|
+
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.153.0...HEAD
|
|
5737
|
+
[0.153.0]: https://github.com/dm1try/samagotchi/compare/v0.152.0...v0.153.0
|
|
5721
5738
|
[0.152.0]: https://github.com/dm1try/samagotchi/compare/v0.151.0...v0.152.0
|
|
5722
5739
|
[0.151.0]: https://github.com/dm1try/samagotchi/compare/v0.150.0...v0.151.0
|
|
5723
5740
|
[0.150.0]: https://github.com/dm1try/samagotchi/compare/v0.149.0...v0.150.0
|
data/docs/bundle-authoring.md
CHANGED
|
@@ -119,7 +119,7 @@ end
|
|
|
119
119
|
`questions` record effects; `bundle.answer` / `model_answers` provide `ask_user` / `ask_model` answers.
|
|
120
120
|
`bundle.judgments` answers `ctx.judge` and records its asks and runs (each judgment is on in the kit, off in chi until the user sets it);
|
|
121
121
|
`bundle.http.stub` stubs `ctx.http`; `bundle.question_asked(text, question:)` fires `:question_asked` and returns
|
|
122
|
-
the refs; `sandbox.config`, `sandbox.fake_executable` and `bundle.install` cover setup.
|
|
122
|
+
the refs; `sandbox.config`, `sandbox.fake_executable` and `bundle.install` cover setup. The kit's `ctx.declared_tools` is every tool, as on `full`; in chi a `chi.tool` is declared only on `full` and by a kit's `add:`, and on `lean` or `child` the model reaches it with `find_tools` (`chi/<name>`) and `call_tool` ([Toolkits](configuration.md#toolkits)), so `bundle.set(declared_tools: %w[read])` tests that case and the tool's description should be easy to search.
|
|
123
123
|
The full list is in [plugins.md, Testing a bundle](plugins.md#testing-a-bundle-experimental). The kit is
|
|
124
124
|
experimental (it may change before chi 1.0). A failing matcher that meets `ENV` prints `#<the process ENV, contents not shown>`, not your keys. If a test needs a fake of chi's own (a stub of a `Samagotchi::`
|
|
125
125
|
class, a hand-built `ctx`), that is a gap in the kit: say so to the user rather than reaching into chi.
|
data/docs/bundles.md
CHANGED
|
@@ -225,6 +225,90 @@ bundles:
|
|
|
225
225
|
tools, or with different args, stay distinct, since the tool is in the
|
|
226
226
|
args).
|
|
227
227
|
|
|
228
|
+
## The web-search bundle
|
|
229
|
+
|
|
230
|
+
`chi bundle install web-search` installs the bundle shipped with chi (`lib/samagotchi/bundles/web-search/plugin.rb`).
|
|
231
|
+
No profile includes it, since it needs a running [SearXNG](https://docs.searxng.org), so `chi update` lists it as
|
|
232
|
+
not installed until you install it. It gives the model one tool, `web_search`, which asks your SearXNG through its
|
|
233
|
+
JSON API. `web_fetch` fetches a URL but can't find one; `web_search` finds them, and the model then reads the ones it
|
|
234
|
+
wants with `web_fetch`. It is an ordinary plugin tool, so it works on every host and loop.
|
|
235
|
+
|
|
236
|
+
- **The tool:** `web_search(query, time_range?, page?)`. `time_range` is `day`, `week`, `month` or `year`; `page`
|
|
237
|
+
starts at 1. It returns plain text: a header, the first answer SearXNG gives (if any), a numbered list of results
|
|
238
|
+
(title, URL, and a snippet of at most 300 characters), and SearXNG's suggestions:
|
|
239
|
+
|
|
240
|
+
```
|
|
241
|
+
SearXNG results for "ruby prism parser" (page 1):
|
|
242
|
+
1. Prism — Ruby parser
|
|
243
|
+
https://github.com/ruby/prism
|
|
244
|
+
A portable, error-tolerant Ruby parser…
|
|
245
|
+
Suggestions: ruby prism gem, prism vs parser
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
With nothing found it says `No results for "<query>".` and names the engines that failed. A SearXNG that can't be
|
|
249
|
+
reached, a 403 (JSON not enabled), a 429 (its limiter) or an answer that isn't JSON each give an `Error:` line that
|
|
250
|
+
says what to fix.
|
|
251
|
+
|
|
252
|
+
```yaml
|
|
253
|
+
# config.yml
|
|
254
|
+
bundles:
|
|
255
|
+
web-search:
|
|
256
|
+
url: http://127.0.0.1:8888 # the SearXNG root
|
|
257
|
+
max_results: 8 # results listed, 1..20
|
|
258
|
+
timeout: 10 # seconds per read
|
|
259
|
+
# optional, sent only when set:
|
|
260
|
+
language: en
|
|
261
|
+
safesearch: 0 # 0, 1 or 2
|
|
262
|
+
categories: general # comma-separated
|
|
263
|
+
engines: duckduckgo,brave # comma-separated
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
| key | default | |
|
|
267
|
+
|---|---|---|
|
|
268
|
+
| `url` | `http://127.0.0.1:8888` | the SearXNG root, `http` or `https` with a host; a trailing `/` is fine |
|
|
269
|
+
| `max_results` | `8` | clamped to 1..20 |
|
|
270
|
+
| `timeout` | `10` | seconds; a value that isn't a positive number uses the default |
|
|
271
|
+
| `language` | unset | unset (the default): SearXNG picks the language from the query; set it (e.g. `en`) to force one; dates and some results may follow your region |
|
|
272
|
+
| `safesearch` | unset | `0`, `1` or `2` |
|
|
273
|
+
| `categories`, `engines` | unset | comma-separated. SearXNG silently drops names it doesn't know, so a typo gives its defaults |
|
|
274
|
+
|
|
275
|
+
- **Privacy:** the query goes to your SearXNG and on to the engines it is configured with. No guardrail rule asks
|
|
276
|
+
about it by default; add one (`tool: web_search`, `verdict: ask`) if you want to approve each search. A review
|
|
277
|
+
turn can't call it.
|
|
278
|
+
- **Toolkits:** the tool follows a kit's list like a built-in ([Toolkits](configuration.md#toolkits)). `full` (and
|
|
279
|
+
any kit on `base: full`) declares it (about 150 tokens per request); on `lean`, `child` and the kits built on them
|
|
280
|
+
it is reachable, not declared: the model finds it with `find_tools` (`chi/web_search`) and calls it with
|
|
281
|
+
`call_tool` (on `api: anthropic` it goes deferred and is called by name). To declare it on such a kit, `add:` it
|
|
282
|
+
(or `/toolkit add web_search` in a session); to keep it away entirely, `remove:` it (or `/toolkit remove
|
|
283
|
+
web_search`).
|
|
284
|
+
|
|
285
|
+
```yaml
|
|
286
|
+
toolkit:
|
|
287
|
+
default: search
|
|
288
|
+
kits:
|
|
289
|
+
search: { base: lean, add: [web_search] }
|
|
290
|
+
quiet: { base: full, remove: [web_search] }
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Running SearXNG locally
|
|
294
|
+
|
|
295
|
+
```sh
|
|
296
|
+
mkdir -p searxng
|
|
297
|
+
cat > searxng/settings.yml <<'YAML'
|
|
298
|
+
use_default_settings: true
|
|
299
|
+
server:
|
|
300
|
+
secret_key: "change-me-to-a-random-string" # openssl rand -hex 32
|
|
301
|
+
limiter: false # a local instance: no rate limiting (else 429s)
|
|
302
|
+
search:
|
|
303
|
+
formats: [html, json] # json is off by default (else 403)
|
|
304
|
+
YAML
|
|
305
|
+
docker run -d --name searxng -p 127.0.0.1:8888:8080 -v ./searxng:/etc/searxng searxng/searxng
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
The port is bound to loopback, not the LAN. Under colima, run it from a folder in your home directory: colima
|
|
309
|
+
shares only that with its VM by default, and elsewhere SearXNG starts on its own default settings (JSON off).
|
|
310
|
+
`curl 'http://127.0.0.1:8888/search?q=ruby&format=json'` should answer JSON.
|
|
311
|
+
|
|
228
312
|
## The loop-guard bundle
|
|
229
313
|
|
|
230
314
|
`chi bundle install loop-guard` installs the bundle shipped with chi (`chi
|
data/docs/cli.md
CHANGED
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
- `chi guardrails replay [--dry-run] [--threshold T] [--model M]` — seed System 1's record from the approvals this machine's sessions already answered: each ask is judged by the configured model and written to `guardrails/judgments.jsonl` with the answer the user gave, then a per-rule table prints (asks, allowed, denied, would-be auto-allows, false allows, asks with no judgment). A parent agent's answer is not the user's: only its deny is replayed, and only where the parent could have allowed. `--dry-run` judges without writing, `--threshold` overrides `guardrails.system_one.threshold` for the table, `--model` the configured model for this run. Exit 2 when `guardrails.system_one.mode` is off or no `host` is set; a usage error exits 2 (see [System 1](guardrails.md#system-1-experimental))
|
|
42
42
|
- `chi guardrails rule <id>` — print exactly `asks` when that rule is loaded, not disabled and votes `ask` in this `guardrails.mode` for every model; any other line says why not (no such rule, disabled, another mode, it denies, for some models only, rules that failed to load, `guardrails.enabled: false`). Exit 0 either way, 2 for a usage error. It reads config.yml and the installed bundles as a worker does (`GuardrailWiring#rules`) and takes the rule's id, never a command; the [coordinator skill](bundles.md#the-coordinator-bundle) reads it before it merges without asking
|
|
43
43
|
- `chi judgments ls` / `chi judgments report NAME [--days N]` — what [System 1's saved runs](system-one.md) hold: each judgment's digests, runs, labels, failures, models and size; one judgment's counts, p histogram and labels by value. Reads only; local only; `--days` must be a positive whole number.
|
|
44
|
-
- `chi toolkit report [--days N]` — grade toolkit suggestions: the kit on mode would have run (the suggested kit when it would have applied it, else the default; an on-mode line's `did` says what it ran) against the tools the session called from that turn on (only that turn when the toolkit was chosen since), `call_tool` targets included, for both the chat and the raw-prompt loop. Each is *covers*, *short of a tool* (naming the tools), *wider than needed* (a narrower kit offered in the same ask would also have covered) or *ungradable* (calls it can't read back); then a per-kit tally with what on mode would have done. Includes archived sessions and caps the report at 14 days and 500 sessions. Reads `toolkit_suggestions.jsonl` (each ask logs every offered kit's rules, expanded against the called tools here); it never runs the model or changes a session. Local only; `--days` must be a positive whole number.
|
|
44
|
+
- `chi toolkit report [--days N]` — grade toolkit suggestions: the kit on mode would have run (the suggested kit when it would have applied it, else the default; an on-mode line's `did` says what it ran) against the tools the session called from that turn on (only that turn when the toolkit was chosen since), `call_tool` targets included, for both the chat and the raw-prompt loop. Each is *covers*, *short of a tool* (naming the tools; a built-in or plugin tool the kit left reachable but didn't declare counts), *wider than needed* (a narrower kit offered in the same ask would also have covered) or *ungradable* (calls it can't read back); then a per-kit tally with what on mode would have done. Includes archived sessions and caps the report at 14 days and 500 sessions. Reads `toolkit_suggestions.jsonl` (each ask logs every offered kit's rules, expanded against the called tools here); it never runs the model or changes a session. Local only; `--days` must be a positive whole number.
|
|
45
45
|
|
|
46
46
|
- `chi bundle try PATH -p TEXT [options]` — install a bundle in a temporary, filtered sandbox, run one real model turn,
|
|
47
47
|
and print its trace (see [Trying a bundle](#trying-a-bundle)).
|
|
@@ -451,7 +451,7 @@ invalid option (exit 1).
|
|
|
451
451
|
| `--dir DIR` | With `--remote`: a new session's folder on that machine, its absolute path (required for a new session there). Without `--remote` it exits 2: cd there instead. |
|
|
452
452
|
| `--model NAME` | Use this model for the run (overrides the configured default and a resumed session's model). |
|
|
453
453
|
| `--thinking LEVEL` | This session's own thinking level: `off`, `low`, `medium` or `high`, saved in the session and first in the order (`default` unsets it). On `--resume` or `--attach` it is set at once, through a `/thinking` on an attached worker. With `chi web` it is instead the default for the sessions its workers run, below a session's own (as env `SAMAGOTCHI_THINKING_LEVEL` is). See "Thinking" in configuration.md. |
|
|
454
|
-
| `--toolkit NAME`, `--tool NAME`, `--no-tool NAME` | A new session's toolkit, the tools its prompts declare: `full` (every built-in), `lean`, `child`, one of config's `toolkit.kits` or a bundle's (without `--toolkit`: config's `toolkit.default`), with tools declared on top of it (`--tool`) or left out (`--no-tool`); both repeatable, a comma list too. Saved in the session as given; a respawned worker, a fork and a continue keep it. Ignored with `--attach`/`--resume` (one line, as `--memory`); `/toolkit` changes it. Not on `chi web` (config's `toolkit.default` covers its sessions). See [Toolkits](configuration.md#toolkits). |
|
|
454
|
+
| `--toolkit NAME`, `--tool NAME`, `--no-tool NAME` | A new session's toolkit, the tools its prompts declare: `full` (every built-in and plugin tool), `lean`, `child`, one of config's `toolkit.kits` or a bundle's (without `--toolkit`: config's `toolkit.default`), with tools declared on top of it (`--tool`) or left out (`--no-tool`); both repeatable, a comma list too. Saved in the session as given; a respawned worker, a fork and a continue keep it. Ignored with `--attach`/`--resume` (one line, as `--memory`); `/toolkit` changes it. Not on `chi web` (config's `toolkit.default` covers its sessions). See [Toolkits](configuration.md#toolkits). |
|
|
455
455
|
| `--parent-answers LEVEL` | A new session's own `guardrails.parent_answers` (`hard`, `standard` or `soft`): what a parent agent may answer on its questions. It may only tighten config.yml's: the stricter of the two applies, and a looser one is saved as given with a warning (`--parent-answers soft is looser than config.yml's guardrails.parent_answers: standard; standard applies`). Saved in the session; a respawned worker, a fork and a continue keep it, a `delegate` child takes its parent's. Ignored with `--attach`/`--resume` (one line, as `--toolkit`); refused with `chi web`. Only you loosen it: `/parent-answers` in the terminal or the web. See [Guardrails](guardrails.md#answers-from-a-parent-agent). |
|
|
456
456
|
| `--set KEY=VALUE` | A setting for a new session (repeatable; a later one of the same key wins). A core key a session may set (`recap.enabled`, `recap.sentences`) is typed by the key and goes first, over the CLI, env and config.yml: `--set recap.enabled=false` turns the idle recap off for this session; a value it doesn't take (`recap.enabled=maybe`) or a key no session sets (`guardrails.mode`) is an error, exit 2 (see [Per-session settings](configuration.md#per-session-settings)). Any other name is a bundle's `BUNDLE.KEY`, laid over config.yml's `bundles: <bundle>:` in what the bundle's plugin reads (`ctx.settings`): `--set cost-cap.cap=3`. The value is typed simply: an integer (`3`, `-1`) or a decimal (`2.5`, `.5`, `1e1`) is a number, `true`, `yes` and `on` are true, `false`, `no` and `off` are false (any case, as in config.yml), anything else stays the text as given (`010`, `0x10`, `3:30`, `y`, `null`), and so does every value of a key the manifest types `string`. For cost-cap, `0`, a negative number or `off` is no cap. Only a key the installed bundle's manifest lists in `session_settings:` is taken, with a value of the type it gives the key: a bundle that isn't installed (or whose plugin doesn't load), a key it doesn't list (a record installed before the bundle listed it says to run `chi update` or `chi bundle upgrade NAME`), a value of another type (`--set cost-cap.cap=five`), or another shape (`--set foo`, `--set a.b.c=1`) is an error naming what it takes, exit 2, nothing started. Saved in the session; a respawned worker, a fork and a continue keep it, a delegate child doesn't. Ignored with `--attach`/`--resume` (one line, as `--toolkit`); refused with `chi web`. See [Per-session settings](plugins.md#per-session-settings). |
|
|
457
457
|
| `--profile NAME` | Prompt profile (`qwen36` or `gemma4`) for every model in this run, over config and the server's template (same as `--model-profile`, env `SAMAGOTCHI_MODEL_PROFILE`). See "Prompt profile" in configuration.md. |
|
|
@@ -888,7 +888,7 @@ line, the web in the session bar's ⚙ panel (`⚙ reasoning all` once the sessi
|
|
|
888
888
|
|
|
889
889
|
`/toolkit` lists the tools the next turn declares (each with its estimated
|
|
890
890
|
tokens), the session's toolkit and the tools it leaves out: `reachable
|
|
891
|
-
(find_tools): …` (built-ins the model reaches with find_tools and call_tool)
|
|
891
|
+
(find_tools): …` (built-ins and plugin tools the model reaches with find_tools and call_tool)
|
|
892
892
|
and `removed: …` (only `/toolkit add` brings one back), or `not loaded: …` when
|
|
893
893
|
the toolkit removes call_tool, and its kit's prompt text when it has one (`prompt: <first line> (+N lines, ~T
|
|
894
894
|
tokens)`). `/toolkit remove TOOL` is hard: a tool the
|
data/docs/configuration.md
CHANGED
|
@@ -1501,17 +1501,18 @@ toolkit:
|
|
|
1501
1501
|
|
|
1502
1502
|
| toolkit | declares |
|
|
1503
1503
|
|---|---|
|
|
1504
|
-
| `full` | every built-in; the default |
|
|
1504
|
+
| `full` | every built-in and every plugin tool (a bundle's `chi.tool`, such as `web_search`); the default |
|
|
1505
1505
|
| `lean` | `execute`, `read`, `write`, `edit`, `memory_read`, `memory_write`, `task_create`, `task_wait`, `task_get`, `task_list`, `task_stop`, `ask_user_question`, `web_fetch`, `context_read` |
|
|
1506
1506
|
| `child` | `lean` plus `send_note`, without `memory_write`, `delegate`, `delegate_result` |
|
|
1507
1507
|
| `coordinator` | `lean` plus `delegate`, `delegate_result`, `list_sessions`, `objectives` (declared by the coordinator bundle) |
|
|
1508
1508
|
| `coding` | `child`, with the prompt text `- Be concise in your responses` / `- Show file paths clearly when working with files` (declared by the coordinator bundle, for build and fix children) |
|
|
1509
1509
|
| `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 |
|
|
1510
1510
|
|
|
1511
|
-
- **Plugin tools**
|
|
1512
|
-
|
|
1513
|
-
`
|
|
1514
|
-
|
|
1511
|
+
- **Plugin tools** (a bundle's `chi.tool`, such as web-search's `web_search`) follow a kit's list as built-ins do:
|
|
1512
|
+
`full` (and every kit on `base: full`, the `kits:` default) declares them, another kit leaves them **reachable**
|
|
1513
|
+
(below) unless its `remove:` names them, and `add:` declares one (`search: { base: lean, add: [web_search] }`).
|
|
1514
|
+
`forget_outputs` is declared in every toolkit under the `forget` layer, unless removed. A `kits:` entry takes
|
|
1515
|
+
`base:` (default `full`), `add:`, `remove:`, `prompt:`, `groups:` and `description:` (any other key, a typo or a `tools:` list, is ignored with a warning); it can't reuse a built-in's name
|
|
1515
1516
|
(`review`'s least of all: it is the participants' boundary). A bundle declares its own with `chi.toolkit`
|
|
1516
1517
|
([plugins](plugins.md#chitoolkitname-base-add--remove--prompt-nil-groups-nil-description-nil)).
|
|
1517
1518
|
`description:` is optional classifier input, up to 300 characters; it is not included in the session prompt.
|
|
@@ -1565,7 +1566,7 @@ toolkit:
|
|
|
1565
1566
|
reaches none. A kit whose base doesn't resolve (an unknown name, a cycle) falls back to `full`, which reaches
|
|
1566
1567
|
every group.
|
|
1567
1568
|
A selector that matches no group in that load-time snapshot warns once. `find_tools` and `call_tool` are
|
|
1568
|
-
declared only when the toolkit reaches a source group or has a reachable built-in. Calls naming an unreached
|
|
1569
|
+
declared only when the toolkit reaches a source group or has a reachable tool (a built-in or a plugin tool). Calls naming an unreached
|
|
1569
1570
|
group are refused before guardrails and do not start its server. The first-run init wait is unchanged: it can
|
|
1570
1571
|
still block the first turn even when its server's group is unreached; `start: eager` and stale-cache refreshes
|
|
1571
1572
|
also still start. `/mcp` marks servers outside the current toolkit.
|
|
@@ -1599,17 +1600,21 @@ toolkit:
|
|
|
1599
1600
|
them with `/toolkit`. The configured default is always listed. A pick closes the list and shows a `tools: lean`
|
|
1600
1601
|
chip on the row (a dot on 🧰 on a phone); it is for that create only, never remembered. A review session is
|
|
1601
1602
|
always `chosen`: with review files set the list says it starts on the default unless you pick a kit.
|
|
1602
|
-
- **Left out: reachable or removed.** A built-in missing from a kit's base list (`lean` has no
|
|
1603
|
-
`kits:` entry on `base: lean` or `child` inherits that) is left out softly: it
|
|
1604
|
-
|
|
1605
|
-
|
|
1603
|
+
- **Left out: reachable or removed.** A built-in or plugin tool missing from a kit's base list (`lean` has no
|
|
1604
|
+
`delegate` and no `web_search`; a `kits:` entry on `base: lean` or `child` inherits that) is left out softly: it
|
|
1605
|
+
is **reachable**. `find_tools` searches it as group `chi` (`chi/delegate`, `chi/web_search`, with its
|
|
1606
|
+
inputSchema) beside the MCP servers' tools, and `call_tool(tool: "chi/delegate", args: {…})` runs it as `delegate`
|
|
1607
|
+
itself, through the usual guardrails, approvals and hooks ([plugins](plugins.md#chitool_sourcecall-acts_as-label-wait-prefix)). A
|
|
1606
1608
|
tool in a toolkit's `remove:`, a `--no-tool` or a `/toolkit remove` is **removed**: hard, also after an add, and
|
|
1607
1609
|
call_tool can't reach it (`child` removes `memory_write`, `delegate` and `delegate_result`, so a child on it
|
|
1608
|
-
starts no children). To keep a
|
|
1609
|
-
are declared when a toolkit has a reachable
|
|
1610
|
-
|
|
1610
|
+
starts no children). To keep a tool away from the model, name it under `remove:`. `find_tools` and `call_tool`
|
|
1611
|
+
are declared when a toolkit has a reachable tool or reaches a source group, as the tools stood when the
|
|
1612
|
+
session's worker started; `full` with no reachable source declares neither, and removing them there
|
|
1613
|
+
(`--no-tool call_tool`) leaves nothing out: no prompt line, no chip. A plugin tool that arrives later
|
|
1614
|
+
(`chi.replace_tools`) on a kit that declared neither stays out of reach for that worker, with one warn notice
|
|
1615
|
+
(`/toolkit add NAME` declares it).
|
|
1611
1616
|
- **On `api: anthropic`, reachable tools are callable by name.** Every tool `call_tool` reaches (the reachable
|
|
1612
|
-
built-ins, tools from reached MCP groups as `mcp_<server>_<tool>`) also goes in the request's tools deferred
|
|
1617
|
+
built-ins and plugin tools, tools from reached MCP groups as `mcp_<server>_<tool>`) also goes in the request's tools deferred
|
|
1613
1618
|
(`defer_loading`: Messages keeps them out of the model's view until something loads them, at a flat ~100 tokens).
|
|
1614
1619
|
A direct call of one (`list_sessions`, `mcp_fs_read`) runs, under the same guardrails, approvals and rows as
|
|
1615
1620
|
through `call_tool` (an MCP tool's call is `call_tool`'s). A `find_tools` hit **loads** its matches (up to 5):
|
data/docs/guardrails.md
CHANGED
|
@@ -841,11 +841,13 @@ trade-off, when such a clash exists:
|
|
|
841
841
|
- deny rules cover both, which is the point.
|
|
842
842
|
|
|
843
843
|
A `call_tool` of one of chi's own tools (`chi/delegate`: a built-in the
|
|
844
|
-
session's toolkit leaves out, [toolkits](configuration.md#toolkits))
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
844
|
+
session's toolkit leaves out, [toolkits](configuration.md#toolkits)), or of
|
|
845
|
+
a bundle's `chi.tool` it leaves out (`chi/web_search`), is gated **as that
|
|
846
|
+
tool**: it is turned into the `delegate` call before the guardrails run, so
|
|
847
|
+
every rule on `delegate` (or `execute`'s `command:`, the `rm` check, a
|
|
848
|
+
delegate child's boundary, a plugin tool's `targets:`) gives the same
|
|
849
|
+
verdict as for a direct call, and an approval is keyed `delegate:…` either
|
|
850
|
+
way. `tool:
|
|
849
851
|
call_tool` rules no longer see such calls, only MCP calls and misses. A
|
|
850
852
|
`chi/` name the toolkit removes is refused before the gate, with no card.
|
|
851
853
|
|
data/docs/hooks.md
CHANGED
|
@@ -644,8 +644,9 @@ A `write`'s `content:` is the file text; an `edit` carries `old_text:` and
|
|
|
644
644
|
arguments whole on `args:` (string keys) instead, and its `content:` is those
|
|
645
645
|
arguments as JSON. An MCP tool's call is chi's `call_tool` (its tool index's, which the mcp
|
|
646
646
|
bundle runs): `tool: "call_tool"`, with `args: {"tool" => "<server>/<tool>", "args" => {…}}`.
|
|
647
|
-
A built-in reached through `call_tool` (`chi/delegate`, a tool the
|
|
648
|
-
declare) fires as that
|
|
647
|
+
A built-in or plugin tool reached through `call_tool` (`chi/delegate`, `chi/web_search`: a tool the
|
|
648
|
+
session's toolkit doesn't declare) fires as that tool, its call built as a direct call's, with
|
|
649
|
+
`call[:via] == "call_tool"`.
|
|
649
650
|
A hook that drops `via:` or rewrites the call to a tool the toolkit removes gets the call refused
|
|
650
651
|
by its name.
|
|
651
652
|
On a Messages host (`api: anthropic`), the model may call those tools by their own names (they
|
|
@@ -710,7 +711,7 @@ Notes:
|
|
|
710
711
|
- Ordering: bundle hooks fire by `(priority, bundle_name, hook_name)` (lower priority first), then plain `config.yml` hooks in registration order.
|
|
711
712
|
- Settings: a hook class with `initialize(settings = {})` gets the bundle's section of `config.yml` `bundles:` (see [Settings](#settings)).
|
|
712
713
|
- A bundle can also ship a `plugin.rb` whose `chi.on(event)` blocks are bundle hooks too, next to commands and tools; see [Plugins](plugins.md). There `ctx.notify`, `ctx.steer` and the other `ctx` helpers act as this page's `event[:notify]`… do for the event the block runs for.
|
|
713
|
-
- Shipped bundles (each in a profile: `core` or `dev`, see [Bundle profiles](memory.md#bundle-profiles-core-and-dev)): `chi bundle install guardrails` (rules, see [Guardrails](guardrails.md#the-guardrails-bundle)) and `chi bundle install known-names` (a hook, see [Guardrails](guardrails.md#the-known-names-bundle)), `chi bundle install source-links` (a hook: announces source refs, see [The source-links bundle](#the-source-links-bundle)), `chi bundle install btw` (a plugin: `/btw`, see [Bundles](bundles.md#the-btw-bundle)), `chi bundle install mcp` (a plugin: tools from MCP servers, see [Bundles](bundles.md#the-mcp-bundle)) `chi bundle install loop-guard` (a plugin: breaks tool-call loops, see [Bundles](bundles.md#the-loop-guard-bundle)), `chi bundle install check-in` (a plugin: checks on a long turn, see [Bundles](bundles.md#the-check-in-bundle)), `chi bundle install slow-commands` (a plugin: commands that ran past the execute limit are run as tasks next time, see [Bundles](bundles.md#the-slow-commands-bundle)), `chi bundle install agents-md` (a plugin: a nested AGENTS.md is steered into the conversation on the first touch under its directory, see [Bundles](bundles.md#the-agents-md-bundle)), `chi bundle install cost-cap` (a plugin: a per-session $ cap, `/cap`, see [Bundles](bundles.md#the-cost-cap-bundle)), `chi bundle install work-cap` (a plugin: a per-session cap on tool calls and active minutes, `/workcap`, see [Bundles](bundles.md#the-work-cap-bundle)), `chi bundle install ruby` (a plugin: an RSpec run with no examples at all, a hint, see [Bundles](bundles.md#the-ruby-bundle)) `chi bundle install skills` (a plugin: `/skill`, versions of skills, see [Bundles](bundles.md#the-skills-bundle)), `chi bundle install github-pr` (attached context: the branch's open PR, see [Bundles](bundles.md#the-github-pr-bundle)), `chi bundle install coordinator` (a plugin: parallel work in worktrees and child sessions, see [Bundles](bundles.md#the-coordinator-bundle)), `chi bundle install claude-peer` / `codex-peer` (a plugin: talk to a Claude Code or Codex session, see [Bundles](bundles.md#the-claude-peer-bundle))
|
|
714
|
+
- Shipped bundles (each but web-search in a profile: `core` or `dev`, see [Bundle profiles](memory.md#bundle-profiles-core-and-dev)): `chi bundle install guardrails` (rules, see [Guardrails](guardrails.md#the-guardrails-bundle)) and `chi bundle install known-names` (a hook, see [Guardrails](guardrails.md#the-known-names-bundle)), `chi bundle install source-links` (a hook: announces source refs, see [The source-links bundle](#the-source-links-bundle)), `chi bundle install btw` (a plugin: `/btw`, see [Bundles](bundles.md#the-btw-bundle)), `chi bundle install mcp` (a plugin: tools from MCP servers, see [Bundles](bundles.md#the-mcp-bundle)) `chi bundle install loop-guard` (a plugin: breaks tool-call loops, see [Bundles](bundles.md#the-loop-guard-bundle)), `chi bundle install check-in` (a plugin: checks on a long turn, see [Bundles](bundles.md#the-check-in-bundle)), `chi bundle install slow-commands` (a plugin: commands that ran past the execute limit are run as tasks next time, see [Bundles](bundles.md#the-slow-commands-bundle)), `chi bundle install agents-md` (a plugin: a nested AGENTS.md is steered into the conversation on the first touch under its directory, see [Bundles](bundles.md#the-agents-md-bundle)), `chi bundle install cost-cap` (a plugin: a per-session $ cap, `/cap`, see [Bundles](bundles.md#the-cost-cap-bundle)), `chi bundle install work-cap` (a plugin: a per-session cap on tool calls and active minutes, `/workcap`, see [Bundles](bundles.md#the-work-cap-bundle)), `chi bundle install ruby` (a plugin: an RSpec run with no examples at all, a hint, see [Bundles](bundles.md#the-ruby-bundle)) `chi bundle install skills` (a plugin: `/skill`, versions of skills, see [Bundles](bundles.md#the-skills-bundle)), `chi bundle install github-pr` (attached context: the branch's open PR, see [Bundles](bundles.md#the-github-pr-bundle)), `chi bundle install coordinator` (a plugin: parallel work in worktrees and child sessions, see [Bundles](bundles.md#the-coordinator-bundle)), `chi bundle install claude-peer` / `codex-peer` (a plugin: talk to a Claude Code or Codex session, see [Bundles](bundles.md#the-claude-peer-bundle)), `chi bundle install model-notes` (memories: reviewed model notes, see [Bundles](bundles.md#the-model-notes-bundle)) and `chi bundle install web-search` (a plugin: a `web_search` tool through your SearXNG, in no profile, see [Bundles](bundles.md#the-web-search-bundle)).
|
|
714
715
|
- Installing a bundle executes its hook code at `Engine` startup. Only install bundles you trust, as you would a gem. Hooks are **not** executed at install time (copy-only); they are `module_eval`'d at `Engine.new` inside per-bundle `Samagotchi::Bundles::<name>` namespaces (no top-level `require` collisions). Keep hook files side-effect-free at load time; do work in `#call` — top-level side effects (require, IO, `at_exit`, global assignment) run once per `Engine.new` (class redefinition is idempotent).
|
|
715
716
|
|
|
716
717
|
Lifecycle:
|
data/docs/internals/anthropic.md
CHANGED
|
@@ -225,7 +225,7 @@ host refused it for that model.
|
|
|
225
225
|
`ChatLoop#tool_definitions(conversation, model:)` appends them (`Tools::DeferredTools.list`) only on such an
|
|
226
226
|
adapter, and only with at least one tool declared (Messages wants one). The list, per request:
|
|
227
227
|
|
|
228
|
-
1. the built-ins the toolkit reaches (`Registry#reachable_names`), in registry order;
|
|
228
|
+
1. the built-ins and plugin tools the toolkit reaches (`Registry#reachable_names`), in registry order;
|
|
229
229
|
2. the tool index's source tools while `call_tool` is declared (`ToolIndex#deferrable`, via
|
|
230
230
|
`Registry::Index#deferrable`): callable, the only tool with its acts_as, a name Messages takes, no registered
|
|
231
231
|
tool's name; by acts_as (`mcp_<server>_<tool>`), sorted by name;
|
|
@@ -270,11 +270,11 @@ sets `:tool_changes` for the model: the retry and every later request drop the s
|
|
|
270
270
|
|
|
271
271
|
A direct call of a deferred tool runs whether something loaded it or not (the API doesn't check): the Engine sets
|
|
272
272
|
`TurnSettings#loads_tools` from the turn's final model (after the before_turn hooks), and then `ToolRunner#run` and
|
|
273
|
-
`KernelLoop#dispatch` take a reachable built-in by its own name (`Registry#indexed`) through the normal gate, and wrap
|
|
273
|
+
`KernelLoop#dispatch` take a reachable built-in or plugin tool by its own name (`Registry#indexed`) through the normal gate, and wrap
|
|
274
274
|
an MCP tool's acts_as into the `call_tool` call it stands for (`ToolIndex#wrap`): `call_tool`'s row, rules and
|
|
275
275
|
approvals. A reload rebuilds that wrapped call from the saved one (`BuiltinCalls.wrap_saved`). Each such call is
|
|
276
276
|
logged (`tool_direct_deferred`, with `loaded:` whether the conversation had loaded it). On `api: openai` and the
|
|
277
|
-
native loop nothing is deferred, and a direct call of a reachable built-in is refused with the `call_tool` route as
|
|
277
|
+
native loop nothing is deferred, and a direct call of a reachable built-in or plugin tool is refused with the `call_tool` route as
|
|
278
278
|
before.
|
|
279
279
|
|
|
280
280
|
## 400 fallbacks
|
|
@@ -35,6 +35,9 @@ Three rules hold the shape:
|
|
|
35
35
|
conversation already holds. Ids are deterministic: `ctx-<name>-<serial>-<revision[0,12]>` (the serial makes A, B, A
|
|
36
36
|
three notes), `ctx-<name>-error-<error_since digits>`, `ctx-<name>-detached-<seen[0,12]>`.
|
|
37
37
|
- **Polling is not activity.** The poller and the absorb never hold a worker up (`WorkerIdleExit`).
|
|
38
|
+
- **The leave absorbs too.** A pass looks at the context and then checks whether it should leave; a change that
|
|
39
|
+
lands in that gap has no later pass (the poller stops with the worker), so `Worker#left` absorbs whatever is
|
|
40
|
+
pending as plain notes before the recap, as it does for a background task that finished in the same gap.
|
|
38
41
|
|
|
39
42
|
## ContextSources
|
|
40
43
|
|
data/docs/internals/engine.md
CHANGED
|
@@ -57,7 +57,8 @@ Engine#run_turn(session, prompt, on_event:, max_iterations:, cancel_controller:,
|
|
|
57
57
|
│ # local System 1 host they wait for the turn's end: started before warm_up_next_turn, whose warm-up
|
|
58
58
|
│ # waits for them, or from run_turn's ensure; docs/context.md, The judge);
|
|
59
59
|
│ # then the session's toolkit (apply_toolkit!: Registry#toolkit, ToolIndex#refresh! for find_tools'
|
|
60
|
-
│ # chi line, a prompt rebuild when it changed
|
|
60
|
+
│ # chi line, a prompt rebuild when it changed; a warn notice per plugin tool that arrived mid-session
|
|
61
|
+
│ # with nothing to reach it, Registry#stranded_names). A Stop during the probes or the waits returns false
|
|
61
62
|
│ # (the ask's line says canceled; an ensure marks one the turn never settled failed).
|
|
62
63
|
│ # fires the :session_start hook on the Engine's first turn, then the :before_turn hook
|
|
63
64
|
│ # (a frozen copy of the history, the prompt, and resumed)
|
|
@@ -298,12 +299,13 @@ One iteration with tool calls:
|
|
|
298
299
|
on_stream_event:, max_tool_output_chars:)
|
|
299
300
|
→ under TurnSettings#loads_tools, a direct call of a source tool's acts_as (mcp_fs_read) is wrapped
|
|
300
301
|
into the call_tool call it stands for (Registry::Index#wrap, ToolIndex#wrap)
|
|
301
|
-
→ first, the call's entry unwraps it (Registry::Entry#unwrap: a call_tool of chi/<built-in
|
|
302
|
-
that
|
|
302
|
+
→ first, the call's entry unwraps it (Registry::Entry#unwrap: a call_tool of chi/<name>, a built-in or
|
|
303
|
+
a plugin tool, becomes that tool's call with via: "call_tool", ToolIndex#unwrap); the rest sees the
|
|
304
|
+
real call
|
|
303
305
|
→ a tool the session's toolkit leaves out (Registry#left_out; with via:, one Registry#indexed doesn't
|
|
304
306
|
reach) skips the gate: no hooks, no card; KernelLoop#dispatch answers it (the call_tool route for a
|
|
305
307
|
reachable tool, the not-loaded error for a removed one). Under TurnSettings#loads_tools a reachable
|
|
306
|
-
built-in called by its own name is no such call: it runs through the gate, and KernelLoop#dispatch
|
|
308
|
+
built-in or plugin tool called by its own name is no such call: it runs through the gate, and KernelLoop#dispatch
|
|
307
309
|
takes it (#loaded_entry)
|
|
308
310
|
→ Guardrails::Gate#evaluate (the before_tool_call hooks, then the core checks)
|
|
309
311
|
→ :tool_call_started
|
|
@@ -644,7 +646,9 @@ Engine initialization (in this order, among other things):
|
|
|
644
646
|
the bundles' plugins load (load_plugins → ExtensionLoad#load_plugins; each plugin's ctx is a
|
|
645
647
|
Plugin::Context over plugin_host, whose ctx.sessions it builds itself: the test kit passes its own, sessions:),
|
|
646
648
|
then (plugins or not) @tool_index.declare(@tools): find_tools + call_tool (source "index", not core),
|
|
647
|
-
declared per toolkit by Registry#declared? (a source registered at load, or a
|
|
649
|
+
declared per toolkit by Registry#declared? (a source registered at load, or a tool registered then that
|
|
650
|
+
the toolkit leaves reachable: Registry::Index#loaded; Toolkit::Resolved#declares? treats built-ins and
|
|
651
|
+
plugin tools alike: full declares both, another kit its list and adds) and
|
|
648
652
|
sets call_tool's unwrap (Tools::ToolIndex: the chi group from Registry#reachable_names, the sources'
|
|
649
653
|
groups, search, call checks, the unwrap; a source runs the call with its own plugin Context)
|
|
650
654
|
@reminder_store = ReminderStore.new (the kernel gets it), @reminder_queue, @reminders
|
|
@@ -471,7 +471,7 @@ fields: a field added to an event later reaches nobody until it is named there.
|
|
|
471
471
|
this matches the rendered text the page selects without treating different casing as the same claim. The quote may
|
|
472
472
|
also be in the answer as the pages show it (`Engine#verify_quote_in?`, `ParticipantView::Redact.text`: the session's
|
|
473
473
|
folder relative, home `~`, secret shapes `[redacted]`): a participant's page has only that text, and the owner's
|
|
474
|
-
page quotes it the same way (`
|
|
474
|
+
page quotes it the same way (`owner_claim.js` `opts()`, `normalizeClaim`; `spec/fixtures/redact_cases.json` keeps the two
|
|
475
475
|
sides alike), so a claim naming a path is taken on both. A fresh child
|
|
476
476
|
checks the same anchor against its parent's history. A completed Verify reply whose final non-empty line has
|
|
477
477
|
a valid `Verdict: confirmed|refuted|unclear — <reason>` appends a verdict to the session. Both pages place its card
|
|
@@ -108,9 +108,13 @@ right-hand column happens.
|
|
|
108
108
|
Claude 5.x cost the preserved thinking each time).
|
|
109
109
|
- find_tools' description is a function of the toolkit and the source groups **at load**: `ToolIndex#declare`
|
|
110
110
|
snapshots source group names and description lines (name, summary, callable count). `ToolIndex#refresh!` (from
|
|
111
|
-
`ToolkitBinding#apply!`
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
`ToolkitBinding#apply!` at a toolkit change, and from `Engine#apply_staged_tools!` when a plugin's tool set
|
|
112
|
+
changed) filters that snapshot by the toolkit and rebuilds the `chi` line from the built-ins and plugin tools it
|
|
113
|
+
reaches; it never consults live group names. The same group filter drives searches and deferred MCP tools.
|
|
114
|
+
Whether `find_tools`/`call_tool` are declared (`Registry#index_needed?`) reads the groups and the entries
|
|
115
|
+
registered at load (`Registry::Index#loaded`), never later ones: a plugin tool `chi.replace_tools` adds doesn't
|
|
116
|
+
bring them in, and a loaded plugin tool it later removes still counts, so `find_tools` stays declared, perhaps
|
|
117
|
+
with an empty `chi` group. That is deliberate: the declared tools stay the same for the worker's life. An MCP server that finishes booting
|
|
114
118
|
between load and the first turn changes search answers only, so two sessions on the same toolkit with the same
|
|
115
119
|
MCP cache send the same bytes. Kits that omit `groups:` keep their previous request bytes; switching only
|
|
116
120
|
groups changes the prompt key and asks `/toolkit` to confirm the cache cost once the session has history.
|
|
@@ -10,8 +10,9 @@ only library. Pure modules (`*_model.js`, `session_state.js`, `sessions_list.js`
|
|
|
10
10
|
and have `node --test` specs under `spec/web/public/` (happy-dom for the DOM ones); the pages as a whole are covered
|
|
11
11
|
by `npm run e2e` (`spec/e2e/`). `app.js` has no unit spec.
|
|
12
12
|
|
|
13
|
-
**`app.js` has a size cap**: `npm run lint` fails past `max-lines` in `eslint.config.js` (counted as `wc -l` counts
|
|
14
|
-
|
|
13
|
+
**`app.js` has a size cap**: `npm run lint` fails past `max-lines` in `eslint.config.js` (counted as `wc -l` counts:
|
|
14
|
+
2000, ratcheted down from 2250 when the versions, the owner's claim links and Verify, the composer's mode and the
|
|
15
|
+
strip's hide went into modules of their own, which left room for a few features' wiring). A new feature goes into a module of its own, wired from `app.js` in a few lines; a
|
|
15
16
|
refactor that shrinks `app.js` ratchets the cap down with it. Never raise it: extract instead.
|
|
16
17
|
|
|
17
18
|
**New exports go in new modules.** The static JS is served at unversioned URLs. Today every load revalidates
|
|
@@ -23,7 +24,7 @@ page. A factory dep a new version stops passing is the same hazard the other way
|
|
|
23
24
|
out of `send_turn.js`, `session_actions.js` and `lists_view.js`); a page loaded before keeps all its old modules
|
|
24
25
|
until a reload, so the risk is a mixed cache, not an open tab. `answer_fold.js`, `details_motion.js`,
|
|
25
26
|
`turn_range.js`, `history_reveal.js`, `latest.js`, `session_payload.js`, `method_api.js`, `session_store.js`,
|
|
26
|
-
`session_fields.js`, `feature_gates.js`, `revise_stack.js`, `revise_switch.js`, `shell_command.js`, `stale_params.js`, `popover_modal.js`, `more_sheet.js`, `hash_intent.js` (what the hash asks for: session, all, start or keep), `worker_features.js`, `block_open.js`, `text_roots.js`, `block_source.js`, `block_frame.js`, `block_host.js` and the `objectives_*.js` modules are new modules for that reason. The last three `block_*` draw a bundle's fenced blocks: [block-renderers.md](block-renderers.md).
|
|
27
|
+
`session_fields.js`, `feature_gates.js`, `revise_stack.js`, `revise_switch.js`, `shell_command.js`, `stale_params.js`, `popover_modal.js`, `more_sheet.js`, `hash_intent.js` (what the hash asks for: session, all, start or keep), `worker_features.js`, `version_notices.js`, `owner_claim.js`, `owner_verify.js`, `composer_mode.js`, `strip_hidden.js`, `block_open.js`, `text_roots.js`, `block_source.js`, `block_frame.js`, `block_host.js` and the `objectives_*.js` modules are new modules for that reason. The last three `block_*` draw a bundle's fenced blocks: [block-renderers.md](block-renderers.md).
|
|
27
28
|
|
|
28
29
|
## Two pages, two models
|
|
29
30
|
|
|
@@ -37,7 +38,7 @@ claim links, `composer_mount.js`, the verdict card and Verify fresh, `verify_tar
|
|
|
37
38
|
`auth_lost.js`, `token_fragment.js`, `format.js`) but none of the data layer, the stream handling or the message
|
|
38
39
|
rendering. It has its own `api` (`{status, ok, body}`, never throws, its own ended / auth-lost handling) and its own
|
|
39
40
|
`EventSource`. Its parallel copies of the owner's Verify and claim helpers (`verifyFreshState`, `verifyAnchorFor`,
|
|
40
|
-
`showPendingClaim`, `CLAIM_ROOTS`) differ on purpose in places (the source kind `reply` vs `answer`, the server's
|
|
41
|
+
`showPendingClaim`, `CLAIM_ROOTS`; the owner's are `owner_verify.js` and `owner_claim.js`) differ on purpose in places (the source kind `reply` vs `answer`, the server's
|
|
41
42
|
`fresh_running` vs `verify_fresh`, no home/cwd in its claim options). Don't converge them as a side effect of another
|
|
42
43
|
change.
|
|
43
44
|
|
|
@@ -46,8 +47,8 @@ change.
|
|
|
46
47
|
| Holder | What | Who writes | How a change reaches the screen |
|
|
47
48
|
|---|---|---|---|
|
|
48
49
|
| **The session store** (`session_store.js`; app.js `store`) | `store.selected` (the open session's id), `store.current` and `store.sessions` (below) | through the store only (see The session store); other modules get getters (`selected: () => store.selected`, `state: () => store.current`) or the store itself (`stream_handlers.js`, `send_turn.js`, `session_actions.js`) | the store runs the redraws that read what changed (below); nobody calls them by hand |
|
|
49
|
-
| **app.js module scope**: about 20 top-level `let`s (`streamControl`, `eventsControl`, `pageCapabilities`/`pageEditorUrl`, `
|
|
50
|
-
| **`store.current`** (`session_state.js`: `emptySessionState` / `sessionStateFrom`; per field: `session_fields.js FIELDS`) | the open session's fields, about 40 (info bar, chips, verdicts, timing, worker, stream cursor) | `store.update` / `setStatus` from app.js, `stream_handlers.js`, `send_turn.js` (`firstPreview`, `owner`), `session_actions.js` (`status`); `forgetSession`'s `replaceCurrent`. Two writers in place, on purpose (D8 in `FIELDS`): live_timing.js's `activeTurn`, verdict_fresh.js's running list | the store runs the field's redraws (`FIELDS[key].redraws`) |
|
|
50
|
+
| **app.js module scope**: about 20 top-level `let`s (`streamControl`, `eventsControl`, `pageCapabilities`/`pageEditorUrl`, `resyncsInFlight`/`resyncAgain`, `resyncAttempts`, `scopeError`…) | the page's mode and its streams | app.js only; other modules get getters and hooks | the writer touches the redraw that reads it (`store.touch("infoBar")`: the stream; `version_notices.js` does it for the versions and its `restarting` set), or calls a redraw only it feeds |
|
|
51
|
+
| **`store.current`** (`session_state.js`: `emptySessionState` / `sessionStateFrom`; per field: `session_fields.js FIELDS`) | the open session's fields, about 40 (info bar, chips, verdicts, timing, worker, stream cursor) | `store.update` / `setStatus` from app.js, `stream_handlers.js`, `send_turn.js` (`firstPreview`, `owner`), `session_actions.js` (`status`), `owner_verify.js` (`verdicts`, `verifyFresh`); `forgetSession`'s `replaceCurrent`. Two writers in place, on purpose (D8 in `FIELDS`): live_timing.js's `activeTurn`, verdict_fresh.js's running list | the store runs the field's redraws (`FIELDS[key].redraws`) |
|
|
51
52
|
| **`store.sessions`** (the hub's projection) | every card | `store.update({sessions})` from app.js: `openSessionEvents` (`applySessionEvent`), `refresh` (the list fallback), `addCreatedSession`, `applyView` (re-sorted), `replaceSelectedCard` (`setLiveStatus` through `setStatus`, `readTurnEnd`, `setUsedMemories`), the stream's close (`withoutWorker`); `session_actions.js` (`setSessions`) | the store: `lists` and `infoBar`. **Always a new array**; an entry is replaced, never changed in place. `followSelectedSession` copies the open one's owner and worker (`fieldsFromCard`) and, when not streaming, its status (`setStatus(…, "hub")`) into `store.current` |
|
|
52
53
|
| **Factory closures**: the `create*` factories (popovers, panels, views) | each component's own state: open-for, busy, error, its own fetched list | the factory | an `onChange` hook back into app.js, or a getter the factory reads at draw time |
|
|
53
54
|
| **The history DOM** | the conversation as drawn: which turn a bubble belongs to (`data-turn-id`, `data-answer-turn`), what opens a turn (`TURN_PROMPT`, `bubbles.js`: a prompt or a wake's note, the same turns `Steer.turn_opening?` counts for a card's or the recap's `turns_since`), kept command bubbles, which texts were shown, unnamed badges, folded answers and long prompts (`.folded`), Revise stamps (`data-revises` on a prompt; a Verify turn's `data-verify`) and the stacks' folded versions (`.stack-hidden`, `.stack-stub.open`); the switcher's version on view is `revise_stack.js`'s own (a copy `.stack-view` before the top answer, the top `.stack-swapped`; never stored) | `bubbles.js`, `cards_place.js`, `question_cards.js`, `turn_block.js`/`stage_view.js`, `answer_fold.js`, `revise_stack.js`, `stream_handlers.js` (a live `turn_completed`'s `revises` and `verify` onto its prompt) | read back as state: `turn_range.js turnElements` (one walker for a turn's elements), `keptCommandBubbles`, `seedSeenFromDom`, `nameUnnamedBadges`/`nameVerdictCards`, `answer_fold.js` and `revise_stack.js` (their observers decorate every path that fills `#history`; the stacks are `stacks()` of the stamps, read again on each change, never a store field), `history_reveal.js revealInHistory` (the one way a jump unfolds what hides its target) |
|
|
@@ -73,8 +74,9 @@ through the store**, never `current.X = …`:
|
|
|
73
74
|
old one writes there alone.
|
|
74
75
|
- `store.batch(fn)`, `store.touch(name)`, `store.on(name, fn)`: the redraws (below).
|
|
75
76
|
|
|
76
|
-
The ESLint block `samagotchi/session-writes` (eslint.config.js) enforces it in the
|
|
77
|
-
(app.js, `stream_handlers.js`, `send_turn.js`, `session_actions.js`, `start_page.js`, `working_set_view.js
|
|
77
|
+
The ESLint block `samagotchi/session-writes` (eslint.config.js) enforces it in the seven modules that hold the state
|
|
78
|
+
(app.js, `stream_handlers.js`, `send_turn.js`, `session_actions.js`, `start_page.js`, `working_set_view.js`,
|
|
79
|
+
`owner_verify.js`): an
|
|
78
80
|
assignment, `++`, `delete` or `Object.assign` into `current`, `store.current` or `state()` is an error
|
|
79
81
|
(`spec/web/public/lint_session_writes.test.js` runs it on a sample). An alias (`const st = state(); st.x = …`) isn't
|
|
80
82
|
caught: don't write through one.
|
|
@@ -105,16 +107,16 @@ stale).
|
|
|
105
107
|
**The redraws are registered, not called.** app.js registers each name once (`store.on`, at the bottom, once every
|
|
106
108
|
piece a redraw calls exists): `lists` → `lists.render()`, `paintActive` → `lists.paintActive()`, `settingsPanel` →
|
|
107
109
|
`settingsControl.refresh()`, `workingSetStrategy` → `workingSet.strategyChanged()`, `featureGates` (below), `composer` →
|
|
108
|
-
`
|
|
110
|
+
`composerMode.update()` (`composer_mode.js`), `infoBar` → `updateInfoBar()`. A writer writes and stops there: no `updateInfoBar()` after
|
|
109
111
|
a store write. A value written unchanged redraws nothing (a chunk with the same ctx %).
|
|
110
112
|
|
|
111
113
|
An input the store doesn't hold calls `store.touch(name)` where it changes: `infoBar` for the clock (liveTurn's
|
|
112
|
-
`onTick`), the versions (`
|
|
114
|
+
`onTick`), the versions and the sessions restarting (`version_notices.js` `notice`, `restart`), the stream (`startStream`, `workerGone`), the working
|
|
113
115
|
set's and the participants' `onChange`, the continue and review in-flight sets
|
|
114
116
|
(`session_actions.js`); `composer` and `infoBar` for `liveTurn.running` (`setTurnRunning`, with `settingsPanel`:
|
|
115
117
|
the open ⚙ panel's running-turn note) and at the end of `renderSessionView` (a whole view drawn). Left as they are,
|
|
116
|
-
on purpose:
|
|
117
|
-
`
|
|
118
|
+
on purpose: `composer_mode.js`'s phone-width listener calls its `update()` (the bar doesn't read it),
|
|
119
|
+
`strip_hidden.js`'s apply and `applyView` call `updateSessionTitle()`, and `closeStream`/`stream.restart` set
|
|
118
120
|
`streamControl = null` without a redraw (a resync or a workerGone follows).
|
|
119
121
|
|
|
120
122
|
A function that writes more than once batches its synchronous part, so each redraw runs once: `select` (before its
|
|
@@ -178,7 +180,7 @@ The redraws, by their store names:
|
|
|
178
180
|
- `infoBar` (`updateInfoBar()`): the session bar's buttons and info line (`info_bar_model.js sessionBar` →
|
|
179
181
|
`info_bar.js`), the annotate bar, the fold controls, `updateSessionTitle()` and the fresh link's picker. Cheap: it
|
|
180
182
|
runs on most writes and every clock tick.
|
|
181
|
-
- `composer` (`
|
|
183
|
+
- `composer` (`composer_mode.js` `update()`): the composer's placeholder, buttons and delivery line (selected, running,
|
|
182
184
|
owner).
|
|
183
185
|
- `settingsPanel` (`settingsControl.refresh()`): the ⚙ chip's **open panel** (`settings_chip.js`; the chip itself is
|
|
184
186
|
drawn by the bar), on `thinking`, `reasoningHistory` and `llmContext`; `workingSetStrategy`: the working set's open
|
|
@@ -190,7 +192,8 @@ The redraws, by their store names:
|
|
|
190
192
|
`forgetSession()` is the one reset: `select` and `clearSelection` both call it, so anything per-session that isn't
|
|
191
193
|
in `store.current` (capabilities, the markdown warning and tip, the init row, the Verify claim, the armed Revise,
|
|
192
194
|
the running turn's DOM) is cleared there, and the feature gates are primed for the empty session. A new per-session holder
|
|
193
|
-
(an armed composer anchor like the Verify claim or the Revise) goes into it. The armed
|
|
195
|
+
(an armed composer anchor like the Verify claim or the Revise) goes into it. The armed Verify claim is
|
|
196
|
+
`owner_verify.js`'s holder (`anchor()`, `set(a)`, `clear(a?)`: `clear(a)` lets go only of `a`). The armed Revise
|
|
194
197
|
(`pendingReviseAnchor`) is set only through `setReviseAnchor`, which redraws its chip and the Revise buttons (a
|
|
195
198
|
redraw only it feeds, not a store field).
|
|
196
199
|
|
|
@@ -222,7 +225,7 @@ each keeps one set of handlers, confirmations and in-flight rules. A chip the sh
|
|
|
222
225
|
matters at exactly 600 px, where the CSS is a phone's but the shell (`SHEET_MAX`, `<`) still draws a popover, not a sheet.
|
|
223
226
|
A new chip on the bar needs its place in that CSS's hidden list and a row in `moreRows`, and its attention state in the row, or
|
|
224
227
|
it is lost on a phone. `app.js`'s `updateInfoBar` feeds the sheet (`update`) on every redraw; its rows and actions draw into
|
|
225
|
-
fixed slots, so a row that joins keeps the focus where it was.
|
|
228
|
+
fixed slots, so a row that joins keeps the focus where it was. `composer_mode.js`'s phone-width listener closes the sheet when the window grows.
|
|
226
229
|
|
|
227
230
|
|
|
228
231
|
## The stage's height
|
|
@@ -321,11 +324,11 @@ reach another device or the server.
|
|
|
321
324
|
| `chi_model`, `chi_model_recent` | `model_pick.js`, `model_picker.js`, `start_page.js` | the start page's model and its recent list |
|
|
322
325
|
| `chi_composer_collapsed` | `composer_fold.js` | the composer folded (a card's text field folds it for the time it has the focus on a narrow or touch screen, `composer_grip.js watchCardField`, on top of this and of the visit's state: neither is written) |
|
|
323
326
|
| `chi_composer_floor` | `composer_grip.js`, `composer_mount.js` | the composer's dragged height |
|
|
324
|
-
| `chi_strip_hidden` (`chi_zen`: the old name, read once) |
|
|
327
|
+
| `chi_strip_hidden` (`chi_zen`: the old name, read once) | `strip_hidden.js` | the session strip hidden |
|
|
325
328
|
| `chi_stage_collapsed` | `stage_view.js` | the stage collapsed |
|
|
326
329
|
| `chi_working_set_collapsed` | `working_set_rail.js` | the working set rail collapsed |
|
|
327
330
|
| `chi_markdown_tip_dismissed` | `markdown_tip.js` | the markdown tip dismissed |
|
|
328
|
-
| `chi_update_available_shown` |
|
|
331
|
+
| `chi_update_available_shown` | `version_notices.js` | update notices already shown |
|
|
329
332
|
| `chi_notify`, `chi_push_device` | `notifications.js` (an injected storage) | the bell's state, this device's push id |
|
|
330
333
|
| `chi_answer_fold:<session>` | `answer_fold.js`, `revise_stack.js` | the answers and long prompts (`<turn id>:prompt`) folded by hand, and the Revise stubs unfolded by hand (`stub:<turn id>`) (swept with the session list) |
|
|
331
334
|
| `chi_review_history:<grant>`, `chi_review_history_session:<session>` | `review_history.js` (participant page) | a participant's composer history |
|